代码之家  ›  专栏  ›  技术社区  ›  Dark Sorrow

C++Regex未返回预期模式

c++
  •  0
  • Dark Sorrow  · 技术社区  · 3 年前

    我有一个通过以太网接收的字符串,我想将该字符串转换为std::float_t数据。
    我正在使用 std::regex_replace 去掉所有非数字、非十进制、无符号(加号、减号)并替换为空字符串。
    当我执行 std::regex_replace ,它只返回数字。
    密码

    std::string::size_type  sz;
    const std::regex        e("[(\D)|(^+-.)]");   // matches not digit, decimal point (.), plus sign, minus sign
    std::string             numeric_string = std::regex_replace(temporary_recieve_data, e, "");
    std::float_t            value = std::stof(numeric_string, &sz);
    
    0 回复  |  直到 3 年前
        1
  •  2
  •   rici    3 年前

    与许多语言一样,C++字符串文字中的反斜杠是由C++编译器解释的,因此当程序实际运行时,它们已经被C++语言定义的任何替换或编译器可能接受的任何其他转义序列所替换。

    C++语言只定义了少数转义序列,并且 \D 不是其中之一。但这并不奇怪,因为 D 仅作为给定的模式的一部分才有意义 std::regex 构造函数,这是一个在运行时执行的函数。因此,该构造函数可以看到一个regex特殊 \ -序列中,反斜杠本身必须存在于传递给构造函数的字符串中。

    也许这会让这一点更加清晰:

    #include <iostream>
    #include <string>
    int main() {
      // Here is a string with a \D escape:
      std::string escaped_D("\D");
      std::cout << "escaped_D produces the string '"
                << escaped_D
                << "'\n";
    
      // Now, we'll do that again the right way
      std::string escaped_escape_and_D("\\D");
      std::cout << "escaped_escape_and_D produces the string '"
                << escaped_escape_and_D
                << "'\n";
    }
    

    如果我们编译并运行它,我们将看到:

    escaped_D produces the string 'D'
    escaped_escape_and_D produces the string '\D'
    

    概括一下,字符串文字 "\D" 生成一个只包含一个大写字符的字符串 D .字符串文字 "\\D" 生成一个包含两个字符的字符串,首先是反斜杠,然后是大写 D 。有关regex库,请参阅 D ,您需要第二个版本。

    (Gcc也很善意地警告你 D 不是C++字符串文字中的有效转义序列。当我实际编译该程序时,编译器报告:

    <stdin>: In function ‘int main()’:
    <stdin>:5:25: warning: unknown escape sequence: '\D'
    

    知道这一点也很有用。GCC将任何未知的转义序列替换为反斜杠后面的字符,以便 D 变成 D ,正如我们刚刚看到的。但这是海湾合作委员会特有的。另一个编译器可能会做其他事情,甚至拒绝编译程序。 \\ 一个有效的转义序列,C++标准要求编译器为其替换一个反斜杠。因此,上面程序中的第二个字符串很好,不会产生警告。

    好的,让我们转到正则表达式。正则表达式是一个“括号表达式”,通常称为字符类,它是一组字符。如果字符类在集合中,则它将与一个字符完全匹配。正则表达式的精确语义各不相同,但您使用的特定风格(带有GCC扩展的C++默认regex语法)确实允许使用反斜杠转义序列,该序列表示要包含在字符类中的字符集。但在字符类中,大多数正则表达式运算符不被视为特殊字符。圆括号和竖线只代表它们自己。除了反斜杠之外,唯一特殊的字符是破折号( - )其用于表示一系列字符。(不过,只在字符类内部。在字符类之外,破折号只代表它自己。)

    因此,以下是作为C++文字字符串的正则表达式:

    "[(\D)|(^+-.)]"
    

    正如我们所看到的,其意图实际上是:

    "[(\\D)|(^+-.)]"
    

    那么,这对正则表达式库意味着什么呢?简单:它意味着一个字符类,其成员为:

    • 角色 (
    • 由数字以外的所有字符组成的字符集( D )
    • 角色 )
    • 角色 |
    • 角色 ( (再说一遍,但没关系。这是一组字符,你可以写两次。)
    • 角色 ^ (如果是在角色类的开头,这会很特别。但事实并非如此。)
    • 字符范围 +-. 由Ascii代码介于之间的所有字符组成 + . 包含全部费用 + 为0x2B,并且 . 为0x2E;该范围还包括介于两者之间的代码,它们是 , (0x2C)和 - (0x2D)。所以,巧合的是 几乎 意思是你认为你写了什么,除了它还包括一个逗号。
    • 角色 ) ,再次重复。

    当然,由于字符集包括所有非数字-- D --其他那些角色都是多余的。它们都是非数字,所以您可以将整个正则表达式替换为 D 。但这不是你想要的;除了数字或 + , - . 。所以要得到它,你需要这样的正则表达式:

    "[^\\d.+-]"  // This is really the regex [^\d.+-]
    

    那个使用 ^ 作为字符类中的第一个符号,这意味着整个字符类被反转。所以列表是你想让模式不匹配的字符集,这就是我使用的原因 \d (一组数字)。我还注意将减号放在字符类的末尾,这样它就不会看起来像一系列字符。我也可以把它放在一开始,就在 ^ 或者我可以用一个 \\ ,但通常最容易把它放在最后。我也可以把数字写成一个范围,这可能更容易理解:

    "[^0-9.+-]"   // This means exactly the same as the previous one.
    

    现在,我们可以尝试使用它:

    #include <iostream>
    #include <regex>
    #include <string>
    int main(int argc, char* argv[]) {
      const std::regex nondigits("[^\\d.+-]");
      std::string text("PI, to five decimals, is 3.14159");
      std::string number = std::regex_replace(text, nondigits, "");
      std::cout << text << " -> " << number << '\n';
      return 0;
    }
    

    它编译时没有任何警告,并产生预期的输出:

    PI, to five decimals, is 3.14159 -> 3.14159
    
        2
  •  1
  •   shy45    3 年前
    1. 在方括号[]内,不能将“(”“)”用作匹配组,也不能将“|”用作OR运算。如果你使用,它被解释为一个简单的字符。
    2. 在方括号[]内,“-”被解释为范围运算符(a-z)。如果你想把它仅仅作为一个字符使用,你应该把“-”放在括号的第一位。
    3. 您可以使用原始字符串R“(…)”轻松处理转义字符。

    因此,你可以这样写,而且效果很好。

    const std::regex        e(R"([^-+.\d])");