C++之字符串视图string_view

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 悬空
相关推荐
fengxin_rou1 小时前
JVM 内存结构与内存溢出 / 泄漏问题全解析
java·开发语言·jvm·分布式·rabbitmq
城俊BLOG1 小时前
C++的注册机制和插件系统
java·服务器·c++
HoneyMoose1 小时前
Discourse 删除版本历史
开发语言
兩尛1 小时前
c++知识点4
开发语言·c++
墨染千千秋1 小时前
C++输入输出全解
c++
云qq1 小时前
C++ 原子操作
开发语言·c++·算法
Aurorar0rua1 小时前
CS50 x 2024 Notes C - 08
c语言·开发语言·学习方法
froginwe111 小时前
SQL GROUP BY 详解
开发语言
A charmer1 小时前
第一章:基础语法破冰|从 C++ 无缝切换 OC 语法
c++·objective-c