C++ STL 之 string_view 详解

📅 2026/7/4 4:25:52 👁️ 阅读次数 📝 编程学习
C++ STL 之 string_view 详解

C++ STL 之 string_view 详解

一、问题:字符串参数传递的隐形成本

在 C++ 中传递字符串最常见的方式是const std::string&,但每次传入const char*或字符串字面量时,都会隐式构造一个临时std::string对象——这意味着堆分配和字符拷贝。对于日志、配置读取、参数解析等高频路径,这个开销不可忽视。

voidparse(conststd::string&s);// 每次传 "hello" 都会构造临时 stringparse("hello world");// 隐式构造,O(n) 分配

二、方案:string_view 的设计与原理

std::string_view(C++17)是一个非 owning 的字符串视图,内部仅保存两个成员:指向外部字符数组的指针和长度。构造和拷贝都是 O(1),不涉及任何堆分配或字符拷贝。

#include<string_view>voidparse(std::string_view sv);// 零拷贝parse("hello world");// O(1),直接指向字面量

size = 11

string_view 对象

data 指针

size 长度

外部字符数组
const char*

'H']['e']['l']['l']['o'][' ']['W']['o']['r']['l']['d']

常用接口

std::string_view sv="hello world";sv.data();// 原始指针,不一定以 '\0' 结尾sv.size();// 长度,O(1)sv.empty();// 判空sv.front();sv.back();// 首尾字符sv[0];// 下标访问,无边界检查sv.at(0);// 下标访问,有边界检查(抛异常)sv.substr(0,5);// 返回另一个 string_view,O(1)sv.remove_prefix(6);// 去掉前 N 个字符sv.remove_suffix(5);// 去掉后 N 个字符sv.compare("hello");// 比较

C++20 新增:starts_with / ends_with

std::string_view sv="hello.cpp";if(sv.starts_with("hello")){/* 匹配 */}if(sv.ends_with(".cpp")){/* 匹配 */}

这两个函数在 C++20 之前需要手写sv.substr(0, 5) == "hello",既不直观也有性能浪费。对string_viewstd::string均可用。

sv 字面量后缀

usingnamespacestd::string_view_literals;autosv="hello world"sv;// std::string_view,而非 const char*

"..."sv字面量在编译期直接构造string_view,零运行时开销。

三、string_view vs const string& 参数选型

函数形参一律用string_view,仅当:

场景推荐原因
只读访问字符串内容string_view零拷贝,兼容所有字符串类型
需要存储字符串副本std::stringstring_view 不 owning,拷贝后悬垂
需要\\0结尾的 C 风格接口const std::string&data() 不保证以\\0结尾
接口被广泛 ABI 暴露视情况string_view 按值传 16 字节,可能比 const ref 大
// 推荐:函数只读访问字符串voidlog(std::string_view msg);voidparse(std::string_view config);voidfind(std::string_view haystack,std::string_view needle);// 不推荐:函数需要持有副本voidset_name(std::string_view name){name_=std::string(name);// 必须显式转换}

经验法则:面向新代码时,函数形参中所有const std::string&都应当改为std::string_view,除非函数需要把字符串存入成员变量或需要\\0结尾。

四、悬垂指针风险

string_view不拥有字符数据,若底层字符数组被销毁,访问string_view就是悬垂指针。

std::string_view sv;{std::string s="temporary";sv=s;// sv 指向 s 的内部缓冲区}// s 销毁,sv 悬垂std::cout<<sv;// 未定义行为,可能崩溃或乱码

data 指向

data 悬垂指针

string_view sv

string s 的内部缓冲区

't']['e']['m']['p']['o']['r']['a']['r']['y']

s 析构后

缓冲区已释放

安全使用原则

// 正确:函数内临时使用,不跨语句存储voidsafe(std::string_view sv){autopos=sv.find('.');// 安全std::cout<<sv.substr(0,pos);// 安全}// 正确:传入静态数据staticconstexprautokConfig="version=1.0"sv;process(kConfig);// 安全// 错误:函数返回 string_view 指向局部 stringstd::string_viewbad(){std::string s="local";returns;// 悬垂!}// 正确:函数返回 string_view 指向静态数据std::string_viewgood(){return"static literal"sv;// 安全}

五、面试题(6 道)

1. string_view 的拷贝是深拷贝还是浅拷贝?

浅拷贝。仅复制指针和长度两个成员,O(1)。不复制字符数据。

2. string_view 的 substr 返回什么?是否分配内存?

返回一个新的string_view,指向原视图的子区间。O(1),无分配。

3. 以下代码的输出是什么?

std::string_view sv="hello";sv.remove_prefix(2);sv.remove_suffix(2);std::cout<<sv;

输出lremove_prefix(2)去掉 “he”(span 变为 “llo”),remove_suffix(2)去掉 “lo”(span 变为 “l”)。

4. 为什么 string_view 不能作为 unordered_map 的 key?

string_view不 owning,无法保证 key 的生命周期。若底层字符串被销毁,map 中的 key 悬垂。需要用std::string作为 key。

5. const char* 转 string_view 是否安全?

安全,只要const char*指向的字符串在string_view使用期间有效。但要注意string_view不要求以\0结尾,sv.data()可能不是合法的 C 字符串。

6. string_view 能否替代 const std::string& 作为返回值?

不能。函数返回string_view时必须保证返回的字符串数据在函数返回后仍然存活——这极大限制使用场景。作为函数参数接收外部数据才是指定用法。

六、总结

特性string_viewconst string&
C++ 版本C++17C++98
构造复杂度O(1)O(n)(从const char*
堆分配从不可能
生命周期跟踪不 owningowning
data() 以\0结尾不保证保证
substr 开销O(1)O(n)

string_view是现代 C++ 中字符串参数传递的首选类型。函数形参一律用string_view,返回值不用string_view返回局部字符串,记住这两条就能用好它。