• 进阶指针(四)—— 加强对指针,数组名,sizeof,strlen的理解


    图片来源于网络

    ✨博客主页:小钱编程成长记
    🎈博客专栏:进阶C语言
    🎈推荐相关博文:进阶C语言(一)进阶C语言(二)进阶C语言(三)

    我们通过对指针和数组笔试题的分析来增强对指针,数组名,sizeof和strlen的理解。

    1.一维数组

    1.1 理解sizeof()里一维数组数组名的意义

    int a[] = {1,2,3,4};//四个元素,每个元素都是int类型的(4字节),共16字节。
    
    • 1

    小知识:

    数组名的理解:数组名是数组首元素地址。
    但有两个例外:
    1.sizeof(数组名)。(sizeof是操作符)
    2.&数组名
    sizeof的返回值是size_t类型的,要用%zd去打印

    1. printf(“%zd\n”, sizeof(a)); // 16
      数组名单独放在sizeof内部,数组名表示整个数组,计算的是整个数组的大小,单位是字节。
    2. printf(“%zd\n”, sizeof(a+0); // 4/8
      a并非单独放在sizeof内部,也没有&,所以a就是首元素地址。
    3. printf(“%zd\n”, sizeof(*a)); // 4
      非单独,解引用首元素地址a,得第一个元素。 *a == a[0] == * (a+0)
    4. printf(“%zd\n”, sizeof(a+1)); // 4/8
      非单独,求的是第一个元素的地址。 *a == a [0] == * (a+0)
    5. printf(“%zd\n”, sizeof(a[1])); // 4
      非单独,求的是第二个元素,下标为1的元素的大小。
    6. printf(“%zd\n”, sizeof(&a)); // 4/8
      &数组名,求的是整个数组的地址的大小。整个数组的地址也是地址。

    小知识:

    数组的地址和数组首元素的地址的本质区别是类型,而非大小。

    》如图:
    在这里插入图片描述

    1. printf(“%zd\n”, sizeof( * &a)); // 16

    小知识:

    第一种理解方式:
    对int类型的元素的地址* ,访问的大小是int的大小。 对整个数组的地址 *,访问的空间是整个数组的大小(单位是字节)。
    第二种理解方式:
    *&a == a 既取地址又解引用,两种操作符的效果相抵消了。

    1. printf(“%zd\n”, sizeof(&a+1)); // 4/8
      &a是数组指针类型的,+1跳过数组的大小,但仍然是地址。
      在这里插入图片描述

    2. printf(“%zd\n”, sizeof(&a[0])); // 4/8
      求的是第一个,下标为0的元素的地址的大小。

    3. printf(“%zd\n”, sizeof(&a[0]+1)); // 4/8
      &a[0]是第一个元素的地址,+1跳过一个元素,指针指向第二个元素,求的是第二个元素的地址的大小。
      &a[1] == &a[0]+1 == a+1

    运行结果:

    在这里插入图片描述

    2.字符数组

    2.1 理解sizeof()里 char arr[] = {‘a’,‘b’,‘c’,‘d’,‘e’,‘f’};的数组名的意义

    和上一组差不多

    char arr[] = {'a', 'b', 'c', 'd', 'e', 'f'};//共6个字节
    
    • 1
    1. printf(“%zd\n”, sizeof(arr)); // 6
      单独放,数组名表示的是整个数组。
    2. printf(“%zd\n”, sizeof(arr+0)); // 4/8
      非单独放,数组名表示的是首元素地址。
    3. printf(“%zd\n”, sizeof(*arr)); // 1
      非单独放,对首元素地址解引用,得到首元素。
    4. printf(“%zd\n”, sizeof(arr[1])); // 1
      求的是下标为1的元素的大小。
    5. printf("%zd\n ", sizeof(&arr)); // 4/8
      数组的地址也是地址。
    6. printf(“%zd\n”, sizeof(&arr+1)); // 4/8
      跳过一个数组大小之后的地址。
    7. printf("%zd\n ", sizeof(&arr[0]+1)); // 4/8
      &arr[0]是第一个元素的地址,+1得到第二个元素的地址。

    运行结果:

    在这里插入图片描述

    2.2 理解strlen()里 char arr[] = {‘a’,‘b’,‘c’,‘d’,‘e’,‘f’};的数组名的意义

    char arr[] = {'a','b','c','d','e','f'};//共6字节
    
    • 1

    小知识:

    ![在这里插入图片描述](https://img-blog.csdnimg.cn/0

    • strlen是库函数,需要头文件string.h。
    • strlen的参数是指针,返回类型是size_t无符号整型,用%zd打印。
    • strlen返回的是 从 给出的指针指向的字符 到 \0之间的字符的个数(不包含\0)。
    1. printf(“%zd\n”, strlen(arr)); // 随机值
      这里的arr不属于那两个例外,所以是数组首元素地址,打印出来的是首元素地址指向的字符 到 \0之间的字符的个数(不包含\0)。又因为字符数组中没有\0,也不知道在内存中字符数组后面什么时候才能遇到\0。所以打印出来的是随机值。

    2. printf(“%zd\n”, strlen(arr+0); // 随机值
      arr+0表示的也是首元素地址,和上面情况一样。

    3. printf(“%zd\n”, strlen(*arr)); // 报错
      1、arr是首元素地址,*arr是首元素’a’,字符’a’在内存中存的是ASCII码97,但strlen需要的参数是地址,strlen会把97当作地址。
      2、strlen要想找到 \0 结束统计,就需要先从传给它的指针开始解引用找\0。 站在strlen的角度认为传参传进去的’a’-97就是地址,97是一个未知地址,是没有理由凭空出现的或对应的空间未提前开辟的地址,若直接进行访问,就是非法访问,会报错。
      在这里插入图片描述

    4. printf("%zd\n ", strlen(arr[1])); // 报错
      和上一个情况一样,都是将数组元素直接传给了strlen函数。在这里插入图片描述

    5. printf("%zd\n ", strlen(&arr)); // 随机值
      strlen的参数和返回类型为:
      size_t strlen(const char * str)
      如果传给strlen的指针的类型不是const char*,则传入函数时会被强制转换成const char类型。
      &arr–char( * )[6]–>const char
      整个数组的地址和首元素的地址在数值上一样,返回这个地址上的字符到\0之间的字符个数(不包括\0),因为数组上没有\0,也不知道内存中数组后面什么地方有\0,所以返回的是随机值。

    6. printf("%zd\n ", strlen(&arr+1)) ; // 随机值
      从跳过一个数组大小之后的地址开始,解引用找\0,返回之间的字符个数(不包括\0)。在这里插入图片描述

    7. printf ("%zd\n ", strlen(&arr[0]+1)); // 随机值
      返回的是第二个元素地址上的字符到\0之前出现的字符的个数。

    运行结果:

    在这里插入图片描述

    2.3 理解sizeof()里 char arr[] = “abcdef”; 的数组名的意义

    char arr[] = "abcdef"; //共7字节,因为还有隐藏的\0
    
    • 1
    1. pirntf("%zd\n ", sizeof(arr)); // 7
      数组名是单独放在sizeof()中的。

    2. printf("%zd\n ", sizeof(arr+0)); // 4/8
      非单独放,数组名是首元素地址。

    3. printf("%zd\n ", sizeof(*arr)); // 1
      非单独放,*arr是首元素。

    4. printf("%zd\n ", sizeof(arr[1])); // 1
      下标为1的元素的大小。

    5. printf("%zd\n ", sizeof(&arr)); // 4/8
      整个数组的地址也是地址,是地址就是4(32位)/8(64位)字节

    6. printf("%zd\n ", sizeof(&arr+1)); // 4/8
      计算的是跳过一个数组之后的地址的大小。
      在这里插入图片描述

    7. printf("%zd\n ", sizeof(&arr[0]+1)); // 4/8
      第二个元素的地址。

    运行结果:

    在这里插入图片描述

    2.4 理解strlen()里 char arr[] = “abcdef”; 的数组名的意义

    char arr[] = "abcdef"; //共7字节,因为还有隐藏的\0
    
    • 1
    1. printf("%zd\n ", strlen(arr)); // 6
      strlen()中数组名是首字符地址,具体分析过程前面说过了。

    2. printf("%zd\n ", strlen(arr+0)); // 6
      返回arr上的字符到\0之间的字符的个数。字符串最后隐藏了一个\0。

    3. printf("%zd\n ", strlen(*arr)); // 非法访问,报错
      *arr是首元素。具体分析前面说过了。
      在这里插入图片描述

    4. printf("%zd\n ", strlen(arr[1])); // 非法访问,报错
      arr[1]是第二个元素。具体分析前面说过了。

    5. printf("%zd\n ", strlen(&arr)); // 6
      数组地址在数值上==首字符地址。

    6. printf("%zd\n ", strlen(&arr+1)); // 随机值
      &arr+1是跳过一个数组之后的地址,从这开始到\0之前有几个字符,未知。
      在这里插入图片描述

    7. printf("%zd\n ", strlen(&arr[0]+1)); // 5
      &arr[0]+1是第二个元素的地址,它对应的字符到\0之间有5个字符。
      在这里插入图片描述

    运行结果:

    在这里插入图片描述

    3.理解字符串指针

    char *p = "abcdef";
    
    • 1

    小知识:

    • 指针是用来存放地址的,所以指针的大小就等于地址的大小。
    • 常量字符串作为表达式时的结果是首字符的地址,所以p中存放的是’a’的地址,但内存中肯定是存储着"abcdef"的。

    3.1 理解sizeof()里字符串指针p的意义

    char *p = "abcdef";
    
    • 1
    1. printf("%zd\n ", sizeof(p )); // 4/8
      p是指针,大小和地址的大小一样,是 4(32位)/ 8(64位)。

    2. printf("%zd\n ", sizeof(p+1)); // 4/8
      指针p+1还是地址。在这里插入图片描述

    3. printf("%zd\n ", sizeof(*p)); // 1
      p中存放的是首字符地址,*p就是首字符,大小为1。

    4. printf("%zd\n ", sizeof(p[0])); // 1
      C语言规定p[0]=*(p+0)
      若将字符串想象成字符数组,则p[0]就是首元素 / 首字符。

    5. printf("%zd\n ", sizeof(&p)); // 4/8
      指针变量的地址也是地址。

    6. printf("%zd\n ", sizeof(&p+1)); // 4/8
      跳过&p存放的p / 跳过一个p的类型的大小。在这里插入图片描述

    7. printf("%zd\n ", sizeof(&p[0]+1)); // 4/8
      p[0]是首字符,&p[0]就是首字符地址,+1跳过一个字符的大小,指向第二个元素。在这里插入图片描述

    运行结果:

    在这里插入图片描述

    3.2 理解strlen()里字符串指针p的意义

    char *p = "abcdef";
    
    • 1
    1. printf("%zd\n ", strlen(p )); // 6
      p是字符串的首字符地址。

    2. printf("%zd\n ", strlen(p+1)); // 5
      在这里插入图片描述

    3. printf("%zd\n ", strlen(*p)); // 非法访问,报错
      参数应该是指针,但这里却传的是字符。具体错误原因分析前面已经讲过了。
      在这里插入图片描述

    4. printf("%zd\n ", strlen(p[0])); // 非法访问,报错
      p[0] == *(p+0) 是首字符,应该传指针。具体错误原因分析前面已经讲过了。

    5. printf("%zd\n, strlen(&p)); // 随机值
      从&p往后开始解引用(因为p的类型的大小是4/8字节,所以每次解引用访问4/8个字节)找\0,并返回之间的字符的个数。
      在这里插入图片描述

    在这里插入图片描述

    1. printf("%zd\n ", strlen(&p+1)); // 随机值
      和上面一个类似。&p+1是&p跳过一个p的类型的大小。

    2. printf("%zd\n ", strlen(&p[0]+1)); // 6
      &p[0] == &*(p+0) == p。&p[0]+1中存放的是第二个字符的地址,返回第二个字符与\0之间的字符的个数。

    运行结果:

    在这里插入图片描述

    4.二维数组

    4.1 理解sizeof()里数组名的意义

    int a[3][4] = {0};
    
    • 1
    1. printf("%zd\n ", sizeof(a)); // 48
      数组名a单独放在了sizeof内部,表示整个数组,sizeof(a)计算的是数组的大小,单位是字节。

    2. printf("%zd\n ", sizeof(a[0][0])); // 4
      a[0][0]是二维数组的第一行第一个元素。

    3. printf("%zd\n ", sizeof(a[0])); // 16
      小知识:
      在这里插入图片描述
      a[0]是第一行这个一维数组的数组名,数组名单独放在了sizeof()内部,a[0]就表示整个第一行这个一维数组,sizeof(a[0])计算的是整个第一行这个一维数组的大小,单位为字节。

    4. printf("%zd\n ", sizeof(a[0]+1)); // 4/8
      a[0]并非单独放在sizeof()内部,也没有&,所以a[0]表示首元素地址,也就是第一行第一个元素的地址。

    5. printf("%zd\n ", sizeof(*(a[0]+1))); // 4
      a[0]是第一行数组的数组名,未单独放在sizeof()里,所以表示第一个数组的首元素地址。在这里插入图片描述

    6. printf("%zd\n ", sizeof(a+1)); // 4/8
      a非单独放在sizeof()里,也没有&,所以表示首元素地址,也就是第一个一维数组的地址。a的类型是int(*)[4],+1跳过一个数组的大小,a+1就是第二个数组的地址
      在这里插入图片描述

    7. printf("%zd\n, sizeof(*(a+1))); // 16
      1、a是首元素地址,就是第一个一维数组的地址,类型是int( * )[4]。 +1时跳过一个int( * ) 的大小。*解引用时访问一维数组的大小。
      2、从另一个角度理解: *(a+1) == a[1],sizeof(a[1])—第二行的数组名单独放在了sizeof内部,计算的是第二行的大小。

    8. printf("%zd\n ", sizeof(&a[0]+1)); // 4/8
      &a[0]是第一个数组的地址(&a[0] == & * (a+0) == a),类型是int(*)[4],+1跳过一个数组的大小,&a[0]+1就是第二个数组的地址。

    9. printf("%zd\n ", sizeof(*(&a[0]+1))); // 16
      由上一题知:&a[0]+1是第二个一维数组的地址,类型为int( * )[4],所以 * 解引用访问的空间大小是有4个元素的整型数组的大小,sizeof计算的是第二个数组的大小。

    10. printf("%zd\n ", sizeof(*a)); // 16
      a是二维数组数组名,非单独放在sizeof()中,所以表示第一个一维数组的地址。解引用后得到第一个一维数组。

    11. printf("%zd\n ", sizeof(a[3])); // 16,不会产生越界访问

    小知识:

    编译器用sizeof计算表达式的大小时,是通过类型计算的大小,并不会真的去访问表达式 / 获取表达式的值。a[3]在这里和a[0]的类型是一样的,都是int( * )[4],所以产生的效果或者说大小是一样的。

    • 比如:int a = 10; 计算a的大小时,表面是sizeof(a),真正的应该是sizeof(int)

    表达式有2个属性:值属性,类型属性。例如:2+3 = 5 或int ,5就是值属性结果,int是类型属性结果。

    运行结果:

    在这里插入图片描述

    知识总结

    数组名的理解:数组名是数组首元素地址。
    但有两个例外:
    1.sizeof(数组名)。(sizeof是操作符)
    2.&数组名
    sizeof的返回值是size_t类型的,要用%zd去打印

    数组的地址和数组首元素的地址的本质区别是类型,而非大小。

    对int类型的元素的地址 * ,访问的大小是int的大小。
    对整个数组的地址 *,访问的空间是整个数组的大小(单位是字节)。

    指针+1跳过几个字节取决于指针指向的数据是什么类型的,指向什么类型的数据,+1就跳过什么类型的大小。

    *&a == a 既取地址又解引用,两种操作符的效果相抵消了。

    &a[0] == &a[0]+1 == a+0

    传给strlen的地址一定要是程序已知的,不是凭空出现的,编译器提前开辟的空间的,未被释放的空间的。

    ![在这里插入图片描述](https://img-blog.csdnimg.cn/0

    • strlen是库函数,需要头文件string.h。
    • strlen的参数是指针,返回类型是size_t无符号整型,用%zd打印。
    • strlen返回的是 从 给出的指针指向的字符 到 \0之间的字符的个数(不包含\0)。

    strlen的参数和返回类型为:
    size_t strlen(const char * str)
    如果传给strlen的指针的类型不是const char*,则传入函数时会被强制转换成const char类型。
    &arr–char( * )[6]–>const char
    整个数组的地址和首元素的地址在数值上一样。

    在这里插入图片描述

    在这里插入图片描述

    编译器用sizeof计算表达式的大小时,是通过类型计算的大小,并不会真的去访问表达式 / 获取表达式的值。a[3]在这里和a[0]的类型是一样的,都是int( * )[4],所以产生的效果或者说大小是一样的。

    • 比如:int a = 10; 计算a的大小时,表面是sizeof(a),真正的应该是sizeof(int)

    表达式有2个属性:值属性,类型属性。例如:2+3 = 5 或int ,5就是值属性结果,int是类型属性结果。

    结语:

    本篇文章通过对题目的讲解,加强了对指针,数组名,sizeof,strlen的理解。
    最后,感谢大家的阅读!大家一起进步!

    点赞收藏加关注,C语言学习不迷路!
    图片来源于网络

  • 相关阅读:
    分布式事务-CAP&Raft原理
    后端跨域解决方案
    用Rust手把手编写一个Proxy(代理), TLS加密通讯
    32.1 Java进阶之注解概念,工作原理
    c语言从入门到实战——分支和循环
    数据库与缓存数据一致性解决方案
    [NLP] LLM---<训练中文LLama2(三)>对LLama2进行中文预料预训练
    【虚拟机】网卡不见了,失效了怎么办
    springboot基于微信小程序的运动软件前端的设计与实现别用设计源码100932
    WebRTC实战-第一章-理论基础
  • 原文地址:https://blog.csdn.net/xue_bian_cheng_/article/details/133469726