引言:
本文围绕sizeof操作符与strlen函数展开,首先列出两者之间的区别,然后介绍了strlen函数的三种实现方法即递归,创建计数变量,指针相减,后分析练习打下基础,最后给出了一系列辨析二者区别的代码,并给出了详细的解释,以帮助我们加强对着二者区别的理解,然后总结了对于二者辨析的心得。
目录
sizeof与strlen的区别:
- sizeof是操作符,strlen是库函数,使用头文件<string.h>
- sizeof更关心的是操作数所占内存的大小,strlen是求字符串长度,统计的是'\0'前的字符个数
- sizeof不在乎内存中存放的数据,所以sizeof(int a+int b)不论括号内的值为多少,总是返回4,即sizeof中的表达式不计算,strlen关注内存中是否有‘\0’,如果没有'\0"就会一直向后寻找,从而出现越界。
strlen函数的三种实现方法
值得注意的几个易忽略点的是,strlen函数的返回值是size_t类型,即没有符号,这一点在后文中的辨析代码练习中会用到,同时strlen函数的参数是所求字符串的地址即const char*。以下是具体实现。
实现效果:
第一种创建计数变量:
assert断言防止所传p为空指针,同时p形参我们不希望所指向的值被修改,所以加const进行修饰。循环部分涉及指针加减一个整数跳过的大小由所指向的类型限制,最后count与函数返回的类型不符合,自动进行强制类型转化。
第二种使用指针相减:
涉及指针相减得到指针之间相差的元素个数
第三种使用函数递归:
函数的结束标志是p解引用为'\0','\0'的ASCII码值为0,取反后为真,否则进行函数递归,但应该注意栈溢出。
代码概览
//strlen函数的三种实现方法
#include<stdio.h>
#include<string.h>
#include<assert.h>
//计算变量
size_t j_strlen(const char* p)
{
assert(p);
int count = 0;
while (*(p+count))
{
count++;
}
return count;
}
//递归实现
size_t d_strlen(const char* p)
{
assert(p);
if (!*p)
{
return 0;
}
else
{
return 1 + d_strlen(p + 1);
}
}
//指针实现
size_t z_strlen(const char* p)
{
assert(p);
char* p1 = p;
while (*++p1)
;
return(p1 - p);
}
int main()
{
char arr[] = "上帝的归上帝,凯撒的归凯撒";
printf("计算变量:%zd\n", j_strlen(arr));
printf("递归实现:%zd\n", d_strlen(arr));
printf("指针实现:%zd\n", z_strlen(arr));
printf("原库函数:%zd\n", strlen(arr));
return 0;
}
辨析代码练习
本节为了加深对sizeof操作符和strlen函数的理解列举了大量代码练习,并且逐一个给出解释。
热身组(基础回顾):
- 9行没有'\0'返回随机值,10.12.13均返回3
(使用%zd打印更严谨)
答案:
1~5:16,4/8,4,4/8,4
6~10:4/8,16,4/8,4/8,4/8
解释:
- 该处a单独放在sizeof中,代表该数组的整个地址,计算的是整个数组在内存中占的大小。值得注意的是,该处是两个特例中的一个,当数组名单独放在sizeof操作符中时,返回的是该数组整体在内存中所占的大小,当&加数组名时,访问的是整个数组的地址。所以,4个元素乘以int类型大小4,结果为16.
- 此处数组名未单独放在sizeof操作符中,所以表示数组首元素的地址,是地址而不知道环境是x86还是x64,所以答案是4个或者8个字节。
- 该处为数组首元素地址解引用,得到的是int类型的数字1,所以结果为4.
- 该处为数组的第二个元素的地址,同2,为4/8。
- 该处表示数组的第二个元素,大小为int,返回4 .
- 此处为对数组首元素地址取地址,是二级指针,仍为4/8。
- 此处有两种解释方法,解引用和取地址是相反的过程,相互抵消,相当于数组名a单独放在sizeof操作符中,返回的是整个数组在内存中的大小,结果为16.
- 此处&加数组名返回的是整个数组的地址,是一个int(*)[4]类型的指针,加1,跳过16个字节,指向的是未知指针,但是仍为指针,所以答案是4/8
- 最后两个分别为第一个元素的地址,第二个元素的地址。所以答案是4/8。
常规组(一维数组):
(使用%zd打印更严谨)
答案1:
1~5:6,4/8,1,1,4/8
6~7:4/8,4/8
解释1:
- arr单独放在sizeof操作符中,访问的是整个数组的地址,返回的是整个数组的大小,6个元素乘以char类型大小一个字节,答案为6.
- arr不是单独放置,代表的是首元素的地址,地址的大小同前,为4/8。
- arr为单独放在sizeof操作符中,代表的是首元素的地址,解引用,为首元素,所以结果为1
- 代表的是第二个元素,大小为1。
- 后三个分别为整个元素的地址,跳过该整个数组指针后的未知指针,数组第二个元素的地址。
(使用%zd打印更严谨)
答案2:
1~5:烫,烫,error,error,烫
6~7:烫,烫
解释2:
- arr代表首元素的地址,而arr中没有'\0'会越界访问。
- 仍是首元素的地址,同上
- 为数组首元素,但是strlen函数的参数是const char*类型的指针,所以程序报错。
- 为数组第二个元素,同上。
- 该处取的是整个元素的地址,同1。
- 该处是跳过整个数组后的未知指针。
- 该处为数组第二个元素的指针,同1.
(使用%zd打印更严谨)
答案3:
1~5:7,4/8,1,1,4/8
6~7:4/8,4/8
解释3:
- arr单独放在sizeof操作符中,计算的是整个数组在内存中所占的大小,但是该处容易忽略\0的存在,错误的认为是6。
- arr未单独放在sizeof操作符中,表示的是数组首元素的地址,是地址所以答案是4/8。
- arr未单独放在sizeof操作符中,表示数组首元素的地址,解引用结果为'a',返回1个字节。
- 该处为数组第二个元素,返回1个字节
- &加数组名,访问的是整个数组的地址,是地址答案为4/8,后两个同理为未知数组和数组第二个元素的地址。故可总结,因为sizeof更关心的是对象的数据类型,所以只要看到&并且没有*的出现,基本上可以判定结果为4/8。
(使用%zd打印更严谨)
答案4:
1~5:6,6,error,error,6
6~7:随机值,5
解释4:
-
该处为常规的strlen求字符串长度,'\0'前面有6个字符。
-
数组名代表首元素的地址,加0不变。
-
首元素地址解引用,得到首元素,不是指针,报错。
-
是首元素,不是地址,报错。
-
&加数组名取出的是整个数组的地址,,但在strlen函数只关注'\0'前的字符数,所以仍然得到6。
-
该处是跳过整个数组的未知指针,所以是随机值
-
传入的是第二个元素的地址,从第二个元素开始计算,得到5
超级组(二维数组)
(使用%zd打印更严谨)
答案:
1~6:48,4,16,4/8,4,4/8
7~11:16,4/8,16,16,16
解释:
逐个解释之前,复习arr [ i ] [ j ] 等效于*( * ( arr + i ) + j ),二维数组的数组名指向的是一维数组的数组名,即是一维数组的首元素的地址的地址。一维数组的数组名,指向的是数组首元素。二维数组相当于,把其他数组的数组名作为自己的元素,这些元素是地址,解引用一次,变为所储存的数组的数组名,即一维数组首元素的地址。
- 二维数组的数组名单独放在sizeof操作符内部,求的是整个数组在内存中所占的大小,3*4*4=48
- 相当于解引用两次(这么称述方便后面理解),求的是单个整型的大小,为4
- 解引用一次,是第一行的数组名,即第一行的的数组名单独放在sizeof操作符中,求的是整个数组占内存空间的大小
- 该处表示第一行的第二个元素的地址,相当于( * ( arr + 0 ) + 1 )
- 相当于*( * ( arr + 0 ) + 1 ),二维数组储存的第一个数组的地址解引用,得到第一个数组的首元素的地址,即第一个数组的数组名,后加1再解引用,得到int类型的数字内容。(这里容易在理解第四点后,被误导,应该认识到第四点是属于特例的)
- 没有进行解引用或者数组名单独放在sizeof操作符中的情况,该处代表被存储数组的数组名的地址加一,得到第二个数组数组名的地址,是地址
- 该处为第二个数组的数组名的地址解引用,得到第二个数组的数组名,数组命单独放在sizeof操作符中,求的是该数组在内存中占的总大小.
- a [ 0 ]相当于解引用一次,地址变到首个被储存的数组的数组名,而后&,相当于抵消了*的作用,仍然是地址,而后加一,表示的是第二个被存储的数组的数组名的地址,是地址。
- 对第二个被存储的数组的数组名的地址解引用,得到第二个被储存的数组的数组名,满足数组名单独放在sizeof操作符中,求的是整个数组占内存空间的大小。
- a未单独放在 sizeof操作符中,表示的是二维数组首元素的地址,即第一个被储存数组的数组名的地址,解引用为第一个数组的数组名,数组名单独放在sizeof操作符中,求的是整个数组在内存空间的大小。
- a [ 3 ]乍一眼看存在越界访问,但是sizeof不关心内存数据的内容,就像之前说的,放进sizeof的表达式不会计算,两个int类型的数据相加,不管结果是否为4,都返回4。这里只认是数组名的地址解引用,结果是数组名,并且是a中所存数组的数组名,结果为16.
总结:
- 对于sizeof操作符,我们更应该关注其所求对象的数据类型,比如只要看到&而且没有*,大部分情况下因为都是求地址,所以结果都是4/8。同时我们要牢记两个特例,当数组名单独放入sizeof操作符的时候,得出的是整个数组在内存中所占的大小,&加数组名取出的是整个数组的地址,+1跳过整个数组。
- 对于strlen函数,我们要清楚,它的参数是const chat*,是一个字符指针,牢记返回值是size_t类型的值,没有符号,同时没有'\0'可能会越界访问,输出烫。
- 二维数组的首元素是第一个数组的数组名的地址,及二维数组的数组名是二级指针,最高可为三级指针(即所存的一维数组中存的是常量字符串)
- 注意二维数组解引用的次数,牢记二维数组的数组名解引用得到的是一维数组的数组名,换句话说,二维数组存的是一维数组数组名的地址,而一维数组的数组名,解引用得到的是首元素。
幸甚至哉,歌以咏志。