指向指针的指针:多级指针

*() 与 [] 的互换表示:

以下为方便课堂展示的写法,在实际的开发中,不能直接光定义指针而不赋值(比如只写 char* p1),这样如果后面要使用* p1,由于 p1 没有赋值里面是垃圾数据,用这个数当作地址去取此地址中的值,这个地址可能不是一个正常的地址或者是个不让访问的地址,那么就会产生报错提醒。

  • 了解 * 的反汇编:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    char* p1;

    //反汇编 了解*的反汇编
    printf("%d\n",*p1);

    //反汇编:*p1 = *(p1+0)
    printf("%d\n",*(p1+0));

    //反汇编:*(p1+0) = p1[0]
    printf("%d\n", p1[0]);


    三条的反汇编都长这样,都是先取 *p1 的值作为地址,然后再取这个内存地址中存的值

    1
    printf("%d %d\n",*(p1+2),p1[2]);			


    Tips: __cdecl 调用约定,特点为外平栈+参数从右至左入栈,所以上面那个是 p1[2]、下面那个是 *(p1+2)

  • 了解 ** 的反汇编:

    1
    2
    3
    4
    char** p2;

    //反汇编:了解**的反汇编
    printf("%d\n",*(*p2));


    和只有一个 * 的时候思路一样,只是多了一个 * 多取了一层,就不细说了

    1
    printf("%d\n",*(*(p2+1)));	//char**


    因为是 **,+1 的时候去掉一个 * 变成 char*,所以是 +4

    1
    2
    printf("%d\n",*(*(p2+1)+1));  //+4 +1
    printf("%d\n",*(*(p2+2)+3)); //+8 +3


    +4、+1,+8、+3,没啥好说的

    1
    printf("%d %d\n",*(*(p2+2)+3), p2[2][3]);


    在编译器眼里,指针可以用 * 取值也可以用 [] 取值,两种写法是完全一样的

  • 了解 *** 的反汇编:

    1
    2
    3
    4
    5
    6
    7
    char*** p3;
    //反汇编 了解***的反汇编
    printf("%d\n",*p3);

    printf("%d\n",*(*p3));

    printf("%d\n",*(*(*p3)));


    三星比两星还多取一次

    1
    2
    3
    4
    printf("%d %d\n",*(*(*(p3+1)+2)+3),p3[1][2][3]);  //+4 +8 +3

    int*** p3; //char改成int
    printf("%d %d\n",*(*(*(p3+1)+2)+3),p3[1][2][3]); //+4 +8 +12
  • 总结:*() 与 [] 可以相互转换

    1
    2
    3
    4
    *(p+i) = p[i]				
    *(*(p+i)+k) = p[i][k]
    *(*(*(p+i)+k)+m) = p[i][k][m]
    *(*(*(*(*(p+i)+k)+m)+w)+t) = p[i][k][m][w][t]

数组指针

数组指针和指针数组的区别:

  • 指针数组:本质是数组,只是是指针类型的,be like: int* p[5]
  • 数组指针:本质是指针,类似于结构体指针,指向的是一个完整的数组(比如二维数组的某一行),be like: int (*p)[5]
  • 内存与大小区别:指针数组存多个指针,sizeof 返回数组总大小;数组指针只存单个地址,sizeof 返回指针大小

数组指针的定义与赋值:

1
2
3
4
5
6
7
8
9
10
int arr[5] = {1,2,3,4,5};  //数组
int (*px)[5]; //数组指针

//赋值
px = (int (*)[5])arr; //数组指针指向arr数组(首地址)

//赋值
//&arr[0]什么含义?--取数组第一个数的地址,结果是int*类型
//&arr什么含义?--取数组的首地址,结果是int (*)[5]类型
px = &arr;

数组指针的特征探测:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int (*px)[5];    //定义数组指针
printf("%d", sizeof(px)); //4,数组指针的本质依旧是指针,所以sizeof是4

//赋值
px = (int (*)[5])10; //赋值的时候要强转

//探测++
printf("%d\n",px);
px++;
printf("%d\n",px); //int (*)[5]的宽度去掉一个*:int宽度*5 = 20,10+20 = 30

//探测+整数
char (*p)[6];
p = (char (*)[6])10;
p = p + 5;
printf("%d\n", p); //注意变成char*了,10+6*5 = 40

用数组指针获取元素内容:

1
2
3
4
5
6
7
8
9
10
int arr[16] = {
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
};
int (*px)[2];
//获取元素的内容
px = (int (*)[2])arr;
//*(px)是啥类型? int [2]类型
printf("%d\n",*(*(px))); //输出1
//px里面存的是数组的首地址,*px代表数组的首地址,*(*px)才是要去数组里的内容
//所以取数组指针里的值必须得有俩*才行

这里可能有点绕,为什么 *px 是数组首地址?我们知道 int *p = &a 这个句子里面 *p 是 a 的值,为什么 *px 就不是数组里的值呢?
关键是:px 指向的不是 int,而是有 2 个 int 的数组(px指向 int[2]),类型关系如下:

1
2
3
4
5
6
px        // 类型:int (*)[2],指向 int[2] 的指针
*px // 类型:int[2],也就是一个数组
**px // 类型:int,也就是数组的第一个元素
(*px)[0] // int,第一个小数组里的第一个元素
(*px)[1] // int,第一个小数组里的第二个元素
px[1][0] // int,下一组小数组两个 int 的第一个,等价于(*(px + 1))[0]

所以更准确地说,*px 本身是一个 int[2] 数组,只是数组在大多数表达式中会退化为指向首元素的指针,所以我们才说 *px 是数组的首地址。
在本例中,px 和 *px 的值相同,是因为它们都从同一块内存开头开始,但是他们的步长是不一样的,当涉及到加减时,px+1 跳过一整个小数组,也就是 2 个 int,*px+1 只跳过一个 int。
包括后面的计算也是,我觉得把这个数组指针视为大数组和小数组会比较便于理解,海东老师上课讲的根据数据类型计算字节,再换算成有几个 int,然后去数数有点太麻烦了,但还是得看一眼,毕竟计算数据类型的过程也有利于提升对指针的理解,接下来就来看看。

1
2
3
4
5
6
7
int arr[16] = {
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
};
int (*px)[2];
px = (int (*)[2])arr;
printf("%d %d",*(*(px+1)+1),px[1][1]); //4
printf("%d %d",*(*(px+2)+1),px[2][1]); //6

直接看 *(*(px+2)+1) 吧,这个懂了上面那个也没问题了。首先 px 是 int (*)[2] 类型,去掉一个 * 是 int[2],宽度为 2 个 int 也就是 8 字节,那 px+2 就是 +2×8=16 字节,也就是 4 个 int。然后 *(px+2) 的类型是 int[2],他去参与 +1 运算你把它当成 int* 用,那去掉一个 * 后为 int 型,宽度为 4 字节,即 1 个 int,那么就是从上一步的基础上往后再数 1 个 int 地址,总共走了 5 个 int,最后取值 arr[5] = 6。

补充:int *p[5] 与 int (*p)[5] 有什么区别?回到最开始介绍的问题上了,一个是指针数组一个是数组指针的区别。原因如下:

  • [] 的优先级高于 *,所以先组合成 p[5] 数组,再由 int * 说明数组存储的类型
  • () 的优先级高于 [],所以 * 先和 p 组合成指针,再由 int[5] 说明数组的宽度

课后作业

  1. *() 与 [] 是可以互换的,也就是说:*(*(p+1)+2) 相当与 p[1][2]。那 *(p+1)[2] 是否一定等于 p[1][2] 呢? 通过反汇编进行论证

    不一样,因为 [] 的优先级比一元 * 高,p[1][2] 应当等价于 (*(p+1))[2],而 *(p+1)[2] 是相当于 *((p+1)[2])。

    1
    2
    3
    4
    5
    (p+1)        类型int (*)[2],指向小数组{3,4}
    (p+1)[2] 等价于*((p+1)+2)=*(p+3)
    p+3 类型int (*)[2],偏移3组=3×8=24字节=6个int,到arr[6]
    *(p+3) 类型int[2],内容是arr[6],arr[7]={7,8}
    最外层* 作用在这个int[2]上时退化成int*,再取第一个int

    所以 *(p+1)[2] == arr[6] == 7

    关键区别:

    1
    2
    p[1][2]        // 先 p[1],再 [2]
    *(p+1)[2] // 先 (p+1)[2],再外面的 *

    看一下汇编:

    汇编长得不一样,所以确实是俩不一样的

  2. 使用数组指针遍历一个一维数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include "stdafx.h"
    int main(int argc, char* argv[]) {
    int arr[16] = {
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
    };
    int (*px)[1];
    px = (int (*)[1])arr;
    for (int i = 0; i < 16; i++) {
    printf("%d ", *(*(px + i)));
    }
    return 0;
    }