C++ 正则表达式基础
简介
正则表达式描述了一种字符串匹配的模式。一般使用正则表达式主要是实现下面三个需求:
- 匹配:检查一个串是否包含某种形式的子串;
- 搜索:从某个串中取出符合特定形式的子串。
- 替换:将匹配的子串替换;
常见用法
C++ 提供了 regex 相关组件,用户只需 #include <regex>
。常用到的函数包括:regex_match、regex_replace、regex_search
。这些函数主要涉及以下参数:
- target sequence: 要查找的字符序列,可以是一对迭代器,也可以是
c-style
字符串或者std::string
; - regular expression:要匹配的正则表达式,默认是
ECMAScript syntax
; - match result:用来保存匹配结果的相关细节,常用的有
smatch, ssub_match
; - replacement string: 用来替换指定格式的字符串。
关于写好一个正则表达式,可以参考 表达式全集,没必要死记!!!
匹配
regex_match 是全文匹配,用来判断整个 target sequence 是否和正则表达式相匹配。它有两种常用的接口:
- bool regex_match(target_sequece, regular_expression);
- bool regex_match(target_sequece, match_result, regular_expression)。
如果不关心匹配结果的具体信息,可以使用第一种。以下是一个具体示例:
string str = "http://www.baidu.com";
string reg = "([a-zA-z]+)://([^\\s]+)";
regex pattern(reg);
smatch result;
bool isMatch = regex_match(str, result, pattern);
if (isMatch) {
for (int i = 0; i < result.size(); i++) {
cout << result[i] << endl;
// cout << result.str(i) << endl;
}
} else {
cout << "match fail" << endl;
}
注:smatch[0] 存储的是匹配的字符串,然后 1 - N,分别是匹配到的各个分组,分组是正则表达式中用括号 "()"
括起来的内容。
搜索
regex_search 是搜索匹配,用来判断 target sequence 中是否包含给定 pattern 的子串。常用的有两种:
- bool regex_search(target_sequece, regular_expression);
- bool regex_search(target_sequece, match_result, regular_expression)。
对于 regex_search 来说,它会在匹配到首个符合条件的子串就截止。因此,match_result 中仅包含首个子串的匹配信息。具体实例如下:
string str = "http://www.baidu.com, http://blog.csdn.net";
string reg = "([a-zA-z]+)://([^\\s]+)";
regex pattern(reg);
smatch result;
bool found = regex_search(str, result, pattern);
if (found) {
cout << "result size = " << result.size() << endl;
for (int i = 0; i < result.size(); i++) {
cout << "result[" << i << "] = " << result.str(i) << endl;
}
} else {
cout << "regex_search(str, result, pattern) not found" << endl;
}
如果想搜索所有符合 pattern 的子串,需要结合字符串迭代器,每次搜索到结果后,将迭代器的起始值改为搜索到的结果的尾部,然后循环往复即可。
string str = "http://www.baidu.com, http://blog.csdn.net";
string reg = "([a-zA-z]+)://([^\\s]+)";
regex pattern(reg);
smatch result;
string::const_iterator iterStart = str.begin();
string::const_iterator iterEnd = str.end();
while (regex_search(iterStart, iterEnd, result, pattern)) {
cout << "result size = " << result.size() << endl;
for (int i = 0; i < result.size(); i++) {
cout << "result[" << i << "] = " << result.str(i) << endl;
}
// end iterator of the current match string
iterStart = result[0].second;
}
【注】:smatch[i].first 和 smatch[i].second 分别指向所匹配子串的首尾。
替换
regex_replace 是替换匹配。用来将 target sequence 中的匹配到的子串s1进行替换为s2。常用的有以下两种:
- string regex_place(target_sequence, regular_expression, replace_string);
- regex_place(output, target_sequence, regular_expression, replace_string)。
以下是一个示例:
string trimSpace(const string &str) {
return regex_replace(str, regex("(^\\s*)|(\\s*$)"), "");
}
进阶
regex 迭代器
在上文中,我们使用 regex_search
搜索所有的匹配的子串时,需要循环地调用 regex_search
并手动更新 iterStart
。
其实,C++ 11 标准库支持了 sregex_iterator
,它可以帮助我们获得所有匹配,从而简化代码编写。下面给出一个具体地示例:
string str = "http://www.baidu.com, http://blog.csdn.net";
string reg = "([a-zA-z]+)://([^\\s]+)";
regex pattern(reg);
for (sregex_iterator it(str.begin(), str.end(), pattern), end_it;
it != end_it; ++it) {
cout << "result size = " << it->size() << endl;
for (int i = 0; i < it->size(); i++) {
cout << "result[" << i << "] = " << it->str(i) << endl;
}
}
当我们将一个 sregex_iterator
绑定到一个 string
和一个 regex
对象时,迭代器自动定位到给定 string
中第一个匹配位置。即,sregex_iterator
构造函数对给定 string
和 regex
调用 regex_search
。当我们解用迭代器时,会得到一个对应最近一次搜索结果地 smatch
对象。当我们递增迭代器时,它调用 regex_search
在输入 string
中查找下一个匹配。
match result
常用于存储匹配信息的数据结构有 smatch, cmatch
。它们均是 match_results
的实例。以下是相关代码:
typedef match_results<const char*> cmatch;
typedef match_results<string::const_iterator> smatch;
match_results
是一个 container-like
的类,用于存储所有匹配的结果,具体以下特点:
- 其中每一个匹配都是一个
sub_match
对象, 而且是常量属性; - 第一个 sub_match 元素对应整个匹配,接下来的是子匹配,即用 “()” 括起来的部分;
- 支持类似顺序容器的操作,支持下标访问,具体参考cplusplus match_results;
sub_match
继承自pair
,sub_match.first
和sub_match.second
指向所匹配的子串的首和尾。
异常
用户编写的正则表达式存在错误,在编译期不会被检测出异常,只有在运行时标准库会抛出一个异常类型为 regex_error 的异常。
try {
regex r("[[:alnum:]+\\.(cpp|cxx|cc)$");
} catch (const std::regex_error& e) {
std::cout << e.what() << "\ncode:" << e.code() << '\n';
}
输出结果如下:
Unexpected character in bracket expression.
code:4
match_flag_type
默认采用 regex::ECMAScript 标志,其他常用的标志如下:
标志 | 含义 |
---|---|
icase | 在匹配过程中忽略大小写 |
nosubs | 不保存匹配的子表达式 |
optimize | 执行速度优先于构造速度 |
合理的使用这些标志,可以简化 regex pattern 的写法,或者提升匹配的速度。
原生字符
在正则表达式中,有些字符是有特殊字符。因此如果想要匹配这些字符,那么就必须使用反斜杠进行转义。比如 $
, 就需要表示为 \\$
, 第一层是 C++ 语言本身的转义,第二层是正则表达式的转义。
自 C++11 开始支持原生字符,这样一来就可以避免正则表达式中的 \\
, 仅需保留正则表达式的转义。原生字符串字面值的语法是 R"(...)"
,其中 ...
是字符串的内容。以下是一个具体的示例:
std::string s("there is a subsequence in the string\n");
std::regex e(R"(\b(sub)([^ ]*))"); // matches words beginning by "sub"
// std::regex e("\\b(sub)([^ ]*)");
std::cout << std::regex_replace(s, e, "sub-$2");
tips
- 避免在循环内使用正则表达式,应该在循环外创建它。构造一个 regex 对象以及向一个已存在的 regex 赋予一个新的正则表达式是非常耗时的。
参考
[1]. regex reference
[2]. 史上最全!C++ 与正则表达式,一次看个够
[3]. C++ 正则表达式速查手册
[4]. 表达式全集
[5]. Regex C++