目录
一. sizeof和strlen的对比
在C语言中有两个比较相似的知识点,就是sizeof和strlen,下面我们来讲一下它们两者之间有什么不同之处?
🎉sizeof:首先sizeof是操作符而不是函数,而且sizeof计算的是变量所占空间的大小,单位是字节,只关注占内存空间的大小,不在乎内存中放的是什么内存数据。
strlen:strlen是一个函数,只针对字符串或字符数组,统计的是"\0"之前的元素个数。
二. 数组和指针笔试题解析
我们通过一些具体的题目来理解sizeof和strlen对于数组或者字符的计算,同时通过这题目加强我们对于数组名以及变量名的理解和它们与指针的关系,以及指针的移动等等问题。
2.1 一维数组
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中计算的是整个数组的长度,所以这个毋庸置疑是4*4=16个,第一个结果计算16。
2 : 此时a并没有单独放在sizeof内部,所以a指的是第一个元素的地址,(a+0)任然是第一个元素的地址,是地址就是4/8个字节。(这个我们就要看是在什么环境下了)。
3 :我们说过只要a不单独放在sizeof中就表示首元素地址,此时*a就是解引用a,现在指的是第一个元素1,是int类型,所以结果是4。
4 :同 2 一样,不过这个表示的是第二个元素的地址,是地址就是4/8个字节。
5 :a[1]就相当于*(a+1),所以表示的是第二个元素,结果就是4。
6 :我们前面也讲过,如果是&a的话表示的也是整个数组,但是&a表示的也是一个地址,是地址就是4/8个字节。
7 :*&a就表示了整个数组元素,所以此时结果是16。
8 :同理我们的&a+1表示跳过一个数组,但任然是一个地址,是地址就是4/8个字节。
9 :&a[0]表示的就是首元素的地址,是地址就是4/8个字节。
10 :任然同上,&a[0]+1表示第二个元素的地址,是地址就是4/8个字节。
2.2 字符数组
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; }
sizeof计算的是字节,我们根据第一个例也能推算出来这些结果,需要注意的是当数组名单独放在sizeof中的时候,计算的是整个数组的字节长度,另外就是如果是地址的话,不管怎样,都是4/8个字节。🍾下面让我们用strlen函数来计算一下,代码如下:
int mian() { 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"之前的元素个数,而这里的arr数组只是一个字符数组,不是以'\0'结束的字符串。所以第一行中strlen(arr)就会出现报错,同样(arr+0)也不是字符串,同样会报错,再比如arr[1]表示的是一个元素'a',结尾并没有'\0',所以都会报错,所以我们会得到只要不是结尾以'\0'结尾,我们strlen的要不然是随机值,要不就是报错,归根结底都是没有找到'\0',没有找到结束标志。
🎃下面我们就将字符数组换成一个字符串来重新使用strlen函数:
int main() { char arr[] = "abcdef"; 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; }
我们都知道,如果是字符串的话,在末尾都会有'\0',就像"abcdef",在程序中则是"a b c d e f /0",所以这样的话使用strlen函数就会找到一个结束标志。
1:strlen从arr所指向位置开始,直到遇见'\0',计算字符个数。
2:(arr+0)等同于arr,所以仍然是输出6。
3:*arr就表示第一个字符'a',不是字符串,会报错。
4:同理arr[1]表示的是字符'b',不是字符串,会报错。
5:同理&arr和&arr+1都是一个地址,会报错。
6:而对于&arr[0]+1是指向'b',所以从b开始计算到'\0'的长度为5
🍾当我们把数组换成指针变量的话,再来计算他们的字节长度或者字符个数会有什么变化吗?首先使用sizeof计算字节长度,代码如下:
int main() { char* p = "abcdef"; printf("%d\n", sizeof(p)); printf("%d\n", sizeof(p + 1)); printf("%d\n", sizeof(*p)); printf("%d\n", sizeof(p[0])); printf("%d\n", sizeof(&p)); printf("%d\n", sizeof(&p + 1)); printf("%d\n", sizeof(&p[0] + 1)); return 0; }
我们逐步分析一下:char* p = "abcdef"; 定义一个字符指针 p,指向字符串常量 "abcdef"
1:sizeof(p) 计算的是指针变量 p 本身所占的字节数,,p是一个地址,通常在 32 位系统为 4 字节,64 位系统为 8 字节,不会报错。
2:sizeof(p + 1),p + 1 仍然是一个指针,所以 sizeof(p + 1) 计算的也是指针所占的字节数,结果与 sizeof(p) 相同,不会报错。
3:sizeof(*p) 表示指针 p 所指向的第一个字符,即 'a',sizeof(*p) 相当于 sizeof(char),结果为 1 字节,不会报错。
4:p[0] 等价于 *p,所以 sizeof(p[0]) 也为 1 字节,不会报错
5:sizeof(&p)是指针 p 的地址,其类型为指向指针的指针,在 32 位系统通常为 4 字节,64 位系统为 8 字节,不会报错。
6:printf("%d\n", sizeof(&p + 1)); // &p + 1 是指向指针的指针的移动,sizeof(&p + 1) 计算的是这种移动后的指针的大小,结果与 sizeof(&p) 相同,不会报错。对于这个大家可能会疑惑有没有可能会越界,但其实sizeof不会真实计算里面的值,不会出先越界情况。
7:printf("%d\n", sizeof(&p[0] + 1)); // &p[0] 等价于 p,&p[0] + 1 仍然是一个指针,所以 sizeof(&p[0] + 1) 计算的也是指针所占的字节数,不会报错。
🎉:使用strlen函数计算,代码如下:
int main() { char* p = "abcdef"; printf("%d\n", strlen(p)); printf("%d\n", strlen(p + 1)); printf("%d\n", strlen(*p)); printf("%d\n", strlen(p[0])); printf("%d\n", strlen(&p)); printf("%d\n", strlen(&p + 1)); printf("%d\n", strlen(&p[0] + 1)); return 0; }
为什么只有6和5两个数值呢?逐步分析:
- printf("%d\n", strlen(p)); :输出 6。从 p 所指向的位置(即字符 'a' )开始,一直到遇到 '\0' ,正好有 6 个字符。
- printf("%d\n", strlen(p + 1)); :输出 5。 p + 1 使得指针向后移动一位,指向了字符 'b' ,从 'b' 开始到 '\0' 的长度为 5。
- printf("%d\n", strlen(*p)); :这里会报错。 *p 表示指针 p 所指向的字符,即 'a' 。 strlen 函数需要的是一个字符指针作为参数,而不是单个字符。这里将字符 'a' (其 ASCII 值为 97)当作指针传递给 strlen 函数,会导致错误的内存访问。
- printf("%d\n", strlen(p[0])); :与 strlen(*p) 类似,会报错。 p[0] 等价于 *p ,也是字符 'a' ,不能作为 strlen 的参数。
- printf("%d\n", strlen(&p)); :会产生随机值或报错。 &p 是指针 p 的地址,而不是一个有效的字符串起始位置,所以 strlen 函数的结果是不确定的。
- printf("%d\n", strlen(&p + 1)); :同样会产生随机值或报错。 &p + 1 是指针的地址再向后移动一位,也不是一个有效的字符串起始位置。
- printf("%d\n", strlen(&p[0] + 1)); :输出 5。 &p[0] 等价于 p , &p[0] + 1 就相当于 p + 1 ,从该位置开始到 '\0' 的长度为 5。
2.3 二维数组
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)); printf("%d\n", sizeof(a[3])); return 0; }
printf("%d\n", sizeof(a)); // 输出 48。a 是一个 3 行 4 列的二维数组,int 类型通常占 4 字节,所以 3×4×4 = 48 字节,不会报错
printf("%d\n", sizeof(a[0][0])); // 输出 4。a[0][0] 是一个 int 类型的元素,通常占 4 字节,不会报错
printf("%d\n", sizeof(a[0])); // 输出 16。a[0] 是第一行,相当于一个包含 4 个 int 元素的一维数组,所以 4×4 = 16 字节,不会报错
printf("%d\n", sizeof(a[0] + 1)); // 输出 4 或 8(取决于系统是 32 位还是 64 位)。a[0] + 1 是一个指向 int 的指针,所以其大小为指针大小,不会报错
printf("%d\n", sizeof(*(a[0] + 1))); // 输出 4。*(a[0] + 1) 是一个 int 类型的值,所以大小为 4 字节,不会报错
printf("%d\n", sizeof(a + 1)); // 输出 4 或 8(取决于系统是 32 位还是 64 位)。a + 1 是一个指向二维数组的指针,所以其大小为指针大小,不会报错
printf("%d\n", sizeof(*(a + 1))); // 输出 16。*(a + 1) 指向第二行,相当于一个包含 4 个 int 元素的一维数组,大小为 16 字节,不会报错
printf("%d\n", sizeof(&a[0] + 1)); // 输出 4 或 8(取决于系统是 32 位还是 64 位)。&a[0] + 1 是一个指向第二行的指针,所以其大小为指针大小,不会报错
printf("%d\n", sizeof(*(&a[0] + 1))); // 输出 16。*(&a[0] + 1) 指向第二行,相当于一个包含 4 个 int 元素的一维数组,大小为 16 字节,不会报错
printf("%d\n", sizeof(*a)); // 输出 16。*a 指向第一行,相当于一个包含 4 个 int 元素的一维数组,大小为 16 字节,不会报错
printf("%d\n", sizeof(a[3])); // 未定义行为。因为数组 a 只有 3 行,访问 a[3] 超出了数组的边界,不会报错,但结果是不可预测的