sizeof(),strlen(),length(),size()区别

面试的时候被问到 sizeof(),strlen() 的区别,对这块还挺好奇的,但是网上看到的很多文章总感觉讲的不全、不深,因此新开一帖,打算将自己开发过程中遇到的类似情况进行归总整理,并进行一些相关测试,希望可以帮到更多和我一样的朋友们。

如果赶时间,可以就看看下面目录中的 “标题一:区别” 这一块,对几种运算符和函数做了总结。
当然,最好看看目录中的几个 “注意” 标题,这是平时很容易遇到的小问题。

全篇全靠笔者手打,难免会有小问题,欢迎大家指正,若是有遗漏,也欢迎大家评论区留言,我看到后会进行补充和修改。



一、区别

  1. sizeof() 的值在编译时即计算好了,参数可以是指针、数组、类型、对象、函数等。
    • sizeof(指针) = 指针大小。
    • sizeof(数组名) = 数组占用总空间。
    • sizeof(类型) = 类型字节对齐后占用总空间。
    • sizeof(函数) = 函数的返回类型所占的空间大小。函数的返回类型不能是void。
  2. strlen() 是函数,其值在运行时计算, 参数必须是字符型指针(char*),该函数实际完成的功能是从代表 该字符串的第一个地址开始遍历,直到遇到结束符’\0’
    • strlen(char*)函数求的是字符串的实际长度,返回的长度大小不包括 ‘\0’。
  3. length() 函数用来获取字符串长度。与strlen()不同的是,length()方法并不需要扫描整个字符串,因为string类会保存字符串的长度。
  4. size() 跟length()函数一样,可以获得字符串长度。但是size()函数还可以用来获取vector的长度。

二、分析

1、sizeof() 和 strlen()

1)sizeof(指针)

sizeof(指针) = 指针大小

char a[] = "abcdef";
char* ptr1 = &a;

int b = 10;
int* ptr2 = &b;

### 每行最后的数字表示运行的结果,下文均采用这种方式,不再另外说明 ###
printf("%d\n",sizeof(ptr1));   8(x64,若是x86下,指针都是4字节)
printf("%d\n",sizeof(ptr2));   8

2)sizeof(数组名),strlen(数组名)

sizeof(数组名) = 数组占用总空间。
strlen(char*) = 字符串的实际长度,返回的长度大小不包括 ‘\0’。

对于一个数组,其数组名往往代表其首元素首地址,但是当数组名与 sizeof() 一起使用时,数组名代表整个数组

i、sizeof(整形数组名):
int arr1[5] = {1,2,3,4,5};   
int arr2[6] = {1,2,3,4,5};   
int arr3[] = {1,2,3,4,5};   

printf("%d\n", sizeof(arr1));  20
printf("%d\n",sizeof(arr2));  24
printf("%d\n",sizeof(arr3));  20

接下来逐个分析:

  • arr1int arr1[5] = {1,2,3,4,5};
    编译时申请了长度为5的数组,每个元素类型为int(4字节),总大小为20字节

  • arr2int arr1[6] = {1,2,3,4,5};
    编译时申请了长度为6的数组,每个元素类型为int(4字节),总大小为24字节。但是在内存中只初始化了前五个元素,第六个值为0,见下图:
    在这里插入图片描述

  • arr3int arr3[] = {1,2,3,4,5};
    如果定义数组时就给数组中所有元素赋初值,那么就可以不指定数组的长度,因为此时元素的个数已经确定了。花括号中有 5 个数,所以系统会自动定义数组 a 的长度为 5。因此总大小为20字节


注意1:

这里需要说明一下arr2的第六个值为什么为0:
1、首先,int a[6] = {1,2,3,4,5,6};“完全初始化”,这个好理解,申请多少长度,就对全部赋初值。
2、其次,int a[6] = {1,2,3,4,5};“不完全初始化”,即只初始化部分值,在C/C++中,没有初始化的值自动为0只限于整形数组,字符型数组为’\0’,在下文会讲到)。
3、最后,int a[6];“完全不初始化”,这种情况下各个元素的值是不确定的,它们取决于数组所在的内存位置上的原始数据。这可能会导致程序出现一些错误和不可预测的结果。比如下图:
在这里插入图片描述

注意2:

这里说明三点语法错误
1、不能写成 int a[5]={}; 如果大括号中什么都不写,那就是极其严重的语法错误。大括号中最少要写一个数。比如 int a[5]={0};这时就是给数组 “清零”,此时数组中每个元素都是零。
2、如果定义的数组的长度比花括号中所提供的初值的个数少,也是语法错误,如 a[2]={1,2,3,4,5};
3、如果定义数组时不初始化,那么省略数组长度就是语法错误。如 int a[];


ii、sizeof(字符型数组名),strlen(字符型数组名):
char arr4[5] = { 'h','e','l','l','o' };
char arr5[7] = { 'h','e','l','l','o' };
char arr6[] = { 'h','e','l','l','o' };
char arr7[] = "hello";
char arr8[] = "he\0llo";
char arr9[] = "he\\0llo";

printf("%d\n", sizeof(arr4));  5
printf("%d\n",sizeof(arr5));  7   结尾有\0
printf("%d\n",sizeof(arr6));  5   结尾没有\0
printf("%d\n",sizeof(arr7));  6
printf("%d\n",sizeof(arr8));  7
printf("%d\n",sizeof(arr9));  8

printf("%d\n", strlen(arr4));  19
printf("%d\n", strlen(arr5));  5
printf("%d\n", strlen(arr6));  21
printf("%d\n", strlen(arr7));  5
printf("%d\n", strlen(arr8));  2
printf("%d\n", strlen(arr9));  7

接下来逐个分析:

  • arr4char arr4[5] = { 'h','e','l','l','o' };
    从下图中可以发现,编译时申请了长度为5的数组,元素类型为char(1字节),一个字符占用一个字节,因此这个字符型数组没有空间来存放 '\0’了。监视窗口的arr4元素如下:
    在这里插入图片描述
    (上图中 ‘h’ 前面的数字 104 表示其十进制ASCII码值)

    • 由于编译时期申请的5字节总空间,因此sizeof(arr4) = 5
    • 由于没有结束符,strlen()就没法判断什么时候结束,因此strlen(arr4) = 19这里19是随机值,可能是后续内存空间中可能还出现了别的’\0’。
  • arr5char arr5[7] = { 'h','e','l','l','o' };
    申请了长度为7的数组,系统对该字符数组未初始化的部分自动设为 ‘\0’,见下图:
    在这里插入图片描述

    • 由于编译时期申请的7字节总空间,因此sizeof(arr5) = 7
    • strlen()根据 ‘\0’ 计算字符串实际长度,且不将 ‘\0’ 计算在内,因此strlen(arr5) = 5
注意3:

可以发现,对于 “不完全初始化”字符型数组未初始化的值系统会将其设为 ‘\0’,这和整形数组中设为0的机制是类似的。

  • arr6char arr6[] = { 'h','e','l','l','o' };
    在整形数组的部分已经讲到,如果不指定数组长度,直接进行初始化,系统会根据初始化元素的个数自动确定数组长度,即不指定数组长度时,初始化多少个元素,数组长度就是多少。因此这里arr6数组长度为5,且没有最后的 ‘\0’。内存分布见下图:
    在这里插入图片描述

    • 不指定数组长度,初始化5个char类型元素,因此总大小为5字节,因此sizeof(arr6) = 5
    • 数组中没有 ‘\0’ ,strlen(arr6)没法判断什么时候结束,因此strlen(arr5) = 2121为随机值
  • arr7char arr7[] = "hello";
    双引号括起来的 "hello"属于 字符串常量,对于字符串常量,系统会自动在末尾补 ‘\0’。数组内存分布见下图:
    在这里插入图片描述
    因此,如下四种数组定义其实是等价的:
    char a1[] = "hello";
    char a2[6] = "hello";
    char a3[] = { 'h','e','l','l','o','\0'};
    char a4[6] = { 'h','e','l','l','o','\0'};

    • 对于上述四种数组定义,sizeof(...) = 6,因为都包含了字符 ‘\0’。
    • stelen(...) = 5,因为根据 ‘\0’ 判断字符实际长度。
注意4:

char arr7[] = "hello";char arr6[] = { 'h','e','l','l','o' };区分:
1、首先,"hello"字符串常量{ 'h','e','l','l','o' }初始值设定项列表,是一个字符数组,对于前者,系统会自动补’\0’。
2、另外,cplusplus.com 官网上的一段原话:
“Sequences of characters enclosed in double-quotes () are literal constants. And their type is, in fact, a null-terminated array of characters. This means that string literals always have a null character () automatically appended at the end. ”
翻译过来就是“用双引号()括起来的字符序列是字面常量。事实上,它们的类型是以空字符结尾的字符数组。这意味着字符串字面量的末尾总是自动附加一个空字符()。”

  • arr8char arr8[] = "he\0llo";
    "he\0llo"是一个字符串常量,其中中间的 ‘\0’ 是一个转义字符,表示空字符,会单独占用一个数组元素位置,另外还有结尾系统自动补上的一个 ‘\0’ ,该数组的内存分布如下图所示。这种初始化方式和char arr8[] = {'h','e','\0','l','l','o','\0'};是等同的。
    在这里插入图片描述

    • 因此,sizeof(arr8) = 7,包括中间的 ‘\0’ 和结尾的 ‘\0’ 。
    • stelen(arr8) = 2,碰到第一个 ‘\0’ 即结束,因此字符实际上只包含了两个字符 ‘h’ 和 ‘e’。
  • arr9char arr9[] = "he\\0llo";
    注意区分和上面arr8的不同,arr9 中 ‘\\’ 是另一个转义字符,表示反斜杠本身。所以,在字符串常量中,如果出现 \\0,那么它表示的是一个反斜杠 \ 和一个 0 字符,并不是空字符。其内存分布如下:
    在这里插入图片描述
    (可以看到,其中 ‘\\’ 和 ‘0’ 的十进制ASCII码分别为 92 和 48,而结束符 ‘\0’ 的十进制ASCII码为 0)

    • 因此,sizeof(arr9) = 8,这和char arr9[] = {'h','e','\\','0','l','l','o','\0'};是等同的。
    • stelen(arr9) = 7,碰到字符串结尾的 ‘\0’ 即结束。

3)sizeof(函数)

sizeof(函数) = 函数的返回类型所占的空间大小

int func1(int x) {
    return x + 1;
}

double func2(double x) {
    double a = 1;
    return x + a;
}

int main() {
    printf("%d\n", sizeof(func1(3)));    4
    printf("%d\n", sizeof(int));         4
    printf("%d\n", sizeof(func2(3)));    8
    printf("%d\n", sizeof(double));      8
    return 0;
}

可以看出,sizeof() 只关心函数的返回类型,而不关心函数的返回值具体是多少

4)sizeof(类型、对象等)

sizeof(类型、对象等) = 占用总空间

i、类型(字节对齐)
struct student1 {
    char name[20];
    int age;
    float score;
};

struct student2 {
    char name[20];
    int age;
    double score; // float 变成 double 
};

struct teacher {
    char name[18];  // 字节对齐
    int age;
    float score;
};

int main() {
    struct student1 s1 = {"Alice", 18, 90.5};    
    printf("%d\n", sizeof(s1));                   28
    printf("%d\n", sizeof(struct student1));      28

	struct student2 s2 = {"Li", 20, 90};    
    printf("%d\n", sizeof(s2));                   32
    printf("%d\n", sizeof(struct student2));      32
	
	struct teacher t1 = {"Bob", 30, 100};
    printf("%d\n", sizeof(t1));                   28
    printf("%d\n", sizeof(struct teacher));       28
    
    return 0;
}

很容易看出,sizeof(类型) = 类型占用总空间,并且从teacher结构体可以看出,这个总空间是字节对齐后的结果。

内存对齐的原则是:
1、每个成员变量的偏移量(相对于结构体或类的首地址)都是该成员变量类型大小的整数倍;
2、结构体或类的总大小必须是最大成员变量类型大小的整数倍。

对于上述teacher类型,如何计算总空间:首先是18个char类型,总共18字节,int类型的开头必须是其本身4字节的整数倍,因此18字节再补充2字节,这样int类型的age变量前面有20字节(不包括age本身),加上int类型的age变量有24字节,24字节是float类型的整数倍,不用再补充,直接加上4字节的float类型,总共28字节,并且28字节是最大成员变量类型(int,float)的整数倍,因此总空间为28字节。

因此,可以得出结论:sizeof(类型) = 类型字节对齐后占用总空间

ii、对象

sizeof(类的对象) = 类的对象所占用的总空间大小

// 空类
class MyBasicString {  

};

class MyString : public MyBasicString  {
public:
    union {
        char _Buf[16]; // 短字符串缓冲区
        char* _Ptr; // 长字符串指针
    };
    char xxx;
    size_t _Size; // 字符串长度
    size_t _Res; // 字符串容量
    static double yyy; // 静态成员不属于某个对象

    // 无参构造函数,将字符串初始化为空
    MyString() {
        _Buf[0] = '\0'; 
        _Size = 0; 
        _Res = 15; 
    }

    // 析构函数,释放字符串占用的资源
    ~MyString() {
        if (_Size > 15) { // 如果使用了动态内存,需要释放它
            delete[] _Ptr;
        }
    }

    // 获取字符串长度的函数,返回字符串长度
    size_t size() const {
        return _Size;
    }
};

int main() {
    MyBasicString s0;
    printf("%d\n\n", sizeof(s0));         1  空类大小为1

    MyString s1;
    printf("%d\n\n", sizeof(s1));         28  是下面四个非静态成员变量字节对齐后的总大小

    printf("%d\n", sizeof(s1._Buf));      16
    printf("%d\n", sizeof(s1.xxx));       1
    printf("%d\n", sizeof(s1._Size));     4
    printf("%d\n\n", sizeof(s1._Res));    4
    
    printf("%d\n\n", sizeof(s1._Buf) + sizeof(s1.xxx) + 
    				sizeof(s1._Size) + sizeof(s1._Res));     25  没有字节对齐的总大小
    
	printf("%d\n", sizeof(s1.yyy));       8  静态成员变量大小
    
    return 0;
}

这里实现了一个简单的字符串string类,为了说明sizeof()运算符的特性,这里只列举string类的几个重要变量和函数。

从测试结果可以得出三个结论:

  1. 类对象的大小只包含了非静态成员变量,而不包含成员函数。
  2. 类对象的总大小是非静态成员变量字节对齐后的结果。
  3. 空类的大小为1(用于区分对象)
注意5:

对于为什么只包含成员变量,解释如下:
1、成员函数并不属于任何一个具体的对象,它只属于类本身。
2、类中的成员函数只有一份代码,它被存储在代码区(code segment),而不是数据区(data segment)。在调用各对象的函数时,都去调用这个公用的函数代码。
在这里插入图片描述

对于为什么空类大小为1,解释如下:
这就是实例化的原因(空类同样可以被实例化),每个实例在内存中都有一个独一无二的地复址,为了达到这个目的,编译器往往会给一个空类或空结构体(C++中结构体也可看为制类)隐含的加一个字节,这样空类或空结构体在实例化后在内存得到了独一无二的地址,所以空类所占的内存大小是1个字节。


2、length() 和 size()

1)计算字符串长度

string s;
s.size();
s.length();

通过查看两个函数的源码,会发现:

在这里插入图片描述
说明在计算字符串长度方面,length() 和 size() 是完全一样的

2)计算容器元素个数

vector<int> vec(10, 1); 
cout << "vec.size() = " << vec.size() << endl; 

查看源码发现:
在这里插入图片描述
因此,size()函数除了可以计算字符串长度,还可以获取其他容器类型(如vector,list,map等)的元素个数

其实string一开始只有 length() ,这是C语言延续下来的习惯。
而 size() 则是在出现STL之后,为了兼容加入了和STL容器求长度的函数 size() ,这样就可以方便的使用于STL的算法。


总结

length() 和 size() 的区别很简单:size函数可以用于多种容器类型,而length函数只能用于字符串类型。

需要注意的是 sizeof() 和 strlen(),不同的初始化方式会得到不同的结果。只需要记住一点:
sizeof() 是运算符,编译的时候就计算好了,是数组总大小。
strlen() 是函数,只能根据 ‘\0’ 计算字符串长度,因此关键在于判断各种初始化方式是否有 ‘\0’ 以及 ‘\0’ 的位置。


参考:
C语言学习之sizeof与strlen
【C语言进阶】sizeof、strlen详解
数组的定义,初始化和使用,C语言数组详解
cplusplus.com | Character sequences
字符数组(用sizeof和strlen计算长度的说明)
C语言转义字符
C语言——转义字符
【C++】类中成员函数的存储方式以及计算类的大小
C++类对象的大小(1)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值