std::string_view 是 C++17 引入的轻量级、只读、非拥有型字符串视图,定义于头文件 <string_view>。
基础
std::string_view 是"零拷贝字符串引用",适合高性能读场景,但必须严格控制生命周期,否则极易产生隐蔽 bug。
结构定义可以抽象为:
C++
struct string_view {
const char* data;
size_t size;
};
构造方式
string_view 支持多种构造方式:
C++
// 默认构造:空视图
std::string_view sv1;
// 从字面量构成(长度至第一个 '\0')
std::string_view sv2 = "hello";
// 从指针+长度构造(可包含 '\0' 在内的任意字符)
const char* data = "hello\0world";
std::string_view sv3(data, 12); // 包含中间的空字符
// 从 std::string 构造(浅拷贝)
std::string str = "C++";
std::string_view sv4 = str; // O(1)
// 从 vector<char> 或其他字符容器(需提供指针和长度)
std::vector<char> vec = {'a','b','c'};
std::string_view sv5(vec.data(), vec.size());
// 拷贝构造 / 赋值(浅拷贝)
auto sv6 = sv5; // 廉价,O(1)
但不能从临时对象中构造 string_view
C++
// ❌ 危险:"hello" 首先构造了一个临时的 std::string,sv 指向该临时内存
// 语句结束后,std::string 析构,sv 变成悬空指针
std::string_view sv = std::string("hello");
常用成员函数
string_view 提供了与 std::string 非常相似的只读接口:
容量与元素访问
string_view 不依赖 '\0' 判断结束,它使用内置的长度字段。
C++
sv.size(); // 长度,O(1)
sv.empty(); // 是否为空
sv[0]; // 下标访问(无边界检查)
sv.at(0); // 边界检查版本(抛出 std::out_of_range)
sv.front(); // 第一个字符
sv.back(); // 最后一个字符
sv.data(); // 返回原始指针(不一定以 '\0' 结尾!)
迭代器
C++
sv.begin(), sv.end(), sv.cbegin(), sv.cend();
sv.rbegin(), sv.rend(); // 反向迭代器
字符串操作
std::string_view 提供了完整的比较操作符(==、!=、<、<=、>、>=),其中相等比较 (==) 的行为是:逐字符比较两段字符序列,要求长度相同且每个对应字符相等。
**陷阱:**当 string_view 包含 '\0' 而与其比较的 C 字符串字面量也包含 '\0' 时,字面量会因 strlen 提前终止,导致长度不一致,比较结果很可能为 false。
C++
// 移除前缀/后缀
sv.remove_prefix(2); // 移动起始指针,O(1)
sv.remove_suffix(3); // 减少长度,O(1)
// 子串视图
sv.substr(pos, count); // 返回新视图,O(1),无拷贝
// 比较 (按字典序)
sv.compare(other_sv);
sv == other_sv; // 逐字符比较
sv < other_sv;
查找算法
C++
sv.find('c'); // 查找字符
sv.find("sub"); // 查找子串
sv.rfind(...); // 反向查找
sv.find_first_of("aeiou"); // 首次匹配集合中任一字符
sv.find_last_not_of(...); // 查找最后一个不在集合中的字符
// 所有查找都返回 size_t 位置,未找到返回 npos
进阶
与 string 比较
std::string 解决"拥有与管理",std::string_view 解决"高效读取与传递"。
核心差异
| 维度 | std::string |
std::string_view |
|---|---|---|
| 所有权 | 拥有数据 | 不拥有数据(视图) |
| 内存管理 | 自动分配/释放 | 不管理内存 |
| 可变性 | 可修改 | 只读 |
| 生命周期 | 自管理 | 依赖外部 |
| 拷贝成本 | O(n) | O(1) |
场景差异
| 场景 | string | string_view |
|---|---|---|
| 临时对象 | 安全 | ❌ 不安全 |
| 返回局部变量 | 安全 | ❌ 不安全 |
| 生命周期管理 | 自动 | 手动保证 |
注意事项
生命周期问题
string_view 不持有数据,必须确保底层数据在视图使用期间保持有效:
C++
std::string_view Dangerous() {
std::string local = "temporary";
return local; // 错误!返回视图指向已被销毁的局部变量
}
即使从函数返回 std::string,再隐式转换为 string_view 也是危险的:
C++
std::string_view sv = []() -> std::string {
return "Hello";
}(); // 临时 string 已销毁,sv 悬挂
正确做法:确保视图的存活期不超过原始字符串对象的存活期。
缺少空终止符
string_view 不保证末尾有 '\0'。不能直接将 sv.data() 传递给期望 C 字符串的函数(如 strlen, printf("%s"), fopen 等)。若必须传给 C 接口,需先构造一个临时 std::string。
C++
std::string_view sv = GetSomeView();
// 危险:printf("%s", sv.data()); // 可能读取越界
printf("%s", std::string(sv).c_str()); // 安全,但有拷贝
对于已知以空字符结尾的视图(例如从字面量构造的),data() 返回的指针可安全以 C 字符串使用。
修改原始字符串会使视图失效
视图指向的底层数据变化(如 std::string 重新分配内存、修改内容),视图会"看到"新内容,但不一定正确(若重新分配,原指针悬空):
C++
std::string s = "hello";
std::string_view sv = s;
s += " world"; // 可能触发 reallocation
// sv 现在可能是悬空指针(如果内存重新分配了)
修改原始字符串内容(如 s[0] = 'H')会立刻反映到视图上。
隐式转换与临时对象
string_view 可以从 std::string 隐式构造,但反过来不行。当函数重载同时有 const std::string& 和 std::string_view 时,字面量会优先匹配 string_view,避免构造临时 string。
**注意:**不要依赖 string_view 的隐式转换来延长临时对象的生命周期
C++
std::string_view sv = std::string("temp"); // 临时 string 在语句结束销毁,sv 悬空