【 C++ 入门】Cyber骇客的 流式文本序列处理器 —— 【 string 类】万字大文带你从0学好C++的string类!
⚡ CYBER_PROFILE ⚡
/// SYSTEM READY ///
[WARNING]: DETECTING HIGH ENERGY
🌊 🌉 🌊 心手合一 · 水到渠成
| >>> ACCESS TERMINAL <<< | |
| [ 🦾 作者主页 ] | [ 🔥 C语言核心 ] |
| [ 💾 编程百度 ] | [ 📡 代码仓库 ] |
---------------------------------------
Running Process: 100% | Latency: 0ms
索引与导读
- 一、为什么学习 string类 ?
- 二、C++ 标准库中的 string 类
- 💻结尾— 核心连接协议
一、为什么学习 string类 ?
C语言中的字符串
🔗Lucy的空间骇客裂缝:C语言字符与字符串函数
🔗Lucy的空间骇客裂缝:C语言字符串
字符串面试题
🔗Lucy的空间骇客裂缝:字符串转化为整数
🔗Lucy的空间骇客裂缝:字符串相加
二、C++ 标准库中的 string 类
🔗Lucy的空间骇客裂缝:C++ string类
- 在使用
string类时,必须包含#include头文件以及using namespace std;
2.1)auto和范围for
- auto关键字
- 核心功能:
auto的核心功能是
让编译器通过初始值来推导变量的类型。这意味着使用auto时,变量必须初始化
auto x =10;// x 被推导为 intauto y =3.14;// y 被推导为 doubleauto ptr =&x;// ptr 被推导为 int*- 用
auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
int x =10;int* p =&x;auto a = p;// a 被推导为 int*auto* b = p;// b 被推导为 int*// 结论:在这种情况下,a 和 b 的类型完全一致int x =10;int& ref = x;// ref 是 x 的引用auto c = ref;// c 的类型是 int (注意:引用被丢弃了!这里是值拷贝)auto& d = ref;// d 的类型是 int& (显式声明为引用) c =20;// x 依然是 10,因为 c 是独立变量 d =30;// x 变成了 30,因为 d 是 x 的别名- 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量
/*正确用法:类型一致*/auto a =10, b =20;// 正确:a 是 int,b 也是 intauto c =1.5, d =3.14;// 正确:c 是 double,d 也是 double// 注意:即使一个是值,一个是引用/指针,只要“基础类型”一致即可int x =0;auto i = x,&j = x;// 正确:基础类型都是 int/*错误用法:类型不一致*/// 错误示例:编译器会报错auto a =10, b =3.14;// 报错原因:编译器对 a 推导为 int,但对 b 推导为 double// 编译器实际上只对第一个类型(a)进行推导,然后尝试用 int 定义 b,导致类型冲突auto m =5,*p =&m;// 正确:基础类型都是 intauto n =5, q =3.14;// 错误:基础类型不同auto不能作为函数的参数,可以做返回值,但是建议谨慎使用
// 错误示例 (C++17 及以前)voidfunc(auto x){...}// 正确方案:模板template<typenameT>voidfunc(T x){...}// 错误示例:推导冲突autocheck(int i){if(i >0)return1;// 推导为 intelsereturn3.14;// 错误!与之前的 int 冲突}auto不能直接用来声明数组
int arr[]={1,2,3};// 正确:传统的数组声明auto a[]={1,2,3};// 错误!编译器无法推导出 a 是一个数组类型auto b[3]={1,2,3};// 错误!即使指定了长度也不行推导为指针
int nums[]={10,20,30};auto p = nums;// 正确:p 的类型被推导为 int*,而不是 int[3]/*同行声明一致性*/int nums[]={1,2};auto p = nums, x =10;// 正确:p 是 int*,x 是 int,基础类型一致auto p = nums, y =3.14;// 错误:基础类型冲突(int vs double)如果你真的想推导数组
如果你希望保留数组的“大小信息”而不是让它退化成指针,可以使用引用:
int nums[]={1,2,3};auto& refArr = nums;// 正确:refArr 的类型是 int(&)[3]- for关键字
- 对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误
- 因此
C++11中引入了基于范围的for循环for循环后的括号由**冒号“:”**分为两部分:- 第一部分是范围内用于迭代的变量
- 第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束
- 语法结构
for(declaration : range){// 循环体}- 第一部分 (
declaration): 用于存放当前迭代到的元素 - 第二部分 (
range): 要遍历的范围(如数组、std::vector、std::string等)
- 代码示例
int arr[]={1,2,3,4,5};for(int x : arr){ cout << x <<" ";// 自动取数据,自动判断结束}- 范围
for可以作用到数组和容器对象上进行遍历
- 数组 (
Arrays):指具有明确大小的静态数组。 - 容器对象 (
Containers):指标准库中的容器(如std::vector、std::list、std::map、std::set、std::string等)。
- 使用示例
#include<iostream>#include<string>#include<map>usingnamespace std;intmain(){int array[]={1,2,3,4,5};// C++98的遍历for(int i =0; i <sizeof(array)/sizeof(array[0]);++i){ array[i]*=2;}for(int i =0; i <sizeof(array)/sizeof(array[0]);++i){ cout << array[i]<< endl;}// C++11的遍历for(auto& e : array) e *=2;for(auto e : array) cout << e <<" "<< endl; string str("hello world");for(auto ch : str){ cout << ch <<" ";} cout << endl;return0;}╔═█▓▒░ CODE CORE 🔥
┌─────────────┐
│ 代码关键点 │使用引用修改数据
└─────────────┘
// C++11的遍历for(auto& e : array) e *=2;auto& e:e是当前元素的引用。使用引用是因为我们需要修改array中的原始值::分隔符,左边是变量,右边是范围(即数组array)。
╔═█▓▒░ CODE CORE 🔥
┌─────────────┐
│ 代码关键点 │自动取数据并打印
└─────────────┘
for(auto e : array) cout << e <<" "<< endl;auto e:编译器自动推导出e的类型为int。每一轮循环,它会自动从array中取出下一个元素赋值给e
╔═█▓▒░ CODE CORE 🔥
┌─────────────┐
│ 代码关键点 │作用于容器对象(string)
└─────────────┘
范围for不仅能作用于数组,也能作用于容器对象
string str("hello world");for(auto ch : str){ cout << ch <<" ";} cout << endl;- 对象:
str是一个std::string容器。 - 过程:循环会自动调用
str.begin()和str.end(),依次提取字符并打印,无需手动管理迭代器或下标。
2.2)string类的常用接口
🚩1)string类的常用构造
| 构造函数 | 功能说明 |
|---|---|
string()(重点) | 构造空的 string 类对象,即空字符串 |
string(const char* s)(重点) | 用 C-string 来构造 string 类对象 |
string(size_t n, char c) | string 类对象中包含 n 个字符 c |
string(const string& s)(重点) | 拷贝构造函数 |
#include<iostream>#include<string>// 包含 string 类的定义usingnamespace std;intmain(){// 1. string() (重点)// 构造空的 string 对象 string s1; cout <<"s1 (空字符串): ["<< s1 <<"]"<< endl;// 2. string(const char* s) (重点)// 用 C-string (字符串常量) 来构造对象 string s2("Hello World"); cout <<"s2 (由字符串常量构造): "<< s2 << endl;// 3. string(size_t n, char c)// 构造包含 n 个字符 c 的对象 string s3(10,'*'); cout <<"s3 (10个星号): "<< s3 << endl;// 4. string(const string& s) (重点)// 拷贝构造函数:用已有的 string 对象 s2 来初始化 s4 string s4(s2); cout <<"s4 (拷贝自s2): "<< s4 << endl;return0;}🚩2)string类对象的容量操作
| 函数名称 | 功能说明 |
|---|---|
size(重点) | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty(重点) | 检测字符串是否为空串,是返回 true,否则返回 false |
clear(重点) | 清空有效字符 |
reserve(重点) | 为字符串预留空间 |
resize(重点) | 将有效字符的个数改成 n 个,多出的空间用字符 c 填充 |
❗注意事项
1)size() 与 length()底层原理相同
size() 是为了与其他容器接口保持一致
string s ="hello"; cout <<"size: "<< s.size()<< endl;// 输出 5 cout <<"length: "<< s.length()<< endl;// 输出 52)clear() 是清空有效字符,但不改变底层空间大小(capacity)
3)resize(size_t n, char c)改变有效字符个数
s ="hello";// 如果 n 大于当前大小,用字符 'x' 填充 s.resize(10,'x'); cout <<"resize(10, 'x'): "<< s << endl;// helloxxxxx// 如果 n 小于当前大小,会发生截断,但底层总空间不变 s.resize(3); cout <<"resize(3): "<< s << endl;// hel4)reserve(size_t res_arg) 为 string 预留空间,不改变有效元素个数;只有当参数大于当前底层空间时才会扩容
s.reserve(100); cout <<"reserve(100)后大小: "<< s.size()<< endl;// 依然是 3🚩3)string类对象的访问及遍历操作
| 函数名称 | 功能说明 |
|---|---|
operator[pos](重点) | operator是字符串名,返回 pos 位置的字符,const string 类对象调用 |
begin + end | begin 获取一个字符的迭代器 + end 获取最后一个字符下一个位置的迭代器 |
rbegin + rend | rbegin 获取一个字符的迭代器 + rend 获取最后一个字符下一个位置的迭代器 |
范围 for | C++11 支持更简洁的范围 for 的新遍历方式 |
#include<iostream>#include<string>usingnamespace std;intmain(){ string s ="Hello";// 1. operator[] (下标访问)// 像数组一样访问 pos 位置的字符 cout <<"下标为1的字符: "<< s[1]<< endl;// 'e' s[0]='h';// 也可以修改// 2. 使用迭代器遍历 (begin + end)// begin 获取第一个字符位置,end 获取最后一个字符下一个位置 cout <<"正向迭代器遍历: ";for(string::iterator it = s.begin(); it != s.end();++it){ cout <<*it <<" ";} cout << endl;// 3. 反向迭代器遍历 (rbegin + rend) cout <<"反向迭代器遍历: ";for(string::reverse_iterator rit = s.rbegin(); rit != s.rend();++rit){ cout <<*rit <<" ";// 输出 o l l e h} cout << endl;// 4. 范围 for (C++11 遍历新方式) cout <<"范围 for 遍历: ";for(char c : s){ cout << c <<"-";} cout << endl;return0;}╔═█▓▒░ CODE CORE 🔥
┌─────────────┐
│ 代码关键点 │string::iterator it = s.begin()
└─────────────┘
string::作用域限定符:告诉编译器:我要找string类里面定义的工具。iterator类型名:这是一个专门用来遍历字符串的“迭代器类型”。it变量名:你给这个“光标”取的名字(iterator的缩写)。s.begin()初始赋值:调用字符串s的函数,把光标放在第一个字符的位置。
╔═█▓▒░ CODE CORE 🔥
┌─────────────┐
│ 代码关键点 │string::reverse_iterator rit = s.rbegin()
└─────────────┘
string::reverse_iterator:这是一个专门用于从后往前读字符串的类型。rit:变量名,通常是reverse iterator的缩写。s.rbegin():这里的r代表reverse(反向)。这个函数返回指向字符串最后一个有效字符的指针。
iterator小拓展
对于 string 来说,你最常用的就是这四种组合:
iterator:能读能改,正着走。const_iterator:只能读,正着走。reverse_iterator:能读能改,倒着走。const_reverse_iterator:只能读,倒着走。
🚩4)string类对象的修改操作
| 函数名称 | 功能说明 |
|---|---|
push_back | 在字符串后尾插字符 c |
append | 在字符串后追加一个字符串 |
operator+=(重点) | 在字符串后追加字符串 str |
c_str(重点) | 返回 C 格式字符串 |
find + npos(重点) | 从字符串 pos 位置开始往后找字符 c,返回该字符在字符串中的位置 |
rfind | 从字符串 pos 位置开始往前找字符 c,返回该字符在字符串中的位置 |
substr | 在 str 中从 pos 位置开始,截取 n 个字符,然后将其返回 |
#include<iostream>usingnamespace std;intmain(){// 0. 初始化 string s ="Hello";// 1. push_back: 只能插入单个字符 s.push_back('!');// s 变为 "Hello!"// 2. append: 追加字符串 s.append(" Welcome");// s 变为 "Hello! Welcome"// 3. operator+= (重点): 最常用的追加方式 s +=" to C++";// s 变为 "Hello! Welcome to C++"// 4. c_str (重点): 返回 C 风格字符串 (const char*)// 方便配合 printf 或旧的 C 函数使用constchar* cStr = s.c_str(); cout <<"C-Style: "<< cStr << endl;// 5. find + npos (重点): 查找子串// string::npos 是一个特殊值,表示“没找到” size_t pos = s.find("Welcome");if(pos != string::npos){ cout <<"Found 'Welcome' at: "<< pos << endl;}// 6. rfind: 从右向左找// 比如找最后一个空格的位置 size_t last_space = s.rfind(' '); cout <<"Last space at: "<< last_space << endl;// 7. substr: 截取子串// 参数为:(起始位置, 截取长度) string sub = s.substr(7,7);// 截取 "Welcome" cout <<"Sub-string: "<< sub << endl;return0;}❗注意事项
1)追加字符的三种方式
char c ='!';// 方式 A: 使用 push_back(c)// 只能插入单个字符 s.push_back(c);// 方式 B: 使用 append(n, c)// 表示追加 n 个字符 c s.append(1, c);// 方式 C: 使用 += (最常用)// 既可以连接单个字符,也可以连接字符串 s += c;// 连接字符 s +=" Hello";// 连接字符串2)使用 reserve 预留空间
string s;// 如果预估要存 50 个字符,先预留空间可以减少频繁扩容带来的性能消耗 s.reserve(50);手动new VS reserve
🚩5)string类非成员函数
| 函数 | 功能说明 |
|---|---|
operator+ | 尽量少用,因为传值返回,导致深拷贝效率低 |
operator>>(重点) | 输入运算符重载 |
operator<<(重点) | 输出运算符重载 |
getline(重点) | 获取一行字符串 |
relational operators(重点) | 大小比较 |
🔗Lucy的空间骇客裂缝:运算符重载
string full_name; cout <<"请输入你的全名: ";// 参数:输入流,存储变量getline(cin, full_name); cout <<"你好, "<< full_name << endl;string str1 ="apple"; string str2 ="banana";if(str1 < str2){ cout <<"apple 在字典中排在 banana 前面"<< endl;}if(str1 =="apple"){ cout <<"字符串相等"<< endl;}🚩6)vs和g++下string结构的说明
注意: 下述结构是在32位平台下进行验证,32位平台下指针占4个字节
VS 下 string 的结构
string 总共占 28 个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义 string 中字符串的存储空间:
- 当字符串长度小于
16时,使用内部固定的字符数组来存放 - 当字符串长度大于等于
16时,从堆上开辟空间
这种设计也是有一定道理的,大多数情况下字符串的长度都小于 16,那 string 对象创建好之后,内部已经有了 16 个字符数组的固定空间,不需要通过堆创建,效率高
其次:还有一个 size_t 字段保存字符串长度,一个 size_t 字段保存从堆上开辟空间总的容量
最后:还有一个指针做一些其他事情
故总共占 16 + 4 + 4 + 4 = 28 个字节
G++ 下 string 的结构
G++ 下,string 是通过写时拷贝实现的,string 对象总共占 4 个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:
- 空间总大小
- 字符串有效长度
- 引用计数
- 指向堆空间的指针,用来存储字符串
💪牛刀小试
🔗Lucy的空间骇客裂缝:string算法题
2.3)string类的模拟实现
上面已经对 string 类进行了简单的介绍,大家只要能够正常使用即可。在面试中,面试官总喜欢让学生自己来模拟实现 string 类,最主要是实现 string 类的构造、拷贝构造、赋值运算符重载以及析构函数
- 大家看下以下
string类的实现是否有问题?
为了和标准库区分,此处使用 String
classString{public:String(constchar* str =""){if(nullptr== str){assert(false);return;} _str =newchar[strlen(str)+1];strcpy(_str, str);}~String(){if(_str){delete[] _str; _str =nullptr;}}private:char* _str;};// 测试voidTestString(){ String s1("hello bit!!!"); String s2(s1);}这段代码展示了一个初学者在模拟实现 C++String 类时经常会遇到的一个经典问题:浅拷贝导致的崩溃
虽然你定义了构造函数和析构函数,但你漏掉了一个非常关键的角色:拷贝构造函数
String s1("hello bit!!!"); String s2(s1);// 这里触发了隐式的拷贝构造█▓▒░█▓▒░█▓▒░█▓▒░ CODE CORE 🔥
s1的创建:调用构造函数,在堆上申请了一块内存存储"hello bit!!!",_str指向这块地址(假设是0x123)s2的创建:因为你没有自己写拷贝构造函数,编译器会为你生成一个默认的。默认拷贝构造函数执行的是浅拷贝****,它只是简单地把s1._str的值(地址0x123)赋值给了s2._str- 析构时的灾难:
- 当
TestString结束时,s2先析构,调用delete[] _str,释放了0x123 - 随后
s1析构,它也尝试调用delete[] _str去释放0x123 - 结果:同一块内存被释放了两次,程序直接崩溃
- 当
在这里插入图片描述
解决方案
你需要手动实现拷贝构造函数,为新对象申请独立的内存空间
String(const String& s){ _str =newchar[strlen(s._str)+1];strcpy(_str, s._str);}🚩1)经典的string类问题
1)构造函数的缺省值设置非常关键
String(constchar* str ="\0")//为什么错?- 逻辑冗余: 在 C/C++ 中,双引号括起来的字符串字面量(如 “”)结尾自带一个隐式的 \0。如果你写 “\0”,实际上这个字符串在内存中包含了两个 \0(一个是显式写的,一个是编译器自动补的)
String(constchar* str =nullptr) 为什么错? - 解引用空指针:
string类的构造函数内部通常会调用strlen(str)来计算传入字符串的长度。 - 底层崩溃:
strlen函数的原理是持续向后读取内存直到遇到\0。如果str是nullptr(空指针),strlen试图访问地址为0的内存,这会导致非法访问(Segmentation Fault)。
🚩2)浅拷贝 VS 深拷贝
🔗Lucy的空间骇客裂缝:深拷贝与浅拷贝
传统 vs 现代 的string类
1)传统
classString{public:String(constchar* str =""){if(nullptr== str){assert(false);return;} _str =newchar[strlen(str)+1];//分配空间,+1用于存放'\0'strcpy(_str, str);}String(const String& s):_str(newchar[strlen(s._str)+1]){strcpy(_str, s._str);}//赋值运算符重载/*避免s1 = s2出现浅拷贝问题*/ String&operator=(const String& s){if(this!=&s){char* Pstr =newchar[strlen(s._str)+1];strcpy(Pstr, s._str);delete[] _str; _str = Pstr;}return*this;// 支持链式赋值}~String(){if(_str){delete[] _str; _str =nullptr;}}2)现代
classString{public:/*构造函数*/String(constchar* str =""){if(str !=nullptr){ _str =newchar[strlen(str)+1];strcpy(_str, str);}else{assert(false);return;}}/*拷贝构造函数*/String(const String& s):_str(nullptr){ String strTmp(s._str);swap(_str, strTmp._str);}/*赋值运算符重载*/ String&operator=(String s){ std::swap(_str, s._str);// 直接交换形参和当前的资源return*this;// s 是局部副本,函数结束时自动释放旧资源}/*另一种赋值实现 (传引用方式)*///String& operator=(const String& s) {// if(this != &s) { // 必须做自赋值检测// String strTmp(s); // 拷贝构造出临时对象// std::swap(_str, strTmp._str);// }// return *this;//}/*析构函数*/~String(){if(_str){delete[] _str; _str =nullptr;}}private:char* _str;};╔═█▓▒░ CODE CORE 🔥
┌─────────────┐
│ 代码关键点 │String strTmp(s._str);
└─────────────┘
╔═█▓▒░ CODE CORE 🔥
┌─────────────┐
│ 代码关键点 │swap(_str, strTmp._str);
└─────────────┘
调用了 String 类的另一个构造函数(通常是 String(const char* str))strTmp 对象在被创建时,已经完美地做好了内存申请和数据拷贝工作(意味着你向编译器保证不会修改 s)
交换指针:swap(_str, strTmp._str);
🚩3)写时拷贝
一、 什么是写时拷贝(COW)?
在了解 COW 之前,我们先回顾一下传统的两种数据拷贝方式:
- 浅拷贝(Shallow Copy): 多个指针指向同一块内存。优点是速度快、省内存;缺点是一旦某个指针修改了数据,其他指针看到的数据也会跟着变,不安全。
- 深拷贝(Deep Copy): 重新分配一块同样大小的内存,把数据原封不动地搬过去。优点是绝对安全,互不影响;缺点是极其消耗时间和内存资源。
写时拷贝(Copy-on-Write)其实就是一种结合了浅拷贝和深拷贝优点的“懒汉(Lazy)”策略:
- 读操作时: 大家共享同一块内存(类似浅拷贝),不产生任何额外的开销。
- 写操作时: 当有人试图修改这块共享内存的数据时,系统才会真正分配一块新内存,把原来的数据复制过来,然后再进行修改(类似深拷贝)。
核心思想只有四个字:延迟执行。 只要你没去改它,大家就一直共享;只有在迫不得已(要修改)的时候,才去真正做拷贝的动作。
二、 写时拷贝的工作机制
我们以内存页(Page)为例,看看 COW 在操作系统中是如何运转的:
- 初始状态: 进程 A 拥有一块内存页 Page 1。
- 发生拷贝(如 fork 进程): 此时需要拷贝出一个进程 B。系统不会真的去复制 Page 1,而是让进程 A 和 B 的页表都指向 Page 1,并将 Page 1 的权限设置为 只读(Read-Only)。
- 安全读取: 只要 A 和 B 只是读取数据,系统相安无事。
- 触发写入: 假设进程 B 要修改 Page 1 的数据。此时 CPU 发现 Page 1 是只读的,就会触发一个 缺页异常(Page Fault)。
- 内核介入(写时拷贝): 操作系统内核捕获到异常,发现这其实是一个 COW 页面。于是内核会默默分配一块新的物理内存(Page 2),把 Page 1 的数据复制到 Page 2 中,将进程 B 的页表映射到 Page 2,并将两者的权限都恢复为 可读写(Read-Write)。
- 继续执行: 进程 B 的写入操作在 Page 2 上顺利完成,进程 A 的数据依然在 Page 1 上保持原样。
三、 写时拷贝的经典应用场景
COW 思想的应用极其广泛,以下是几个面试常考、工作中也常遇到的经典场景:
1. Linux 中的 fork() 系统调用
在早期的 Unix 系统中,fork() 创建子进程时会把父进程的所有内存空间全部深拷贝一份。但大多数情况下,子进程马上就会调用 exec() 去执行全新的程序,之前费时费力拷贝过来的内存完全浪费了。
引入 COW 后,fork() 瞬间变得轻量级:父子进程初始共享全部物理内存。只有当某一方试图修改数据(如修改变量)时,才会针对那一小块特定的内存页进行拷贝。这极大地提升了进程创建的速度,并节省了内存。
2. Redis 的 bgsave 持久化
Redis 是单线程的,如果在主线程里把庞大的内存数据写入磁盘(RDB 镜像),会严重阻塞正常的客户端请求。
Redis 的做法是:调用 fork() 创建一个子进程去专门负责写磁盘。得益于操作系统的 COW 机制,子进程在创建瞬间就拥有了和父进程一模一样的内存视图,而且速度极快。父进程依然可以毫无阻塞地处理客户端的新请求,只有被修改的数据页才会发生实际的内存拷贝。
3. Java 中的 CopyOnWriteArrayList
这是 Java 并发包(java.util.concurrent)中提供的一个线程安全的 List。
它的内部实现原理是:任何读操作都不加锁,直接读取底层数组;而一旦有写操作(如 add、set、remove),就会先将原数组复制一份出一个新数组,在新数组上进行修改,修改完成后,再把原数组的引用指向新数组。
- 适用场景: 读多写少的并发场景(如黑白名单、系统配置缓存等)。
4. 容器技术与存储(Docker / ZFS)
Docker 镜像的分层存储(如 OverlayFS、AUFS)也深度依赖了 COW。当我们启动一个容器时,底层镜像层是只读的。只有当我们在容器内修改或新建文件时,系统才会把该文件从底层镜像层复制到最上面的可写层(Container Layer)进行修改。这使得成百上千个容器可以共享同一份底层镜像,极大地节省了磁盘空间。
四、 写时拷贝的优缺点总结
🌟 优点:
- 极大地节省内存/磁盘资源: 只有在发生修改时才去分配资源,未修改的部分永远共享。
- 显著提升性能: 避免了不必要的、耗时的初始化全量拷贝操作。
- 并发读安全: 在某些语言级别实现中(如 Java),由于原始数据不可变,天然支持无锁的并发读取。
⚠️ 缺点:
- 不可预知的延迟: 拷贝动作被延迟到了写操作触发的瞬间。如果数据量大,突然触发的大量写操作会导致系统出现短暂的卡顿(比如 Redis 在写密集的场景下进行
bgsave,容易发生频繁的 Page Fault 导致性能抖动)。 - 写放大开销: 在 Java 的
CopyOnWriteArrayList中,哪怕只修改数组中的一个元素,也要把整个数组复制一遍,写操作的开销极大。
2.4)面试中string的一种正确写法
C++ 的一个常见面试题是让你实现一个String类,限于时间,不可能要求具备std::string的功能,但至少要求能正确管理资源。具体来说:
1. 能像 int 类型那样定义变量,并且支持赋值、复制
2. 能用作函数的参数类型及返回类型
3. 能用作标准库容器的元素类型,即 vector/list/deque 的 value_type
(用作 std::map 的 key_type 是更进一步的要求,本文从略)
🔗Lucy的空间骇客裂缝:面试中C++的写法
2.5)STL 中的 string 类怎么了?
🔗Lucy的空间骇客裂缝
💻结尾— 核心连接协议
警告:🌠🌠正在接入底层技术矩阵。如果你已成功破解学习中的逻辑断层,请执行以下指令序列以同步数据:🌠🌠
【📡】 建立深度链接:关注本终端。在赛博丛林中深耕底层架构,从原始代码到进阶协议,同步见证每一次系统升级。
【⚡】 能量过载分发:执行点赞操作。通过高带宽分发,让优质模组在信息流中高亮显示,赋予知识跨维度的传播力。
【💾】 离线缓存核心:将本页加入收藏。把这些高频实战逻辑存入你的离线存储器,在遭遇系统崩溃或需要离线检索时,实现瞬时读取。
【💬】 协议加密解密:在评论区留下你的散列码。分享你曾遭遇的代码冲突或系统漏洞(那些年踩过的坑),通过交互式编译共同绕过技术陷阱。
【🛰️】 信号频率投票:通过投票发射你的选择。你的每一次点击都在重新定义矩阵的进化方向,决定下一个被全量拆解的技术节点。