跳到主要内容
C++ 标准库 string 类详解与模拟实现 | 极客日志
C++
C++ 标准库 string 类详解与模拟实现 C++ 标准库 string 类是处理字符串的核心工具。涵盖 string 类的构造、容量操作、访问遍历及修改接口,对比 size 与 length 原理。深入解析 auto 关键字与范围 for 循环在 string 中的应用。重点讲解浅拷贝与深拷贝的区别,以及写时拷贝(COW)机制在 Linux fork、Redis bgsave 等场景的应用。包含 VS 与 G++ 下 string 结构差异分析,并提供面试中 string 类模拟实现的正确写法与注意事项。
草莓泡芙 发布于 2026/3/28 更新于 2026/4/25 4 浏览C++ 标准库中的 string 类
在使用 string 类时,必须包含 #include <string> 头文件以及 using namespace std;。
一、为什么学习 string 类?
C 语言中的字符串
参考相关 C 语言字符与字符串函数知识。
字符串面试题
常见题目包括字符串转化为整数、字符串相加等。
二、C++ 标准库中的 string 类
2.1)auto 和范围 for
auto 关键字
核心功能: auto 的核心功能是让编译器通过初始值来推导变量的类型。这意味着使用 auto 时,变量必须初始化。
auto x = 10 ;
auto y = 3.14 ;
auto ptr = &x;
用 auto 声明指针类型时,用 auto 和 auto* 没有任何区别,但用 auto 声明引用类型时则必须加 &。
int x = 10 ;
int * p = &x;
auto a = p;
auto * b = p;
int x = 10 ;
int & ref = x;
auto c = ref;
auto & d = ref;
c = 20 ;
d = 30 ;
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错。
auto a = 10 , b = 20 ;
auto c = 1.5 , d = 3.14 ;
int x = 0 ;
auto i = x, &j = x;
auto a = 10 , b = 3.14 ;
auto m = 5 , *p = &m;
auto 不能作为函数的参数,可以做返回值,但是建议谨慎使用。
void func (auto x) { ... }
template <typename T>
void func (T x) { ... }
int arr[] = {1 , 2 , 3 };
auto a[] = {1 , 2 , 3 };
auto b[3 ] = {1 , 2 , 3 };
int nums[] = {10 , 20 , 30 };
auto p = nums;
如果你希望保留数组的'大小信息'而不是让它退化成指针,可以使用引用:
int nums[] = {1 , 2 , 3 };
auto & refArr = nums;
for 关键字 C++11 中引入了基于范围的 for 循环 。for 循环后的括号由冒号':'分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
for (declaration : range) {
}
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>
using namespace std;
int main () {
int array[] = {1 , 2 , 3 , 4 , 5 };
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;
}
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;
return 0 ;
}
2.2)string 类的常用接口
🚩1)string 类的常用构造 构造函数 功能说明 string()(重点)构造空的 string 类对象,即空字符串 string(const char* s)(重点)用 C-string 来构造 string 类对象 string(size_t n, char c)string 类对象中包含 n 个字符 cstring(const string& s)(重点)拷贝构造函数
#include <iostream>
#include <string>
using namespace std;
int main () {
string s1;
cout << "s1 (空字符串): [" << s1 << "]" << endl;
string s2 ("Hello World" ) ;
cout << "s2 (由字符串常量构造): " << s2 << endl;
string s3 (10 , '*' ) ;
cout << "s3 (10 个星号): " << s3 << endl;
string s4 (s2) ;
cout << "s4 (拷贝自 s2): " << s4 << endl;
return 0 ;
}
🚩2)string 类对象的容量操作 函数名称 功能说明 size(重点)返回字符串有效字符长度 length返回字符串有效字符长度 capacity返回空间总大小 empty(重点)检测字符串是否为空串,是返回 true,否则返回 false clear(重点)清空有效字符 reserve(重点)为字符串预留空间 resize(重点)将有效字符的个数改成 n 个,多出的空间用字符 c 填充
❗注意事项
size() 与 length() 底层原理相同
size() 是为了与其他容器接口保持一致。
clear() 是清空有效字符,但不改变底层空间大小(capacity)
resize(size_t n, char c) 改变有效字符个数
s = "hello" ;
s.resize (10 , 'x' );
s.resize (3 );
reserve(size_t res_arg) 为 string 预留空间,不改变有效元素个数;只有当参数大于当前底层空间时才会扩容
🚩3)string 类对象的访问及遍历操作 函数名称 功能说明 operator[pos](重点)返回 pos 位置的字符,const string 类对象调用 begin + end获取迭代器 rbegin + rend获取反向迭代器 范围 for C++11 支持更简洁的范围 for 的新遍历方式
#include <iostream>
#include <string>
using namespace std;
int main () {
string s = "Hello" ;
cout << "下标为 1 的字符:" << s[1 ] << endl;
s[0 ] = 'h' ;
cout << "正向迭代器遍历:" ;
for (string::iterator it = s.begin (); it != s.end (); ++it) {
cout << *it << " " ;
}
cout << endl;
cout << "反向迭代器遍历:" ;
for (string::reverse_iterator rit = s.rbegin (); rit != s.rend (); ++rit) {
cout << *rit << " " ;
}
cout << endl;
cout << "范围 for 遍历:" ;
for (char c : s) {
cout << c << "-" ;
}
cout << endl;
return 0 ;
}
Iterator 小拓展
对于 string 来说,最常用的四种组合:
iterator:能读能改,正着走。
const_iterator:只能读,正着走。
reverse_iterator:能读能改,倒着走。
const_reverse_iterator:只能读,倒着走。
🚩4)string 类对象的修改操作 函数名称 功能说明 push_back在字符串后尾插字符 c append在字符串后追加一个字符串 operator+=(重点)在字符串后追加字符串 str c_str(重点)返回 C 格式字符串 find + npos(重点)查找子串 rfind从右向左找字符 substr截取子串
#include <iostream>
using namespace std;
int main () {
string s = "Hello" ;
s.push_back ('!' );
s.append (" Welcome" );
s += " to C++" ;
const char * cStr = s.c_str ();
cout << "C-Style: " << cStr << endl;
size_t pos = s.find ("Welcome" );
if (pos != string::npos) {
cout << "Found 'Welcome' at: " << pos << endl;
}
size_t last_space = s.rfind (' ' );
cout << "Last space at: " << last_space << endl;
string sub = s.substr (7 , 7 );
cout << "Sub-string: " << sub << endl;
return 0 ;
}
❗注意事项
追加字符的三种方式
方式 A: 使用 push_back(c)
方式 B: 使用 append(n, c)
方式 C: 使用 += (最常用)
使用 reserve 预留空间
如果预估要存较多字符,先预留空间可以减少频繁扩容带来的性能消耗。
🚩5)string 类非成员函数 函数 功能说明 operator+尽量少用,因为传值返回,导致深拷贝效率低 operator>>(重点)输入运算符重载 operator<<(重点)输出运算符重载 getline(重点)获取一行字符串 relational operators(重点)大小比较
string full_name;
cin >> full_name;
cout << "你好," << full_name << endl;
string str1 = "apple" ;
string str2 = "banana" ;
if (str1 < str2) {
cout << "apple 在字典中排在 banana 前面" << endl;
}
🚩6)vs 和 g++ 下 string 结构的说明 注意: 下述结构是在 32 位平台下进行验证,32 位平台下指针占 4 个字节。
VS 下 string 的结构 string 总共占 28 个字节。内部结构稍微复杂一点,先是有一个联合体,联合体用来定义 string 中字符串的存储空间:
当字符串长度小于 16 时,使用内部固定的字符数组来存放
当字符串长度大于等于 16 时,从堆上开辟空间
其次:还有一个 size_t 字段保存字符串长度,一个 size_t 字段保存从堆上开辟空间总的容量。
最后:还有一个指针做一些其他事情。
故总共占 16 + 4 + 4 + 4 = 28 个字节。
G++ 下 string 的结构 G++ 下,string 是通过写时拷贝实现的,string 对象总共占 4 个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:
空间总大小
字符串有效长度
引用计数
指向堆空间的指针,用来存储字符串
2.3)string 类的模拟实现 上面已经对 string 类进行了简单的介绍,大家只要能够正常使用即可。在面试中,面试官总喜欢让学生自己来模拟实现 string 类,最主要是实现 string 类的构造、拷贝构造、赋值运算符重载以及析构函数。
🚩1)经典的 string 类问题
1)构造函数的缺省值设置非常关键 String (const char * str = nullptr )
解引用空指针 :string 类的构造函数内部通常会调用 strlen(str) 来计算传入字符串的长度。如果 str 是 nullptr(空指针),strlen 试图访问地址为 0 的内存,这会导致非法访问(Segmentation Fault)。
🚩2)浅拷贝 VS 深拷贝
1)传统 class String {
public :
String (const char * str = "" ) {
if (nullptr == str) {
assert (false );
return ;
}
_str = new char [strlen (str) + 1 ];
strcpy (_str, str);
}
String (const String& s) : _str(new char [strlen (s._str) + 1 ]) {
strcpy (_str, s._str);
}
String& operator =(const String& s) {
if (this != &s) {
char * Pstr = new char [strlen (s._str) + 1 ];
strcpy (Pstr, s._str);
delete [] _str;
_str = Pstr;
}
return *this ;
}
~String () {
if (_str) {
delete [] _str;
_str = nullptr ;
}
}
private :
char * _str;
};
2)现代 class String {
public :
String (const char * str = "" ) {
if (str != nullptr ) {
_str = new char [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 ;
}
~String () {
if (_str) {
delete [] _str;
_str = nullptr ;
}
}
private :
char * _str;
};
🚩3)写时拷贝
一、什么是写时拷贝(COW)? 写时拷贝(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 上保持原样。
三、写时拷贝的经典应用场景
Linux 中的 fork() 系统调用
引入 COW 后,fork() 瞬间变得轻量级:父子进程初始共享全部物理内存。只有当某一方试图修改数据时,才会针对那一小块特定的内存页进行拷贝。
Redis 的 bgsave 持久化
Redis 的做法是:调用 fork() 创建一个子进程去专门负责写磁盘。得益于操作系统的 COW 机制,子进程在创建瞬间就拥有了和父进程一模一样的内存视图,而且速度极快。
Java 中的 CopyOnWriteArrayList
这是 Java 并发包中提供的一个线程安全的 List。它的内部实现原理是:任何读操作都不加锁,直接读取底层数组;而一旦有写操作,就会先将原数组复制一份出一个新数组,在新数组上进行修改。
容器技术与存储(Docker / ZFS)
Docker 镜像的分层存储也深度依赖了 COW。当我们启动一个容器时,底层镜像层是只读的。只有当我们在容器内修改或新建文件时,系统才会把该文件从底层镜像层复制到最上面的可写层进行修改。
四、写时拷贝的优缺点总结
🌟 优点:
极大地节省内存/磁盘资源。
显著提升性能。
并发读安全。
⚠️ 缺点:
2.4)面试中 string 的一种正确写法 C++ 的一个常见面试题是让你实现一个 String 类,限于时间,不可能要求具备 std::string 的功能,但至少要求能正确管理资源。具体来说:
能像 int 类型那样定义变量,并且支持赋值、复制。
能用作函数的参数类型及返回类型。
能用作标准库容器的元素类型,即 vector/list/deque 的 value_type。
2.5)STL 中的 string 类怎么了? 关于 STL 中 string 类的演进细节,可参考相关技术文档分析其优化策略。
相关免费在线工具 Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online