进一步理解指针2:双指针、指针数组和数组指针-LMLPHP进一步理解指针2:双指针、指针数组和数组指针.pdf

目录

目录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

 

1. 概念

1.1. 指针

对于“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指向类型的大小。

1.1. 双指针

指向一个指针的指针。

1.2. 指针数组

由指针值组成的数组,也就是说数组的每个元素值的数据类型均为指针类型,如:int* p[2];

1.3. 数组指针

指向一个数组的指针。

2. 区别

 

 

行数

列数

说明

int** p1;

双指针

不固定

不固定

列数和行数都不确定,而且每行可以列数不等。

int* p2[3];

指针数组

固定

不固定

3行,每行多少列不确定,而且每行可以列数不等

int (*p3)[3];

数组指针

不固定

固定

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; // 两者列数均不确定,可兼容

 

“列数相等”或“列数不确定”是兼容的提前条件,如上述的p3p4p5三者的列数均相同。

4. 为何列数须相等?

指针支持加减操作,比如:

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。

5. 初始化

如何来初始化双指针、指针数组和数组指针?直接看下面的代码:

#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

        {

                size_t num_cols_p1 = i + 1; // 列数不固定,可运行时设定

                p1[i] = new int[num_cols_p1];

                for (j=0; j

                        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

        {

                size_t num_cols_p2 = i + 1; // 列数不固定,可运行时设定

                p2[i] = new int[num_cols_p2];

                for (j=0; j

                        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

        {

                for (j=0; j

                        p3[i][j] = i;

        }

        printf("p3[2][1]=%d\n", p3[2][1]);

        printf("p3[2][2]=%d\n", p3[2][2]);

 

        return 0;

}

6. 转化

下面这个表格,在内存中即可为“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;

}

7. 双指针

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*)”的内存数据。

8. 关系图

8.1. 数组、指针和双指针关系图

进一步理解指针2:双指针、指针数组和数组指针-LMLPHP

8.2. 数组和双指针关系图

进一步理解指针2:双指针、指针数组和数组指针-LMLPHP

8.3. 演示代码

#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;

}

9. 相关参考

《进一步理解指针:一维数组和二维数组转换》:

http://blog.chinaunix.net/uid-20682147-id-4967871.html

《常见指针定义解读》

http://blog.chinaunix.net/uid-20682147-id-4344901.html

 

 


02-12 09:55