C++使用std::regex替换字符串中同一正则多个匹配项的实现方案
问题描述
我是std::regex初学者,尝试编写代码替换DEFAULTS_SS_MARKER_REGEX定义的特定模式。
- 单次替换可正常实现,但尝试替换该模式的多个实例时遇到问题
- 源字符串可能包含的场景:
- 仅单个模式实例
- 多个匹配完全相同文本的模式实例
- 多个匹配不同文本的模式实例
- 上述多匹配场景的任意组合
- 当前卡点:替换过程中需要忽略此前已匹配、已完成替换的文本,避免重复匹配
- 目标:找到实现字符串中这类“标记”多实例替换的最优方案
初始编码尝试
正则定义与可匹配样例:
using std::regex_match, std::regex_replace, std::regex; const char * DEFAULTS_SS_MARKER_REGEX = R"(\$\{([\w ._-]+):([\w ._-]+):([\w ._-]+)\})"; #pragma region Sample Matches /* ${Spreadsheet:Sheet:key} ${Hello1:b2:c3} ${Hello1.scss:b2:c3} ${Hello1_-scss:b2:c3} abc${Hello1:b2:c3}def ${a:b:c} */ #pragma endregion Sample Matches
第一次尝试(运行卡住):
std::string r = "${abc:def:ghi} and ${123:456:789} and ${abc:def:ghi}"; // 待替换测试字符串 bool checkForMoreMatches = true; while (checkForMoreMatches) { std::cmatch m; auto x = regex_search(r.c_str(), m, regex(DEFAULTS_SS_MARKER_REGEX, regex::icase), std::regex_constants::match_default); if (m.size() == 4) { auto spreadsheet = m[1].str(); auto sheet = m[2].str(); auto key = m[3].str(); std::string zz = lookup(spreadsheet, sheet, key); r = regex_replace(r, regex(DEFAULTS_SS_MARKER_REGEX, regex::icase), zz); } else checkForMoreMatches = m.size(); }
第二次尝试(运行卡住):
std::string r = "${abc:def:ghi} and ${123:456:789} and ${abc:def:ghi}"; // 待替换测试字符串 std::smatch m; while (std::regex_search(r, m, regex(DEFAULTS_SS_MARKER_REGEX, regex::icase))) { if (m.size() == 4) { auto spreadsheet = m[1].str(); auto sheet = m[2].str(); auto key = m[3].str(); std::string zz = lookup(spreadsheet, sheet, key); r = regex_replace(r, regex(DEFAULTS_SS_MARKER_REGEX, regex::icase), zz); } }
所有尝试共用的lookup函数:
std::string lookup(std::string spreadsheet, std::string sheet, std::string key) { // 占位逻辑,实际业务中为从电子表格查询对应值 return "ss=" + spreadsheet + ",sheet=" + sheet + ",key=" + key; }
附记
用户sigma曾提供一版非常简洁的实现,基于上述简单lookup函数可正常运行,但在实际业务场景下无法工作。由于示例lookup函数逻辑过于简单,无法验证sigma后续给出的新版方案是否有效,这里提供一个可以打破sigma初版代码逻辑的lookup测试函数,同时附上包含sigma初版、新版两种方案的完整测试代码。
#include <regex> #include <iostream> #include <algorithm> #include <cctype> #pragma region Lookup functions // sigma初版方案使用的测试lookup函数 std::string lookup(std::string spreadsheet, std::string sheet, std::string key) { // 占位逻辑,实际业务中为从电子表格查询对应值 return "ss=" + spreadsheet + ",sheet=" + sheet + ",key=" + key; } std::string upper(const std::string& str) { std::string upper; transform(str.begin(), str.end(), std::back_inserter(upper), toupper); return upper; } // 可打破sigma初版代码逻辑的测试lookup函数 std::string lookupUpper(std::string spreadsheet, std::string sheet, std::string key) { // 占位逻辑,实际业务中为从电子表格查询对应值 return "ss=" + upper(spreadsheet) + ",sheet=" + upper(sheet) + ",key=" + upper(key); } #pragma endregion Lookup functions #pragma region Sigma初版方案 // sigma初版方案,无法适配复杂lookup逻辑 void testDefaultsSpreadsheetFieldMarkersReplaceSimpleLookup() { std::regex markers{ R"(\$\{([\w ._-]+):([\w ._-]+):([\w ._-]+)\})", std::regex::icase }; const std::string s = "${abc:def:ghi} and ${123:456:789} and ${abc:def:ghi}"; std::string q = s; std::string r = s; std::cout << "SIGMA ORIGINAL ANSWER with Original Lookup" << std::endl; std::cout << std::regex_replace(q, markers, lookup("$1", "$2", "$3")) << std::endl; std::cout << "SIGMA ORIGINAL ANSWER with More Complex Lookup (breaks Sigma code)" << std::endl; std::cout << std::regex_replace(r, markers, lookupUpper("$1", "$2", "$3")) << std::endl; } #pragma endregion Sigma初版方案 #pragma region Sigma新版方案 std::string replace(std::string const& s, std::regex const& re, const bool originalLookup) { std::sregex_iterator rbegin{ s.begin(), s.end(), re }; std::sregex_iterator rend{}; if (rbegin == rend) return s; std::string out; for (auto i = rbegin; i != rend; ++i) { auto match = *i; out += match.prefix(); if (match.size() == 4) { auto spreadsheet = match[1].str(); auto sheet = match[2].str(); auto key = match[3].str(); out += originalLookup ? lookup(spreadsheet, sheet, key) : lookupUpper(spreadsheet, sheet, key); } if (std::next(i) == rend) out += match.suffix(); } return out; } void testDefaultsSpreadsheetFieldMarkersReplaceWhatWeNeed() { std::regex markers{ R"(\$\{([\w ._-]+):([\w ._-]+):([\w ._-]+)\})", std::regex::icase }; const std::string s = "${abc:def:ghi} and ${123:456:789} and ${abc:def:ghi}"; std::string q = s; std::string r = s; std::cout << "SIGMA NEW ANSWER with Original Lookup" << std::endl; std::cout << replace(q, markers, true) << std::endl; std::cout << "SIGMA NEW ANSWER with More Complex Lookup" << std::endl; std::cout << replace(r, markers, false) << std::endl; } #pragma endregion Sigma新版方案
问题原因与最优方案说明
- 前两次尝试失败的核心原因:
- 每次匹配到单个标记后,调用全局
regex_replace会替换字符串中所有匹配项,而非仅替换当前匹配到的单个标记,导致不同标记被错误替换为相同值 - 循环中反复修改原字符串,会导致已经替换完成的内容被重新扫描匹配,引发重复替换、死循环等问题
- 每次匹配到单个标记后,调用全局
- 最优实现思路:
用std::sregex_iterator一次性遍历原字符串中所有不重叠的匹配项,按顺序拼接「上一个匹配到当前匹配之间的未匹配文本」+「当前匹配项经lookup处理后的替换值」,最后拼接末尾剩余的未匹配文本。全程不修改原字符串,从根源避免重复匹配问题。
上述sigma新版实现就是符合该思路的可用方案,无论lookup逻辑多复杂(比如大小写转换、查表返回和捕获组内容完全无关的值)都能正常工作。
内容的提问来源于stack exchange,提问作者ycomp
相关产品推荐
相关产品推荐

