字符串与范围小工具¶
| 标准 | 难度 | 状态 | 分级 | 证据 | 审阅 | 最后校验 |
|---|---|---|---|---|---|---|
| C++20 | 2/5 | final | A | 3 条(manual 3) | 单审(zyuhao7) | 2026-10-24 |
编译器支持:GCC 10+ · Clang 11+ · MSVC 19.27+。版本查询:cppreference 编译器支持。
主张与证据¶
| 主张 | 支持证据 |
|---|---|
| 同时使用 starts_with 与 ends_with 处理通配符 | ev-mold-string-prefix-suffix-glob-002 |
| 用 starts_with 判断路径是否绝对 | ev-mold-string-starts-with-path-001 |
| 在 string_view 上检查 Itanium 名称前缀 | ev-mold-string-view-starts-with-demangle-003 |
本站证据页:ev-mold-string-prefix-suffix-glob-002 · ev-mold-string-starts-with-path-001 · ev-mold-string-view-starts-with-demangle-003
解决什么问题¶
C++20 之前,检查字符串前缀要写 s.compare(0, prefix.size(), prefix) == 0 或 s.find(prefix) == 0。前者参数顺序容易写错,后者在空串与越界边界上不直观;检查后缀更绕。删除满足条件的元素则要手写 container.erase(std::remove_if(...), container.end()),对 std::string 和关联容器还要分别处理。
C++20 补了一组小而常用的设施:starts_with / ends_with、std::erase / std::erase_if、std::ssize。它们不是新容器,也不改变数据结构,只是把高频且易错的写法标准化。contains 要分清对象:关联容器的 contains 是 C++20;std::string 与 std::string_view 的 contains 到 C++23 才进入标准。
基础用法¶
#include <iostream>
#include <string>
#include <vector>
int main() {
std::string url = "https://example.com/index.html";
std::cout << url.starts_with("https://") << '\n';
std::cout << url.ends_with(".html") << '\n';
std::vector<int> values{1, 2, 3, 4, 5, 6};
std::erase_if(values, [](int v) { return v % 2 == 0; });
std::string text = "banana";
std::erase(text, 'a');
std::cout << text << ' ' << std::ssize(values) << '\n';
}
完整示例见 examples/cpp20/string_range_utilities.cpp:
进阶用法¶
前缀/后缀检查有三个重载。 starts_with(char)、starts_with(string_view)、starts_with(const char*)。传 const char* 时按空终止字符串处理;传 string_view 时长度由视图决定,可以检查包含 '\0' 的片段。空前缀/后缀恒为真,这与 find(...) == 0 的结果一致,但表达更直接。
ends_with 不是反向的 find。 s.rfind(suffix) == s.size() - suffix.size() 需要处理 suffix 比 s 长的下溢;ends_with 直接处理该边界。
std::erase 与 std::erase_if 是自由函数。 std::erase(container, value) 删除所有等于 value 的元素;std::erase_if(container, pred) 删除谓词为真的元素。std::erase 覆盖序列容器(如 std::vector、std::deque)与 std::string;std::erase_if 另外覆盖关联容器与无序关联容器。对序列容器保持剩余元素相对顺序,复杂度线性;对关联容器按节点删除。
不要和成员 erase 混淆。 container.erase(it) 仍按迭代器删除单个元素;std::erase(container, value) 是按值删除全部。成员 erase 返回迭代器,自由函数返回删除数量(C++20 起)。
std::ssize 返回有符号大小。 它把 size() 转成 std::ptrdiff_t,避免 size() - 1 在空容器上回绕成巨大无符号值。对数组、容器和范围都可用;std::size 仍返回无符号值。循环里用 for (auto i = 0; i < std::ssize(v); ++i) 可以安全地做递减。
contains 的标准版本要分清。 关联容器的 contains 是 C++20;std::string 与 std::string_view 的 contains 都是 C++23。写 C++20 代码时,字符串包含检查仍需 s.find(part) != npos。std::map::contains 与字符串无关,只是同名。
异构查找是另一项能力。 当比较器提供 is_transparent(例如 std::less<>)时,std::map<std::string, V, std::less<>> 的 find、contains 等接受 std::string_view,无需构造 std::string;普通 std::less<std::string> 没有这个重载。不要把异构查找与字符串的 contains 混为一谈。
真实项目证据¶
| 项目 | 位置 | 用法 | 为什么典型 |
|---|---|---|---|
| rui314/mold | lib/common.h#L957-L960 |
检查路径是否以 / 开头 |
把前缀判断写成单个谓词,替代 compare 或 find 的位置运算 |
| rui314/mold | lib/multi-glob.cc#L80-L93 |
同时用 starts_with 与 ends_with 处理通配符 |
前后缀检查成对出现,边界条件由标准库承担 |
| rui314/mold | lib/demangle.cc#L13-L27 |
在 std::string_view 上检查 Itanium 前缀 |
视图上直接判断,不构造 std::string |
三条证据均为 manual 级:作者复核,尚未引入第二名审核者。mold 的构建配置声明 cxx_std_20,这些调用发生在生产路径。
陷阱与边界¶
starts_with("")恒为真。 空前缀不是错误;如果业务要求非空前缀,在调用方校验。const char*重载依赖空终止。 传入的不是以'\0'结尾的缓冲区会越界读取;二进制数据用string_view。std::erase删除全部匹配项。 只想删第一个时用find+ 成员erase。- 谓词不要修改容器。
erase_if遍历期间修改容器会导致未定义行为;需要联动删除时先收集再处理。 std::ssize不改变底层类型。 它只是返回有符号值;与无符号size()混用仍可能触发隐式转换警告。contains的标准版本不同。 C++20 的std::string与std::string_view都没有contains;跨标准写作时先确认目标标准。string_view版本不保证空终止。 用starts_with后若把视图交给 C API,仍需自行补'\0'。erase_if对std::map的谓词接收value_type。 想按 key 删除时,谓词里访问.first,不要误以为参数是 key。
库联动¶
- mold:链接器在参数解析与 glob 匹配的热路径上直接使用这些谓词,避免临时字符串与手写索引运算。
- fmt / spdlog:格式化库内部有各自的
string_view兼容层;把用户输入转成std::string_view后再做前缀判断,可以同时适配两套实现。代价是视图生命周期必须覆盖调用。 - nlohmann/json:JSON Pointer 有自己的
contains语义,和容器/字符串的contains不同名同义;混用时以类型为准,不要按名字推断行为。 - 与 ranges 的分工:
erase_if处理容器原地删除;views::filter只做惰性视图,不修改底层数据。需要保留原容器时用视图,需要真正移除时用erase_if。
横向对比¶
| 需求 | C++20 写法 | 旧写法 | 主要差异 |
|---|---|---|---|
| 前缀检查 | s.starts_with(p) |
s.compare(0, p.size(), p) == 0 |
参数顺序固定,空串与超长边界由库处理 |
| 后缀检查 | s.ends_with(p) |
s.rfind(p) == s.size() - p.size() |
避免长度下溢 |
| 按值删除 | std::erase(c, v) |
c.erase(std::remove(...), c.end()) |
一步完成,返回删除数量 |
| 按条件删除 | std::erase_if(c, pred) |
c.erase(std::remove_if(...), c.end()) |
对关联容器也有统一接口 |
| 有符号大小 | std::ssize(c) |
static_cast<std::ptrdiff_t>(c.size()) |
避免无符号回绕 |
| 子串包含 | C++23 s.contains(p) |
s.find(p) != npos |
C++20 的 string 与 string_view 均无此成员 |
跨标准演进:C++20 引入 starts_with、ends_with、std::erase、std::erase_if、std::ssize;C++23 为 std::string 与 std::string_view 补上 contains。C++26 未改变这些接口的语义。