string_view — 零拷贝的字符串”窗口”
#include <string_view>
// 不拥有数据,只是"看"一段字符
string_view sv = "hello"; // C 字符串
string s = "world";
string_view sv2 = s; // std::string
string_view sv3 = s.substr(0, 3); // "wor" — 不分配新内存!
// 对比:string::substr 会产生新 string + 堆分配
string sub = s.substr(0, 3); // 分配 + 拷贝
核心操作
string_view sv = "hello world";
sv.size(); // 11
sv.empty(); // false
sv[0]; // 'h'
sv.front(); // 'h'
sv.back(); // 'd'
// 子视图(O(1),不分配)
sv.substr(0, 5); // "hello"
sv.remove_prefix(6); // sv 变为 "world"
sv.remove_suffix(3); // sv 变为 "wo"
// 查找(O(n))
sv.find("wo"); // 返回位置
sv.starts_with("he"); // C++20
sv.ends_with("ld"); // C++20
// 转换回 string(这时才分配)
string s(sv);
函数参数 — 首选 string_view
// ❌ 老写法 — 只能接受 std::string
void process(const string &s);
// ❌ 或者重载两个
void process(const string &s);
void process(const char *s);
// ✅ 一个解决所有
void process(string_view sv) {
// 接受:string, const char*, char[], string_view
}
process("literal"); // ✅ 不构造临时 string
process(s); // ✅
process(sv); // ✅
悬空陷阱
// ⚠️ string_view 不拥有数据 → 必须保证数据比 view 活得久
string_view danger() {
string s = "temp";
return string_view(s); // ❌ s 析构后 view 悬空
}
string_view safe() {
return "literal"; // ✅ 字符串字面量静态存储,永远有效
}
// ⚠️ 临时 string 的陷阱
string_view sv = string("temp") + "file"; // ❌ 临时 string 已析构
string s = string("temp") + "file";
string_view sv2 = s; // ✅ s 还在
span — 缓冲区的 string_view
#include <span> // C++20
// 不拥有数据,只是"看"一段连续内存
void process(span<int> data) {
for (int &x : data) x *= 2;
}
int arr[] = {1, 2, 3, 4, 5};
vector<int> v = {6, 7, 8};
process(arr); // ✅ 接受 C 数组
process(v); // ✅ 接受 vector
// 一个接口覆盖所有连续容器
子区间
vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};
span<int> sp = v;
auto first4 = sp.first(4); // {1, 2, 3, 4}
auto last3 = sp.last(3); // {6, 7, 8}
auto middle = sp.subspan(2, 3); // {3, 4, 5}
字节视图
void writeFile(span<const byte> data) {
// 接受任何连续内存 → 写入文件
}
struct Header { int id; double timestamp; };
Header hdr{1, 3.14};
// 结构体 → 字节视图
writeFile(as_bytes(span{&hdr, 1}));
// 修改底层数据(不能是 const span)
void zeroInit(span<byte> data) {
fill(data.begin(), data.end(), byte{0});
}
对比总结
| string | string_view | vector<T> | span<T> |
|---|
| 拥有数据 | ✅ | ❌ | ✅ | ❌ |
| 堆分配 | ✅ | ❌ | ✅ | ❌ |
| 可修改 | ✅ | ❌ | ✅ | ✅ (非 const) |
| 适用场景 | 需存储/修改 | 传参/读取 | 需动态扩容 | 传参/子区间 |
| 版本 | C++98 | C++17 | C++98 | C++20 |
最佳实践
// 函数参数选择决策树:
// 只读字符串参数 → string_view
// 只读连续内存 → span<const T>
// 需要修改的连续内存 → span<T>
// 需要持有的字符串 → string
// 需要动态增长的容器 → vector<T>
// ❌ 不必要的拷贝
void print(const string &s);
void print(const vector<int> &v);
// ✅ 统一的零拷贝接口
void print(string_view s);
void print(span<const int> v);