在C++中,正则表达式是处理文本模式匹配和字符串操作的强大工具。C++11及以后的标准库提供了<regex>
头文件,支持正则表达式的使用。下面是C++正则表达式的核心语法规则和用法:
一、基本正则表达式语法
1. 普通字符
直接匹配自身,例如:a
匹配字符 a
。
2. 元字符(需转义)
具有特殊含义的字符,需用反斜杠 \
转义(在C++字符串中需用双反斜杠 \\
)。
.
:匹配除换行符外的任意字符。^
:匹配字符串的开头。$
:匹配字符串的结尾。*
:匹配前面的元素0次或多次。+
:匹配前面的元素1次或多次。?
:匹配前面的元素0次或1次(可选)。{n}
:精确匹配n次。{n,}
:至少匹配n次。{n,m}
:匹配次数在n到m之间(包含n和m)。
3. 字符类
[ ]
:匹配方括号内的任意一个字符。- 例如:
[abc]
匹配a
、b
或c
;[0-9]
等价于\d
。
- 例如:
[^ ]
:否定字符类,匹配不在方括号内的任意字符。- 例如:
[^0-9]
匹配非数字字符。
- 例如:
4. 预定义字符类
\d
:匹配数字(等价于[0-9]
)。\D
:匹配非数字(等价于[^0-9]
)。\s
:匹配空白字符(空格、制表符、换行符等)。\S
:匹配非空白字符。\w
:匹配单词字符(字母、数字、下划线,等价于[a-zA-Z0-9_]
)。\W
:匹配非单词字符。
5. 位置锚点
^
:匹配字符串的开头。$
:匹配字符串的结尾。\b
:匹配单词边界(如空格、标点或字符串首尾)。\B
:匹配非单词边界。
6. 分组与捕获
( )
:定义捕获组,可以提取匹配的子串。- 例如:
(\\d{4})-(\\d{2})-(\\d{2})
可捕获日期的年、月、日。
- 例如:
(?: )
:非捕获组,只用于分组,不捕获匹配结果。
二、C++中的正则表达式类
1. std::regex
用于表示一个正则表达式对象。
#include <regex>
std::regex pattern("\\d+"); // 匹配一个或多个数字
2. 匹配函数
std::regex_match
:判断整个字符串是否匹配正则表达式。std::string str = "123"; bool match = std::regex_match(str, pattern); // true
std::regex_search
:在字符串中搜索第一个匹配的子串。std::string str = "abc123def"; bool found = std::regex_search(str, pattern); // true
std::regex_replace
:替换匹配的子串。std::string result = std::regex_replace(str, pattern, "X"); // "abcXdef"
3. 迭代器
std::sregex_iterator
:遍历所有匹配的子串。std::string str = "a1b2c3"; std::regex pattern("\\d"); for (std::sregex_iterator it(str.begin(), str.end(), pattern); it != std::sregex_iterator(); ++it) { std::cout << it->str() << " "; // 输出: 1 2 3 }
三、正则表达式标志
通过 std::regex
的第二个参数指定匹配模式:
std::regex::icase
:忽略大小写。std::regex pattern("hello", std::regex::icase); // 匹配"Hello"、"HELLO"等
std::regex::ECMAScript
:使用ECMAScript语法(默认)。std::regex::grep
:使用POSIX grep语法。
四、捕获组的使用
用圆括号 ()
定义捕获组,可以提取匹配的子串。
std::string str = "2023-06-26";
std::regex pattern("(\\d{4})-(\\d{2})-(\\d{2})");
std::smatch match;
if (std::regex_search(str, match, pattern)) {
std::cout << "Year: " << match[1] << std::endl; // 2023
std::cout << "Month: " << match[2] << std::endl; // 06
std::cout << "Day: " << match[3] << std::endl; // 26
}
五、原始字符串字面量(Raw String Literals)
C++11引入的原始字符串语法 R"(...)"
避免了对反斜杠的双重转义,使正则表达式更易读。
// 普通字符串(需双重转义)
std::regex pattern1("\\d{3}-\\d{4}");
// 原始字符串(无需转义)
std::regex pattern2(R"(\d{3}-\d{4})"); // 等价于上面的pattern1
六、示例:验证有效数字的正则表达式
针对LeetCode 65题,可以使用正则表达式验证有效数字:
#include <regex>
#include <string>
class Solution {
public:
bool isNumber(std::string s) {
// 使用原始字符串字面量定义正则表达式
std::regex pattern(
R"(^[+-]?(\d+\.?|\.\d+)(\d+)?([eE][+-]?\d+)?$)"
);
return std::regex_match(s, pattern);
}
};
七、性能注意事项
- 正则表达式在处理复杂模式时可能效率较低,尤其是包含回溯或嵌套量词的情况。
- 对于简单的字符串匹配,手动实现状态机可能比正则表达式更高效。
八、常见错误
- 忘记转义:在C++字符串中使用元字符时需双反斜杠(如
\\d
)。 - 贪婪匹配:
*
和+
默认是贪婪的,可能匹配尽可能多的字符。使用*?
或+?
进行非贪婪匹配。 - 性能陷阱:避免使用过度复杂的正则表达式,如
.*a.*a.*
在某些情况下会导致灾难性回溯。
通过掌握这些规则,您可以在C++中灵活使用正则表达式处理各种文本匹配问题。
** 静态常量模式的编译优化
- 静态常量模式的编译优化
您的正则表达式版本使用了静态常量成员变量 static const regex pattern:
class Solution{
// ...
static const regex pattern;
// ...
};
const std::regex Solution::pattern(...);
优势:正则表达式只在程序启动时编译一次,后续调用直接使用预编译的模式,避免了重复编译开销。
对比:如果您的 DFA 版本在每次调用时都重新初始化状态转移表(如通过构造函数),会产生额外的内存分配和初始化开销。
2. 正则表达式引擎的优化
现代 C++ 标准库的正则表达式引擎(如 GCC 的 libstdc++ 或 Clang 的 libc++)可能针对简单模式进行了高度优化:
编译时优化:对于固定的正则表达式模式,引擎可能在编译阶段进行部分求值或生成更高效的匹配代码。
DFA/NFA 混合实现:某些正则表达式引擎在处理简单模式时会自动转换为 DFA,从而获得接近手动 DFA 的性能。