string 深入
更新时间:2026-08-27。本文是
languages/cpp/主题入门层第 33 篇。std::string是 C 字符串的全面升级:不用数长度、不用操心释放、还能当容器遍历。但c_str()的悬垂陷阱、查找返回的npos、拼接的效率,这些细节不懂就翻车。本篇把 string 真正讲透。
本文要回答的问题
std::string相对 C 字符串升级了什么?find返回的npos是什么?为什么老有人在这栽跟头?c_str()的悬垂陷阱是怎么回事?
一、string 是升级版的 C 字符串
第 05 篇讲过 string 入门,这里往深里走:
#include <string>
std::string s = "hello";
s += " world"; // 拼接
s.push_back('!'); // 追加字符
s.length(); // 长度(和 size 等价)
s.empty(); // 是否为空
s.substr(0, 5); // 取子串 "hello"
s.find("wor"); // 查找,返回位置
s.compare("hello world"); // 比较(返回 <0/=0/>0)对比 C 字符串(char* + <string.h>):
| 对比项 | C 字符串 | std::string |
|---|---|---|
| 长度 | strlen 每次 O(n) | .length() O(1) |
| 拼接 | strcat 要目标缓冲够大 | += 自动扩容 |
| 比较 | strcmp | ==、< 直接比 |
| 释放 | 手动 | 自动(析构) |
| 越界 | 未定义行为 | .at() 抛异常 |
strcat 的缓冲区溢出是 C 世界最大的安全坑之一;std::string 用自动扩容把它彻底消灭。这也是 C++ 项目普遍禁用裸 char* 字符串的原因。
二、npos:查找失败的返回值
std::string s = "hello world";
size_t pos = s.find("xyz");
if (pos != std::string::npos) { // 查到了,pos 是下标
// ...
} else { // 没查到
// ...
}find 找不到时返回 std::string::npos——它是个特殊值(size_t 的最大值)。两个坑:
- 忘了和
npos比较就当下标用,直接越界(npos 是个天文数字); - 返回类型是
size_t(无符号),if (s.find(x) >= 0)这种判断永远为真——无符号数不可能 < 0。
标准写法只有一个:if (pos != std::string::npos)。记住这个,能躲开一大批线上 bug。
三、c_str() 的悬垂陷阱
.c_str() 返回一个 const char*,指向 string 内部缓冲区。这个指针随时可能失效:
const char* p = s.c_str();
s += " more text"; // 触发扩容,内部缓冲区换了位置
use(p); // p 指向已释放/无效的内存!悬垂规则两条:
c_str()拿到的指针,在下次修改 string 之前有效;- 修改过 string 之后,旧指针作废,必须重新
c_str()。
// 正确姿势:用完即丢,不存起来
printf("%s\n", s.c_str()); // 临时用,安全
// 或者先拷贝到自己的缓冲区
std::string copy = s; // 要长期持有就自己存一份四、拼接效率:+= 比 + 聪明
std::string s = "a";
s += "b"; // 追加:尽量复用已有空间
s = s + "c"; // 加法:创建新对象再赋值高频拼接场景:
// 慢:每次 + 都新建一个 string(大量分配)
std::string r;
for (int i = 0; i < 10000; ++i) r = r + std::to_string(i);
// 快:reserve + append/+=(复用空间)
std::string r;
r.reserve(60000); // 预分配(第 32 篇的 reserve 思想)
for (int i = 0; i < 10000; ++i) r += std::to_string(i);r += x 是原地追加(容量不够才扩容),r = r + x 先构造新 string 再整体拷贝——同样道理,+= 配合 reserve 是拼接的标准姿势。需要更灵活的拼接还有 std::ostringstream(第 07 篇讲过)。
五、string 也是容器
std::string 本质上就是 vector<char> 的亲戚:支持下标、迭代器、范围 for:
for (char c : s) { /* 逐字符遍历 */ }
for (auto it = s.begin(); it != s.end(); ++it) { /* 迭代器 */ }
std::sort(s.begin(), s.end()); // 字符排序,算法库直接上所以第 32 篇的 vector 知识大部分直接迁移到 string。区分好"字符数组"(char*,C 思维)和"字符容器"(string,C++ 思维),字符串处理就顺了。
六、与本站主线衔接
- string 底层的 SSO(短字符串优化:小字符串直接放对象内部,不堆分配),见高手层流式 IO 与 string 深挖;
- 字符串字面量、编码问题见 C 篇字符串字面量;
string_view(不拷贝的只读字符串视图)见 std::string_view;- 下一篇:
std::array——固定大小数组的正规军。
一句话总结
std::string 是 C 字符串的容器化升级:.length() O(1)、+= 自动扩容、析构自动释放,还能当容器遍历排序;find 失败返回 npos(别当下标用、别和 0 比);c_str() 的指针在 string 被修改后就悬垂,即取即用别存着;高频拼接用 reserve + +=,别用 r = r + x 反复建新对象。
上一篇:vector 用法与容量 下一篇:std::array 与 C 数组