1分钟-从例题加深对C语言中指针的了解

指针试题1:

解析:

数组a是一个整型数组,数组有5个整形元素。

数组名a在大部分情况下表示的数组首元素的地址,除了两个情况

1:数组名单独作为sizeof操作数的时候,表示的是数组的大小

2:&数组名表示的是数组的地址

&a表示的数组的地址,&a + 1,即指向了数组最后一个元素的下一个内存单元的地址

从指针的角度看

指针的类型决定了指针+/-整数,跨越的距离,指针的类型是 int (*)[5], 表示其指向了一个含有5个整形元素的数组,prt + 1,跨域的距离是20字节

所以,实际上&a + 1就如下

然后将&a + 1强制类型转换成(int*)类型,赋值给int*类型的指针变量ptr,就相当于,ptr也保存了0x0000FF24这个地址,它会将该地址所指向的内存单元中的值以整形的角度去看待。

接下来就是*(a + 1),a是数组首元素的地址,a + 1实际上就是数组元素a[1]的地址,从指针的角度解读就是

指针的类型决定了指针+/-整数,跨越的距离,指针的类型是int*,表示其指向了一个整形,所以str + 1,跨越的距离是4字节,即指向了0x0000FF14,即a[1]的地址

 

而*(a + 1)  <==> *(str + 1)  <==> *(&a[1])  <==>a[1]

所以*(a + 1)输出2

最后一个*(ptr -1),ptr-1,指针的类型决定了指针+/-整数,跨越的距离,指针的类型是int*,表示其指向了一个整形,所以ptr - 1,跨越的距离是4字节即指向了0x0000FF20,

即数组最后一个元素a[4]的地址,同样的对其解引用,就得到了a[4]

所以*(ptr -1) 输出5

指针试题2:

解析:

0x1 <==> 0x00000001 <==> 1

指针变量p的类型是结构体指针类型,即struct Test*类型。并且,可以通过计算,得到一个结构体变量内存的内存空间的大小是20字节, 也就是指针变量p +/-1跨越的距离是20字节。

p+1, 指针变量p中存放的值是0x100000,即0x00100000, 指针变量+/-整数,跨越的距离取决于指针类型,由上述可知,跨越的距离是20字节,所以,实际上p + 1就指向了如下的地址。

 

 

所以p + 1指向的地址是0x00100014,然后将该值以十六进制打印出来,得到的结果就是00100014

(unsigned long)p + 1,将指针变量p强制类型转换成unsigned long类型,强制类型转换改变的只是我们看待它的方式,而并没有改变其在内存中的二进制值。指针变量在内存中存放的是地址,即0x00100000,所以将该值当成是无符号整形看待

 (unsigned long)p + 1就相当于两个整数相加

即0000 0000 0001 0000 0000 0000 0000 0000

+ 0000 0000 0000 0000 0000 0000 0000 0001

= 0000 0000 0001 0000 0000 0000 0000 0001

将该二进制值以%p(十六进制)的形式打印,得到的结果是 00100001

(unsigned int*)p + 1,将指针变量p强制类型转换成unsigned int*类型,指针的类型决定了指针+/-整数跨越的步长,p从struct Test*变成了unsigned int*,表明其指向了一个unsigned int类型的元素,所以其+1跨越的步长从20字节变成了4字节

所以,p + 1实际上指向了如下位置

 

 

所以将0x00100004以%p(十六进制)打印到标准输出上,就是00100004

指针试题3:

解析:

数组a是一个有4个元素的整形数组。

&a表示的数组的地址,&a + 1表示的是数组最后一个元素下一个内存单元的地址

从指针的角度看待就是

指针的类型决定了指针+/-整数跨越的步长,ptr指针的类型是int(*)[4],表明其指向了一个具有4个元素的一维数组,所以其+1跨越的步长是16字节,所以&a + 1如下所示

然后将&a + 1强制类型转换成(int*)类型,赋值给int*类型的指针变量ptr1,就相当于,ptr1也保存了0x0000FF20这个地址,它会将该地址所指向的内存单元中的值以整形的角度去看待。

然后ptr1[-1] 实际在底层被解析成 *(ptr1 -1),ptr1是int*类型,表示其指向了一个整形元素,所以ptr1-1跨越的距离是4个字节。所以等于指向了0x0000FF1C,也就是数组元素a[3]的地址。也就是说,ptr1-1之后,ptr1中保存的就是a[3]的地址0x0000FF1C,而对其解引用也就是取出了a[3]所在的内存空间的值,也就是a[3],所以以%x(十六进制)的形式输出,结果输出4

(int)a + 1, a是数组首元素的地址,即&a[0], 即0x0000FF10,将a强制类型转换成整形,那么就是将0x0000FF10的值当成整形看待,而该值+1,就相当于是

0000 0000 0000 0000 1111 1111 0001 0000

0000 0000 0000 0000 0000 0000 0000 0001

= 0000 0000 0000 0000 1111 1111 0001 0001  <==>0x0000FF11

然后将该值又强制类型转换成了(int*)类型,即将0x0000FF11作为地址,存放到整形指针变量ptr2中,如下所示

 

然后*ptr2,对指针变量解引用,也就是通过指针变量中存放的地址,找到该地址所在的内存空间,然后根据指针类型的大小,读取其对应权限大小的空间内容,ptr2是int*类型的指针,其操作权限是4字节,所以从0x0000FF11所在的内存单元开始,读取4个内存单元的值,

这个时候,就需要考虑到内存单元中存放的是啥,所以要考虑到大端存储和小端存储的问题

如下所示

如果是大端存储

那么,从地址0x0000FF11开始,在内存中连续的4个内存单元的值是00 00 01 00

按照大端存储的方式取出,即数据高位在低地址,数据低位在高地址,

还原原数据是:0x00000100

如果是小端存储

那么,从地址0x0000FF11开始,在内存中连续的4个内存单元的值是00 00 00 02

按照小端存储的方式取出,即数据高位在高地址,数据低位在低地址,

还原原数据是:0x02000000

所以,根据不同的机器,最后一个得到的结果以%x形式输出,得到的可能是

100, 或者2000000

指针试题4:

解析:

数组a是一个3行2列的二维数组,这里最大的一个坑,在于二维数组的初始化

可以看到,代码中的初始化是使用了逗号表达式,而逗号表达式的执行原理是从左到右执行每个表达式,最终的结果以最右边的表达式为准

所以,上述初始化其实等价于

 

而如果真正想要使用上述012345完成完全初始化,那么需要如下方式

了解了坑所在,这道题就好做了,p[0]在底层是被解析成了 *(p + 0), 而p是一个整形指针,p指向了a[0], 即p中保存了a[0]的地址,a[0]是二维数组的首元素,即可以看成是一个一维数组的数组名。

 

数组名表示的是数组首元素的地址,所以,相当于指针变量p指向了a[0]数组的首地址,

相当于&a[0][0],所以,p + 0,还是等于p,然后解引用就得到了a[0][0],也就是1

指针试题5:

解析:

看到这道题要明确的第一个概念是,当两个指向同一块内存空间的指针相减,得到的是两个指针之间的元素个数。

数组a是一个5行5列的二维数组,其在内存中的分布可以如下所示

 

假设说a[4][2]的地址就是0x0000FF68,现在再来考虑指针变量p,可以看到,p是一个数组指针,其类型是int(*)[4],表示其指向的是一个含有4个元素的数组,其+/-1跨越的距离是一个数组的距离,即16字节

p = a;这里a是二维数组的数组名,即其首元素的地址。数组a的首元素是一个一维数组a[0],a[0]相当于是一个一维数组的数组名,a[0]的地址,就是这个一维数组的数组地址,即&a[0], 但是因为数组在内存中是连续存储的,所以,实际上数组的地址,数组名(数组首元素的地址)实际上指的都是相同的一块内存单元的编号(地址),也就是如上的0x0000FF10,也就是p中保存了该地址。

p[4][2]在底层被解析成 *(*(p + 4) + 2), 通过p+4以及p的类型int(*)[4],可知p跨越了64字节,也就是来到了如下所示所在的内存单元

 

而*(p + 4)对其解引用,就得到了p[4], 在这里,需要扩展的一点是,p它不知道自己指向的是一个5*5的二维数组,它只知道自己指向的是一个含有4的元素的一维数组,所以,当p指向0x0000FF58这个内存单元的时候,它其实是把这个地址当成是一个含有4个元素的一维数组的地址去解读的,也就是从0x0000FF50作为数组地址开始,一直往后4个整形内存空间,在p看来就是一个数组,所以,*(p + 4)就相当于拿到了那个数组的数组名, 然后

*(p + 4) + 2 <==> p[4] + 2,这里p[4]既然数组的数组名,p[4]+2就相当来到了该数组下标为2的元素位置,也就是如下的位置

这里实在不明白,可以换成指针的角度看,指针p+4之后,指向了0x0000FF50这个内存的单元,因为p是一个数组指针,其指针类型是int(*)[4], 所以,在p认为,0x0000FF50这个地址就是数组的地址,所以,在p看到,上述空间就如下所示

从代码的角度看,完全可以看成这样

所以,*(p + 4)就等价于array, 所以 *(p + 4) + 2就等价于 array + 2,

所以 *(*(p + 4) + 2)在解引用就等价于array[2],而&p[4][2]就等价于&(*(*(p + 4) + 2)),即

&array[2],所以相当于是拿到了下标为2这个元素的地址

在图上可得,地址是0x0000FF58

现在就回到了地址相减的问题上,指向同一块内存的两个指针相减,得到的两个指针之间的元素个数,也就是0x0000FF58 – 0x0000FF68之间的元素个数,因为都是整形指针,所以,之间元素个数是4个,但是因为是低地址-高地址,所以结果是-4

然后就是将-4分别以%p和%d的形式打印,%d毋庸置疑,打印-4,而%p是十六进制打印

-4的补码是 1111 1111 1111 1111 1111 1111 1111 1100, 转换成十六进制就是0xFFFFFFFC

所以最终结果:0xFFFFFFFC和4

 

指针试题6:

解析:

数组aa是一个2行5列的二维数组,数组每个元素都有了确定值。

指针变量ptr1和ptr2都是一个整形指针,其+/-1跨越的距离是4字节

&aa表示的二维数组其数组的地址,从指针的角度去理解就是

&aa+1就等价于 p + 1,指针p的类型是 int(*)[2][5],所以其跨越的距离是40字节,相当于跨越了一整个二维数组,指向了二维数组最后一个元素的下一个内存单元,如下所示

然后将(p + 1)强制类型转换之后,赋值给指针变量ptr1,因为ptr1是int*类型,所以,它会将该地址以整形的角度去看待。

*(ptr1 -1),ptr1是整形指针,跨越的距离是4字节,ptr1-1,就指向了aa[1][4]所在的内存单元,即保存了该内存单元的地址,然后解引用*(ptr -1),又因为ptr1是int*类型,其解引用的权限是4字节,所以就相当于拿到了aa[1][4]这个元素。所以输出10

*(aa + 1),这里aa是二维数组的数组名,表示其首元素的地址,即&aa[0],从指针的角度去理解,就是

 

所以 aa + 1 <==> p + 1, p的类型是int (*) [5],所以其跨越的距离是20字节,即指针指向了如下位置

该内存单元不仅表示aa[1]这个数组首元素aa[1][0]的地址,也表示了数组aa[1]的地址,但是因为p是数组指针,所以p+1,指向该内存单元之后,其看待该地址的方式就是一个数组的地址。

所以(aa + 1)就相当于拿到了aa[1]这个一维数组的地址而*(aa + 1)就相当于该数组的数组名,aa[1]是一个一维数组,其数组名就是首元素的地址,首元素本来就是一个整形,所以强不强转无所谓,然后赋值给ptr2。

*(ptr2 - 1),同ptr1,指针变量ptr2是int*类型,其+/-1跨越的距离是4字节,所以相当于指向了aa[0][5]所在的内存单元,然后解引用就拿到了aa[0][5],所以输出5

指针试题7:

解析:

数组a是一个指针数组,数组一共有三个元素,a[0], a[1], a[2]

每个元素都是一个char*的指针,分别指向了常量字符串 “work”, “at”, “alibaba”,如下所示

char**pa = a; 这里a是数组名,表示的是数组首元素的地址,数组首元素是char*的指针,所以其地址可以用二级指针pa接收,也就是说,指针pa中存放了a[0]的地址0x0000FF10,

pa++,pa是char**类型,其+/-整数跨域的距离是一个指针所在内存空间大小,也就是

4字节所以指向了a[1]所在的内存空间,也即是pa中保存了a[1]的地址0x0000FF14,

相当于

 

而*pa,就相当于得到了a[1], a[1]就存放的地址是0x00000F15, 也就是字符串”at”的首地址,将其以%s(字符数组)的形式格式化输出,就相当于输出了”at”

指针试题8:

解析:

直接画图,先明白指针数组c,指针数组cp,以及三级指针的内存分布

**++cpp,cpp中存放了数组cp的数组名,相当于保存了其数组首元素的地址,即0x00000FF0,

又因为++优先级高于*,所以先前自增,cpp类型是char***,其+/-1跨越的距离是4字节,所以相当于指向了0x00000FF4所在的内存单元。即

 

第一次*cpp,等价于拿到了cp[1], cp[1]是一个二级指针,其值是0x0000FFFC,是数组c的元素c[2]的地址,如果从指针看的话,如下所示

所以*(cp[1])  <==> *(cp_1)  <==> c[2]

然后将c[2]中的地址0x00000F1传递给printf函数,以%s格式化输出字符串”POINT”

Ps:要注意 cpp指针经过这次的++之后,已经指向了cp[1]所在的内存空间,即cpp中保存的地址是0x00000FF4

*--*++cpp + 3,依旧是++优先级最高,所以先++,cpp类型是char***,其+/-1跨越的距离是4字节,所以相当于指向了0x00000FF8所在的内存单元。即

*cpp等价于拿到了cp[2], cp[2]是一个二级指针,其值是0x0000FFF8,是数组c的元素c[1]的地址,如果从指针看的话,如下所示

然后—cp[2]  <==> --cp_2,这里cp_2是二级指针,其+/-1跨越的距离是一个指针所占用内存空间大小的距离,即4字节,所以,--cp_2相当于cp_2指针指向了0x0000FFF4所在的内存单元,即cp[0]的地址,从指针看的话就是

而解引用之后,就相当于拿到了c[0],所以*--*++cpp + 3变成了c[0] + 3,

c[0]是一个一级指针,其指向了一个常量字符串,从指针角度看的话,如下所示

c[0] + 3  <==> c_0 + 3,指针变量c_0是char*类型的指针,其+/-3跨域的距离是3字节,所以相当于c_0指向了字符串”ENTER”中第二个E所在的内存单元,相当于c_0中保存了0x00000F13, 然后将c_0中的地址0x00000F13传递给printf函数,以%s格式化输出字符串”ER”

Ps:要注意 cpp指针经过这次的++之后,已经指向了cp[2]所在的内存空间,即cpp中保存的地址是0x00000FF8

*cpp[-2] + 3,在底层被解析成 *(*(cpp -2)) + 3,这里cpp-2,cpp类型是char***,其+/-2跨越的距离是8字节,所以相当于指向了0x00000FF0所在的内存单元。即

然后解引用*(cpp -2 )就相当于拿到了cp[0],cp[0]是一个二级指针,其值是0x0000FFFF,是数组c的元素c[3]的地址,如果从指针看的话,如下所示

然后解引用,*(*(cpp -2))  <==> *(cp[0])  <==> *(cp_0)  <==> c[3],相当于拿到了c[3],

c[3]是一个一级指针,其指向了一个常量字符串,从指针角度看的话,如下所示

c[3] + 3  <==> c_3 + 3,指针变量c_3是char*类型的指针,其+/-3跨域的距离是3字节,所以相当于c_0指向了字符串”FIRST”中S所在的内存单元,相当于c_3中保存了0x00000F26, 然后将c_3中的地址0x00000F26传递给printf函数,以%s格式化输出字符串”ST”

Ps:要注意 cpp指针这次并没有改变指向,仍然指向了cp[2]所在的内存空间,即cpp中保存的地址依旧是0x00000FF8

cpp[-1][-1] + 1在底层被解析成 *(*(cpp -1) - 1) + 1,这里cpp-1,cpp类型是char***,其+/-1跨越的距离是4字节,所以相当于指向了0x00000FF4所在的内存单元。即

然后解引用*(cpp -1 )就相当于拿到了cp[1],cp[1]是一个二级指针,其值是0x0000FFFC,是数组c的元素c[2]的地址,如果从指针看的话,如下所示

*(cpp -1) – 1  <==> cp[1] – 1  <==> cp_1 – 1, 这里cp_1是二级指针,其+/-1跨越的距离是一个指针所占用内存空间大小的距离,即4字节,所以,cp_1 - 1相当于cp_1指针指向了0x0000FFF8所在的内存单元,即cp[1]的地址,从指针看的话就是

而解引用之后,就相当于拿到了c[1],所以*(*(cpp -1) - 1) + 1变成了c[1] + 1,

c[1]是一个一级指针,其指向了一个常量字符串,从指针角度看的话,如下所示

c[1] + 1  <==> c_1 + 1,指针变量c_1是char*类型的指针,其+/-1跨域的距离是1字节,所以相当于c_1指向了字符串”NEW”中E所在的内存单元,相当于c_1中保存了0x00000F18, 然后将c_1中的地址0x00000F18传递给printf函数,以%s格式化输出字符串”EW”

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值