【C语言】指针(6)
前言:上期我们介绍了回调函数的实现,qsort快速排序函数的使用以及qsort的模拟实现。这期我们主要通过一些小练习和面试题来巩固之前所学的知识。
往期文章:
指针1
指针2
指针3
指针4
指针5
文章目录
- 一,sizeof和strlen的辨析
- 1,sizeof
- 2,strlen
- 3,sizeof与strlen的对比总结
一,sizeof和strlen的辨析
1,sizeof
前面讲操作符的时候我们也介绍了sizeof,sizeof是用来计算变量所占内存空间大小的操作符,单位是字节,如果操作数是类型的话,计算的是使用类型创建的变量所占内存空间的大小。
#include<stdio.h>
int main()
{
int x = 1, z = 5;
double y = 0;
printf("%zd\n", sizeof(x));//打印sizeof的返回值需使用%zd格式来打印
printf("%zd\n", sizeof(int));
printf("%zd\n", sizeof(y));
printf("%zd\n", sizeof(double));
printf("%zd\n", sizeof(x + z));
return 0;
}
从运行结果来看我们知道sizeof不在乎内存中放什么数据只关注其所占用空间的大小。还有一点需要注意的是sizeof里面如果有表达式是不会进行计算的! 这一点我们可以从最后一行printf的运行结果看出来。
2,strlen
strlen是C语言中的库函数使用它时需要包含<string.h>这个头文件,它的功能是求字符串长度,是统计一个字符串中\0之前的字符个数的函数。它的函数原型如下:size_t strlen ( const char * str );
但要注意一点,strlen会从字符串的首地址开始依次向后查找\0直到找到为止,如果没有\0就会一直查找下去直至程序奔溃。
#include<stdio.h>
int main()
{
char arr1[3]={'a','b','c'};//没有存放\0
char arr2[]="abc";//实质存放的是"a b c \0"
printf("%zd\n",strlen(arr1));
printf("%zd\n",strlen(arr2));
printf("%zd\n",sizeof(arr1));
printf("%zd\n",sizeof(arr2));
return 0;
}
我们来分析一下运行结果:
首先
char arr1[3]={'a','b','c'};
这个数组中是没有\0
的所以在我们使用strlen
求arr1
的时候strlen会从字符a开始往后查找\0
但arr1
里面确实没有\0
那么这个时候编译器也不知道到底是多少所以就给出了一个随机值35
而如果是
sizeof
去求arr1
的话这里就要注意了arr1
是数组首元素的地址,将首元素的地址放在sizeof里面求得的是整个数组得大小;整个数组的大小就等于数组元素个数*
数组的类型。上面的例子就是3*1=3所以结果为3
接着我们来看看:
char arr2[]="abc";//实质存放的是"a b c \0"
这个数组
首先使用strlen
去求arr2
的时候由于arr2
中实质上存放的是a,b,c,\0;strlen统计的是\0之前的字符个数所以是3这毫无疑问。而使用sizeof
去求arr2
的时候与上面使用sizeof去求arr1的时候一样求的是整个数组的大小,而为什么这里是4而上面求得的是3呢?其实就是多了一个\0。这就是为什么我们前面说arr2实质上存放的是a,b,c\0。
对于数组名的含义还不了解的话可以去看我的指针3那篇文章!
3,sizeof与strlen的对比总结
sizeof | strlen |
---|---|
1,sizeof是操作符 | 1,strlen是库函数需要包含头文件<string.h> |
2,sizeof计算的是操作数所占内存的大小,单位是字节 | 2. srtlen是求字符串长度的,统计的是 \0 之前字符的字符个数 |
3,sizeof不关注内存中存放什么数据,只关注其所占空间的大小 | 3. 关注内存中是否有 \0 ,如果没有 \0 ,就会持续往后找,可能越界 |
有了上面的知识来些笔试题练练手:
#include<stdio.h>
int main()
{
int a[] = {1,2,3,4};
printf("%zd\n",sizeof(a));
printf("%zd\n",sizeof(a+0));
printf("%zd\n",sizeof(*a));
printf("%zd\n",sizeof(a+1));
printf("%zd\n",sizeof(a[1]));
printf("%zd\n",sizeof(&a));
printf("%zd\n",sizeof(*&a));
printf("%zd\n",sizeof(&a+1));
printf("%zd\n",sizeof(&a[0]));
printf("%zd\n",sizeof(&a[0]+1));
return 0;
}
分析如下:
a
是数组首元素地址,单独放在sizeof里面计算整个数组大小 16a+0
拿到的是首元素的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节 这与你的平台有关x64 8 x86 4*a
是对首元素解引用 放在sizeof里面计算元素的类型大小 4a+1
拿到的就是第二个元素的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节a[1]
拿到的是数组中的第二个元素 计算的是元素的大小 所以是 4&a
取地址a取出的是整个数组的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节*&a
取完地址再解引可以抵消就相当于sizeof(a)
与第一种情况一样 16&a+1
取出整个数组的地址再加1就相当于跳过一个数组指向该数组的末尾 但其本质拿到的还是一个地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节- &a[0]取出首元素的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节
&a[0]+1
取出第二个元素的地址 情况同上
以上的关键就是知道求的是地址的大小 整个元素的大小 还是元素大小
由于我使用的是x64平台 所以地址打印的都为8
答案如下:
#include<stdio.h>
int main()
{
//字符数组
char arr[] = {'a','b','c','d','e','f'};
printf("%zd\n", sizeof(arr));
printf("%zd\n", sizeof(arr+0));
printf("%zd\n", sizeof(*arr));
printf("%zd\n", sizeof(arr[1]));
printf("%zd\n", sizeof(&arr));
printf("%zd\n", sizeof(&arr+1));
printf("%zd\n", sizeof(&arr[0]+1));
return 0;
}
分析如下
arr
是数组的首元素的地址 单独放在sizeof里面计算整个数组大小 6arr+0
拿到的是首元素的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节*arr
是对首元素解引用 放在sizeof里面计算元素的类型大小 所以是1arr[1]
拿到的是数组中的第二个元素 计算的是元素的大小 所以是 1&arr
取地址a取出的是整个数组的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节&arr+1
取出整个数组的地址再加1就相当于跳过一个数组指向该数组的末尾 但其本质拿到的还是一个地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节&arr[0]+1
拿到的是第二个元素的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节
答案如下:
#include<stdio.h>
int main()
{
char arr[] = {'a','b','c','d','e','f'};
printf("%zd\n", strlen(arr));
printf("%zd\n", strlen(arr+0));
printf("%zd\n", strlen(*arr));
printf("%zd\n", strlen(arr[1]));
printf("%zd\n", strlen(&arr));
printf("%zd\n", strlen(&arr+1));
printf("%zd\n", strlen(&arr[0]+1));
return 0;
}
分析如下:
arr
是数组首元素的地址,意味着是从首元素开始统计直至\0,但这个数组内部是没有\0的 所以计算出来的结果就是随机值。arr+0
拿到的是首元素的地址,情况同上结果是随机值。*arr
是对首元素解引用拿到的是第一个元素 这个时候就要注意了!strlen
接受的参数应该是指针是地址,而这里解引用拿到的是一个字符 字符的本质是ASCAII码值;首元素是字符a对应的ASCAII码码值为97 将97这个地址传给strlen
;而97处的地址对应的空间是不属于当前程序的,也就相当于给strlen里传入了一个野指针 这时程序就会报错!。arr[1]
拿到的是数组中的第二个元素strlen
接受的不是地址 所以结果同上会报错!。&arr
取地址a取出的是整个数组的地址 整个元素的地址是从首元素开始的但这个数组内是没有\0的所以情况跟第一种是一样的!产生随机值。。&arr+1
取出整个数组的地址再加1就相当于跳过一个数组指向该数组的末尾 只要数组内部无\0再怎么去查找都是找不到的 所以也是随机值但是会与从第一个元素开始查找得到得随机值相差6(6为元素个数)。&arr[0]+1
拿到的是第二个元素的地址 是随机值 会与从第一个元素开始查找得到得随机值差5。
答案如下:
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "abcdef";//实质存放着"a,b,c,d,e,f,\0"
printf("%zd\n", strlen(arr));
printf("%zd\n", strlen(arr+0));
printf("%zd\n", strlen(*arr));
printf("%zd\n", strlen(arr[1]));
printf("%zd\n", strlen(&arr));
printf("%zd\n", strlen(&arr+1));
printf("%zd\n", strlen(&arr[0]+1));
return 0;
}
分析如下:
arr
是数组首元素的地址,意味着是从首元素开始统计直至\0 这次数组是含有\0的了 所以结果为6。arr+0
拿到的是首元素的地址 情况同上 结果为6。*arr
是对首元素解引用拿到的是第一个元素 会报错!。arr[1]
拿到的是数组中的第二个元素strlen
接受的不是地址 所以结果同上会报错!。&arr
取地址a取出的是整个数组的地址 整个元素的地址是从首元素开始的从第一个元素开始直到碰到\0 所以结果为6。
6.&arr+1
取出整个数组的地址再加1就相当于跳过一个数组指向该数组的末尾 该函数本身含有\0但是跳过了整个数组之后再想找\0就不知道能不能找到了 所以是随机值。但是会与从第一个元素开始查找得到得随机值相差6(6为元素个数)&arr[0]+1
拿到的是第二个元素的地址那就是从第二个元素开始统计 所以结果为5。
答案如下:
同样将strlen
换成sizeof
会怎样呢?
#include<stdio.h>
int main()
{
char arr[] = "abcdef";//实质存放着"a,b,c,d,e,f,\0"
printf("%zd\n", sizeof(arr));
printf("%zd\n", sizeof(arr+0));
printf("%zd\n", sizeof(*arr));
printf("%zd\n", sizeof(arr[1]));
printf("%zd\n", sizeof(&arr));
printf("%zd\n", sizeof(&arr+1));
printf("%zd\n", sizeof(&arr[0]+1));
return 0;
}
分析如下:
arr
是数组的首元素的地址 单独放在sizeof里面计算整个数组大小 所以为7。arr+0
拿到的也是首元素的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节 。*arr
是对首元素解引用 放在sizeof里面计算元素的类型大小 所以是1。arr[1]
是对首元素解引用 放在sizeof里面计算元素的类型大小 所以是1。&arr
取地址a取出的是整个数组的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节 。&arr+1
取出整个数组的地址再加1就相当于跳过一个数组指向该数组的末尾 但其本质拿到的还是一个地址 所以是 4或8个字节 。&arr[0]+1
拿到的是第二个元素’b’的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节
答案如下:
改成指针又会有什么变化呢?
#include<stdio.h>
int main()
{
char *p = "abcdef"; //本质存放着"a,b,c,d,e,f,\0"
printf("%zd\n", strlen(p));
printf("%zd\n", strlen(p+1));
printf("%zd\n", strlen(*p));
printf("%zd\n", strlen(p[0]));
printf("%zd\n", strlen(&p));
printf("%zd\n", strlen(&p+1));
printf("%zd\n", strlen(&p[0]+1));
return 0;
}
分析如下:
p
拿到的是首元素a的地址 从首元素开始统计直到\0
所以结果为6。p+1
拿到的是第二个元素的地址 从第二个元素开始统计 所以所以结果为5。*p
对首元素进行解引用不是一个地址 所以会报错。p[0]
拿到首元素 情况同上不是一个地址 所以会报错。&p
取地址p
这时候取出来的就不是整个数组的地址了,因为p
为指针变量它不是数组名 拿到p
的地址后往后统计直到遇到\0
拿编译器也不知道什么时候遇到\0
所以是随机值。&p+1
跳过一个char *
类型的指针变量p
的地址,往后统计仍然不知道什么时候碰到\0 所以是随机值。&p[0]+1
相当于&*(p+0)+1==p+1
相当于拿到第二个元素的地址 从第二个元素开始统计 所以所以结果为5。
答案如下:
注意!第3个结果是随机值:
&p
是p
这个变量的地址,strlen
就是从p
这块空间的起始地址开始向后找\0
的p
中存放的地址是不确定的,所有有没有\0
,什么时候会遇到\0
都不确定。
最后一个二维数组:
#include<stdio.h>
int main()
{
int a[3][4] = {0};
printf("%zd\n",sizeof(a));
printf("%zd\n",sizeof(a[0][0]));
printf("%zd\n",sizeof(a[0]));
printf("%zd\n",sizeof(a[0]+1));
printf("%zd\n",sizeof(*(a[0]+1)));
printf("%zd\n",sizeof(a+1));
printf("%zd\n",sizeof(*(a+1)));
printf("%zd\n",sizeof(&a[0]+1));
printf("%zd\n",sizeof(*(&a[0]+1)));
printf("%zd\n",sizeof(*a));
printf("%zd\n",sizeof(a[3]));
return 0;
}
分析如下:
a
是二维数组的数组名 将数组名单独放在sizeof里面计算整个数组大小 所以为3*4*4=48
。a[0][0]
拿到的是第0行第0个元素 放在sizeof里面计算元素的类型大小 所以是4a[0]
是二维数组的第一个元素 也就是第一行这个一维数组的数组名 将数组名 单独放在sizeof里面计算的是这一行的一维数组的大小 所以为4*4=16
。a[0]+1
首先a[0]
是数组名是第一行第一个元素的地址 +1跳过一个元素指向下一个元素的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节 。*(a[0]+1))
相当于a[0][1]
是计算一个元素大小 所以是4a+1
a是二维数组的首元素地址,也就是二维数组中整个第一行的地址+1就跳到第二行 指向第二行的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节 。这里要和第二行第一个元素的地址区分开 区分行地址 和单个元素的地址 行地址就是整个一维数组的地址!*(a+1)
是第二行的地址,*(a+1)
得到的就是第二行
int(*)[4]
对数组指针解引用,放一个数组,就是一行的一维数组
*(a+1)
==a[1]
,a[1]
是第二行的数组名,sizeof(arr[1])
计算的是第二行的大小所以为4*4=16
。&a[0]+1
首先&a[0]
就是拿到第一行的地址 +1偏移1就拿到了第二行的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节 。*(&a[0]+1)
首先&a[0]+1
拿到第二行的地址 即a[1]
a[1]是第二行的一维数组的数组名 放在sizeof
里面计算一行的大小 所以为4*4=16
。*a
首先a
为二维数组首元素的地址 代表第一行的地址 再解引用拿到的就是第一行*a
==*(a+0)
所以sizeof(*(a+0))计算的是一行的大小 所以为4*4=16
。a[3]
看到a[3]有人就会不自觉的想到数组越界(包括博主也一样),认为它是非法访问 但是放在这就不一样了 这里的a[3]根本就没有访问 这是由于sizeof内部的表达式是不计算的 所以求的还是一行的大小, 所以为4*4=16
。
最后我们给出答案:
到这strlen与sizeof相关的笔试题就讲完了,还有指针运算相关的笔试题由于篇幅放到下期。
感谢能够看到这里的读者,如果我的文章能够帮到你那我甚是荣幸,文章有任何问题都欢迎指出!制作不易还望给一个免费的三连,你们的支持就是我最大的动力!