分類: C/C++
目錄 1
1. 概念 1
1.1. 雙指針 1
1.2. 指針數組 1
1.3. 數組指針 1
2. 區別 2
3. 兼容性 2
4. 為何列數須相等? 2
5. 初始化 3
6. 轉化 4
7. 雙指針 6
8. 關系圖 8
8.1. 數組、指針和雙指針關系圖 8
8.2. 數組和雙指針關系圖 9
8.3. 演示代碼 9
9. 相關參考 10
對于“p + 1”,這里的“1”是啥?
1) 如果int* p,則“1”實際是sizeof(int),也就是p指向的類型大小;
2) 如果int** p,則“1”實際是sizeof(int*),仍然是p指向的類型大小;
3) 如果int p[5],則“1”是sizeof(int),而“&p+1”中的“1”則為sizeof(p),依然是p或&p指向類型的大小。
指向一個指針的指針。
由指針值組成的數組,也就是說數組的每個元素值的數據類型均為指針類型,如:int* p[2];
指向一個數組的指針。
|
| 行數 | 列數 | 說明 |
int** p1; | 雙指針 | 不固定 | 不固定 | 列數和行數都不確定,而且每行可以列數不等。 |
int* p2[3]; | 指針數組 | 固定 | 不固定 | 共3行,每行多少列不確定,而且每行可以列數不等。 |
int (*p3)[3]; | 數組指針 | 不固定 | 固定 | 共3列,多少行不確定。 |
int** p1; int* p2[3]; int (*p3)[3]; int p4[2][3]; int p5[3];
// 兼容性 p1 = p2; p3 = p4; p3 = &p5; // p5的列數必須和p3的列數相同
p1 = p2; // 兩者列數均不確定,可兼容 |
“列數相等”或“列數不確定”是兼容的提前條件,如上述的p3、p4和p5三者的列數均相同。
指針支持加減操作,比如:
int m[3][3]; int (*pm)[3] = m + 1;
上述第二行的m是指二維數組“int m[3][3];”在內存中的首地址,如:0x7fff82521370。而這個“1”是指這個二維數組一行的大小,也就是“int m[3];”的大小。因此,pm的值為:0x7fff82521370 + 12 = 0x7fffd5afd94c。 |
如果列數不相等,則加減操作無法進行,因此需要“列數相等”。假設:
int** b1; int** b2 = b1 + 1; |
上述中的“1”實際是多少?這個就要看b1的類型是什么?在這里,b1是一個雙指針,也就是指向指針的指針。本質上就是一個指針,因此在32位平臺上它的值是4,在64位平臺上它的值是8。
如何來初始化雙指針、指針數組和數組指針?直接看下面的代碼:
#include
int main() { size_t i; // 行 size_t j; // 列
int** p1; // 行數和列數,均不固定 int* p2[3]; // 行數固定為3,列數不固定 int (*p3)[3]; // 列數固定為3,行數不固定
size_t num_rows_p1 = 3; // 行數不固定,可運行時設定 p1 = new int*[num_rows_p1]; for (i=0; i<num_rows_p1; ++i)< span=""> { size_t num_cols_p1 = i + 1; // 列數不固定,可運行時設定 p1[i] = new int[num_cols_p1]; for (j=0; j<num_cols_p1; ++j)< span=""> p1[i][j] = i; } PRintf("p1[2][1]=%d/n", p1[2][1]); printf("p1[2][2]=%d/n", p1[2][2]);
const size_t num_rows_p2 = sizeof(p2)/sizeof(p2[0]); // 行數固定,不可運行時設定 for (i=0; i<num_rows_p2; ++i)< span=""> { size_t num_cols_p2 = i + 1; // 列數不固定,可運行時設定 p2[i] = new int[num_cols_p2]; for (j=0; j<num_cols_p2; ++j)< span=""> p2[i][j] = i; } printf("p2[2][1]=%d/n", p2[2][1]); printf("p2[2][2]=%d/n", p2[2][2]);
size_t num_rows_p3 = 5; // 行數不固定,可運行時設定 const size_t num_cols_p3 = 3; // 列數固定,不可運行時設定 p3 = new int[num_rows_p3][num_cols_p3]; for (i=0; i<num_rows_p3; ++i)< span=""> { for (j=0; j<num_cols_p3; ++j)< span=""> p3[i][j] = i; } printf("p3[2][1]=%d/n", p3[2][1]); printf("p3[2][2]=%d/n", p3[2][2]);
return 0; } |
下面這個表格,在內存中即可為“int** p1;”,也可以為“int* p2[3];”,還可以為“int (*p3)[3];”
1 | 2 | 3 |
4 | 5 | 6 |
7 | 8 | 9 |
如下來操作它:
#include
int main() { int m[3][3] = { {1,2,3}, {4,5,6}, {7,8,9} };
int** p1; int* p2[3]; int (*p3)[3];
p1 = new int *[3]; p1[0] = m[0]; // 列數不固定 p1[1] = m[1]; // 列數不固定 p1[2] = m[2]; // 列數不固定 printf("p1[1][2]=%d/n", p1[1][2]);
p2[0] = m[0]; // 列數不固定 p2[1] = m[1]; // 列數不固定 p2[2] = m[2]; // 列數不固定 printf("p2[1][2]=%d/n", p2[1][2]);
p3 = m; // 列數固定 printf("p3[1][2]=%d/n", p3[1][2]);
delete []p1; return 0; }
|
實際上,還可以當作一維數組,但仍然可以使用“int** p1;”、“int* p2[3];”和int (*p3)[3];”來操作,看下面的代碼:
#include
int main() { int n[9] = { 1,2,3, 4,5,6, 7,8,9 };
int** p1; int* p2[3]; int (*p3)[3];
p1 = new int *[3]; p1[0] = n; p1[1] = n + 3; p1[2] = n + 6; printf("p1[1][2]=%d/n", p1[1][2]);
p2[0] = n; p2[1] = n + 3; p2[2] = n + 6; printf("p2[1][2]=%d/n", p2[1][2]);
p3 = (int (*)[3])n; // 這里也可改成:p3 = (int (*)[3])&n; printf("p3[1][2]=%d/n", p3[1][2]);
delete []p1; return 0; } |
二維數組同樣也可以當一維數組使用,如:
#include
int main() { int m[3][3] = { {1,2,3}, {4,5,6}, {7,8,9} };
int *p = (int*)m; // 這里同樣也可以改成:int *p = (int*)&m; printf("p[1]=%d, p[3]=%d, p[6]=%d/n", p[1], p[3], p[6]); return 0; } |
int m[3]; int* p1; int** p2; int* p3[3]; int (*p4][3]);的本質是相同的,都表示一塊內存,只所以有區分,是為了編譯器能夠按照不同的方式去訪問這塊內存。更通俗點說,它們都是對內存訪問的協議。
從前面的例子不難看出,對于雙指針“int** p1;”在使用之前,總是會先做“new int*[]”操作。如果讓p1直接指向數組首地址是否可以了?
答案是不行的,假設有如下的代碼:
int m[9] = { 0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9 }; int** pp = (int**)m; |
pp[0]、pp[1]、pp[2]。。。是什么?用下面這段代碼來觀察:
#include
int main() { int** pp = NULL; int m[9] = { 0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9 };
pp = (int**)m; printf("pp[0]=%p, pp[1]=%p, pp[2]=%p, pp[3]=%p/n", pp[0], pp[1], pp[2], pp[3]); return 0; } |
上面這段代碼中的數組元素值特意使用了16進制,以便更好的觀察,它的實際輸出和機器的字節序,以及位數相關,在x86輸出為:
pp[0]=0x1, pp[1]=0x2, pp[2]=0x3, pp[3]=0x4 |
在x86_64上輸出為:
pp[0]=0x200000001, pp[1]=0x400000003, pp[2]=0x600000005, pp[3]=0x800000007 |
不要被雙指針“**”迷惑了,可對比下“int* p;”
#include
int main() { int* p = NULL; int** pp = NULL; int m[9] = { 0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9 };
pp = (int**)m; printf("pp[0]=%p, pp[1]=%p, pp[2]=%p, pp[3]=%p/n", pp[0], pp[1], pp[2], pp[3]);
p = m; printf("p[0]=%d, p[1]=%d, p[2]=%d, p[3]=%d/n", p[0], p[1], p[2], p[3]); return 0; } |
因為pp是雙指針類型,因此它不能直接指向數組內存。
假設有一指針:int* p;,它的地址為x,則p[N]和*(p+N)都是取地址為“x+sizeof(int)”的內存數據;如果是“int** pp;”,設地址為y,則pp[N]和*(pp+N)是取地址為“y+sizeof(int*)”的內存數據。
#include
int main() { int m[] = { 1,2,3,4,5,6,7,8,9 }; int* p = m; int** pp = &p;
printf("sizeof(p)=%d/n", sizeof(p)); printf("sizeof(*p)=%d/n", sizeof(*p));
printf("m=%p/n", m); printf("&p=%p/n", &p); printf("*p=%lx/n", *pp); printf("**p=%d/n", **pp); // 這實際是“*((*pp)+0))”而不是“*(*(pp+0))” printf("*((*p)+0)=%d/n", *((*pp)+0)); printf("pp=%p/n", pp); printf("pp+1=%p/n", pp+1);
// 不要將“pp[0][1]”理解成:**(pp+0+1), // 這里的1實際是sizeof(*pp),也就是sizeof(int*), // 而pp是p的地址,注意不是m的地址 printf("&m[1]=%p/n", &m[1]); printf("&pp[0][1]=%p/n", &pp[0][1]); // p[0]也就是*(p+0) printf("pp[0][1]=%d/n", pp[0][1]); // p[0][1]也就是*((*(pp+0))+1)) printf("*((*(pp+0))+1)=%d/n", *((*(pp+0))+1));
printf("*((*pp)+1)=%d/n", *((*pp)+1)); // 正確,*pp是m的地址 printf("**(pp+1)=%d/n", **(pp+1)); // 越界了,因為pp的值是p的地址,不是m的地址 return 0; } |
《進一步理解指針:一維數組和二維數組轉換》:
http://blog.chinaunix.net/uid-20682147-id-4967871.html
《常見指針定義解讀》
http://blog.chinaunix.net/uid-20682147-id-4344901.html
|
新聞熱點
疑難解答