不能直接用find+erase循环,因erase导致索引失效且会跳过重叠匹配;递归需每次从头扫描,值传递实现简洁但有拷贝开销,引用+索引版本更高效但需防栈溢出,务必校验空子串等边界。

递归删除子串时,为什么不能直接用 std::string::find + erase 循环?
因为每次 erase 后,后续字符前移,原位置索引失效;若只依赖单次 find 结果继续搜索,会跳过紧邻重叠的匹配(比如删 "aa" 时,"aaa" 变成 "a" 后漏删)。递归的关键是「删完一次后,从头重新扫描」,而非在原索引上累加。
最简递归实现:每次找到就删,然后递归处理新字符串
适合小规模、非性能敏感场景。核心逻辑清晰:有匹配就删,删完立即递归;没匹配就返回当前串。
std::string removeSubstringRecursive(std::string s, const std::string& sub) {
size_t pos = s.find(sub);
if (pos == std::string::npos) {
return s;
}
s.erase(pos, sub.length());
return removeSubstringRecursive(s, sub); // 递归调用,传新字符串
}
- 注意:参数
s是值传递,每次递归都拷贝一份,避免副作用 - 不修改原字符串,函数纯且无状态,适合嵌入表达式
- 极端情况(如
sub为空)会导致无限递归 —— 调用前必须校验sub.empty()
避免重复拷贝:用引用 + 迭代器范围递归(C++17+)
若字符串很大,或需复用已有缓冲区,可改用索引范围递归,避免逐层拷贝。本质是把「待处理区间」作为递归状态。
void removeSubstringInPlace(std::string& s, const std::string& sub, size_t start = 0) {
if (sub.empty()) return;
size_t pos = s.find(sub, start);
if (pos == std::string::npos) return;
s.erase(pos, sub.length());
// 删除后,从同一位置重新检查(可能产生新重叠,如 "abab" 删 "ab" → "ab")
removeSubstringInPlace(s, sub, pos);
}
- 关键点:
removeSubstringInPlace(s, sub, pos)中传pos而非pos + 1,否则漏掉重叠匹配 - 该版本修改原字符串,调用方需自行保证
sub非空 - 对长字符串更友好,但递归深度受子串出现频次影响,极端 case 可能栈溢出
实际使用时最容易被忽略的边界
真正上线前,这几个点常被跳过,但一出问题就难定位:
立即学习“C++免费学习笔记(深入)”;
-
sub为"":find("")永远返回0,导致死循环或栈溢出 —— 必须前置判断 - 子串含 null 字符(
'\0'):std::string支持 embed null,但find行为正常;若混用 C 风格字符串操作会出错 - 编码与 locale:默认
find是字节级匹配,不处理 UTF-8 多字节字符 —— 若需 Unicode 子串删除,得先转std::u8string或用 ICU 库 - 性能陷阱:递归本身无优势,只是逻辑直白;高频调用建议改用迭代 +
while+find,避免函数调用开销和栈风险


















