C++ 进阶(25):span 与 string_view——不拥有所有权的视图
更新时间:2026-09-01。本文是
languages/cpp/intermediate/进阶第 25 篇,接 optional/variant/any。std::string_view(C++17)和std::span(C++20)是不拥有所有权的视图——它们只引用数据,不管理生命周期。理解它们,才能写出高效的函数接口。
本文要回答的问题
- string_view 是什么?和 const string& 比有什么优势?
- span 是什么?怎么传递数组/vector/array 的切片?
- 视图的生命周期怎么管理?为什么不能返回 string_view?
- string_view 的空字符问题怎么处理?
一、string_view:字符串视图
cpp
#include <string_view>
// 函数参数用 string_view,而不是 const string&
void process(std::string_view sv) {
std::cout << sv << "\n"; // 只读访问
std::cout << sv.size() << "\n";
std::cout << sv.substr(0, 3) << "\n"; // 子串,不拷贝
}
// 可以传各种字符串类型
std::string s = "hello";
const char* c = "world";
process(s); // 从 string 转换
process(c); // 从 C 字符串转换
process("hello!"); // 从字面量转换
process(s.substr(0, 3)); // ❌ 临时对象问题!见下文为什么用 string_view 代替 const string&?
cpp
// ❌ const string&:传 C 字符串需要构造临时 string
void process(const std::string& s);
process("hello world"); // 隐式构造 std::string,堆分配
// ✅ string_view:不拷贝,不分配
void process(std::string_view sv);
process("hello world"); // 直接引用字面量,无分配二、span:连续内存视图
cpp
#include <span>
// 函数参数用 span,接受各种连续容器
void print(std::span<const int> arr) {
for (auto v : arr) {
std::cout << v << " ";
}
std::cout << "\n";
std::cout << arr.size() << "\n";
std::cout << arr.subspan(0, 3).size() << "\n"; // 子数组
}
// 可以传各种数组
int arr[] = {1, 2, 3, 4, 5};
std::vector vec = {1, 2, 3};
std::array arr2 = {1, 2, 3, 4};
print(arr); // 从 C 数组
print(vec); // 从 vector
print(arr2); // 从 array
print({1, 2, 3}); // 从初始化列表span 的修改变得方便:
cpp
void increment(std::span<int> arr) {
for (auto& v : arr) {
v++;
}
}三、生命周期管理
cpp
// ❌ 错误:返回 string_view 到临时对象
std::string_view get_name() {
std::string name = "hello";
return name; // name 离开作用域,引用悬空!
}
// ✅ 正确:返回 string_view 到静态或全局数据
std::string_view get_name() {
static std::string name = "hello";
return name;
}
// 或者返回 string_view 到函数参数
std::string_view extract(std::string_view sv) {
return sv.substr(0, 3); // 安全,引用的是参数的数据
}规则: view 的引用数据的生命周期必须比 view 长。
四、string_view 的空字符
cpp
// string_view 不保证以 '\0' 结尾!
std::string_view sv = "hello";
sv.data()[5]; // 未定义行为!可能没有 '\0'
// 如果要传给 C 函数,需要拷贝
void c_func(const char* s);
// ❌ 不安全
c_func(sv.data());
// ✅ 安全,但需要拷贝
std::string tmp(sv);
c_func(tmp.c_str());五、性能对比
| 方式 | 分配 | 拷贝字符 | 适用场景 |
|---|---|---|---|
const string& | 可能分配(传 C 字符串时) | 不拷贝 | 需要修改或存储 |
const char* | 无 | 不拷贝 | C 兼容 |
string_view | 无 | 不拷贝 | 只读,不存储 |
经验: 函数参数只读字符串用 string_view,只读数组用 span。
六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 返回 string_view 到临时对象 | 悬空引用,UB | 确保 view 引用的数据生命周期更长 |
| string_view 不是 '\0' 结尾 | 传给 C 函数出问题 | 需要 '\0' 结尾时拷贝到 string |
| 修改 span 引用的数据 | 影响原始数据 | 用 span<const T> 只读 |
| 把临时对象传给 string_view 参数 | 参数是临时对象,函数结束后悬空 | 函数参数用 string_view 安全,但注意 substr 的临时对象 |
相关与延伸
下一篇:位操作——C++20 bit 库;C++17 string_view 的底层,见 string 深入。
一句话总结
C++ span 与 string_view:两者都是不拥有所有权的视图,string_view 用于字符串,span 用于连续内存数组;函数参数用 string_view 代替 const string& 避免不必要的分配;span 接受 C 数组、vector、array,替代 (T* data, size_t len) 接口;视图的生命周期必须比引用的数据长,不能返回视图到临时对象;string_view 不保证 '\0' 结尾,传给 C 函数需拷贝到 string。