You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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新版方案

问题原因与最优方案说明

  • 前两次尝试失败的核心原因:
    1. 每次匹配到单个标记后,调用全局regex_replace会替换字符串中所有匹配项,而非仅替换当前匹配到的单个标记,导致不同标记被错误替换为相同值
    2. 循环中反复修改原字符串,会导致已经替换完成的内容被重新扫描匹配,引发重复替换、死循环等问题
  • 最优实现思路:
    用std::sregex_iterator一次性遍历原字符串中所有不重叠的匹配项,按顺序拼接「上一个匹配到当前匹配之间的未匹配文本」+「当前匹配项经lookup处理后的替换值」,最后拼接末尾剩余的未匹配文本。全程不修改原字符串,从根源避免重复匹配问题。
    上述sigma新版实现就是符合该思路的可用方案,无论lookup逻辑多复杂(比如大小写转换、查表返回和捕获组内容完全无关的值)都能正常工作。

内容的提问来源于stack exchange,提问作者ycomp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 08:06:19