> 技术文档 > 【C语言系列】深入理解指针(5)

【C语言系列】深入理解指针(5)


深入理解指针(5)

  • 一、sizeof和strlen的对比
    • 1.1sizeof
    • 1.2strlen
    • 1.3sizeof和strlen的对比
  • 二、数组和指针笔试题解析
    • 2.1 一维数组
    • 2.2 字符数组
      • 2.2.1代码1:
      • 2.2.2代码2:
      • 2.2.3代码3:
      • 2.2.4代码4:
      • 2.2.5代码5:
      • 2.2.6代码6:
    • 2.3 二维数组
  • 三、指针运算笔试题解析
    • 3.1题目1:
    • 3.2题目2:
    • 3.3题目3:
    • 3.4题目4:
    • 3.5题目5:
    • 3.6题目6:
    • 3.7题目7:
  • 四、总结
      • 1. `sizeof`与`strlen`的区别
      • 2. 数组和指针的关系
      • 3. 字符数组与字符串
      • 4. 指针运算
      • 总结

一、sizeof和strlen的对比

1.1sizeof

sizeof单目操作符绝不是函数!!!),sizeof计算变量所占内存空间的大小的,单位是字节。
如果操作数是类型的话,计算的是使用类型创建的变量所占内存空间的大小。
注:sizeof只关注占用内存空间的大小,不在乎内存中存放什么数据。
比如:

 #inculde <stdio.h> int main() { int a = 10; printf(\"%d\\n\", sizeof(a)); printf(\"%d\\n\", sizeof a); printf(\"%d\\n\", sizeof(int)); return 0; }

1.2strlen

strlen是C语言库函数功能是求字符串长度。
函数原型是:

size_t strlen ( const char * str );

统计的是从strlen函数的参数str中这个地址开始向后,\\0之前字符串中字符的个数。
注:strlen 函数会一直向后找 \\0 字符,直到找到为止,所以可能存在越界查找。
代码如下:

#include int main(){ char arr1[3] = {\'a\', \'b\', \'c\'}; char arr2[] = \"abc\"; printf(\"%d\\n\", strlen(arr1)); printf(\"%d\\n\", strlen(arr2)); printf(\"%d\\n\", sizeof(arr1)); printf(\"%d\\n\", sizeof(arr2)); return 0;}

运行结果如下:
【C语言系列】深入理解指针(5)

1.3sizeof和strlen的对比

sizeof strlen 1.sizeof是操作符 1.strlen是库函数,使用需要包含头文件string.h 2.sizeof计算操作数所占内存的大小,单位是字节 2.strlen是求字符串长度的,统计的是\\0之前字符的个数 3.不关注内存中存放什么数据 3.关注内存中是否有\\0,如果没有\\0,就会持续往后找,可能会越界 4.sizeof括号中有表达式的话,表达式是不参与计算的!!!

用代码检验4,代码如下:

#include int main(){int a = 8;short s = 4;printf(\"%d\\n\",sizeof(s = a + 2));//2printf(\"%d\\n\",s);//4return 0;}

运行结果如下:
【C语言系列】深入理解指针(5)
那么为什么sizeof中的表达式不计算?
C语言是编译型语言,在编译期这个表达式并不会被执行,sizeof 运算的结果是在编译期间就已知的常数值,并不需要等到运行时才求解。因此,对于其中涉及到的操作数或者操作本身都不需要实际执行。

二、数组和指针笔试题解析

2.1 一维数组

*a == a[0] == *(a + 0)
数组名的理解:数组名是数组首元素(第一个元素)的地址。
但是有2个是例外:1.sizeof(数组名) —— 数组名表示整个数组,计算的是整个数组的大小,单位是字节。
2.&数组名 —— 数组名表示的是整个数组,取出的是整个数组的地址。
除此之外,所有的数组名是数组首元素(第一个元素)的地址。

笔试题代码和解析如下:

#include int main(){int a[] = {1,2,3,4};//数组有几个元素?//4printf(\"%zd\\n\",sizeof(a));//16printf(\"%zd\\n\",sizeof(a + 0));//a是首元素的地址 —— 类型是int*,a + 0还是首元素的地址,是地址大小就是4/8。printf(\"%zd\\n\",sizeof(*a));//a是首元素的地址,*a是首元素,大小就是4个字节。printf(\"%zd\\n\",sizeof(a + 1));//a是首元素地址,类型是int*,a + 1跳过1个整型,a + 1就是第二个元素的地址,是地址大小就是4/8。printf(\"%zd\\n\",sizeof(a[1]));//a[1]就是第二个元素,大小是4个字节。printf(\"%zd\\n\",sizeof(&a));//&a是数组的地址,数组的地址也是地址,是地址大小就是4/8字节。printf(\"%zd\\n\",sizeof(*&a));//1.*&互相抵消了,等价于sizeof(a),16//2.&a是数组的地址,类型是int(*)[4],对数组指针解引用访问的是数组,计算的是数组的大小,16//char* —— 解引用访问的是char//int* —— 解引用访问的是intprintf(\"%zd\\n\",sizeof(&a + 1));//&a + 1是跳过这个数组后的那个位置的地址,是地址大小就是4/8字节。printf(\"%zd\\n\",sizeof(&a[0]));//首元素的地址,大小就是4/8字节。printf(\"%zd\\n\",sizeof(&a[0] + 1));//&a[0] + 1 —— 数组第二个元素的地址,大小就是4/8字节。return 0;}

运行结果如下图:
【C语言系列】深入理解指针(5)

2.2 字符数组

2.2.1代码1:

笔试题代码和解析如下:

#include int main(){char arr[] = {\'a\',\'b\',\'c\',\'d\',\'e\',\'f\'};printf(\"%d\\n\",sizeof(arr));//数组名单独放在sizeof内部,计算的是数组的大小,单位为字节,6。printf(\"%d\\n\",sizeof(arr + 0));//arr是数组名表示首元素的地址,arr + 0还是首元素的地址,是地址就是4/8字节。printf(\"%d\\n\",sizeof(*arr));//arr是首元素的地址,*arr就是首元素,大小就是1个字节。//*arr == arr[0] == *(arr + 0)printf(\"%d\\n\",sizeof(arr[1]));//arr[1]是第二个元素,大小也是1个字节。printf(\"%d\\n\",sizeof(&arr));//&arr是数组的地址,数组的地址也是地址,是地址大小就是4/8个字节。//&arr —— char(*)[6]printf(\"%d\\n\",sizeof(&arr + 1));//4/8个字节,&arr + 1,跳过整个数组,指向了数组后边的空间。printf(\"%d\\n\",sizeof(&arr[0] + 1));//第二个元素的地址,是地址就是4/8个字节。return 0;}

运行结果如下图:
【C语言系列】深入理解指针(5)

2.2.2代码2:

笔试题代码和解析如下:

#include #include int main(){char arr[] = {\'a\',\'b\',\'c\',\'d\',\'e\',\'f\'};printf(\"%d\\n\",strlen(arr));//arr是首元素的地址,数组中没有\\0,就会导致越界访问,结果就是随机的。printf(\"%d\\n\",strlen(arr + 0));//arr + 0是数组首元素的地址,数组中没有\\0,就会导致越界访问,结果就是随机的。printf(\"%d\\n\",strlen(*arr));//arr是首元素的地址,*arr是首元素,就是\'a\',\'a\'的ASCII码值是97,就相当于97作为地址传递给了strlen,strlen得到的就是野指针,代码是有问题的。printf(\"%d\\n\",strlen(arr[1]));//arr[1] —— \'b\' —— 98,传给strlen函数也是错误的。printf(\"%d\\n\",strlen(&arr));//&arr是数组的地址,起始位置是数组的第一个元素的位置,随机值。printf(\"%d\\n\",strlen(&arr + 1));//随机值。printf(\"%d\\n\",strlen(&arr[0] + 1));//从第二个元素开始向后统计的,得到的也是随机值。return 0;}

运行结果如下图:
【C语言系列】深入理解指针(5)
【C语言系列】深入理解指针(5)

2.2.3代码3:

笔试题代码和解析如下:

#include int main(){char arr[] = \"abcdef\";printf(\"%d\\n\",sizeof(arr));//7,计算的是数组总大小,数组名单独放在sizeof内部。printf(\"%d\\n\",sizeof(arr + 0));//arr表示数组首元素的地址,arr + 0还是首元素的地址,4/8字节。printf(\"%d\\n\",sizeof(*arr));//arr表示数组首元素的地址,*arr是首元素,大小是1字节。printf(\"%d\\n\",sizeof(arr[1]));//arr[1]是第二个元素,大小是1个字节。printf(\"%d\\n\",sizeof(&arr));//&arr是数组的地址,是地址就是4/8字节。printf(\"%d\\n\",sizeof(&arr + 1));//&arr是数组的地址,是地址就是4/8字节。printf(\"%d\\n\",sizeof(&arr[0] + 1));//&arr是数组的地址,+1跳过整个数组,还是地址,是地址就是4/8字节。return 0;}

运行结果如下图:
【C语言系列】深入理解指针(5)

2.2.4代码4:

笔试题代码和解析如下:

#include #include int main(){char arr[] = \"abcdef\";printf(\"%d\\n\",strlen(arr));//6printf(\"%d\\n\",strlen(arr + 0));//arr首元素的地址,arr + 0还是首元素的地址,向后统计在\\0之前的字符个数。//6printf(\"%d\\n\",strlen(*arr));//\'a\' —— 97,出错。printf(\"%d\\n\",strlen(arr[1]));//\'b\' —— 98.出错。printf(\"%d\\n\",strlen(&arr));//&arr是数组的地址,也是从数组第一个元素开始向后找,6。printf(\"%d\\n\",strlen(&arr + 1));//随机值。printf(\"%d\\n\",strlen(&arr[0] + 1));//5//&arr —— char(*)[7]//size_t strlen(const char*s);return 0;}

运行结果如下图:
【C语言系列】深入理解指针(5)
【C语言系列】深入理解指针(5)

2.2.5代码5:

笔试题代码和解析如下:

#include int main(){const char*p = \"abcdef\";printf(\"%d\\n\",sizeof(p));//p是指针变量,我们计算的是指针变量的大小,4/8个字节。printf(\"%d\\n\",sizeof(p + 1));//p + 1是b的地址,是地址大小就是4/8个字节。printf(\"%d\\n\",sizeof(*p));//p的类型是char*,*p就是char类型了,1个字节。printf(\"%d\\n\",sizeof(p[0]));//1.p[0]->*(p + 0)->*p->\'a\',大小1个字节。//1.把常量字符串想象成数组。//2.p可以理解为数组名,p[0],就是首元素。printf(\"%d\\n\",sizeof(&p));//取出的是p的地址,地址的大小就是4/8个字节。printf(\"%d\\n\",sizeof(&p + 1));//&p + 1是跳过p指针变量后的地址,地址的大小是4/8个字节。printf(\"%d\\n\",sizeof(&p[0] + 1));//4/8,取出首元素的地址,+1是第二个字符的地址。return 0;}

运行结果如下图:
【C语言系列】深入理解指针(5)

2.2.6代码6:

笔试题代码和解析如下:

#include #include int main(){char*p =\"abcdef\";printf(\"%d\\n\",strlen(p));//6printf(\"%d\\n\",strlen(p + 1));//5printf(\"%d\\n\",strlen(*p));//*p就是\'a\' —— 97,errprintf(\"%d\\n\",strlen(p[0]));//p[0]->*(p + 0)->*p//errprintf(\"%d\\n\",strlen(&p));//&p是指针变量p的地址,和字符串“abcdef”关系就不大了,从p这个指针变量的起始位置开始向后数的,p变量存放的地址是什么,不知道,所以答案是随机值。printf(\"%d\\n\",strlen(&p + 1));//随机值printf(\"%d\\n\",strlen(&p[0] + 1));//5return 0;}

运行结果如下图:
【C语言系列】深入理解指针(5)
【C语言系列】深入理解指针(5)
【C语言系列】深入理解指针(5)

2.3 二维数组

二维数组笔试题代码和解析如下:

#include int main(){int a[3][4] = {0};printf(\"%d\\n\",sizeof(a));//a是数组名,单独放在sizeof内部,计算的是数组的大小,单位是字节 —— 48 = 3*4*sizeof(int)。printf(\"%d\\n\",sizeof(a[0][0]));//a[0][0]第一行第一个元素,大小是4个字节。printf(\"%d\\n\",sizeof(a[0]));//a[0]是第一行的数组名,数组名单独放在sizeof内部了,计算的是数组的总大小16个字节。printf(\"%d\\n\",sizeof(a[0] + 1));//a[0]并没有单独放在sizeof内部,所以这里的数组名a[0]就是数组首元素的地址,即a[0]->&a[0][0],+1后是&a[0][1]的地址,大小是4/8个字节。printf(\"%d\\n\",sizeof(*(a[0] + 1)));//第一行第一个元素,大小是4printf(\"%d\\n\",sizeof(a + 1));//a作为数组名并没有单独放在sizeof内部,a表示数组首元素的地址,是二维数组首元素的地址,也就是第一行的地址,a + 1,跳过一行,指向了第二行,a + 1是第二行的地址,a + 1是数组指针,是地址大小就是4/8个字节。printf(\"%d\\n\",sizeof(*(a + 1)));//1.a + 1是第二行的地址,*(a + 1)就是第二行,计算的是第二行的大小 —— 16。2.*(a + 1) == a[1],a[1]是第二行的数组名,sizeof(*(a + 1))就相当于sizeof(a[1]),意思就是把第二行的数组名单独放在sizeof内部,计算的是第二行的大小。printf(\"%d\\n\",sizeof(&a[0] + 1));//a[0]是第一行的数组名,&a[0]取出的就是数组的地址,就是第一行的地址,&a[0] + 1就是第二行的地址,是地址大小就是4/8字节。printf(\"%d\\n\",sizeof(*(&a[0] + 1)));//对第二行地址解引用,访问的就是第二行,大小是16个字节。printf(\"%d\\n\",sizeof(*a));//a作为数组名并没有单独放在sizeof内部,a表示数组首元素的地址,是二维数组首元素的地址,也就是第一行的地址,*a就是第一行,计算的就是第一行的大小,16个字节。printf(\"%d\\n\",sizeof(a[3]));//a[3]无需真实存在,仅仅通过类型的推断就能算出长度,a[3]是第四行的数组名,单独放在sizeof内部,计算第四行的大小,16个字节。//sizeof(int);//4//sizeof(3+5);//4return 0;}

运行结果如下图:
【C语言系列】深入理解指针(5)

三、指针运算笔试题解析

3.1题目1:

指针运算笔试题代码和解析如下:

#include int main(){int a[5] = {1,2,3,4,5};int*ptr = (int*)(&a + 1);//&a —— int(*)[5]printf(\"%d %d\",*(a + 1),*(ptr - 1));//ptr跳过了原来a数组指向下一个位置,*(ptr-1)访问的就是数组a中的5。return 0;}

运行结果如下图:
【C语言系列】深入理解指针(5)

3.2题目2:

在X86(32位)环境下,假设结构体的大小是20个字节,程序输出的结果是什么?
指针运算笔试题代码和解析如下:

#include struct Test{int Num;char*PcName;short sDate;char cha[2];short sBa[4];}*p = (struct Test*)0x100000;//结构体指针+1,跳过一个结构体;整型值+1,就是+1。//指针+-整数int main(){printf(\"%p\\n\",p + 0x1);//0x100000 + 20 -> 00100014printf(\"%p\\n\",(unsigned long)p + 0x1);//0x100000 + 1 ->0x100001 -> 00100001printf(\"%p\\n\",(unsigned int*)p + 0x1);//0x100000 + 4 -> 0x100004 -> 00100004return 0;}

运行结果如下图:
【C语言系列】深入理解指针(5)

3.3题目3:

指针运算笔试题代码和解析如下:

#include int main(){int a[3][2] = {(0,1),(2,3),(4,5)};//1 3 5//初始化int*p;p = a[0];//&a[0][0];printf(\"%d\",p[0]);//1//*(p + 0) -> *preturn 0;}

运行结果如下图:
【C语言系列】深入理解指针(5)

3.4题目4:

假设环境是X86环境,程序的输出结果是什么?
指针运算笔试题代码和解析如下:

#include int main(){//%d —— 是打印有符号的整数//%p —— 是打印地址的int a[5][5];//a —— 类型是:int(*)[5]int(*p)[4];//p —— 类型是:int(*)[4]//p是一个数组指针,p指向的数组是4个整型元素的p = a;//类型的差异 —— 警告printf(\"%p,%d\\n\",&p[4][2] - &a[4][2],&p[4][2] - &a[4][2]);//FFFFFFFC,-4return 0;}//指针-指针绝对值得到的是指针和指针之间的元素个数

运行结果如下图:
【C语言系列】深入理解指针(5)
【C语言系列】深入理解指针(5)

3.5题目5:

指针运算笔试题代码和解析如下:

#include int main(){int aa[2][5] = {1,2,3,4,5,6,7,8,9,10};int*ptr1 = (int*)(&aa + 1);int*ptr2 = (int*)(*(aa + 1));printf(\"%d %d\",*(ptr1 - 1),*(ptr2 - 1));//10 5return 0;}//*(aa + 1)->aa[1],aa[1]是第二行的数组名,数组名表示首元素的地址。//aa[1]也是&aa[1][0]//*(aa + 1)->aa[1],&aa[1]->第二行的地址//sizeof(aa[1])->计算的是第二行的大小

运行结果如下图:
【C语言系列】深入理解指针(5)

3.6题目6:

指针运算笔试题代码和解析如下:

#include int main(){char*a[] = {\"work\",\"at\",\"alibaba\"};//a是指针数组char**pa = a;pa++;printf(\"%s\\n\",*pa);//at//%s是打印字符串,给一个地址,从这个地址向后打印字符串,直到\\0return 0;}

画图分析:
【C语言系列】深入理解指针(5)

运行结果如下图:
【C语言系列】深入理解指针(5)

3.7题目7:

指针运算笔试题代码和解析如下:

#include int main(){char*c[] = {\"ENTER\",\"NEW\",\"POINT\",\"FIRST\"};char**cp[] = {c + 3,c + 2, c + 1,c};char***cpp = cp;printf(\"%s\\n\",**++cpp);//POINTprintf(\"%s\\n\",*--*++cpp + 3);//ERprintf(\"%s\\n\",*cpp[-2] + 3);//**(cpp - 2) + 3//STprintf(\"%s\\n\",cpp[-1][-1] + 1);//*c*(cpp-1) - 1//EWreturn 0;}

运行结果如下图:
【C语言系列】深入理解指针(5)

四、总结

本文深入探讨了C语言中关于sizeofstrlen、数组和指针的一些基础概念,并通过代码示例进行了详细的讲解。以下是对主要内容的总结:

1. sizeofstrlen的区别

sizeof是一个运算符,用于计算变量或类型所占的内存空间大小,单位是字节。它与数据存储内容无关,只关注内存的占用。例如,sizeof(int)会返回一个整数类型的大小,而sizeof(a)会返回数组a的总字节数。需要注意的是,sizeof中的表达式不会被计算,仅仅是编译时确定的常量。

与此不同,strlen是C标准库中的一个函数,用于计算以\'\\0\'(空字符)结尾的字符串的长度。它统计的是字符串中的字符个数,而不包括\'\\0\'字符。因此,strlen在处理字符串时,必须确保字符串正确地以\'\\0\'结尾,否则可能导致越界访问。

2. 数组和指针的关系

数组和指针是C语言中常见的概念,它们密切相关。数组名通常被认为是指向数组首元素的指针。通过数组名,可以访问数组的元素,但是数组名和指针在某些情况下也有所不同。例如,sizeof(a)计算的是整个数组的大小,而sizeof(a + 1)计算的是数组中某个元素的指针大小。此外,数组名也可以通过&a表示整个数组的地址,&a[0]表示数组首元素的地址。

3. 字符数组与字符串

字符数组在内存中的存储方式可能导致不同的行为。在没有\'\\0\'结尾的情况下,使用strlen函数可能会导致越界访问,进而产生随机结果。通过具体的代码示例,文章展示了不同数组类型在使用sizeofstrlen时的差异,特别是字符数组和字符串常量。

4. 指针运算

指针运算是C语言中强大的功能之一。指针可以进行加减操作,指向内存中的不同位置。通过对数组指针进行运算,可以访问数组中的不同元素。指针间的运算遵循指针类型的大小,比如int*指针加1时会跳过一个int类型的大小,指向下一个int类型的数据。文章通过一系列例子展示了指针和数组在内存中的操作,包括指针的解引用、指针数组的运算等。

总结

本篇文章深入分析了sizeofstrlen、数组与指针等概念,并通过一系列代码示例加深了对这些概念的理解。对于初学者来说,掌握这些基础知识是学习C语言的关键。文章不仅揭示了这些基本概念的使用方法,还通过具体例子帮助理解如何避免常见的错误,如越界访问和指针运算中的误解。