字符串减法指从原字符串中移除减数字符串所含字符(集合语义),保持剩余字符顺序,常用std::remove_if配合std::unordered_set实现O(n+m)高效过滤。

什么是字符串减法?C++里没有内置操作符
“字符串减法”不是C++标准定义的操作,它通常指从一个字符串中移除另一个字符串中出现的所有字符(或子串),同时保持剩余字符的原始顺序。比如 "hello" 减去 "el" 得到 "hlo" —— 注意不是删除子串,而是逐字符过滤:只要某个字符在减数中存在,就删掉所有该字符。
关键点在于:不是 std::string::erase 某个子串位置,也不是用 std::remove_if 配合模糊条件;而是明确按「字符集合」做剔除,且不打乱原顺序。
用 std::remove_if + std::unordered_set 最高效
最常用也最推荐的做法是把减数字符串转成查找快的容器(如 std::unordered_set<char></char>),再用 std::remove_if 遍历被减字符串。这是 O(n+m) 时间复杂度,避免了每次查字符都遍历减数字符串的 O(n×m) 开销。
std::string s = "programming";
std::string sub = "gr"; // 要减去的字符集合
std::unordered_set<char> to_remove(sub.begin(), sub.end());
s.erase(std::remove_if(s.begin(), s.end(),
[&to_remove](char c) { return to_remove.find(c) != to_remove.end(); }),
s.end());
// s 变为 "poammn"
- 必须先构造
std::unordered_set,不能直接在 lambda 里调sub.find(c)——std::string::find是 O(m) 线性查找,整体退化成 O(n×m) -
std::remove_if不真正删除,只重排;必须配合erase才生效 - 如果减数含重复字符(如
"aa"),unordered_set自动去重,符合语义:删一次和删十次效果一样
如果要按子串减法(非字符级),得用 std::string::find 循环
有些场景实际想要的是“删掉所有出现的子串”,比如 "abababc" 减去 "ab" → "c"。这属于子串替换/擦除,不是字符过滤,逻辑完全不同。
立即学习“C++免费学习笔记(深入)”;
这时不能用 remove_if,而要用循环 find + erase,注意从左往右删时索引会偏移:
std::string s = "abababc";
std::string pattern = "ab";
size_t pos = 0;
while ((pos = s.find(pattern, pos)) != std::string::npos) {
s.erase(pos, pattern.length());
// 不加 pos += pattern.length(),因为删完后新内容已前移
}
// s 变为 "c"
- 漏掉
pos = 0初始化会导致第一次find从末尾开始 - 删完不更新
pos是对的:erase后后续字符已左移,下一次find从当前pos继续找即可 - 若 pattern 为空,
find会永远返回 0,必须提前检查
容易忽略的细节:大小写、空格、Unicode
上述所有方法默认区分大小写,且把空格、制表符、不可见字符都当普通字符处理。如果你的“减法”需要忽略大小写,得统一转换:
std::transform(sub.begin(), sub.end(), sub.begin(), ::tolower); 再塞进 unordered_set,同时被减字符串也要同样转换后再删 —— 但注意:这样改的是副本,原始顺序仍由原字符串决定,别混淆。
至于 UTF-8 字符串(如中文),std::string 的 char 级操作会按字节拆分,导致乱码。真要支持 Unicode,得用 std::u8string(C++20)或第三方库(如 ICU),不能直接套用上面的 char 集合方案。
字符集语义一旦变复杂,就不再是“减法”这个说法能概括的事了。


















