Regex(正则表达式)
Regex的作用
- 匹配:将整个输入匹配某个正则表达式
- 查找:在字符串中进行查找
- 切分:根据正则表达式对字符串进行切分
- 替换:将与正则表达式吻合的子序列进行替换操作
Regex的匹配和查找
通过使用regex来定义一个正则表达式,如下:
regex reg1("<.*>.*</.*>");
复制代码
在这个正则表达式中,“.”表示除了"\n"以外的任何字符,“*”表示0次或者多次。因而上述正则表达式可以用来匹配html或xml格式的字符串。但上述正则表达式在匹配html或xml时,前后的标记可以不相同。为了达到前后标记相同的目的,需要使用组的概念。(...)用来定义捕获组,之后就可以借由正则表达式\1来再次指代它, 如下所示:
regex reg1("<(.*)>.*</\\1>");
复制代码
由于正则表达式也是个正常的字符序列,所以在使用“\1”时需要进行转义。当然在C++11之后,也可以通过使用raw string(原生字符串)的方式来取消字符串的转义,raw string以“R"(”开头,以“)"”结束。上述正则表达式可以写作:
regex reg2(R"(<(.*)>.*</\1>)");
复制代码
使用regex_match()来匹配字符串(完全匹配),用regex_search()来对字符串进行搜索(部分匹配),即
regex_search(data, regex(pattern));
复制代码
等价于
regex_match(data, regex("(.|\n)*" + pattern + "(.|\n)*"));
复制代码
其中(.|\n)*指任何数量和任何字符。
regex_match()和regex_search()的简单示例如下:
#include <regex>
#include <iostream>
using namespace std;
void out(bool b)
{
cout << (b ? "found" : "not found") << endl;
}
int main()
{
regex reg1("<.*>.*</.*>");
bool found = regex_match("<tag>value</gat>", reg1);
out(found);
regex reg2("<(.*)>.*</\\1>");
found = regex_match("<tag>value</tag>", reg2);
out(found);
found = regex_match("<tag>value</tag>", regex(R"(<(.*)>.*</\1>)"));
out(found);
cout << endl;
found = regex_match("XML tag: <tag>value</tag>",
regex("<(.*)>.*</\\1>"));
out(found);
found = regex_match("XML tag: <tag>value</tag>",
regex(".*<(.*)>.*</\\1>.*"));
out(found);
found = regex_search("XML tag: <tag>value</tag>",
regex("<(.*)>.*</\\1>"));
out(found);
found = regex_search("XML tag: <tag>value</tag>",
regex(".*<(.*)>.*</\\1>.*"));
out(found);
system("pause");
}
复制代码
正则表达式匹配结果处理
regex_match()和regex_search()可以通过使用match_results对象来获得正则表达式匹配的细节。std::match_results是个模板类,必须依据其所处理的字符的迭代器类型而实例化。C++标准库提供了以下预定义的实例化实现:
- smatch:针对string而设计的
- cmatch:针对C-string(const char*)而设计的
- wsmatch:针对wstring而设计的
- wcmatch:针对wide C-string(const wchar_t*)而设计的
因而对于C++中的string类型必须使用smatch类型;而对于寻常的字符串常量则必须选用cmatch类型。
一般而言,match_result对象包含以下接口:
- 一个sub_match对象m[0],表示匹配到的所有字符
- prefix()接口,表示第一个匹配到的对象前的所有字符
- suffix()接口,表示最后一个匹配到的对象后的所有字符
- 对于任何捕获组,可以通过m[n]下标方式来进行访问
- size()接口可以取得sub_match对象的个数(包括m[0])
- str()接口可以以string形式取得字符
- length()接口可以取得字符数量
sub_match对象派生自pair<>,其first成员是第一个字符的位置,second成员是最末字符的下一位置。
str()、length()、position()接口既可以不带参数获取match_result对象整体的值,也可以带参数来获取第n个匹配到的子串的信息。此外,match_result还可以通过begin()、cbegin()、end()、cend()来进行迭代访问。
不管是在regex_match()还是在regex_search()使用match_results对象,其所提供的接口是一样的,唯一的区别是regex_match()中prefix和suffix总是为空。
match_results使用示例如下:
void regex_match_result()
{
string data = "XML tag: <tag-name>the value</tag-name>.";
cout << "data: " << data << "\n\n";
smatch m;
bool found = regex_search(data, m, regex("<(.*)>(.*)</(\\1)>"));
cout << "m.empty(): " << boolalpha << m.empty() << endl;
cout << "m.size(): " << m.size() << endl;
if (found)
{
cout << "m.str(): " << m.str() << endl;
cout << "m.length() " << m.length() << endl;
cout << "m.position() " << m.position() << endl;
cout << "m.prefix().str(): " << m.prefix().str() << endl;
cout << "m.suffix().str(): " << m.suffix().str() << endl;
cout << endl;
for (int i = 0; i < m.size(); ++i)
{
cout << "m[" << i << "].str(): " << m[i].str() << endl;
cout << "m.str(" << i << "): " << m.str(i) << endl;
cout << "m.position(" << i << "): " << m.position(i) << endl;
}
cout << endl;
cout << "matches:" << endl;
for (auto pos = m.begin(); pos != m.end(); ++pos)
{
cout << " " << *pos << " ";
cout << "(length: " << pos->length() << ")" << endl;
}
}
}
复制代码
拥有了正则表达式所有匹配结果的一切信息后,可以通过给regex_search()传入字符串区间来搜索整个字符串中所有匹配的子串,示例如下:
void regex_search_test()
{
string data = "<person>\n"
" <first>Nico</first>\n"
" <last>Josuttis</last>\n"
"</person>\n";
regex reg("<(.*)>(.*)</(\\1)>");
auto pos = data.cbegin();
auto end = data.cend();
smatch m;
for (; regex_search(pos, end, m, reg); pos = m.suffix().first) // m.suffix().first等同于m[0].second
{
cout << "match: " << m.str() << endl;
cout << "tag: " << m[1].str() << endl;
cout << "value: " << m.str(2) << endl;
}
}
复制代码
Regex Iterator
为了逐一迭代正则查找的所有匹配结果,可以使用regex iterator。模板类regex_iterator<>对于string和C-string也有着实例化的实现,分别以s、c、ws和wc开头。regex iterator的定义方式如下:
sregex_iterator pos(data.cbegin(), data.cend(), reg);
sregex_iterator end;
复制代码
第一个初始化了一个regex iterator,用来迭代data,找出符合正则规则reg的匹配。第二个为其默认构造,定义出了一个指向末尾下一位置的迭代器。regex iterator是一个双向迭代器,可以使用++、--操作进行前向和后向移动。示例如下:
void regex_iterator_test()
{
string data = "<person>\n"
" <first>Nico</first>\n"
" <last>Josuttis</last>\n"
"</person>\n";
regex reg("<(.*)>(.*)</(\\1)>");
sregex_iterator pos(data.cbegin(), data.cend(), reg);
sregex_iterator end;
for (; pos != end; ++pos)
{
cout << "match: " << pos->str() << endl;
cout << "tag: " << pos->str(1) << endl;
cout << "value: " << pos->str(2) << endl;
}
}
复制代码
Regex Token Iterator
regex_token_iterator<>是用来处理字符串中子序列之前的内容的,可以用来将string拆分为一个个词汇单元或者用以正则表达式为分隔符来分割string。regex_token_iterator<>有着针对string和C-string实现的实例化,分另带有s、c、ws和wc前缀。
在初始化regex_token_iterator<>时,除了可以传入给它字符串的起点和终点,以及一个正则表达式外,还可以指明一列数值,用来表示语汇化过程中的元素:
- -1表示对每一个匹配的正则表达式之前的子序列感兴趣
- 0表示对每一个匹配的正则表达式感兴趣
- 任何其它数字n表示对正则表达式中第n个匹配结果感兴趣
regex_token_iterator允许以下列各种方式指明所在意的token:
- 单一整数值
- 一个整数值初值列
- 一个由整数组成的vector
- 一个由整数组成的array
regex_token_iterator示例如下:
void regex_token_iterator_test()
{
string data = "<person>\n"
" <first>Nico</first>\n"
" <last>Josuttis</last>\n"
"</person>\n";
regex reg("<(.*)>(.*)</(\\1)>");
sregex_token_iterator pos(data.cbegin(), data.cend(), reg, { 0, 2 });
sregex_token_iterator end;
for (; pos != end; ++pos)
{
cout << "match: " << pos->str() << endl;
}
cout << endl;
string names = "nico, jim, helmut, paul, tim, john paul, rita";
regex sep("[ \t\n]*[,;.][ \t\n]*"); //被, ; .和空格分隔
sregex_token_iterator p(names.cbegin(), names.cend(), sep, -1);
sregex_token_iterator e;
for (; p != e; ++p)
{
cout << "name; " << p->str() << endl;
}
}
复制代码
Regex Replacement
regex_replace()用于将匹配到的正则表达式字符序列替换为另一个字符序列,它的替换规则如下:
默认的Pattern | sed Pattern | 意义 |
---|---|---|
$& | & | matched pattern |
$n | \n | 第n个捕获组 |
$‘ | 匹配到结果的前缀 | |
$’ | 匹配到结果的后缀 | |
$$ | 字符$ |
示例如下:
void regex_replace_test()
{
string data = "<person>\n"
" <first>Nico</first>\n"
" <last>Josuttis</last>\n"
"</person>\n";
regex reg("<(.*)>(.*)</(\\1)>");
cout << regex_replace(data, reg, "<$1 value=\"$2\"/>") << endl;
string reg2;
regex_replace(back_inserter(reg2),
data.begin(), data.end(),
reg,
"<$1 value=\"$2\"/>",
regex_constants::format_no_copy | regex_constants::format_first_only);
cout << reg2 << endl;
}
复制代码
Regex Flag
Regex中还有一些常量可作为regex构造函数或regex的各个函数的最后一个可有可无的参数,这些常量若被传入则会影响regex接口的行为,下表列举了Regex的常量:
regex常量 | 意义 |
---|---|
Regex Grammar | |
ECMAScript | 使用ECMAScript文法(默认) |
basic | 使用POSIX的basic regular expression文法 |
extended | 使用POSIX的extended regular expression文法 |
awk | 使用UNIX工具awk的文法 |
grep | 使用UNIX工具grep的文法 |
egrep | 使用UNIX工具egrep的文法 |
其它Creation Flag | |
icase | 忽略大小写 |
nosubs | 不将子序列存储于匹配结果中 |
optimize | 优化matching速度,然后才考虑regex的创建速度 |
collate | 形式为[a-b]的字符区间会受locale影响 |
Algorithm Flag | |
match_not_null | 不匹配空序列 |
match_not_bol | 第一个字符不匹配beginning-of-line |
match_not_eol | 最末字符不匹配end-of-line |
match_not_bow | 第一个字符不匹配beginning-of-word |
match_not_eow | 最末字符不匹配end-of-word |
match_continuous | 只方式图匹配“从第一个字符开始”的子序列 |
match_any | 如果多于一个匹配,任何匹配皆可接受 |
match_prev_avail | 第一字符的前一位置是个有效位置 |
Replacement Flag | |
format_default | 使用默认的ECMAScript替换语法 |
format_sed | 使用UNIX工具sed的替换语法 |
format_first_only | 只替换第一个匹配 |
format_no_copy | 对于未获匹配的字符,不予以复制 |
Regex ECMAScript文法
以下表格列举了ECMAScript文法中常见的Regex表达式:
表达式 | 意义 |
---|---|
. | \n以外的任何字符 |
[...] | ...字符中任何一个 |
[^...] | ...字符以外的任何一个 |
[[:charclass:]] | 指定字符类charclass中的一个 |
\n, \t, \f, \r, \v | 一个换行、tab、form feed、carriage return、vertical tab |
\xhh, \uhhh | 一个16进制字符或Unicode字符 |
\d, \D, \s, \S, \w, \W | 某字符类中的一个字符 |
* | 前一字符或群组任意次数 |
? | 前一字符或群组可有可无 |
+ | 前一字符或群组至少一次 |
{n} | 前一字符或群组n次 |
{n,} | 前一字符或群组至少n次 |
{n,m} | 前一字符或群组至少n次,至多m次 |
...|... | 在|之前或之后的pattern |
(...) | 设定分组 |
\1, \2, \3, ... | 第n个组 |
\b | 一个正字词边界(字词的起点或终点) |
\B | 一个负字词办界(字词的非起点或非终点) |
^ | 一行的起点 |
$ | 一行的终点 |
下表展示了字符类及其效果
字符类 | 缩写 | 转义 | 效果 |
---|---|---|---|
[[:alnum:]] | 一个字母或数字,相当于[[:alpha:][:digit:]] | ||
[[:alpha:]] | 一个字母 | ||
[[:blank:]] | 一个空格或tab | ||
[[:cntrl:]] | 一个控制符 | ||
[[:digit:]] | [[:d:]] | \d | 一个数字 |
\D | 一个非数字,相当于[[1]] | ||
[[:graph:]] | 一个可打印非空白字符,相当于[[:alnum:][:punct:]] | ||
[[:lower:]] | 一个小写字母 | ||
[[:print:]] | 一个可打印字符,包括空白符 | ||
[[:punct:]] | 一个标点号字符 | ||
[[:space:]] | [[:s:]] | \s | 一个空白符 |
\S | 一个非空白字符 | ||
[[:upper:]] | 一个大写字母 | ||
[[:xdigit:]] | 一个十六进制数 | ||
[[:w:]] | \w | 一个字母、数字或下划线 | |
\W | 不是字母、数字或下划线 |
示例:
[_[:alpha:]][_[:alnum:]]* //C++标识符
(.|\n)* //任意个任何字符
[123]?[0-9]\.1?[0-9]\.20[0-9]{2} //2000之后的一个日期复制代码
:digit: ↩︎