前言
接下里会用吞噬星空中对怪兽的级别命名来定义题目难度。初级兽兵级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和&连用时,才是代表整个数组,此外都是代表第一行的地址(二维数组),第一个元素的地址(一维数组),为什么是这样设计呢?因为方便遍历,而要取出整个数组的地址则需要用&+数组名。
这篇博客是我至今耗时最多的博客,希望大家能给个赞,以兹鼓励。大家如果有什么补充的,请在下方进行评论。