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


    目录

    目录 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. 双指针

    指向一个指针的指针。

    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 <stdio.h>

    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)

            {

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

                    p1[i] = new int[num_cols_p1];

                    for (j=0; j<num_cols_p1; ++j)

                            p1[i][j] = i;

            }

            printf("p1[2][1]=%d ", p1[2][1]);

            printf("p1[2][2]=%d ", p1[2][2]);

            const size_t num_rows_p2 = sizeof(p2)/sizeof(p2[0]); // 行数固定,不可运行时设定

            for (i=0; i<num_rows_p2; ++i)

            {

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

                    p2[i] = new int[num_cols_p2];

                    for (j=0; j<num_cols_p2; ++j)

                            p2[i][j] = i;

            }

            printf("p2[2][1]=%d ", p2[2][1]);

            printf("p2[2][2]=%d ", 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)

            {

                    for (j=0; j<num_cols_p3; ++j)

                            p3[i][j] = i;

            }

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

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

            return 0;

    }

    6. 转化

    下面这个表格,在内存中即可为“int** p1;”,也可以为“int* p2[3];”,还可以为“int (*p3)[3];”

    1

    2

    3

    4

    5

    6

    7

    8

    9

    如下来操作它:

    #include <stdio.h>

    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 ", p1[1][2]);

            p2[0] = m[0]; // 列数不固定

            p2[1] = m[1]; // 列数不固定

            p2[2] = m[2]; // 列数不固定

            printf("p2[1][2]=%d ", p2[1][2]);

            p3 = m; // 列数固定

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

            delete []p1;

            return 0;

    }

    实际上,还可以当作一维数组,但仍然可以使用“int** p1;”、“int* p2[3];”和int (*p3)[3];”来操作,看下面的代码:

    #include <stdio.h>

    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 ", p1[1][2]);

            p2[0] = n;

            p2[1] = n + 3;

            p2[2] = n + 6;

            printf("p2[1][2]=%d ", p2[1][2]);

            p3 = (int (*)[3])n; // 这里也可改成:p3 = (int (*)[3])&n;

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

            delete []p1;

            return 0;

    }

    二维数组同样也可以当一维数组使用,如:

    #include <stdio.h>

    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 ", 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 <stdio.h>

    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 ", 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 <stdio.h>

    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 ", pp[0], pp[1], pp[2], pp[3]);

            p = m;

            printf("p[0]=%d, p[1]=%d, p[2]=%d, p[3]=%d ", 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. 数组、指针和双指针关系图

     

    8.2. 数组和双指针关系图

     

    8.3. 演示代码

    #include <stdio.h>

    int main()

    {

            int m[] = { 1,2,3,4,5,6,7,8,9 };

            int* p = m;

            int** pp = &p;

            printf("sizeof(p)=%d ", sizeof(p));

            printf("sizeof(*p)=%d ", sizeof(*p));

            printf("m=%p ", m);

            printf("&p=%p ", &p);

            printf("*p=%lx ", *pp);

            printf("**p=%d ", **pp); // 这实际是“*((*pp)+0))”而不是“*(*(pp+0))”

            printf("*((*p)+0)=%d ", *((*pp)+0));

            printf("pp=%p ", pp);

            printf("pp+1=%p ", pp+1);

            // 不要将“pp[0][1]”理解成:**(pp+0+1),

            // 这里的1实际是sizeof(*pp),也就是sizeof(int*),

            // 而pp是p的地址,注意不是m的地址

            printf("&m[1]=%p ", &m[1]);

            printf("&pp[0][1]=%p ", &pp[0][1]); // p[0]也就是*(p+0)

            printf("pp[0][1]=%d ", pp[0][1]); // p[0][1]也就是*((*(pp+0))+1))

            printf("*((*(pp+0))+1)=%d ", *((*(pp+0))+1));

            printf("*((*pp)+1)=%d ", *((*pp)+1)); // 正确,*pp是m的地址

            printf("**(pp+1)=%d ", **(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

  • 相关阅读:
    明星球队的傲慢
    VS2010测试方面的文章重点
    项目管理有感
    团队从小到大,再到体验团队
    css实现文字渐变
    echarts坐标轴文字过长省略
    mustache+mock
    你有选择的权利
    呵呵,初学者小编
    Centos7升级到OpenSSH_8.8p1、OpenSSL 1.1.1l版本
  • 原文地址:https://www.cnblogs.com/aquester/p/11469893.html
Copyright © 2020-2023  润新知