std::string 是 C++ 标准库(<string> 头文件)提供的字符串处理类,封装了字符序列的存储、操作和内存管理,是替代 C 风格字符数组(char[])的核心工具。
一、核心特性(与 C 风格字符串的对比)


二、内存模型(底层实现核心)
std::string 的底层实现依赖小字符串优化(SSO, Small String Optimization),这是理解其性能和行为的关键:
1. 小字符串优化(SSO)
- 核心思想:短字符串直接存在
string对象内部的静态缓冲区,避免堆内存分配;长字符串使用独立堆内存。 - 阈值:主流编译器(GCC/Clang/MSVC)默认阈值为 15 字节(15 个字符 + 1 个 '\0',共 16 字节,对齐优化);部分编译器为 22/23 字节。
// 简化版内存结构
struct string {
union {
struct { // 小字符串(≤15字节)
char buf[16]; // 内置缓冲区,含 '\0'
size_t size; // 实际长度
};
struct { // 大字符串(>15字节)
char* ptr; // 堆内存指针
size_t size; // 实际长度
size_t cap; // 总容量(不含 '\0')
};
};
};
2. 容量(capacity)与大小(size)
- size/length:字符串的实际有效字符数(不含末尾的 '\0'),
size()和length()是等价的(历史原因保留两个接口)。 - capacity:已分配内存的总容量(可存储的最大字符数,不含 '\0'),
capacity() ≥ size()。 - reserve(n):预分配至少
n字节的容量,避免频繁扩容(提升性能)。 - shrink_to_fit():将容量缩减至与大小一致(释放多余内存,C++11 引入)。
示例:容量与大小操作
string s = "hello";
cout << s.size(); // 5(有效字符数)
cout << s.capacity(); // GCC下:15(SSO阈值,小字符串容量=15)
cout << s.length(); // 5(等价于size())
s.reserve(100); // 预分配100字节容量
cout << s.capacity(); // 100(转为大字符串,堆内存)
s.shrink_to_fit(); // 容量缩减为5
cout << s.capacity(); // 5
3. 扩容规则
当字符串长度超过当前容量时,string 会自动扩容,主流编译器的扩容因子为 2 倍(部分为 1.5 倍):
- 示例:初始容量 15 → 扩容后 30 → 再扩容 60 → ...
- 注意:扩容会触发「内存重新分配 + 数据拷贝」,频繁扩容影响性能,可通过
reserve()提前预分配。
三、基础用法
1. 构造与初始化
std::string 提供多种构造方式,覆盖不同场景:


示例:构造函数
// 基础构造
string s1; // 空字符串
string s2 = "hello"; // 拷贝初始化
string s3("world", 3); // "wor"
string s4(4, 'x'); // "xxxx"
string s5(s2, 1, 3); // 从s2的索引1取3个字符 → "ell"
// 移动构造(s1变为空,s6接管资源)
string s6 = std::move(s2);
cout << s2.empty(); // true
cout << s6; // "hello"
2. 元素访问
提供 4 种访问方式,各有适用场景:

示例:元素访问
string s = "hello";
cout << s[0]; // 'h'(无越界检查)
cout << s.at(1); // 'e'(越界抛异常)
s[4] = 'o'; // 修改为 'o' → "hello"
s.back() = '!'; // 修改尾字符 → "hell!"
// 兼容C接口
printf("%s\n", s.c_str()); // 输出 "hell!"
3. 赋值与拼接
(1)赋值操作
string s;
s = "hello"; // 赋值C风格字符串
s = 'a'; // 赋值单个字符
s.assign("world", 3); // 赋值前3个字符 → "wor"
s.assign(5, 'x'); // 赋值5个'x' → "xxxxx"
(2)拼接操作

4. 插入与删除
(1)插入(insert)
string s = "hello";
s.insert(2, "xx"); // 在索引2插入 → "hexxllo"
s.insert(0, 3, 'y'); // 在开头插入3个'y' → "yyyhexxllo"
s.insert(s.end(), 'z');// 在末尾插入 → "yyyhexxlloz"
(2)删除(erase)
string s = "hello world";
s.erase(5); // 从索引5删除到末尾 → "hello"
s.erase(1, 2); // 从索引1删除2个字符 → "hlo"
s.clear(); // 清空字符串,size=0,capacity不变
5. 查找与替换
(1)查找(find 系列)
find 是最常用的查找方法,返回 size_t 类型的索引,未找到返回 string::npos(size_t(-1),无效位置)。
| 查找方法 | 功能 |
|---|---|
find(str, pos) | 从 pos 开始查找 str,返回首次出现的索引 |
rfind(str) | 反向查找,返回最后一次出现的索引 |
find_first_of(str) | 查找 str 中任意字符的首次出现索引 |
find_last_of(str) | 查找 str 中任意字符的最后出现索引 |
find_first_not_of(str) | 查找第一个不在 str 中的字符索引 |
示例:查找
string s = "hello world hello";
size_t pos = s.find("hello"); // 0(首次出现)
pos = s.find("hello", 1); // 12(从索引1开始找)
pos = s.rfind("hello"); // 12(最后一次出现)
pos = s.find("test"); // string::npos(未找到)
// 查找任意字符
pos = s.find_first_of("abc"); // string::npos(无a/b/c)
pos = s.find_first_of("wor"); // 6('w'的索引)
(2)替换(replace)
string s = "hello world";
s.replace(6, 5, "cpp"); // 从索引6替换5个字符为"cpp" → "hello cpp"
s.replace(s.find("cpp"), 3, "c++"); // 替换"cpp"为"c++" → "hello c++"
6. 子串(substr)
string s = "hello world";
string sub1 = s.substr(6); // 从索引6到末尾 → "world"
string sub2 = s.substr(0, 5); // 从索引0取5个字符 → "hello"
注意:substr(pos, len) 中,若 len 超过剩余字符数,会自动截取到末尾;若 pos 越界,抛 out_of_range 异常。
7. 比较(compare)
支持直接用 ==/!=/>/< 运算符,也可通过 compare() 方法(返回 int,兼容 C 风格):
string s1 = "abc", s2 = "abd";
cout << (s1 < s2); // true(按字典序比较)
cout << s1.compare(s2); // -1(s1 < s2)
cout << s1.compare(1, 2, "bc"); // 0(s1[1:2] = "bc")
四、高级操作(C++11+ 特性)
1. 移动语义(C++11)
std::string 实现了移动构造和移动赋值,避免深拷贝,提升性能:
// 移动构造:s1 转移资源给 s2,s1 变为空
string s1 = "hello";
string s2 = std::move(s1);
// 移动赋值
string s3;
s3 = std::move(s2);
2. 数值转换(C++11)
<string> 提供数值与字符串的双向转换,替代 atoi/itoa:
| 转换方向 | 函数 | 示例 |
|---|---|---|
| 数值→字符串 | to_string(val) | to_string(123) → "123" |
| 字符串→整数 | stoi()/stol()/stoll() | stoi("123") → 123 |
| 字符串→浮点数 | stof()/stod()/stold() | stod("3.14") → 3.14 |
示例:数值转换
string s = to_string(123.45); // "123.45"
int i = stoi("456"); // 456
double d = stod("78.9"); // 78.9
// 处理异常(如转换失败)
try {
stoi("abc"); // 抛 invalid_argument 异常
} catch (const exception& e) {
cout << e.what() << endl;
}
3. 范围 for 循环(C++11)
string s = "hello";
for (char c : s) {
cout << c << " "; // h e l l o
}
// 修改字符
for (char& c : s) {
c = toupper(c); // 转为大写 → "HELLO"
}
4. 非成员函数
(1)getline:读取整行(含空格)
string line;
// 读取一行(直到换行,忽略换行符),替代 cin >> s(遇空格停止)
getline(cin, line);
cout << line;
(2)swap:交换两个字符串(O (1))
string s1 = "hello", s2 = "world";
swap(s1, s2); // s1="world", s2="hello"(仅交换指针/缓冲区,无拷贝)
五、实现细节(深入理解)
1. 写时拷贝(COW)
- 概念:多个
string对象共享同一块堆内存,仅当其中一个对象修改时,才拷贝内存(Copy On Write)。 - 现状:C++11 后已废弃(因线程安全问题),现代编译器(GCC 5+、MSVC)的
string均为深拷贝 + SSO。
2. 线程安全
const成员函数(如size()、c_str())是线程安全的(多个线程只读);- 非
const成员函数(如+=、erase())不是线程安全的,多线程修改需加锁; - 不同
string对象的操作互不影响。
3. 空字符串
- 空字符串的
size()=0,capacity()通常为 SSO 阈值(15); - 空字符串的
c_str()返回指向"\0"的指针,非空指针。
六、避坑要点(高频错误)
1. 索引越界
s[i]越界是未定义行为(UB),可能崩溃;- 优先用
s.at(i)(抛异常),或先检查i < s.size()。
2. npos 的使用
string::npos是size_t(无符号整数),不能用int接收返回值:// 错误 int pos = s.find("test"); if (pos == string::npos) { ... } // 隐式转换导致错误 // 正确 size_t pos = s.find("test"); if (pos == string::npos) { ... }
3. c_str ()/data () 的生命周期
c_str()返回的指针仅在string未修改时有效,修改后可能失效:string s = "hello"; const char* p = s.c_str(); s += " world"; // s 扩容,p 变为野指针 cout << p; // 未定义行为
4. 扩容导致迭代器失效
- 修改字符串(如
+=、insert)可能触发扩容,导致原有迭代器 / 指针失效:string s = "hello"; char* p = &s[0]; s.reserve(100); // 扩容,p 失效 cout << *p; // 未定义行为
5. 与 C 风格字符串的混用
- 避免将
string的data()传给需要\0结尾的 C 函数(string内部已保证\0,但修改后需确认); - 不要用
delete[] s.c_str()(string管理内存,手动释放会重复释放)。
七、总结(核心知识点回顾)
- 核心优势:自动内存管理、SSO 性能优化、丰富的操作接口、类型安全;
- 核心概念:
size()(实际长度)、capacity()(总容量)、npos(无效位置)、SSO(小字符串优化); - 关键操作:元素访问(
[]/at())、查找(find)、替换(replace)、子串(substr)、数值转换(to_string/stoi); - 避坑重点:迭代器失效、npos 类型匹配、c_str () 生命周期、越界访问;
- 性能优化:提前
reserve()避免扩容、使用移动语义减少拷贝、优先用+=而非+拼接。
std::string 是 C++ 开发中最常用的容器之一,掌握其底层实现和接口用法,能大幅提升字符串处理的效率和安全性,避免 C 风格字符串的各种坑。
下一章会发的mystd::mystring,帮助我自己加深印象。

1328

被折叠的 条评论
为什么被折叠?



