c++oding="utf-8" ?>
“字符串减法”指安全删除子串,非原生语法,需用find+erase循环实现,避免越界、漏删和结构破坏;对ansi序列宜用状态机扫描,而非正则。

什么是“字符串减法”?其实就是删除子串
在 C++ 里没有原生的 str - pattern 这种语法,所谓“字符串减法”本质是反复查找并移除指定子串(比如 HTML 标签、ANSI 转义序列、Markdown 样式符等)。关键不是“减”,而是“安全地删”——不能删错位置、不能漏删嵌套、不能破坏剩余内容的结构。
常见错误现象:std::string::erase() 配合 find() 单次调用只删第一个匹配;循环中未更新查找起始位置导致无限循环或越界;用 replace() 替换为空串但没处理重叠匹配。
- 使用场景:清洗日志中的 ANSI 颜色码(如
"\033[32mOK\033[0m"→"OK")、剥离 Markdown 的***_格式、去掉 XML/HTML 片段中的标签(非全解析,仅简单剔除) - 推荐做法:用
while循环 +find()定位,每次找到后用erase()删除,并将下一次查找起点设为当前起始位置(不是pos + len,避免跳过紧邻匹配) - 注意:若要删的是正则模式(如所有
\033[...m),必须用<regex></regex>,但性能开销大,且 C++11/14 的std::regex在某些编译器(如 libstdc++)中 bug 较多,建议优先用手工扫描
删 ANSI 转义序列:用字节扫描比正则更稳
ANSI 控制序列形如 "\033[1;34m" 或 "\x1B[2J",以 ESC('\033' 或 '\x1B')开头,中间是可变长度参数,以 'm'、'J'、'K' 等结尾。正则 R"(\033\[[^a-zA-Z]*[a-zA-Z])" 看似简洁,实则容易误杀含 [ 的正常文本,且无法处理中间的 BEL、BS 等控制字符。
实操建议:
- 遍历
std::string每个字节,遇到'\033'就启动状态机:检查后续是否为'[',再跳过数字、分号,直到遇到字母终止符 - 用
std::string::erase(start, length)批量删除整段,不要逐字符删(性能差且易出错) - 示例片段(只处理常见 SGR 序列):
size_t i = 0; while (i
删 Markdown 样式:注意成对性和边界
删 * 或 _ 时,不能无脑删所有——"*bold* and _italic_" 可删,但 "price: $19.99" 中的 $ 不是样式符,"a*b*c" 中若只删中间 * 会错乱。真正要删的是“用于格式化的成对符号”,但 C++ 没有内置 Markdown 解析器,所以得加约束条件。
实操建议:
- 只删满足「前后都是空白或标点/边界」的单个
*或_,或成对出现且中间无嵌套的**/__(即s.find("**", pos)成功后,检查前后字符是否非字母数字) - 避免用
replace(),它不改变字符串长度逻辑,容易让后续find()偏移错乱;坚持用erase()+ 更新pos - 性能影响:多次
erase()会触发内存重分配,若预估删除量大(>10%),可先收集所有待删区间,最后一次性拼接剩余片段
为什么不用 std::regex?兼容性与陷阱太多
写 std::regex re(R"(\*\*(.*?)\*\*)"); 看起来省事,但实际踩坑频繁:std::regex_replace 在 GCC 7–9 中对空匹配行为异常;std::sregex_iterator 遍历时若被替换内容含正则元字符,可能二次解析;最麻烦的是,C++ 标准未规定 regex 引擎必须支持回溯控制,遇到长文本 + 复杂模式极易栈溢出或卡死。
除非你明确控制输入长度、已禁用用户可控输入、且使用 Clang + libc++(regex 实现较稳),否则不建议在生产逻辑中依赖 <regex></regex> 做样式清洗。手工扫描虽然代码多几行,但行为完全可控、无隐式分配、调试直观。
真正复杂的需求(如保留部分标签、处理 HTML 实体、支持嵌套)应该交给专用库(如 libxml2、litesax 或 boost::spirit),而不是硬塞进字符串减法里。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











