携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第12天,点击查看活动详情
前言
在学习了数组和指针后,有可能会认为就这就这,实际上目前也只是学了,跟熟练掌握还差的远呢,需要不断练习来熟悉。墙裂建议还没弄懂数组和指针初阶、进阶的读者先去补一补对应知识,不然可能有些地方会一脸懵逼。
本文就来分享一波作者的C指针刷题心得见解。本篇属于笔试面试真题刷题篇第一篇,后续还有,可以期待一下。
笔者水平有限,难免存在纰漏,欢迎指正交流。
一维整型数组
题目汇总
先看看题,想一想再看后面分析。
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));
讲解
printf("%d\n",sizeof(a));
数组名在sizeof中单独出现,代表整个数组,计算的是该数组的大小,含有四个int型变量,所以大小为16。
printf("%d\n",sizeof(a+0));
注意一下,这里的数组名不是单独出现在sizeof中的,同时也没有取地址,结合我们在数组博文提到过的,这里a就是首元素地址,而a+0偏移量为0,被认为是int指针类型,计算得得到4或8。
补充: 我们在以前的博文中提过,地址就是指针,地址在sizeof中就是指针,计算的就是指针大小,无论什么类型的指针,在32位平台下大小为4,64位平台下大小为8。
printf("%d\n",sizeof(*a));
a不是单独出现在sizeof中的吧,那它就代表首元素地址即&a[0],对其解引用也就相当于*&a[0],得到的不就是a[0]嘛,也就是1呀,类型是int,计算得到的大小就为4。
printf("%d\n",sizeof(a+1));
一直重复讲有点赘余了,我就直说a在这里代表首元素地址,这里就是指针+-整数运算,顺便讲讲吧,指针+-整数,发生指针偏移,以原指针指向位置为基准、指针指向类型的变量所占大小为单位(比如int指针的偏移单位就是4字节,char[5]指针的偏移单位就是5等等)以及所加减的整数为系数,实际上是指针+-(整数*单位)。
可能有人就会问了,那啥叫偏移呀?嘛,简单来说就是相较于原来的位置发生了移动。那既然是移动,我们可以粗略的将偏移单位看成步长(走一步的长度),这样看的话其实偏移量也就是指针这跑跑那逛逛走的路程。
拉回正题,a在这里代表首元素地址,指针类型是啥,那看它指向的是什么类型变量,它是什么的地址呀,是int型变量的地址哦,指向的就是int类型变量,那好,步长就是4咯,a+1的话偏移量就是1*4也就是4,也就正好移动到a[1]处,所以是&a[1],是指针嘛,计算得到4或8。
实际上指针偏移的设计是趋于容易理解和使用的方向的,如果完全不清楚指针+-整数要考虑偏移单位也问题不大,比如指针+1就是让指针跳过一个对应类型的变量,不论该类型的变量所占空间大小有多大,指针运算都是你加减多少就移动略过多少对应类型变量所占空间大小,方便对数组之类的数据结构的访问(毕竟数组一块内存中都是同类型的),自然也让人想到数组的指针表示法如*(a+1)即a[1],你细品就对了。
printf("%d\n",sizeof(a[1]));
这个就简单了,a[1]是int型,计算得到4。
printf("%d\n",sizeof(&a));
&a取到整个数组a的地址,是指针,先甭管你是什么类型指针,反正计算得到4或8。再讨论&a就是说它类型是int(*)[4]
printf("%d\n",sizeof(*&a));
&a取到整个数组的地址,指针类型为
int(*)[4]
,而指针类型决定了其在解引用时的访问权限有多大,比如char*
指针解引用能访问1个字节,int*
指针解引用能访问4个字节,所以int(*)[4]
解引用能访问int[4]
类型数组,也就是说*&a
就能拿到整个a数组,所以计算得到结果就是16。或者可以这样理解,*和&作用相反,放一起不就抵消了嘛,所以就相当于
sizeof(a)
,因此计算结果就为16。
printf("%d\n",sizeof(&a+1));
还记得前面刚讲的指针+-整数运算吧,那你说说这里该如何理解呀?
&a取出整个数组的地址,指针类型是int(*)[4],所以加个1它能跳多远?算算?
实际上一般不会一个字节一个字节地算,而是关注它的类型,加1的话就是向高地址方向跳过一个对应类型大小地空间,这是数组指针,当然是跳过一个数组,不过他仍是指针,计算得到4或8。
printf("%d\n",sizeof(&a[0]));
这就是首元素地址,是指针,结果就为4或8。
printf("%d\n",sizeof(&a[0]+1));
首元素地址为基准发生一个单位的偏移,也就是偏移到a[1]处,也就相当于&a[1],还是指针,结果就为4或8。(与前面讲的a+1其实一样)。
一维字符数组
第一类
题目汇总
先看看题,想一想再看后面分析。
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));
讲解
printf("%d\n", sizeof(arr));
经过前面的历练,这个就显得很简单了吧,这代表整个数组,计算得到6。
printf("%d\n", sizeof(arr+0));
emmm,没啥好说的,这里是指针,计算得到4或8。
printf("%d\n", sizeof(*arr));
arr这里代表首元素地址,解引用就得到首元素,元素对应类型为char,所以计算得到1。
printf("%d\n", sizeof(arr[1]));
就是数组的第2个元素,类型为char,计算得到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函数了。
printf("%d\n", strlen(arr));
答案会是6吗?要不要自己在编译器试试看?
实际上答案是随机值,为什么?
大家了解过strlen函数吧,鉴于C中字符串以'\0'结尾,strlen在计算字符串长度的时候也是以'\0'为结束标志而不包括'\0'的。那好,arr数组中并没有放入'\0',而数组后面的空间我们不清楚到底放的是什么值,而strlen函数内会一直往后找,直到遇见'\0'结束,所以到底什么时候遇见'\0'全看命,不同编译器下结果很大可能不同,所以才说答案是随机值。
printf("%d\n", strlen(arr+0));
指针偏移量为0,搁这原地踏步呢。
一定要注意strlen和sizeof大有不同,strlen从传入的指针处开始计算字符串长度,而sizeof才不管你指针偏移量多少,只要你是指针就是4或8。
这个的结果同上面那个一样的是随机值。
printf("%d\n", strlen(*arr));
注意了!strlen函数的参数是
const char*
指针类型,你给它传入的值都会被认为是指针,arr代表首元素地址,所以*arr
就是'a'对应值为97,也就相当于strlen(97),但是97这个地址我们有读写权限吗?内存空间并不是都有效可用的,我们只有权限使用系统分配给我们的地址,其余的地址一律不合法,这样的指针是野指针,我们硬要使用的话很有可能报错:访问发生冲突。
悄悄提一嘴,实际上比较小的地址(比如上面的97之类的)是完全不能给用户使用的,是有特殊的重要的作用的。
所以呢,这样搞会出问题,是没有答案的。
printf("%d\n", strlen(arr[1]));
这个道理同上,我就不赘述了。
printf("%d\n", strlen(&arr));
strlen函数的形参类型是
const char*
,而&arr取出整个数组地址,是char(*)[6]
类型,那是不是就会出错呢?其实吧,传参是一回事,但是传入后的值被如何解释又是一回事,也就是说不管你传入的值原来是什么类型的,在传的时候只保留了值,传入后要被使用时便根据形参的类型来看待它,所以传入&arr在被使用时就是const char*
类型的。这么说的话,因为&arr和arr在值上是一样的,所以
strlen(&arr)
相当于strlen(arr)
。答案就是随机值。
printf("%d\n", strlen(&arr+1));
在上面分析的基础上,这个不就是指针向后跳了一整个数组嘛,从原来字符数组的后面开始计算字符串长度,还是随机值,因为后面到底什么时候能遇到'\0'犹未可知嘛,不过可以确定的是,这个随机值要比strlen(arr)小6。
printf("%d\n", strlen(&arr[0]+1));
这个就是从arr[1]处开始计算字符串长度,也是随机值,可以确定的是这个随机值要比strlen(arr)小1。
第二类
题目汇总
先看看题,想一想再看后面分析。
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));
讲解
printf("%d\n", sizeof(arr));
遇到" "编译器会认为是字符串,所以初始化的时候会自动在末尾加上'\0',所以数组元素个数其实是7,计算得到7。
printf("%d\n", sizeof(arr+0));
讲到吐了有木有…
是指针,计算得到4或8。
printf("%d\n", sizeof(*arr));
arr不是单独出现在sizeof中,这里代表首元素地址,再解引用就得到第一个字符,类型为char,计算得到1。
printf("%d\n", sizeof(arr[1]));
数组第二个字符,类型为char,计算得到1。
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));
是指针,计算得到4或8。
这些没有细讲的题其实跟前面讲第一类的题差不多的,不太理解的话回到前面看看。
讲了这么多了,我们来巩固一下strlen和sizeof的区别吧。
strlen是库函数,是求字符串长度的,关注的是字符串中的'\0',计算的是'\0'之前出现的字符个数。
sizeof是操作符,只关注对应类型的变量占用内存空间的大小,而不在乎内存中放的是啥。
继续吧,不过下面的和第一类的差不多,唯一差别就是第二类的数组多了个'\0',简单看看吧
printf("%d\n", strlen(arr));
这回的数组就有'\0'了,所以答案是6。
printf("%d\n", strlen(arr+0));
strlen看重的是指针指哪,加个0原地打转没影响,答案还是6。
printf("%d\n", strlen(*arr));
arr是首元素地址,所以解引用得到的就是首字符,后面的讲解完全同前面第一类的讲过的一样,可以翻回去看看。
printf("%d\n", strlen(arr[1]));
道理同上。
printf("%d\n", strlen(&arr));
相较于前面第一类讲的,这个数组是带有'\0'的,所以在字符数组末尾就停下来了,答案就是6。
printf("%d\n", strlen(&arr+1));
。。。不赘述了,答案是随机值,不清楚的翻回前面有一样的题型。
printf("%d\n", strlen(&arr[0]+1));
从arr[1]开始计算字符串长度,答案是5。
第三类
题目汇总
先看看题,想一想再看后面分析。
char *p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p+1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p+1));
printf("%d\n", sizeof(&p[0]+1));
printf("%d\n", strlen(p));
printf("%d\n", strlen(p+1));
printf("%d\n", strlen(*p));
printf("%d\n", strlen(p[0]));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p+1));
printf("%d\n", strlen(&p[0]+1));
讲解
char *p = "abcdef";
注意啦,字符串字面量(常量)在表达式中代表字符串首元素的地址。常量也有地址吗?有的,它们存储在只读常量区,也有地址。
printf("%d\n", sizeof(p));
p就是指针,存放该字符串首元素地址,计算得到4或8。
printf("%d\n", sizeof(p+1));
p+1偏移到下一个字符处,还是指针,计算得到4或8。
printf("%d\n", sizeof(*p));
解引用得到第一个字符,类型是int,计算得到1。
printf("%d\n", sizeof(p[0]));
直接就是数组第一个元素,类型为char,计算得到1。
printf("%d\n", sizeof(&p));
要注意啦,这里是用char*指针来引用字符串的,不像前面讲的字符数组,这里的p是指针变量,也有自己的地址,这里取得就是这个指针变量的地址,其实就是二级指针,计算得到4或8。
printf("%d\n", sizeof(&p+1));
取出的是指针变量p的地址,+1就向后偏移跳过该指针变量,仍然是指针,计算得到4或8。
printf("%d\n", sizeof(&p[0]+1));
首先,这里是指针的数组表示法,p[0]即*(p+0),即得到首元素,所以这里取的是字符串首字符地址,再加个1就是第二个字符的地址,仍是指针,计算得到4或8。
printf("%d\n", strlen(p));
传的是指针,指向的就是字符串(自带了'\0'),计算得到就是该字符串长度6。
printf("%d\n", strlen(p+1));
指针向后移了一个单位,也就是计算除开第一个字符剩下的字符串,答案是5。
printf("%d\n", strlen(*p));
p在这代表首元素的地址,解引用得到首字符,放进去就出问题,原因可以参考前面讲过的题。
printf("%d\n", strlen(p[0]));
p[0]相当于*(p+0)即*p,所以同上。
printf("%d\n", strlen(&p));
取出的是指针变量p的地址,所以是从p处开始往后找'\0',啥时候遇见完全是个未知数,答案就是随机值。
printf("%d\n", strlen(&p+1));
也是随机值,但是跟 strlen(&p)的值不存在固定的关系,p的值完全不确定,同时指针变量是占四字节的,而strlen是一个字节一个字节地找'\0'的,'\0'ASCII码值就是0,无法确定p的四个字节中会不会有'\0',最少都有两种情况(有或没有'\0'),所以能借此断定它们没有必然的联系。
printf("%d\n", strlen(&p[0]+1));
取得是p[0]的地址,再向后移动一个单位,也就是计算除开第一个字符剩下的字符串,计算得到5。
以上就是本文全部内容了,感谢观看,你的支持就是对我最大的鼓励~