【C语言】指针(6)

ops/2025/2/24 5:09:53/

前言:上期我们介绍了回调函数的实现,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'};//没有存放\0char 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的所以在我们使用strlenarr1的时候strlen会从字符a开始往后查找\0arr1里面确实没有\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的对比总结

sizeofstrlen
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;
}

分析如下:

  1. a是数组首元素地址,单独放在sizeof里面计算整个数组大小 16
  2. a+0拿到的是首元素的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节 这与你的平台有关x64 8 x86 4
  3. *a是对首元素解引用 放在sizeof里面计算元素的类型大小 4
  4. a+1拿到的就是第二个元素的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节
  5. a[1]拿到的是数组中的第二个元素 计算的是元素的大小 所以是 4
  6. &a取地址a取出的是整个数组的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节
  7. *&a取完地址再解引可以抵消就相当于sizeof(a)与第一种情况一样 16
  8. &a+1取出整个数组的地址再加1就相当于跳过一个数组指向该数组的末尾 但其本质拿到的还是一个地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节
  9. &a[0]取出首元素的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节
  10. &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;
}

分析如下

  1. arr是数组的首元素的地址 单独放在sizeof里面计算整个数组大小 6
  2. arr+0拿到的是首元素的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节
  3. *arr是对首元素解引用 放在sizeof里面计算元素的类型大小 所以是1
  4. arr[1]拿到的是数组中的第二个元素 计算的是元素的大小 所以是 1
  5. &arr取地址a取出的是整个数组的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节
  6. &arr+1取出整个数组的地址再加1就相当于跳过一个数组指向该数组的末尾 但其本质拿到的还是一个地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节
  7. &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;
}

分析如下:

  1. arr是数组首元素的地址,意味着是从首元素开始统计直至\0,但这个数组内部是没有\0的 所以计算出来的结果就是随机值
  2. arr+0拿到的是首元素的地址,情况同上结果是随机值
  3. *arr是对首元素解引用拿到的是第一个元素 这个时候就要注意了!strlen接受的参数应该是指针是地址,而这里解引用拿到的是一个字符 字符的本质是ASCAII码值;首元素是字符a对应的ASCAII码码值为97 将97这个地址传给strlen;而97处的地址对应的空间是不属于当前程序的,也就相当于给strlen里传入了一个野指针 这时程序就会报错!
  4. arr[1]拿到的是数组中的第二个元素 strlen接受的不是地址 所以结果同上会报错!
  5. &arr取地址a取出的是整个数组的地址 整个元素的地址是从首元素开始的但这个数组内是没有\0的所以情况跟第一种是一样的!产生随机值。
  6. &arr+1取出整个数组的地址再加1就相当于跳过一个数组指向该数组的末尾 只要数组内部无\0再怎么去查找都是找不到的 所以也是随机值但是会与从第一个元素开始查找得到得随机值相差6(6为元素个数)
  7. &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;
}

分析如下:

  1. arr是数组首元素的地址,意味着是从首元素开始统计直至\0 这次数组是含有\0的了 所以结果为6
  2. arr+0拿到的是首元素的地址 情况同上 结果为6
  3. *arr是对首元素解引用拿到的是第一个元素 会报错!
  4. arr[1]拿到的是数组中的第二个元素 strlen接受的不是地址 所以结果同上会报错!
  5. &arr取地址a取出的是整个数组的地址 整个元素的地址是从首元素开始的从第一个元素开始直到碰到\0 所以结果为6
    6.&arr+1取出整个数组的地址再加1就相当于跳过一个数组指向该数组的末尾 该函数本身含有\0但是跳过了整个数组之后再想找\0就不知道能不能找到了 所以是随机值。但是会与从第一个元素开始查找得到得随机值相差6(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;
}

分析如下:

  1. arr是数组的首元素的地址 单独放在sizeof里面计算整个数组大小 所以为7
  2. arr+0拿到的也是首元素的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节
  3. *arr是对首元素解引用 放在sizeof里面计算元素的类型大小 所以是1
  4. arr[1]是对首元素解引用 放在sizeof里面计算元素的类型大小 所以是1
  5. &arr取地址a取出的是整个数组的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节
  6. &arr+1取出整个数组的地址再加1就相当于跳过一个数组指向该数组的末尾 但其本质拿到的还是一个地址 所以是 4或8个字节
  7. &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;
}

分析如下:

  1. p拿到的是首元素a的地址 从首元素开始统计直到\0 所以结果为6
  2. p+1拿到的是第二个元素的地址 从第二个元素开始统计 所以所以结果为5
  3. *p对首元素进行解引用不是一个地址 所以会报错
  4. p[0] 拿到首元素 情况同上不是一个地址 所以会报错
  5. &p 取地址p这时候取出来的就不是整个数组的地址了,因为p为指针变量它不是数组名 拿到p的地址后往后统计直到遇到\0拿编译器也不知道什么时候遇到\0 所以是随机值
  6. &p+1 跳过一个char *类型的指针变量p的地址,往后统计仍然不知道什么时候碰到\0 所以是随机值
  7. &p[0]+1相当于&*(p+0)+1==p+1 相当于拿到第二个元素的地址 从第二个元素开始统计 所以所以结果为5

答案如下:
在这里插入图片描述
注意!第3个结果是随机值:
&pp这个变量的地址,strlen就是从p这块空间的起始地址开始向后找\0p中存放的地址是不确定的,所有有没有\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;
}

分析如下:

  1. a是二维数组的数组名 将数组名单独放在sizeof里面计算整个数组大小 所以为3*4*4=48
  2. a[0][0]拿到的是第0行第0个元素 放在sizeof里面计算元素的类型大小 所以是4
  3. a[0]是二维数组的第一个元素 也就是第一行这个一维数组的数组名 将数组名 单独放在sizeof里面计算的是这一行的一维数组的大小 所以为4*4=16
  4. a[0]+1首先a[0]是数组名是第一行第一个元素的地址 +1跳过一个元素指向下一个元素的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节
  5. *(a[0]+1))相当于a[0][1]是计算一个元素大小 所以是4
  6. a+1a是二维数组的首元素地址,也就是二维数组中整个第一行的地址+1就跳到第二行 指向第二行的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节这里要和第二行第一个元素的地址区分开 区分行地址 和单个元素的地址 行地址就是整个一维数组的地址!
  7. *(a+1)是第二行的地址,*(a+1)得到的就是第二行
    int(*)[4] 对数组指针解引用,放一个数组,就是一行的一维数组
    *(a+1) == a[1], a[1]是第二行的数组名,sizeof(arr[1])计算的是第二行的大小所以为4*4=16
  8. &a[0]+1首先&a[0]就是拿到第一行的地址 +1偏移1就拿到了第二行的地址 是地址那么使用sizeof计算得到的大小就是 4或8个字节
  9. *(&a[0]+1) 首先&a[0]+1拿到第二行的地址 即a[1] a[1]是第二行的一维数组的数组名 放在sizeof里面计算一行的大小 所以为4*4=16
  10. *a首先a为二维数组首元素的地址 代表第一行的地址 再解引用拿到的就是第一行 *a==*(a+0) 所以sizeof(*(a+0))计算的是一行的大小 所以为4*4=16
  11. a[3]看到a[3]有人就会不自觉的想到数组越界(包括博主也一样),认为它是非法访问 但是放在这就不一样了 这里的a[3]根本就没有访问 这是由于sizeof内部的表达式是不计算的 所以求的还是一行的大小所以为4*4=16

最后我们给出答案:
在这里插入图片描述

到这strlen与sizeof相关的笔试题就讲完了,还有指针运算相关的笔试题由于篇幅放到下期。
感谢能够看到这里的读者,如果我的文章能够帮到你那我甚是荣幸,文章有任何问题都欢迎指出!制作不易还望给一个免费的三连,你们的支持就是我最大的动力!
在这里插入图片描述


http://www.ppmy.cn/ops/160888.html

相关文章

三星Galaxy S24系列手机被曝3月推送One UI 7稳定版更新

在智能手机的激烈竞争中&#xff0c;系统更新往往是决定用户体验的关键因素之一。2025年2月22日&#xff0c;一则振奋人心的消息在科技圈传开&#xff1a;三星计划于今年3月&#xff0c;向Galaxy Z Fold6、Galaxy Z Flip6折叠手机以及Galaxy S24系列推送One UI 7稳定版更新。这…

DeepSeek 助力 Vue 开发:打造丝滑的导航栏(Navbar)

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…

Windows辉煌的发展历程

2021年6月24号晚上11点&#xff0c;微软正式发布了下一代Windows操作系统——Windows 11。但在惊叹于Windows 11的精妙之余&#xff0c;我们不妨先先回顾一下windows的发展进程&#xff0c;看看在全新的Windows 11中&#xff0c;我们能看到那些熟悉的影子。 一切的开端&#x…

k8s学习记录(二):Pod基础篇

一、前言 上一篇文章中&#xff0c;我们对于k8s有了初步的认识&#xff0c;学习了k8s的架构&#xff08;Master-Worker&#xff09;&#xff0c;同时也简单的了解了k8s中比较重要的的几个组件&#xff0c;Pod、ReplicaSet、Deployment、Service等等&#xff08;当然了还有更多…

Java多线程三:补充知识

精心整理了最新的面试资料&#xff0c;有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 Lambda表达式 简介&#xff1a; 希腊字母表中排序第十一位的字母&#xff0c;英语名称为Lambda避免匿名内部类定义过多其实质属于函数式编程的概念 为什么要使用lam…

51单片机学习之旅——在LCD1602上显示时钟

新建工程 打开软件 LCD1602模块代码添加 因为我们在LCD1602上显示时钟&#xff0c;因此我们需要添加LCD1602的模块代码 跳转到这条博客51单片机学习之旅——模块化编程集_51单片机ruminant-CSDN博客&#xff0c;复制相关代码跳转到这条博客51单片机学习之旅——模块化编程集…

web的分离不分离:前后端分离与不分离全面分析

让我们一起走向未来 &#x1f393;作者简介&#xff1a;全栈领域优质创作者 &#x1f310;个人主页&#xff1a;百锦再新空间代码工作室 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[1504566…

Uniapp 设计思路全分享

一、引言 在当今移动应用开发领域&#xff0c;Uniapp 凭借其一套代码多端运行的特性&#xff0c;极大地提高了开发效率&#xff0c;降低了开发成本。然而&#xff0c;要开发出高质量、用户体验佳的 Uniapp 应用&#xff0c;清晰合理的设计思路至关重要。本文将全面分享 Uniapp…