网上建站赚钱,校园网站推广方案怎么做,淘客app,商丘软文推广文章目录 底层结构概述扩容机制浅拷贝与深拷贝插入和删除的效率浅谈VS和g的优化总结 底层结构概述
string可以帮助我们很好地管理字符串#xff0c;但是你真的了解她吗#xff1f;事实上#xff0c;string的设计是非常复杂的#xff0c;拥有上百个接口#xff0c;但最常用… 文章目录 底层结构概述扩容机制浅拷贝与深拷贝插入和删除的效率浅谈VS和g的优化总结 底层结构概述
string可以帮助我们很好地管理字符串但是你真的了解她吗事实上string的设计是非常复杂的拥有上百个接口但最常用的就那几个。如果不了解string的底层就很难优雅地写出高效的代码
要想高效地管理一个string类至少需要3个成员变量分别是
char* _str;
size_t _size;
size_t _capacity;比如要存储字符串abcde那么_str指向了a_size5表示有5个有效字符不包括’\0’_capacity8表示当前空间最多存储8个字符实际上是9个因为有’\0’。此时_str就是c_str的返回值_size就是size的返回值_capacity就是capacity的返回值堆区上的空间总大小是9个字节最多保存除了’\0’之外的8个字符换句话说当前再插入3个字符空间就满了需要扩容。
扩容机制
_str指向的空间是动态开辟出来的当容量不够用时会扩容。扩容的步骤是
申请新空间。把旧空间的数据拷贝到新空间中。释放旧空间。 设想一下当字符串很长时第2步的拷贝代价就会非常大。所以我们要想方设法地减少甚至避免扩容
假设我们要反复地插入字符插入100次容量会怎么变化呢
#include iostream
#include string
using namespace std;int main()
{string s;size_t capacity s.capacity();cout init: capacity capacity endl;for (size_t i 0; i 100; i){s.push_back(x);if (s.capacity() ! capacity){capacity s.capacity();cout new: capacity capacity endl;}}return 0;
}VS2022运行结果 可以观察到一开始容量是15第一次扩容为原来容量的2倍后面每次扩容都为原来容量的1.5倍。
g运行结果 可以观察到每次扩容都是原来容量的2倍。
如果我们能提前知晓即将插入100个字符就可以调用reserve提前保留足够的空间从而避免扩容的消耗
#include iostream
#include string
using namespace std;int main()
{string s;// 提前开空间从而避免扩容的消耗s.reserve(100);size_t capacity s.capacity();cout init: capacity capacity endl;for (size_t i 0; i 100; i){s.push_back(x);if (s.capacity() ! capacity){capacity s.capacity();cout new: capacity capacity endl;}}return 0;
}VS2022运行结果 g运行结果 浅拷贝与深拷贝
string是如何拷贝的呢
如果不写拷贝构造函数编译器会生成默认的拷贝构造函数对内置类型按照字节拷贝这种拷贝称作浅拷贝
举个例子有一个string s1的结构如下 此时来了另一个string s2把s1的_str_size和_capacity都拷贝过去此时两个string的_str就指向了同一块空间 此时如果我们修改其中一个string另一个string也会同时被修改更可怕的是当对象的生命周期结束时会调用析构函数由于两个string中的_str存储的是同一个地址这个地址就会被delete两次从而导致进程崩溃
为了解决这个问题string必须实现深拷贝也就是说我们需要重新申请一块空间把abcde拷贝过去让s2的_str指向新的空间 这样修改其中一个string就不会影响另一个string而且两个string的_str指向不同的空间不会出现同一块空间释放两次的问题了
插入和删除的效率
如果要在字符串尾部插入一个字符底层是如何实现的呢只需要在_str[_size]的位置插入字符再让_size最后再填一个’\0’即可 当然如果插入前_size_capacity说明空间不够用了要扩容扩容的逻辑前面讲过这里不再重复。
但是如果要在中间插入一个字符呢甚至在头部插入呢就要先挪动数据腾出空间才能插入 比起在尾部插入数据多出了挪动数据的消耗所以应尽可能地少在string的头部或中间插入数据
同理如果要删除头部或中间的数据也要挪动数据覆盖删除所以应尽可能地避免删除头部或中间的数据
浅谈VS和g的优化
VS2022的X86环境下一个string类对象的大小是28字节X64环境下大小是40个字节。32位环境下char*大小是4字节size_t大小是4字节那么_str_size_capacity的总大小是12字节64位环境下char*大小是8字节size_t大小是8字节那么_str_size_capacity的总大小是24字节。那么剩下还有16字节去哪了呢
观察一下监视窗口 注意到有一个char[16]类型的数组_Buf。也就是说VS在栈区上也申请了一块空间长度是16个字节当字符串的size15时就存储在这个数组中当size15时才会存储到堆区这是为了减少堆区的内存碎片因为字符串的长度一般不会超过15。
g的X86环境下一个string对象的大小是4字节X64环境下大小是8字节。这是由于底层只存储了一个指针指针指向的空间中存储了引用计数_size和_capacity以及C-string的数据。
这个引用计数又是啥玩意呢这是g对string做的优化实现了写时拷贝(Copy On Write)创建对象时把引用计数cnt初始化成1拷贝的时候cnt。这样析构的时候如果cnt不是1就cnt--如果cnt是1再释放空间。当要对对象写入数据时再进行深拷贝。这样极大地提升了拷贝的效率
总结
string的底层可以理解为一个指针和两个无符号整形变量分别代表了c_strsize和capacity的返回值。扩容是有代价的尽可能使用reserve减少甚至避免扩容。string底层实现了深拷贝。尽可能少地在string头部或者中间插入、删除数据。VS和g对string做了一些优化。