你对指针理解了吗?

本文阅读量 Posted by Kird on 2020-08-18

想知道对指针的理解怎样?试着看下这道题吧。这个题是我在学习指针的时候测试的,包括指针运算,指针数组,数组指针等易混淆的概念,如果这题能想出结果的话,基本就过关啦!

1
2
3
4
5
6
7
8
9
int main()
{
int a = 0;
int b = 1;
int *c[2] = {&a, &b};
int *(*pc)[2] = &c;
int **int_pc = (int **)(pc + 1);
printf("%d", **(int_pc - 1));
}

如果有困难或者不太确定结果的话,可以继续看后面的内容。本文为个人记录所用,如有问题请留言指正。

接下来拆解几个容易混淆的概念,记忆这些概念的前提是理解所表示的含义。

指针和数组

指针数组和数组指针容易混淆的地方是名字太像,大家不要被名字混淆,按照中文的习惯,定语放在名次的前面,指针数组就是数组,数组指针就是指针。那我们先来梳理下指针和数组。

数组:相同类型的数据组合,如[1,2,3,4],[‘a’,‘b’,‘c’,’\0’],[&a,&b]…

指针:就是一个地址,如0x11111111,如果0x11111111的含义是个地址(嗯,0x11111111也有可能是个整数),那么它就是个指针。当然指针一般是存在变量里的,比如a=0x11111111,b=&a…

定义一个变量需要指定类型,b=&a这种形式只是把地址存在了b变量中,那b是什么类型的?

指针有很多类型,如下这几种你肯定见过:

  • int* a=&b;
  • char* a=&b;
  • double* a=&b;

如果b是同一个变量,那么上面几种a的值不都是一样的吗?那上面几种有什么区别的?

指针类型决定了:

  • 指针解引用的时候读取内存中的几个字节,如int* a读取4个字节,char* a读取1个字节;
  • 指针加减1运算的时候地址跳转几个字节,int* a加减1,地址跳转4个字节,char* a地址跳转1个字节;

指针的类型决定指针加1跳转几个字节,如下图:

所以我们可以理解指针类型是一个类型的总称,具体的指针是什么类型,也就是指向的是什么类型的变量,解引用的时候系统怎么读取内存,都需要通过限定给定的类型。那么这个类型总称包含多少种类型呢?

只要是能表示一个类型的,再定义一个指针,就是一个新类型的指针类型了。

我们可以用数组来举例,char a[10] ,char* a[10],char a[5],分别定义了三个数组,如果给这三个数组取地址再赋值给一个指针变量的话,如果不给指针类型,那么就是单纯的一个地址,如果想知道这个地址解引用的时候数组长度是多少,数组里的内容是数字还是地址,就完全不知道了。所以为了区分开来,我们需要这么定义三个数组的指针。

char a[10],为一个10个字符组成的数组,那么定义指针来表示这个数组,方法为:

  • 第一步:定义一个指针变量 (*pa)
  • 第二步:取数组地址,&a
  • 第三步:将地址赋值给指针变量, (*pa) = &a
  • 第四步:确定地址类型, (*pa)[10] = &a,这表示一个指向长度为10的数组的指针,即地址
  • 第五步:描述数组的类型 char (*pa)[10] = &a

我们已经知道了一个指向数组的指针的定义方式了,按照这个步骤定义下指向 char* a[5] 的指针吧!

  • 第一步:定义一个指针变量 (*pa)
  • 第二步:取数组地址,&a
  • 第三步:将地址赋值给指针变量, (*pa) = &a
  • 第四步:确定地址类型, (*pa)[5] = &a,这表示一个指向长度为10的数组的指针,即地址
  • 第五步:描述数组的类型,数组的类型为char* ,所以最终定义为 char* (*pa)[5] = &a

数组指针和指针数组

聪明的你已经发现,上节中举例定义一个指针的时候,就是用的数组指针,也就是说数组指针是指针类型中的一种,但是数组指针也是一个总类,因为数组有太多种啦,比如,5个int元素的数组的指针,10个int*元素的数组指针。明白数组指针后看看指针数组吧!

指针数据就是数组。

按照上节的定义方法:

  • 第一步:定义一个数组a [2]
  • 第二步:描述数组存放的元素类型,为指针。何为指针,即地址 ,char* a[2] = {&a,&b};

有没有发现,在数组指针定义的时候,写的char (*pa)[10]char* a[2]是不是有点像,这也就是为什么定义数组指针的第一步中增加括号表示(*pa)

题目分析

1
2
3
4
5
6
7
8
9
int main()
{
int a=0;
int b=1;
int* c[2]={&a,&b}; //定义指针数组
int* (*pc)[2]=&c; //定义一个指针数组的数组指针,指针指向一个2个元素的数组,数组的类型是int型指针(int*)
int** int_pc=(int**)(pc+1);//数组指针+1,指向下一个数组,强制转换成int型号指针,指针地址指向为int*
printf("%d",**(int_pc-1));//int_pc指针减1,地址减去一个int大小,指向&b,第一次解引用得到&b,再次解引用得到b的内容
}
1
int* c[2]={&a,&b};

定义一个数组,数组类型为指针,即指针数组。初始化为a,b变量的地址;

1
int* (*pc)[2]=&c;

定义一个指针(*pc),指针类型为长度为2的数组,即 (*pc)[2],数组内存放的类型为int*

1
int** int_pc=(int**)(pc+1);

pc+1为指针运算,前面讲解了指针类型决定加减1运算的时候地址跳转几个字节,int* a加减1,地址跳转4个字节,char* a地址跳转1个字节,那么pc+1跳转了“pc类型大小”的字节。pc类型是一个2个元素的数组,数组大小取决于数组元素大小,元素为指针,所有“pc类型大小”为2个指针的大小,即8个字节。其实pc+1就是指向了c[2]={&a,&b}内存的末尾。

1
(int**)(pc+1)

这是一个强制类型转换:

  • 原类型:pc+1的类型,即“pc的类型”,即“pc类型是一个2个元素的数组,元素为int*指针”
  • 目的类型:(int**),即“一个指向int*的指针”

好,那么(int**)加减一操作,跳转几个字节呢?一个int*大小,即4个字节;

1
int_pc-1

上面分析道int_pc就是指向了c[2]={&a,&b}内存的末尾,那么int_pc-1就是指针往前挪了4个字节,即挪到了&b这个数组单元的地址。

1
**(int_pc-1)

两次解引用得到b的值,即为1;

再来一题

1
2
3
4
5
6
7
int main()
{
int a[5] = {1, 2, 3, 4, 5};
int *ptr = (int *)(&a + 1);
printf("%d,%d\n", *(a + 1), *(ptr - 1));
return 0;
}

输出:

1
2,5


支付宝打赏 微信打赏

赞赏支持一下