c语言题库|【C语言】从入门到入土(数组笔试题的解析)

前言:
本篇在了解指针和数组的关联后,利用已知道的知识去进行一些数组面试题的解析(指针篇后面也会更),让大家更深入了解知识的背后是如何运用的。
c语言题库|【C语言】从入门到入土(数组笔试题的解析)
文章图片

数组面试题的解析:
  • 第一类:一维数组
  • 第二类:字符数组
    • 1.sizeof类题
    • 2.strlen类题
    • 3.字符串数组
    • 4.指针与常量字符串
      • ① sizeof
      • ② strlen
  • 第三类:二维数组

第一类:一维数组
请写出输出结果并解释说明:
//一维数组 int main() { 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)); return 0; }

解析:本题考察我们对数组名的使用和含义。在之前,我们已经学到,除了在sizeof()里单独放的数组名表示整个数组,和&数组名表示整个数组的地址外,数组名表示首元素地址。
输出结果:
c语言题库|【C语言】从入门到入土(数组笔试题的解析)
文章图片

接下来我们一个一个的进行分析:
首先是1-5个:
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]));

①数组名单独放在sizeof内部,数组名表示整个数组,计算整个数组大小,数组中每一个元素是int类型,所以一个元素是4个字节。总就16字节。
②因为并不是在sizeof内部单独放置,所以a是首元素地址,a+0还是首元素地址是地址的话大小就是4/8个字节,取决于系统,x86是4,x64是8。
【c语言题库|【C语言】从入门到入土(数组笔试题的解析)】③a表示首元素地址,*解引用,得到的就是首元素,相当于a[0],所以大小是4字节
④a表示首元素地址,a+1就是第二个元素地址,大小是4/8字节
⑤a[1]表示第二个元素,所以大小是4个字节。
然后是6-10个:
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));

&a取到的是数组的地址,但是无论是谁的地址,都是同样大小,大小是4/8字节,取决于系统。
⑦首先a先与&结合,&a拿到的是数组a的地址,然后与*解引用结合,得到的是整个数组,所以大小是16个字节。
&a得到的是数组的地址,数组的地址+1就是跳过一个数组的距离,虽然跳过了一个数组,但还是地址,是地址就是4/8字节,取决于系统。
a先与[0]结合,a[0]表示首元素,首元素再与&结合,取首元素的地址,是地址就是4/8,取决于系统。
⑩同上一个一样,&a[0]表示的就是首元素地址,首元素地址+1就是第二个元素的地址,是地址就是4/8,取决于系统。
由于我使用的vs2019的时候用的是x86的环境,也就是32位系统,所以地址的大小都是4,最后的结果:
c语言题库|【C语言】从入门到入土(数组笔试题的解析)
文章图片
第二类:字符数组 1.sizeof类题
请写出输出结果并解释说明:
int main() { 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)); return 0; }

解析:这里是对字符数组关于数组名的含义与理解,数组名与不同的符号结合得到的是哪一种意思,类型是什么大小是多少。
输出结果:
这里仍然是x86环境下编译,得到的地址大小都是4字节。
c语言题库|【C语言】从入门到入土(数组笔试题的解析)
文章图片

接下来我们一个一个的进行分析(由于在是一个一维数组中介绍的比较详细了,这里我们就简便的分析一下):
printf("%d\n", sizeof(arr)); //数组名单独存放,表示整个数组,char类型每个元素一个字节,所以是6字节 printf("%d\n", sizeof(arr + 0)); //arr表示首元素地址,arr+0还是首元素地址,是地址大小为4/8字节 printf("%d\n", sizeof(*arr)); //arr是首元素地址,地址解引用,得到的就是首元素,大小为1字节 printf("%d\n", sizeof(arr[1])); //arr[1]是第二个元素,char类型,大小为1字节 printf("%d\n", sizeof(&arr)); //&arr表示取出整个数组的地址,是地址大小为4/8字节 printf("%d\n", sizeof(&arr + 1)); //&arr同上,是整个数组地址,&arr+1是跳过一个地址的大小,但还是地址,4/8个字节 printf("%d\n", sizeof(&arr[0] + 1)); //arr先与[0]结合,是首元素,&首元素,得到首元素地址,首元素地址+1得到第二个元素的地址,是地址大小为4/8字节

总结起来就是:
c语言题库|【C语言】从入门到入土(数组笔试题的解析)
文章图片

2.strlen类题 首先我们要理解sizeof和strlen的区别:
sizeof不是库函数,是一个操作符,单位是字节,表达的是求变量所占的空间大小或者求类型变量创建所占的空间大小。
strlen是库函数,是求字符串长度的库函数,当在字符串中遇到\0才会停止计算。计算的值是字符串起始到\0的长度,但不把\0算进去。
接下来我们看题目:
#include int main() { char arr[] = { 'a','b','c','d','e','f' }; 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)); return 0; }

解析:strlen计算的是字符串长度,需要找到\0才可以停止,所以在数组内无\0,超出区域后在未知的空间中找到\0时,计算的长度就已经是一个随机值了。
c语言题库|【C语言】从入门到入土(数组笔试题的解析)
文章图片
接下来我们来逐一分析:
第一第二行:
printf("%d\n", strlen(arr)); printf("%d\n", strlen(arr + 0));

首先来看第一行strlen(arr)arr表示的是首元素的地址,然后strlen就开始从首元素a的地址开始往后读取,但arr的空间内并没有\0,所以strlen会一直读到后面的空间直到遇到一个\0。而第二行的strlen(arr + 0),实际上也是首元素地址加0还是首元素地址,得到的和第一行的一样,是一个相等的随机值。
c语言题库|【C语言】从入门到入土(数组笔试题的解析)
文章图片
第三第四行:
printf("%d\n", strlen(*arr)); printf("%d\n", strlen(arr[1]));

然后我们来看第三行,在这里,第三行是strlen(*arr),arr表示的是首元素地址,而*解引用得到的就是首元素a了,当strlen想获取的是地址的时候传过去的却是a,所以strlen会误会读到a的ASCII码值97以为就是地址,所以会去访问97的地址,这样就进行了非法访问,无法读取。第四行的arr[1]读取到的第二个元素b传给strlen也一样,读取到98访问,一样是非法访问,程序错误。
我们将代码一步一步调试起来后就会看到:
c语言题库|【C语言】从入门到入土(数组笔试题的解析)
文章图片
c语言题库|【C语言】从入门到入土(数组笔试题的解析)
文章图片
第五第六第七:
printf("%d\n", strlen(&arr)); printf("%d\n", strlen(&arr + 1)); printf("%d\n", strlen(&arr[0] + 1));

第五行这里 strlen(&arr)&arr表示整个数组的地址,类型应该是char * [6],但是这里传给strlen的不应该是char *吗,实际上他会自动类型转换,虽然&arr取出的是整个数组的地址,但是显示的时候也是首元素地址,所以这里得到的依然是和第一第二个一样的随机值。
c语言题库|【C语言】从入门到入土(数组笔试题的解析)
文章图片

而第六行的strlen(&arr + 1),实际上就是跳过了一个数组大小,到了下一个数组大小的地址,和第五行一样,虽然是整个数组的大小,但实际上也是首元素处的地址显示,所以读取到的还是从首元素开始的字符串长度。所以第六行得到的是比第五行少6的一个随机值,因为他比第五行离未知的\0近了6个元素。
c语言题库|【C语言】从入门到入土(数组笔试题的解析)
文章图片
第七行的strlen(&arr[0] + 1),实际上就是首元素地址+1,也就是第二个元素的地址,即b的地址,从b开始往后读直到读到\0,所以得到的是比第五行少1的一个随机值,因为比第五行离未知的\0近了1个元素。
c语言题库|【C语言】从入门到入土(数组笔试题的解析)
文章图片
这就是strlen类和数组名结合的题目啦。
最后的结果就是:
c语言题库|【C语言】从入门到入土(数组笔试题的解析)
文章图片
3.字符串数组 前面的两种例子都是以字符数组中是单个单个元素作为题设来进行的,那当以字符串作为字符数组的元素又有什么不同呢?我们来简单看一下:
字符串和sizeof:
int main() { char arr[] = "abcdef"; //实际上相当于{'a','b','c','d','e','f','\0'} printf("%d\n", sizeof(arr)); //多了'\0',7字节 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字节 return 0; }

字符串和strlen:
int main() { char arr[] = "abcdef"; //实际上相当于{'a','b','c','d','e','f','\0'} printf("%d\n", strlen(arr)); //字符串中最后会有\0,所以读取的时候可以结束,得到是6字节 printf("%d\n", strlen(arr + 0)); //和上一个一样,arr+0后还是首元素地址开始读取,是6字节 printf("%d\n", strlen(*arr)); //虽然加了\0,但是*arr得到的依然是a,也就是读取97的地址,err!无法运行 printf("%d\n", strlen(arr[1])); //和上面一样,arr[1]得到的依然是b,也就是读取98的地址,err!无法运行 printf("%d\n", strlen(&arr)); //取地址得到数组的地址,但自动转换后还是从首元素地址开始读取,6字节 printf("%d\n", strlen(&arr + 1)); //&arr整个数组地址,+1跳过整个数组,然后往后访问,我们也不知道下一个\0在那里,所以得到的是一个随机值。 printf("%d\n", strlen(&arr[0] + 1)); //&arr[0]是首元素地址,+1是第二个元素地址,往后读取,得到的是5字节(少了a)。 return 0; }

4.指针与常量字符串 看完上面的数组存储,我们再来试一下指针存储字符串的时候,得到的strlensizeof是什么样子的。
① sizeof
int main() { char* p = "abcdef"; //指针中存储常量字符串 //实际上[a,b,c,d,e,f,\0] }

c语言题库|【C语言】从入门到入土(数组笔试题的解析)
文章图片
int main() { char* p = "abcdef"; //实际上[a,b,c,d,e,f,\0] printf("%d\n", sizeof(p)); //p是一个指针变量,用于存放地址,所以地址多大存放就多大就够了,所以是4/8字节。 printf("%d\n", sizeof(p + 1)); //我们知道,p存放字符串的时候,存放的是a的地址,然后接着可以找到后面的一串字符串,所以p存放的地址是a的地址。p+1就是b的地址,所以是4/8字节 printf("%d\n", sizeof(*p)); //p是char *类型,解引用就是char类型,所以是1字节 printf("%d\n", sizeof(p[0])); //这里虽然不是数组,但我们知道,p[0]实际上是*(p+0),所以也就是*p,1字节 printf("%d\n", sizeof(&p)); //p也是变量,所以&p也就是取出p的地址,是地址就是4/8字节 printf("%d\n", sizeof(&p + 1)); //&p就是p的地址,而&p+1就是跳过p地址的大小后,指向了p的地址大小的地址,所以还是地址,也就是4/8字节。 printf("%d\n", sizeof(&p[0] + 1)); //p[0]实际上就是a,&p[0]就是a的地址,+1就是b的地址,所以就是b的地址,也就是1字节。 return 0; }

所以最终结果是(x86系统):
c语言题库|【C语言】从入门到入土(数组笔试题的解析)
文章图片
② strlen
int main() { char* p = "abcdef"; //指针中存储常量字符串 //实际上[a,b,c,d,e,f,\0] }

c语言题库|【C语言】从入门到入土(数组笔试题的解析)
文章图片
int main() { char* p = "abcdef"; printf("%d\n", strlen(p)); //p读取到a的地址,然后往后计算,直到\0,6字节 printf("%d\n", strlen(p + 1)); //p存储的是a的地址,所以p+1是b的地址,直到\0,是5字节 printf("%d\n", strlen(*p)); //p存储是a的地址,*p就是解引用得到a,和上面一样,非法访问,err printf("%d\n", strlen(p[0])); //p[0]相当于*(p+0)也就是*p,和上面一样,非法访问err printf("%d\n", strlen(&p)); //p是指针,&p实际上是取出p指针的地址,p的地址我们不知道在哪里,也不知道什么时候遇到\0,所以是一个随机值。 printf("%d\n", strlen(&p + 1)); //p是指针,&p是p的地址,+1是p指针下一个的地址,也是不知道的,所以也是随机值。 printf("%d\n", strlen(&p[0] + 1)); //&p[0]表示a的地址,+1表示b的地址,所以是5字节 return 0; }

所以最后的结果是:
c语言题库|【C语言】从入门到入土(数组笔试题的解析)
文章图片
第三类:二维数组 二维数组,我们先要理解一下:二维数组也是数组!
是数组就有下面这两个条件和一个规则:
除了在sizeof内部单独放置表示整个数组或&数组名表示取出整个数组的地址,其他情况下数组名表示的是首元素的地址。
接下来我们以int a[3][4] = { 0 }; 去作为例题的题干:
c语言题库|【C语言】从入门到入土(数组笔试题的解析)
文章图片
开搞:
int main() { int a[3][4] = { 0 }; printf("%d\n", sizeof(a)); printf("%d\n", sizeof(a[0][0])); printf("%d\n", sizeof(a[0])); printf("%d\n", sizeof(a[0] + 1)); printf("%d\n", sizeof(*(a[0] + 1))); printf("%d\n", sizeof(a + 1)); printf("%d\n", sizeof(*(a + 1))); printf("%d\n", sizeof(&a[0] + 1)); printf("%d\n", sizeof(*(&a[0] + 1))); printf("%d\n", sizeof(*a)); return 0; }

我们来一行一行分析:
首先是1-5行:
printf("%d\n", sizeof(a)); printf("%d\n", sizeof(a[0][0])); printf("%d\n", sizeof(a[0])); printf("%d\n", sizeof(a[0] + 1)); printf("%d\n", sizeof(*(a[0] + 1)));

第一个是sizeof(a),a单独放在sizeof内部,表示的是整个数组,整个数组里面有12个int,每个int是4个字节,所以是48字节
第二个是sizeof(a[0][0])a[0][0]不就是其中一个元素吗,类型是int,所以是4个字节。
第三个是sizeof(a[0]),那这里的是把arr[i][j]看成是一个一维数组arr[i],每一行有j个元素,所以这里a[0]就是相当于是第一行的数组名,表示整行数组所有元素也就是4个元素16字节。
第四个是sizeof(a[0] + 1),a[0]没有单独放在sizeof内部,也没有&,所以a[0]就表示了首元素地址,也就是第一行第一列的元素地址,所以a[0]+1表示的第二个元素的地址,也就是4/8字节。
第五个是sizeof(*(a[0] + 1)),首先上面分析到a[0]+1是第一行第二个元素的地址,那解引用就是拿到这个变量,是int类型,所以是4字节。
再看6-11的:
printf("%d\n", sizeof(a + 1)); printf("%d\n", sizeof(*(a + 1))); printf("%d\n", sizeof(&a[0] + 1)); printf("%d\n", sizeof(*(&a[0] + 1))); printf("%d\n", sizeof(*a)); printf("%d\n",sizeof(a[3]));

第一个是sizeof(a + 1),那二维数组的数组名表示的是首元素地址是哪一个地址呢,其实是表示第一行的地址,也就是我们把他想象成一个一维数组,第一行是一个元素,第二行是一个元素这样。所以a是第一行的地址,a+1就是第二行的地址,是地址就是4/8字节。
第二个是sizeof(*(a + 1)),上面分析到a+1是第二行的地址,所以*解引用a+1得到的就是第二行的所有元素,也就是4个元素的大小,是16字节。
第三个是sizeof(&a[0] + 1),a[0]是第一行的数组名,&a[0]就是拿到的就是第一行的地址,然后第一行的地址+1就是得到的&a[0] + 1 就是第二行的地址。是地址就是4/8字节。
第四个是sizeof(*(&a[0] + 1)),上面推导到&a[0] + 1是第二行的地址,然后再*进行解引用得到的就是第二行的元素,也就是4个元素16字节。
第五个是sizeof(*a),在这里a表示的就是首元素(第一行)的地址,然后解引用也就是得到第一行的元素,所以是16字节。
第六个是sizeof(a[3]),在这里a[3],我们二维数组a只有0-2行啊,是不是越界了呢,实际上不是。sizeof只是看一下这一行有多少元素,并不会真正访问。 a[3]假设存在,假设第四行数组名,sizeof(a[3])就相当于把第四行的数组名单独放在sizeof里面,所以是16字节。
所以说其实二维数组a有几个数组名,第一个数组名是a,表示整个二维数组的数组名,而a[0],a[1],a[2]这三个就是每一行的数组名,a[0]代表第一行的一维数组,a[1]代表的是第二行的一维数组,a[2]代表第三行的一维数组。所以这几个在sizeof内单独存在都表示他们对应的数组。而不是单独存在的时候,就代表各自首元素的地址。
c语言题库|【C语言】从入门到入土(数组笔试题的解析)
文章图片
c语言题库|【C语言】从入门到入土(数组笔试题的解析)
文章图片
总结: 数组名的意义:
  1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
  3. 除此之外所有的数组名都表示首元素的地址。
好啦,本篇的内容就到这里,关于指针进阶的内容也到这里啦,最后也请继续关注我哦。关注一波,互相学习,共同进步。
还有一件事:
c语言题库|【C语言】从入门到入土(数组笔试题的解析)
文章图片
c语言题库|【C语言】从入门到入土(数组笔试题的解析)
文章图片
c语言题库|【C语言】从入门到入土(数组笔试题的解析)
文章图片

    推荐阅读