C语言指针练“级”题(相信我,让你的头发掉得值)

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: C语言指针练“级”题(相信我,让你的头发掉得值)

前言

接下里会用吞噬星空中对怪兽的级别命名来定义题目难度。初级兽兵级H→中级兽兵级G→高级兽兵级F→初级兽将级E→中级兽将级D→高级兽将级C→初级领主级B→中级领主级A→高级领主级S→王级怪兽SS(行星级)→兽皇(较强大的行星级)。

我们的等级:战士(初-中-高)、战将级(初-中-高)、战神(初-中-高);行星(1-9)——恒星(1-9)——宇宙(1-9)——域主(1-9)——界主(1-9)。

如果这些题都搞明白了,段位就会来到高级战神水平了

假设操作系统是32位,大家最喜欢的,最健康的颜色的部分为答案(doge)(加深的绿色部分为答案)。

希望各位小伙伴能给我一波四连(关注,点赞,收藏,转发)

一、难度:中级兽将级

1.一维数组

int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));//(1)
printf("%d\n", sizeof(a + 0));//(2)
printf("%d\n", sizeof(*a));//(3)
printf("%d\n", sizeof(a + 1));//(4)
printf("%d\n", sizeof(a[1]));//(5)
printf("%d\n", sizeof(&a)); //(6)
printf("%d\n", sizeof(* &a));//(7)
printf("%d\n", sizeof(&a + 1));//(8)
printf("%d\n", sizeof(&a[0]));//(9)
printf("%d\n", sizeof(&a[0] + 1));//(10)

(1)printf("%d\n", sizeof(a));
解析:sizeof(数组名) - 数组名表示整个数组的-计算的是整个数组的大小,所以是整个数组的大小,
也就是4*4=16。

(2)printf("%d\n", sizeof(a + 0));
解析: a + 0 是第一个元素的地址,sizeof(a + 0)计算的是地址的大小,地址
大小为4。只有sizeof直接加数组名,没有其他乱七八糟的,才是计算整个数组的大小。

(3)printf("%d\n", sizeof(*a));
解析:*a表示一维数组的第一个元素值的大小,也就是一个int的
大小为4。

(4)printf("%d\n", sizeof(a + 1));
解析:sizeof(a+1)的其中的a不代表整个数组,而是第一个数组的元素的地址,a+1就是第二个数组元素的地址,
大小是4个字节。

(5)printf("%d\n", sizeof(a[1]));
解析:a[1]在编译时为*(a+1),是第二个元素的值的大小,也就是
4个字节。

(6)printf("%d\n", sizeof(&a));
解析:&a表示计算数组的大小,它的类型是int(*)[4],
大小是4*4=16。

(7)printf("%d\n", sizeof(* &a));
解析:&和*相互抵消,就剩下a,也就是第一个元素的地址。
大小是4个字节。

(8)printf("%d\n", sizeof(&a + 1));
解析:我们得明确一点一个表达式具有值属性和类型属性,值属性是通过运算得出,而类型属性是通过推断得出了,也就是编译时期,sizeof就是进行了类型推断,发生在编译期。&a+1分两步走,&a代表了整个数组的地址,&a+1代表了整个数组前进了一步(步长为4*4=16),得到了数组后面的空间地址,那么无论是什么,
地址的大小都是4个字节地址===指针。图示如下:

(9)printf("%d\n", sizeof(&a[0]));
解析:[]的优先级高于&,a与[]先结合a[0]等于*(a+0),表示第一个元素的值;再与&结合又变成了第一个元素的地址,那么地址的
大小就是4个字节。

(10)printf("%d\n", sizeof(&a[0] + 1));
解析:[]的优先级高于&,a与[]先结合a[0]等于*(a+0),表示第一个元素的值;再与&结合又变成了第一个元素的地址,元素的指针指向的类型是int,所以步长为4个字节,&a[0] + 1代表了第一个元素的地址跨一步,也就到了第二个元素的地址,
地址大小也是4个字节。

2.字符数组1.0

//字符数组
char arr[] = { 'a','b','c','d','e','f' };
//strlen
printf("%d\n", strlen(arr));//(1)
printf("%d\n", strlen(arr + 0));//(2)
printf("%d\n", strlen(*arr));//(3)
printf("%d\n", strlen(arr[1]));//(4)
printf("%d\n", strlen(&arr));//(5)
printf("%d\n", strlen(&arr + 1));//(6)
printf("%d\n", strlen(&arr[0] + 1));//(7)
//sizeof
printf("%d\n", sizeof(arr));//6
printf("%d\n", sizeof(arr + 0));//4/8
printf("%d\n", sizeof(*arr));//1
printf("%d\n", sizeof(arr[1]));//1
printf("%d\n", sizeof(&arr));//4/8
printf("%d\n", sizeof(&arr + 1));//4/8
printf("%d\n", sizeof(&arr[0] + 1));//4/8

strlen

是不是有许多小伙伴没有分析怪兽是什么品种就莽上去,那不就是在送嘛!前七行是strlen,不是sizeof,不要问我为什么要这么强调,因为吾有一友就是这样送掉的。

遁天梭(分析strlen和char arr[]的特点):①strlen的定义:strlen(const char* string);strlen函数当遇到0或’\0’是就会停下来,记录遇到0或’\0’之前的字符有几个。②通过花括号的挨个元素赋值是没有自动加’\0’的。

(1)printf("%d\n", strlen(arr));
解析:arr是数组名,复习一下:数组名没有和sizeof或&连用时,代表了数组的第一个元素的地址,此题arr代表了’a’的地址。通过使用遁天梭发现了strlen什么时候停下来是不可知的,
所以为随机值。

(2)printf("%d\n", strlen(arr + 0));
解析:arr代表了第一个元素的地址,arr+0表示该地址原地踏了一步,strlen又开始从这个地址向后记录个数,直到遇到’\0’为止,而我们没有给定’\0’,
所以结果为随机值。

(3)printf("%d\n", strlen(*arr));
解析:*arr的类型是char,strlen的参数类型是char*。两者的类型都对不上,这已经被怪兽一口吞了好吧(寄),
结果为err。

(4)printf("%d\n", strlen(arr[1]));
解析:arr[1]等于*(arr+1)代表了第二个元素的值,也就是’b’,它的类型是char,strlen的参数类型是char*。两者的类型都对不上,
结果为err。

(5)printf("%d\n", strlen(&arr));
解析:&arr代表了整个数组的地址,也是这个数组的开头地址,也就是第一个元素的地址,同样的道理,我们没有给定’\0’,
所以结果为随机值。

(6)printf("%d\n", strlen(&arr + 1));

解析:我们知道了&arr代表了整个数组的地址,类型是char(*)[6],步长(1*6=6)。所以&arr+1代表了从第一个元素的地址跨了6个字节,来到了数组末,strlen从改地址开始向后记录个数,直到遇到’\0’为止,因为’\0’的位置是未知的,所以结果就是随机值。如果精神念力高(基础比较好)的小伙伴就能感到一丝不同,相比上面的随机值,是不是应该写成随机值-6更好呢?为什么呢?因为别人是从数组头开始算,你是从数组末开始,是不是就差了6个字符长度也就是6个字节长度嘛。图示如下:

(7)printf("%d\n", strlen(&arr[0] + 1));
解析:[ ]的优先级高于&,arr先与[0]结合等于*(arr+0),arr+0表示该地址原地踏了一步,解引用得到里面的元素值’b’,接着&又取出了’b’的地址,strlen从此地址向后记录个数,直到遇到’\0’为止,所以结果是
随机值,类比(6)可知是不是写成随机值-1更好点呢?

sizeof

(8)printf("%d\n", sizeof(arr));
解析:再再复习一下:数组名和sizeof或&连用时代表了整个数组,sizeof代表数组大小,&代表整个数组的地址。char arr[6]的数组
大小为1*6=6

(9)printf("%d\n", sizeof(arr + 0));
解析:arr代表第一个元素的地址,arr+0代表第一个元素的地址原地踏了一步,还是代表第一个元素的地址。那么第一个元素的地址
就是4个字节只有sizeof直接加数组名,没有其他乱七八糟的,才是计算整个数组的大小。

(10)printf("%d\n", sizeof(*arr));
解析:*arr代表了第一个元素的值,*arr的类型是char,所以
大小为1个字节

(11)printf("%d\n", sizeof(arr[1]));
解析:arr[1]等于*(arr+1)中的arr+1代表了第一个元素的地址跨了一步,再解引用得到第二个元素的值,类型是char,
结果是1个字节。

(12)printf("%d\n", sizeof(&arr));
解析:&arr代表了整个数组的地址(整个数组代表了步长和解引用的权限,而存放的地址永远都是第一个元素的地址,这样做方便统一表示地址)。所以&arr虽然代表了整个数组的地址,但是&arr的值指向数组的第一个元素的地址就足够了,
所以就是4个字节

(13)printf("%d\n", sizeof(&arr + 1));
解析:&arr代表了整个数组的地址,类型为char(*)[6],步长1*6=6,&arr+1代表跨过数组arr的字节长度来到了数组末,不管怎么样指针的大小都是为
4个字节图示如下:

(14)printf("%d\n", sizeof(&arr[0] + 1));
解析:[ ]的优先级高于&,arr先与[0]结合等于*(arr+0),arr+0表示该地址原地踏了一步,解引用得到里面的元素值’b’,接着&又取出了’b’的地址。类型是char*,所以
大小为4个字节。

3.字符数组2.0

char arr[] = "abcdef";
printf("%d\n", strlen(arr));//(1)
printf("%d\n", strlen(arr + 0));//(2)
printf("%d\n", strlen(*arr));//(3)
printf("%d\n", strlen(arr[1]));//(4)
printf("%d\n", strlen(&arr));//(5)
printf("%d\n", strlen(&arr + 1));//(6)
printf("%d\n", strlen(&arr[0] + 1));//(7)
printf("%d\n", sizeof(arr));//(8)
printf("%d\n", sizeof(arr + 0));//(9)
printf("%d\n", sizeof(*arr));//(10)
printf("%d\n", sizeof(arr[1]));//(11)
printf("%d\n", sizeof(&arr));//(12)
printf("%d\n", sizeof(&arr + 1));//(13) 
printf("%d\n", sizeof(&arr[0] + 1));//(14)

strlen

(1)printf("%d\n", strlen(arr));
解析:如图所示,在第七位时有个‘\0’,
所以strlen(arr)的长度为6。

(2)printf("%d\n", strlen(arr + 0));
解析:arr代表数组第一个元素的地址,arr+0代表了arr原地踏了一步,strlen从地址开始向后记录字符的个数,直到遇到‘\0’为止,不把‘\0’算进去。
所以结果为6。

(3)printf("%d\n", strlen(*arr));
解析:strlen的定义:strlen(const char* string);strlen函数当遇到0或’\0’是就会停下来,记录遇到0或’\0’之前的字符有几个。*arr表示取得数组的第一个元素的值,类型是char,这显然和strlen需要的参数类型char*不匹配,
所以结果为err。

(4)printf("%d\n", strlen(arr[1]));
解析:arr[1]等于*(arr+1)代表了数组的第二个元素的值,类型是char,这显然和strlen需要的参数类型char*不匹配,
所以结果为err。

(5)printf("%d\n", strlen(&arr));
解析:再再再复习一下:sizeof(数组名) - 数组名表示整个数组的-计算的是整个数组的大小;&数组名 - 数组名表示整个数组,取出的是整个数组的地址。除此之外,所有的数组名都是数组首元素的地址。&arr代表了整个数组,但是取出来的地址值和第一个元素的地址值相等,从地址开始向后记录字符的个数,直到遇到‘\0’为止,不把‘\0’算进去。
所以结果为6

(6)printf("%d\n", strlen(&arr + 1));
解析:&arr+1,代表整个数组跨了一步,步长为1*7(需要把‘\0’算进去,因为这里是整个数组)。strlen又开始从这个地址向后记录个数,直到遇到‘\0’为止,而我们无法确定哪里有‘\0’,
所以结果为随机值图示如下:

(7)printf("%d\n", strlen(&arr[0] + 1));
解析:[ ]的优先级高于&,所以arr和[ ]结合arr[0]等于*(arr+0),表示数组第一个元素的值,接着再执行&,取出第一个元素的地址,元素的类型char。&arr[0] + 1,向前跨了一个字节,从‘b’的地址开始向后记录字符的个数,直到遇到‘\0’为止,不把‘\0’算进去。
所以结果为5

sizeof

(8)printf("%d\n", sizeof(arr));
解析:sizeof(arr)代表了计算整个数组的大小,
1*7=7个字节。

(9)printf("%d\n", sizeof(arr + 0));
解析:只有sizeof直接加数组名,没有其他乱七八糟的,才是计算整个数组的大小,sizeof计算是看类型的大小。arr代表了第一个数组元素的地址,arr+0表示第一个元素地址原地踏了一步,还是表示第一个元素的地址,类型是char*,地址的大小
就是4个字节。

(10)printf("%d\n", sizeof(*arr));
解析:*arr对第一个数组元素解引用,得到了‘a’,类型是char,
所以大小是1个字节。

(11)printf("%d\n", sizeof(arr[1]));
解析:arr[1]等于*(arr+1)表示数组第二个元素的值,类型是char,
所以大小是1个字节。

(12)printf("%d\n", sizeof(&arr));
解析:&arr代表了整个数组的地址(整个数组代表了步长和解引用的权限,而存放的地址永远都是第一个元素的地址,这样做方便统一表示地址)。所以&arr虽然代表了整个数组的地址,但是&arr的值指向数组的第一个元素的地址就足够了,
所以就是4个字节。

(13)printf("%d\n", sizeof(&arr + 1));
解析:&arr代表了整个数组的地址,类型为char(*)[7],步长1*6=6,&arr+1代表跨过数组arr的字节长度来到了数组末,类型还是char(*)[7],不管怎么样指针的大小都是为
4个字节。

(14)printf("%d\n", sizeof(&arr[0] + 1));
解析:[ ]的优先级高于&,所以arr和[ ]结合arr[0]等于*(arr+0),表示数组第一个元素的值,接着再执行&,取出第一个元素的地址,元素的类型char。&arr[0] + 1,向前跨了一个字节,得到‘b’的地址,地址大小
就为4个字节。

4.字符指针

char* p = "abcdef";
printf("%d\n", strlen(p));//(1)
printf("%d\n", strlen(p + 1));//(2)
printf("%d\n", strlen(*p));//(3)
printf("%d\n", strlen(p[0]));//(4)
printf("%d\n", strlen(&p));//(5)
printf("%d\n", strlen(&p + 1));//(6)
printf("%d\n", strlen(&p[0] + 1));//(7)
printf("%d\n", sizeof(p));//(8)
printf("%d\n", sizeof(p + 1));//(9)
printf("%d\n", sizeof(*p));//(10)
printf("%d\n", sizeof(p[0]));//(11)
printf("%d\n", sizeof(&p));//(12)
printf("%d\n", sizeof(&p + 1));//(13)
printf("%d\n", sizeof(&p[0] + 1));//(14)

strlen

(1)printf("%d\n", strlen(p));
解析:p指向字符串的开头,也就是指向了字符‘a’的地址,strlen从这地址开始向后记录字符的个数,直到遇到‘\0’为止,不把‘\0’算进去。
所以结果为6。

(2)printf("%d\n", strlen(p + 1));
解析:p+1代表了从第一个元素的地址前进一步,来到了第二个元素的地址,strlen从这地址开始向后记录字符的个数,直到遇到‘\0’为止,不把‘\0’算进去。
所以结果为5。

(3)printf("%d\n", strlen(*p));
解析:*p对p进行了解引用,得到数组第一个元素的值‘a’。strlen的参数类型是char*,而*p的类型是char,所以
结果为err。

(4)printf("%d\n", strlen(p[0]));
解析:p[0]等于*(p+0),类型是char,与strlen的参数类型不匹配,所以
结果为err。

(5)printf("%d\n", strlen(&p));
解析:p的类型是char*,&p取出一级指针的地址,也就是二级指针类型char**,也是一个地址,strlen从这地址开始向后记录字符的个数,直到遇到‘\0’为止,而我们并不知道哪里有‘\0’,所以
结果为随机值。

(6)printf("%d\n", strlen(&p + 1));
解析:&p + 1,在二级指针&p处,在跨一步,类型是char*,所以
结果为随机值思考一下,这里能不能写成随机值-4呢?答案是不可以的。为什么?因为你不知道&p和&p+1中间是否存在‘\0’,所以&p和&p+1被strlen没有必然的关系。

(7)printf("%d\n", strlen(&p[0] + 1));
解析:[]的优先级高于&,所以p和[]结合p[0]等于*(p+0),表示第一个字符的值‘a’,接着在执行&,取出第一个字符的地址,类型char,&p[0] + 1,向前跨了一个字节,得到第二个字符‘b’的地址,strlen从这地址开始向后记录字符的个数,直到遇到‘\0’为止,
所以结果为5。

sizeof

(8)printf("%d\n", sizeof(p));
解析:p的类型是char*,是一个指针,那么大小就是
4个字节。

(9)printf("%d\n", sizeof(p + 1));
解析:p是一指针,类型是char*,那么p+1也是一个指针,类型是char*,所以大小是
4个字节。

(10)printf("%d\n", sizeof(*p));
解析:*p对p指向的地址进行解引用,得到值‘a’,类型是char,所以大小就是
1个字节。

(11)printf("%d\n", sizeof(p[0]));
解析:p[0]等于*(p+0),得到值‘a’,类型是char,所以大小就是
1个字节。

(12)printf("%d\n", sizeof(&p));
解析:&p是字符指针p的指针,也就是地址,所以大小是
4个字节。

(13)printf("%d\n", sizeof(&p + 1));
解析:&p+1的类型是char**,是一个指针,所以大小是
4个字节。

(14)printf("%d\n", sizeof(&p[0] + 1));
解析:[]的优先级高于&,所以p和[]结合p[0]等于*(p+0),得到第一个字符的值‘a’,再取地址&等到第一个字符的地址,+1后得到第二个字符的地址,结果为
4个字节。

5.二维数组

int a[3][4] = { 0 };
printf("%d\n", sizeof(a));//(1)
printf("%d\n", sizeof(a[0][0]));//(2)
printf("%d\n", sizeof(a[0]));//(3)
printf("%d\n", sizeof(a[0] + 1));//(4)
printf("%d\n", sizeof(*(a[0] + 1)));//(5)
printf("%d\n", sizeof(a + 1));//(6)
printf("%d\n", sizeof(*(a + 1)));//(7)
printf("%d\n", sizeof(&a[0] + 1));//(8)
printf("%d\n", sizeof(*(&a[0] + 1)));//(9)
printf("%d\n", sizeof(*a));//(10)
printf("%d\n", sizeof(a[3]));//(11)
printf("%d\n", sizeof(a[-1]));//(12)

(1)printf("%d\n", sizeof(a));
解析:sizeof+数组名代表整个数组的大小,所以
大小为3*4*sizeof(int)=48。

(2)printf("%d\n", sizeof(a[0][0]));
解析:[]的结合性是从左往右,a[0]编译成*(a+0),得到了第一行第一列的地址==也是第一个一维数组的数组名,a[0][0]编译成*(*(a+0)+0)得到了第一行第一列的元素值,类型是int,所以
大小是4个字节。(数组名代表进入数组一步,例如一维数组就是到第一个元素的地址;二维数组就是第一行的地址。*(二维数组名==行)->列,*(列)->该位置的元素值,解引用*是对地址而言)。

(3)printf("%d\n", sizeof(a[0]));
解析:a[0]编译成*(a+0),a+0代表第一行的地址,*(a+0)代表了第一个第一个一维数组的数组名,也是第一行第一列的地址。那么代表数组名,sizeof(arr[0])就是第一个一维数组的
大小为4*4=16个字节。

(4)printf("%d\n", sizeof(a[0] + 1));
解析:a[0]编译成*(a+0),a+0代表第一行的地址,*(a+0)代表了第一个第一个一维数组的数组名,也是第一行第一列的地址。那么代表第一行第一列的地址+1,得到第一行第二列的地址,也就是
4个字节。

(5)printf("%d\n", sizeof(*(a[0] + 1)));
解析:a与[]结合,编译成*(a+0);*(a[0] + 1))编译成*(*(a+0)+1)。*(a+0)代表第一个一维数组的数组名,也代表了第一行第一列元素的地址。*(a+0)+1代表也代表了第一行第二列元素的地址,*(*(a+0)+1)代表了第一行第二列元素的值,类型是int,所以字节大小为
4个字节。

(6)printf("%d\n", sizeof(a + 1));
解析:a即为二维数组的数组名,也是第一行的地址。可以把a的类型理解为int(*)[4],a+1就跨过了4*4=16个字节来到了第二行的地址,所以结果就为
4个字节。

(7)printf("%d\n", sizeof(*(a + 1)));
解析:a即为二维数组的数组名,也是第一行的地址。可以把a的类型理解为int(*)[4],a+1就跨过了4*4=16个字节来到了第二行的地址。*(a + 1)从数组名维度来思考,是解引用后二维数组名->一维数组名;从第一个元素的维度来思考,是解引用后第一行的地址继续深入变成了第一行第一列的地址。此题要从数组名维度思考,*(a + 1)代表了第一个一维数组的数组名,所以sizeof(*(a + 1))是求整个一维数组的大小
4*4=16个字节。

(8)printf("%d\n", sizeof(&a[0] + 1));
解析:[]的优先级高于&,a与[0]结合为a[0]等于*(a+0),代表了第一个一维数组的数组名,也代表了第一行第一列的地址。接着&取地址后,&a[0]又回到代表第一行的地址,也代表二维数组的数组名。+1后表示第二行的地址,也就是整个第二行数组的地址,类型为int(*)[4],所以结果为
4个字节。

(9)printf("%d\n", sizeof(*(&a[0] + 1)));
解析:[]的优先级高于&,a与[0]结合为a[0]等于*(a+0),代表了第一个一维数组的数组名,也代表了第一行第一列的地址。接着&取地址后,&a[0]又回到代表第一行的地址,也代表二维数组的数组名。+1后表示第二行的地址,也就是整个第二行数组的地址,类型为int(*)[4],*(&a[0] + 1)得到了第二个二维数组的数组名,也是第二行第一列的元素地址,所以结果为
4*4=16个字节

(10)printf("%d\n", sizeof(*a));
解析:*a表示二维数组名->一维数组名、第一行的地址->第一行第一列的元素地址。所以大小是
4*4=16个字节。

(11)printf("%d\n", sizeof(a[3]));
解析:a[3]等于*(a+3),a+3就是二维数组a的第四行,虽然不存在,但是我们并没有进行访问,而是进行了类型推断,所以不会发生下标越界的风险。*(a+3)代表了第四个一维数组的数组名,也代表了第四行第一列元素的地址,类型是int(*)[4],结果是
4*4=16个字节图示如下:

(12)printf("%d\n", sizeof(a[-1]));
解析:a[-1]等于*(a-1),a-1就是二维数组a的第负一行,虽然不存在,但是我们并没有进行访问,而是进行了类型推断,所以不会发生下标越界的风险。*(a-1)代表了第负一个一维数组的数组名,也代表了第负一行第一列的元素地址,类型是int(*)[4],结果是
4*4=16个字节。图示如上:

总结

(1)strlen的定义:strlen(const char* string);strlen函数当遇到0或’\0’是就会停下来,记录遇到0或’\0’之前的字符有几个。

(2)通过花括号的挨个元素赋值是没有自动加’\0’的。

(3)只有sizeof直接加数组名,没有其他乱七八糟的,才是计算整个数组的大小。

(4)sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小;&数组名,这里的数组名表示整个数组,取出的是整个数组的地址;除此之外的所有的数组名都表示首元素的地址。

(5)是否自动加‘\0’看是不是字符串赋值(“字符串”)。

(6)%s要求输出列表是地址,如果解引用了,得到是第一个字符的值。

 (7)int arr[3][4];其中arr是数组名,也是代表第一行的地址(也是后面就是代表整个进去一层,一个二维数组进去一层,看到第一眼就是第一行地址嘛)。

  • arr表示二维数组名时:*arr表示第一个一维数组名;**arr表示第一行第一列元素的值。
  • arr表示第一行地址时:*arr表示第一行第一列元素的地址;**arr表示第一行第一列元素的值。
  • 所以arr有两层意思,一维数组也是一样,有代表一维数组名的意思,也有代表第一个元素的地址的意思。总之记住行、列与值相关;二维数组名、一维数组名和值相关。可以把二维数组的数组名理解为二级指针。

(8)int arr[3][4];sizeof(arr)时,始终是把它当成数组名,而非第一行地址。

二、难度:领主级

1.怪兽A(初级领主级)

int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int *ptr = (int *)(&a + 1);//(1)
    printf( "%d,%d", *(a + 1), *(ptr - 1));//(2)
    return 0; 
}
//程序的结果是什么?

解析:

(1)&a取到整个数组的地址,类型为int(*)[5]。&a+1的结果是跨了一步,步长为4*5=20,来到了数组的末尾,然后将int(*)[5]类型转换成int*类型,赋值给int* ptr。

(2)格式字符都是%d,%d。*(a+1)表示第二个元素的地址执行解引用,得到第二个元素的值2*(ptr-1)表示ptr指向的地址向后退一步,步长为4个字节,指向第五个元素的地址,再进行解引用得到第五个元素的值5图示如下:

运行结果如下:

2.怪兽B(中级领主级)

//这里告知结构体的大小是20个字节
struct Test
{
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p;//(1)
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
 printf("%p\n", p + 0x1);//(2)
 printf("%p\n", (unsigned long)p + 0x1);//(3)
 printf("%p\n", (unsigned int*)p + 0x1);//(4)
 return 0; 
}

解析:

注意容器,地址和值的区分。例如指针p,p是容器,p的值是指向某个地址;整型a,a是容器,a的值是一个非地址的数值。

(1)p是一个结构体指针,步长为20个字节,解引用的权限的20个字节。

(2)p是指针,p+0x1代表指针p向前跨一步,步长为20个字节,大小是:0x100000+20 == 0x100014。

(3)(unsigned long)p进行了强转,则(unsigned long)p+0x1:0x100000+1 == 0x100001

(4)(unsigned int*)p进行了强转,该类型的步长为4个字节,则(unsigned int*)p+0x1:0x100000+4 ==0x100004图示如下:

3.怪兽C(中级领主级)

int main()
{
    int a[4] = { 1, 2, 3, 4 };//(1)
    int *ptr1 = (int *)(&a + 1);//(2)
    int *ptr2 = (int *)((int)a + 1);//(3)
    printf( "%x,%x", ptr1[-1], *ptr2);//(4)
    return 0; 
}

解析:

(1)创建一个一维含有4个整型元素的数组。

(2)&a的类型int(*)[4],步长为4*4=16个字节,&a+1来到了数组末,然后被强转为int*赋值给int* ptr1。

(3)a代表第一个元素的地址,(int)a变成了一个算术值,(int)a+1代表a原本的地址值变成算术值,(int *)((int)a + 1)将int类型强转为int*。

(4)ptr1[-1]等于*(ptr1-1),*ptr2执行解引用。所以结果为0x00000004,0x02000000图示如下:

运行结果如下:

4.怪兽D(中级领主级)

#include <stdio.h>
int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };//(1)
    int *p;
    p = a[0];//(2)
    printf( "%d", p[0]);//(3)
    return 0; 
}

解析:

(1)得注意里面有逗号表达式,因变成int a[3][2] = {1,3,5};

(2)*(a+0)代表第一个一维数组的数组名,代表第一行第一列元素的地址,赋值给int* p。

(3)p[0]等于*(p+0)得到第一行第一列元素的值,值为1图示如下:

  运行结果如下:

5.怪兽E(高级领主级)

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

解析:

(1)int a[5][5];的值是二维数组。

(2)p是数组指针类型int(*)[4],数组名a代表了第一行的数组,同时也表示

(3)[]的优先级高于&地址,例如&p[4][2]编译为&(*(*(p+4)+2)),类型为int(*)[4],步长为4*4=16个字节,代表了第四行第四列的元素地址。指向同块连续内存空间的指针相减是得到他们中间的元素个数差。a[][]本来就是取值的地址,在前面加个&取地址符,不就是该值的地址吗?所以&p[4][2] - &a[4][2] == FFFF FFFC(%p), &p[4][2] - &a[4][2] == -4(%d)图示如下:

  运行结果如下:

6.怪兽F(高级领主级)

int main()
{
    int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//(1)
    int *ptr1 = (int *)(&aa + 1);//(2)
    int *ptr2 = (int *)(*(aa + 1));//(3)
    printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));//(4)
    return 0; 
}

解析:

(1)int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };创建了一个二维数组。

(2)(&aa+1)的指针指向了数组末尾,类型为int(*)[2][5],最终强转成int*赋值给ptr1。

(3)*(aa+1)表示第二个一维数组的数组名,也代表了第二行第一列元素的地址。类型从int(*)[5]强转为int*赋值给ptr2。

(4)*(ptr1 - 1)表示第十个元素的值10,*(ptr2 - 1)表示第五个元素的值5图示如下:

  运行结果如下:

7.怪兽G(中级领主级)

#include <stdio.h>
int main()
{
    char *a[] = {"work","at","alibaba"};//(1)
    char**pa = a;//(2)
    pa++;//(3)
    printf("%s\n", *pa);//(4)
    return 0; 
}

解析:

(1)work的‘w’字符的地址赋值给char* a[0],at的‘a’字符的地址赋值给char* a[1],alibaba的‘a’字符的地址赋值给char* a[2]。

(2)数组名的类型就是数组元素的类型,数组名代表了第一个元素的地址。则char** pa指向了数组char* a[]的首个元素的地址&a[0]。

(3)pa++,pa指向的类型是一级指针,那么步长为4个字节,指向了第二个元素的地址&a[1]。

(4)*pa == a[1],a[1]是char*类型,里存放了at的‘a’字符的地址。%s要求输出列表是地址,符合条件,从而输出字符串“at”图示如下:

运行结果如下:

8.怪兽R(王级怪兽)

int main()
{
    char *c[] = {"ENTER","NEW","POINT","FIRST"};//(1)
    char**cp[] = {c+3,c+2,c+1,c};//(2)
    char***cpp = cp;//(3)
    printf("%s\n", **++cpp);//(4)
    printf("%s\n", *--*++cpp+3);//(4)
    printf("%s\n", *cpp[-2]+3);//(4)
    printf("%s\n", cpp[-1][-1]+1);//(4)
    return 0; 
}

解析:

(1)ENTER的‘E’的地址赋值给char* c[0],NEW的‘N’的地址赋值给char* c[1],POINT的‘P’的地址赋值给char* c[2],FIRST的‘F’的地址赋值给char* c[3]。

(2)c[3]的地址赋值给char** cp[0],c[2]的地址赋值给char** cp[1],c[1]的地址赋值给char** cp[2],c[0]的地址赋值给char** cp[3]。

(3)cp[0]的地址赋值给char*** cpp

(4)**++cpp结果为POINT;*--*++cpp+3结果为ER;*cpp[-2]+3 == **(cpp-2)+3结果为ST;cpp[-1][-1]+1 == *(*(cpp-1)-1)+1结果为EW图示如下:

运行结果如下:

总结

(1)解引用代表要知道指向内存空间里面是什么操作。

(2)只有当数组名和sizeof和&连用时,才是代表整个数组,此外都是代表第一行的地址(二维数组),第一个元素的地址(一维数组),为什么是这样设计呢?因为方便遍历,而要取出整个数组的地址则需要用&+数组名。

这篇博客是我至今耗时最多的博客,希望大家能给个赞,以兹鼓励。大家如果有什么补充的,请在下方进行评论。

相关文章
|
2月前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
52 0
|
14天前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
66 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
14天前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
42 9
|
14天前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
38 7
|
24天前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
86 13
|
17天前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
17天前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
51 3
|
24天前
|
存储 C语言 开发者
C 语言指针与内存管理
C语言中的指针与内存管理是编程的核心概念。指针用于存储变量的内存地址,实现数据的间接访问和操作;内存管理涉及动态分配(如malloc、free函数)和释放内存,确保程序高效运行并避免内存泄漏。掌握这两者对于编写高质量的C语言程序至关重要。
46 11
|
18天前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
17天前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
32 1