C++ STL 之 string_view 详解 C STL 之 string_view 详解一、问题字符串参数传递的隐形成本在 C 中传递字符串最常见的方式是const std::string但每次传入const char*或字符串字面量时都会隐式构造一个临时std::string对象——这意味着堆分配和字符拷贝。对于日志、配置读取、参数解析等高频路径这个开销不可忽视。voidparse(conststd::strings);// 每次传 hello 都会构造临时 stringparse(hello world);// 隐式构造O(n) 分配二、方案string_view 的设计与原理std::string_viewC17是一个非 owning 的字符串视图内部仅保存两个成员指向外部字符数组的指针和长度。构造和拷贝都是 O(1)不涉及任何堆分配或字符拷贝。#includestring_viewvoidparse(std::string_view sv);// 零拷贝parse(hello world);// O(1)直接指向字面量size 11string_view 对象data 指针size 长度外部字符数组const char*H][e][l][l][o][ ][W][o][r][l][d]常用接口std::string_view svhello world;sv.data();// 原始指针不一定以 \0 结尾sv.size();// 长度O(1)sv.empty();// 判空sv.front();sv.back();// 首尾字符sv[0];// 下标访问无边界检查sv.at(0);// 下标访问有边界检查抛异常sv.substr(0,5);// 返回另一个 string_viewO(1)sv.remove_prefix(6);// 去掉前 N 个字符sv.remove_suffix(5);// 去掉后 N 个字符sv.compare(hello);// 比较C20 新增starts_with / ends_withstd::string_view svhello.cpp;if(sv.starts_with(hello)){/* 匹配 */}if(sv.ends_with(.cpp)){/* 匹配 */}这两个函数在 C20 之前需要手写sv.substr(0, 5) hello既不直观也有性能浪费。对string_view和std::string均可用。sv 字面量后缀usingnamespacestd::string_view_literals;autosvhello worldsv;// 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::stringdata() 不保证以\\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 stemporary;svs;// sv 指向 s 的内部缓冲区}// s 销毁sv 悬垂std::coutsv;// 未定义行为可能崩溃或乱码data 指向data 悬垂指针string_view svstring s 的内部缓冲区t][e][m][p][o][r][a][r][y]s 析构后缓冲区已释放安全使用原则// 正确函数内临时使用不跨语句存储voidsafe(std::string_view sv){autopossv.find(.);// 安全std::coutsv.substr(0,pos);// 安全}// 正确传入静态数据staticconstexprautokConfigversion1.0sv;process(kConfig);// 安全// 错误函数返回 string_view 指向局部 stringstd::string_viewbad(){std::string slocal;returns;// 悬垂}// 正确函数返回 string_view 指向静态数据std::string_viewgood(){returnstatic literalsv;// 安全}五、面试题6 道1. string_view 的拷贝是深拷贝还是浅拷贝浅拷贝。仅复制指针和长度两个成员O(1)。不复制字符数据。2. string_view 的 substr 返回什么是否分配内存返回一个新的string_view指向原视图的子区间。O(1)无分配。3. 以下代码的输出是什么std::string_view svhello;sv.remove_prefix(2);sv.remove_suffix(2);std::coutsv;输出l。remove_prefix(2)去掉 “he”span 变为 “llo”remove_suffix(2)去掉 “lo”span 变为 “l”。4. 为什么 string_view 不能作为 unordered_map 的 keystring_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 stringC 版本C17C98构造复杂度O(1)O(n)从const char*堆分配从不可能生命周期跟踪不 owningowningdata() 以\0结尾不保证保证substr 开销O(1)O(n)string_view是现代 C 中字符串参数传递的首选类型。函数形参一律用string_view返回值不用string_view返回局部字符串记住这两条就能用好它。