std::regex,用于匹配字符串的开头和结尾

19

在JS正则表达式中,符号^$分别表示字符串的开头和结尾。只有使用/m修饰符(多行模式)时,它们才匹配每行的开头和结尾 - 即CR/LF之前和之后的位置。

但是,在std::regex/ECMAscript模式下,符号^$总是匹配每行的开头和结尾

在std::regex中是否有任何方法来定义字符串的开头和结尾的匹配点?换句话说,是否支持JavaScript的多行模式...


1
重点是 ^$ 匹配字符串的开头和结尾。请参见 https://ideone.com/amatBf 和 https://ideone.com/0D7eS7。 - Wiktor Stribiżew
@WiktorStribiżew 好的,如何修改您的示例来匹配行的开头/结尾的 ^$ - c-smile
1
我已经提到了:对于行尾,它是 (?=\n|$),对于行首,它只能是像 (^|\n) 这样的消耗模式。我知道这非常不舒服。如果你真的需要 ^ / $ 的多行行为,转换到 Boost 正则表达式可能是最好的选择。 - Wiktor Stribiżew
4个回答

7

TL;DR

  • MSVC: ^$ 已经匹配了行的开头和结尾
  • C++17: 使用 std::regex_constants::multiline 选项
  • 其他编译器只能使用 ^ 匹配字符串开头,$ 匹配字符串结尾,并且无法重新定义它们的行为。

在除了MSVC和C++17之外的所有std::regex实现中,^$匹配字符串的开头和结尾,而不是一行。参见this demo,它使用^\d+$正则表达式无法在"1\n2\n3"中找到任何匹配项。当您添加替代方案时(请参见下文),有3个匹配项
然而,在MSVC和C++17中,^$可能与的开头和结尾匹配。 C++17 使用std::regex_constants::multiline选项。 MSVC编译器 在Visual Studio的C++项目中,可以使用以下内容:
std::regex r("^\\d+$");
std::string st("1\n2\n3");
for (std::sregex_iterator i = std::sregex_iterator(st.begin(), st.end(), r);
    i != std::sregex_iterator();
    ++i)
{
    std::smatch m = *i;
    std::cout << "Match value: " << m.str() << " at Position " << m.position() << '\n';
}

将输出

Match value: 1 at Position 0
Match value: 2 at Position 2
Match value: 3 at Position 4

适用于所有C++编译器的解决方案

std::regex中没有一种通用选项,可以使锚点在所有编译器中匹配行的开头/结尾。您需要使用替代方法来模拟它:

^ -> (^|\n)
$ -> (?=\n|$)

请注意,$ 可以通过 (?=\n|$)(您可以添加更多行终止符号或符号序列,例如 (?=\r?\n|\r|$))来完全“模拟”,但是使用 ^,您无法找到100%的解决方法。
由于没有支持反向查找,您可能需要调整正则表达式模式的其他部分,因为 (^|\n),例如更经常地使用捕获组。

我觉得我的措辞有点混乱,但我的意思是你最初的假设是错误的。符号“^”只匹配字符串的开头,“$”只匹配字符串的结尾。 - Wiktor Stribiżew
2
断言 ^(行首)匹配紧随 LineTerminator 字符之后的位置... http://zh.cppreference.com/w/cpp/regex/ecmascript - c-smile
1
@c-smile:我知道你的意思,但我的答案是基于实际经验的。 - Wiktor Stribiżew

6
默认情况下,ECMAscript模式已经将^视为输入开头和行首,并将$视为输入结尾和行尾。无法使它们仅匹配输入开头或结尾,但可以使它们仅匹配行首或行尾:
在调用std::regex_matchstd::regex_searchstd::regex_replace时,有一个类型为std::regex_constants::match_flag_type的参数,默认值为std::regex_constants::match_default
  • 要指定^只匹配行首,需要指定std::regex_constants::match_not_bol
  • 要指定$只匹配行尾,需要指定std::regex_constants::match_not_eol
  • 由于这些值是位标志,要同时指定两个,请使用按位或运算符(std::regex_constants::match_not_bol | std::regex_constants::match_not_eol
  • 请注意,无论是否使用std::regex_constants::match_not_bol,都可以通过指定std::regex_constants::match_continuous来暗示输入的开头。

这在ECMAScript语法文档上很好地解释了,在cppreference.com上,我通常比cplusplus.com更加推荐。

警告:我已经测试了MSVC、Clang + libc++和Clang + libstdc++,目前只有MSVC具有正确的行为。


2
从您的链接cppreference.com断言^(行的开始)匹配 1)紧随LineTerminator字符之后的位置(如果支持,请参见LWG问题2343) 2)输入的开头(除非启用std::regex_constants::match_not_bol(仅限C ++))这与所需的非常不同。我需要^只匹配“输入的开头”,而不是其他任何内容。 - c-smile
@c-smile:非常正确,我完全误读了。回答已更新。 - ildjarn
我的心理解析器无法解析: “要指定$仅匹配行尾,请指定std :: regex_constants :: match_not_eol”对我来说,match_not_eol应该意味着完全相反的事情:如果设置了该标志,则不应匹配EOL,只能匹配输入的结尾,对吧?这确实有意义。按照您的解释,该标志是无用的。 - c-smile
@c-smile:它的意思是“不要将first视为BOL或将last视为EOL”,这不是你想要的。我提供文档链接是有原因的。;-] - ildjarn
不清楚这里的“first”和“last”是什么意思。无论如何,问题是:要使用哪些标志来匹配^仅匹配输入的开头(与$和输入的结尾相同)?在boost中,有明确的\A\z标记,可以显式地匹配输入的头部/尾部:http://www.boost.org/doc/libs/1_31_0/libs/regex/doc/syntax.html 看起来std失去了这个功能。 - c-smile
@c-smile:firstlast是传递给正则表达式算法(搜索、匹配、替换)的迭代器范围。我认为std::regex不支持您想要的ECMAScript语法,但POSIX语法可能有您想要的内容。我对这些并不是全面了解,但cppreference.com上有它们的语法链接。 - ildjarn

1
以下代码片段匹配以[a-z]开头的电子邮件地址,后跟0或1个点,然后是0或多个a-z字母,最后以"@gmail.com"结尾。我进行了测试。
string reg = "^[a-z]+\\.*[a-z]*@gmail\\.com$";

regex reg1(reg, regex_constants::icase);
reg1(regex_str, regex_constants::icase);
string email;
cin>>email;
if (regex_search(email, reg1))

0

您可以使用JavaScript正则表达式^(?<!(.|\n)])来模拟Perl/Python/PCRE的\A,它在字符串开头匹配但不匹配换行符之后的字符。这个正则表达式的意思是“匹配没有前导字符的行的开头”。

您可以使用(?!(.|\n))$来模拟Perl/Python/PCRE的\z,它仅匹配字符串结尾。为了实现\Z的效果,即仅在字符串结尾处允许单个换行符,只需添加一个可选的换行符:\n?(?!(.|\n))$


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接