std::string_view 主要解决了 std::string 在某些场景下不必要的内存分配和拷贝问题
1. 什么是 std::string_view?
简单来说,std::string_view 是一个轻量级的、非拥有的、只读的字符串"视图"。
- 非拥有 (Non-owning): 它不负责管理字符串内存的生命周期(不分配也不释放内存)。
- 只读 (Read-only): 你不能通过它修改原字符串的内容。
- 轻量级: 它内部通常只包含两个成员:
-
- 指向字符串起始位置的指针 (
ptr) - 字符串的长度 (
length)
- 指向字符串起始位置的指针 (
本质公式:

2. 为什么要使用它?(核心优势)
在 C++17 之前,我们在编写接收只读字符串的函数时,通常使用 const std::string&。但这有一个性能陷阱。
场景对比:
假设有一个函数 void process(const std::string& s);
- 情况 A: 你传入
std::string对象。
-
process(myStr);-> 高效(只是引用)。
- 情况 B: 你传入 C 风格字符串(字面量)。
-
process("Hello World");-> 低效。- 原因: 编译器必须先创建一个临时的
std::string对象(发生一次new内存分配,并将 "Hello World" 拷贝进去),然后将这个临时对象传给函数。函数结束后,再销毁它。
使用 std::string_view 之后:
如果函数签名改为 void process(std::string_view sv);
process("Hello World");-> 零拷贝,零分配。
-
string_view只是简单地记录了 "Hello World" 的地址和长度。
3. 使用场景
A. 作为函数参数(最主要用途)
这是 string_view 的最佳击球点。当你的函数只需要"读取"字符串,不需要修改,也不需要持有它时,请优先使用 std::string_view。
注意: std::string_view 本身很小(通常只是两个 64 位寄存器的大小),所以应该按值传递,而不是按引用传递。
// 推荐写法 (C++17 及以后)
void logMessage(std::string_view message) {
std::cout << "[LOG]: " << message << std::endl;
}
int main() {
std::string s = "Error 404";
logMessage("Starting..."); // 0 分配,高效
logMessage(s); // 0 分配,高效 (std::string 隐式转换为 string_view)
logMessage("User: Admin" + s); // 如果必须拼接,还是会产生临时 string,但这是拼接的代价
}
B. 字符串解析与子串处理 (Parsing)
这是 string_view 的另一个杀手级特性。
在 std::string 上调用 substr() 会创建一个新的字符串对象(内存分配 + 拷贝)。
在 std::string_view 上调用 substr() 只是调整内部的指针和长度,复杂度为 O(1)。
std::string_view sv = "Apple, Banana, Cherry";
// 移除前缀 (O(1) 操作,仅仅是移动了指针)
sv.remove_prefix(7);
// 现在 sv 代表 "Banana, Cherry"
// 获取子串 (O(1) 操作)
auto token = sv.substr(0, 6);
// token 是 "Banana",原字符串完全未受影响
4. 必须注意的"坑" (Caveats)
string_view 虽然好用,但它也是一把"悬在头顶的剑"。因为它不拥有 内存,所以必须时刻警惕生命周期问题。
1. 悬垂引用 (Dangling Reference) ------ 最危险!
如果在 string_view 还在使用时,它指向的原字符串已经被销毁了,就会发生未定义行为(程序崩溃或乱码)。
// ❌ 错误示范
std::string_view getBadView() {
std::string s = "Hello temporary";
return s; // s 在函数结束时被销毁!返回的 view 指向垃圾内存。
}
// ❌ 另一个隐蔽的错误
std::string_view sv = std::string("Hello") + " World";
// 临时 string 创建 -> 赋值给 sv -> 语句结束临时 string 销毁 -> sv 悬空
2. 不保证以 Null (\0) 结尾
std::string 和 C 风格字符串 (const char*) 总是以 \0 结尾。
但是 string_view 可能指向一个大字符串中间的一段,所以它不一定有 \0。
std::string full = "abcde";
std::string_view sv = full;
sv = sv.substr(0, 3); // sv 内容为 "abc"
// ❌ 危险!
printf("%s", sv.data());
// sv.data() 指向 'a',但 printf 会一直打印直到遇到 \0。
// 这里可能会打印出 "abcde",甚至更多乱码。
// ✅ 正确做法
std::cout << sv; // C++ IO 流对 string_view 有重载,是安全的
// 或者如果必须用 C API:
std::string temp(sv); // 拷贝一份以此获得 \0
printf("%s", temp.c_str());
3. 与旧 API 的兼容性
很多旧的 C++ 库接口只接受 const char* 或 const std::string&。
- 如果接口需要
std::string,你需要显式转换:std::string(sv)(会发生拷贝)。 - 如果接口需要
const char*且你无法保证sv是 null-terminated 的,你也需要转成std::string再调.c_str()。
5. 详细对比总结表
|-------------|------------------|-----------------|----------------------|
| 特性 | const char* | std::string | std::string_view |
| 所有权 | 不拥有 | 拥有 (RAII) | 不拥有 |
| 内存分配 | 无 | 有 (Heap) | 无 |
| 拷贝开销 | 指针拷贝 (极小) | 深拷贝 (大) | 浅拷贝 (极小) |
| Null 结尾 | 必须 | 必须 | 不一定 |
| 生命周期 | 手动管理或静态 | 自动管理 | 调用者负责 (需小心) |
| 作为参数 | 传统 C 方式 | 方便但可能慢 | 推荐 (C++17+) |
6. 实战建议 (Best Practices)
- 参数传递: 将函数的参数从
const std::string&改为std::string_view(按值传递)。 - 避免返回: 尽量不要 让函数返回
std::string_view,除非你非常确定返回的 View 指向的是全局常量区,或者生命周期长于调用者的对象。返回std::string通常更安全。 - 解析器编写: 在编写解析器(Json、XML、Config)时,内部处理大量使用
string_view代替string,性能提升会非常明显。 - 不要滥用: 如果你需要把字符串存下来以后用(例如存到
std::vector或类成员变量中),请使用std::string进行拷贝存储,不要存string_view,除非你极其清楚内存的生命周期。