C++11新特性之正则表达式


前八篇在这里:
C++11新特性之新类型与初始化: http://blog.guoyb.com/2016/06/18/cpp11-1
C++11新特性之类型推断与类型获取: http://blog.guoyb.com/2016/06/25/cpp11-2
C++11新特性之lambda: http://blog.guoyb.com/2016/06/30/cpp11-3
C++11新特性之容器相关特性: http://blog.guoyb.com/2016/07/09/cpp11-4
C++11新特性之智能指针: http://blog.guoyb.com/2016/08/02/cpp11-5
C++11新特性之Class: http://blog.guoyb.com/2016/08/14/cpp11-6
C++11新特性之右值引用与移动: http://blog.guoyb.com/2016/08/20/cpp11-7
C++11新特性之template: http://blog.guoyb.com/2016/08/31/cpp11-8


这是C++11新特性介绍的第九部分,涉及到正则表达式相关的新特性。
不想看toy code的读者可以直接拉到文章最后看这部分的总结。

题外话

对regex的完整支持,直到g++4.9才算完善。我使用的系统是Ubuntu14.04,默认g++版本号是4.8.x,所以有一些regex功能无法编译通过。可以通过以下方法安装g++4.9:

1
2
3
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt-get update
sudo apt=get install gcc-4.9 g++-4.9 c++-4.9

安装完成之后,需要在Makefile中强制指定所使用的g++版本。

1
CXX=g++-4.9

简单用法

一切就绪,先看看如何用C++11中的regex匹配一个电子邮箱地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
std::cout<<"test regex simple usage:\n";
std::string email_pattern("(\\w)+(\\.\\w+)*@(\\w)+((\\.\\w+)+)");
try
{
std::regex email_regex(email_pattern);
std::smatch results;
std::string test_email_str = "My email is yubo1911@163.com";
if(std::regex_search(test_email_str, results, email_regex))
{
std::cout<<results.str()<<std::endl;
}
}
catch (std::regex_error e)
{
std::cout<<e.what()<<'\t'<<e.code()<<std::endl;
}
std::cout<<"test regex simple usage done.\n"<<std::endl;

C++中的regex默认使用ECMA-262正则表达式规范,这也是众多浏览器所使用的标准。
注意到email_pattern中有好多双斜线,这是因为除了regex模块要做一次转义解析外,C++ string也会对字符串做一次转义解析。

regex选项

在构造regex对象时,可以指定多种标识已实现特定的效果。这里以使用regex::icase达到不区分大小写的匹配为例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
std::cout<<"test regex icase:\n";
try
{
std::regex cpp_regex("(\\w)+\\.(cpp|hpp)$", std::regex::icase);
std::vector<std::string> test_filenames = {"regex.cpp", "iostream.h", "template.CPP", "class.hPP", "Ah, regex.cpp", "regex.cpp Ah"};
for(auto fn : test_filenames)
{
if(std::regex_match(fn, cpp_regex))
{
std::cout<<"cpp file: "<<fn<<'\n';
}
}
}
catch (std::regex_error e)
{
std::cout<<e.what()<<'\t'<<e.code()<<std::endl;
}
std::cout<<"test regex icase done.\n"<<std::endl;

regex iterator

regex提供了一个迭代器,这个迭代器生成时需要一个所搜寻字符串的范围以及一个regex对象。之后,迭代器在迭代时,会遍历搜寻字符串中的所有匹配位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::cout<<"test regex iterator usage:\n";
try
{
std::regex email_regex(email_pattern);
std::string test_email_str = "I have three emails, yubo1911@163.com, yubo@gmail.com and guoyubo@gmail.com.";
for(std::sregex_iterator it(std::begin(test_email_str), std::end(test_email_str), email_regex), end_it; it != end_it; it++)
{
std::cout<<it->str()<<std::endl;
}
}
catch (std::regex_error e)
{
std::cout<<e.what()<<'\t'<<e.code()<<std::endl;
}
std::cout<<"test regex iterator usage done.\n"<<std::endl;

子表达式

regex也支持子表达式,和其他正则表达式一样,使用括号括起来的构成一个子表达式。在匹配结果中,序号0表示整个匹配结果,序号1表示子表达式1的匹配结果……

下面以一个座机电话号码的匹配验证为例,说明子表达式的运用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
std::cout<<"test regex sub_match usage:\n";
std::string phone_pattern = "(\\()?(\\d{3,4})(\\))?([- ])?(\\d{7,8})";
try
{
std::regex phone_regex(phone_pattern);
std::smatch results;
std::vector<std::string> test_phones = {"010-82224567", "(010-83332345", "(020)62334567", "(021) 22346543", "0357-4223456", "0358-465788"};
for(auto fn : test_phones)
{
if(std::regex_match(fn, results, phone_regex))
{
if(results[1].matched)
{
if(!results[3].matched) continue;
if(results[4].matched && results[4].str() == "-") continue;
}
else
{
if(results[3].matched) continue;
if(!(results[4].matched && results[4].str() == "-")) continue;
}
std::cout<<results.str()<<std::endl;
}

}
}
catch (std::regex_error e)
{
std::cout<<e.what()<<'\t'<<e.code()<<std::endl;
}
std::cout<<"test regex sub_match usage done.\n"<<std::endl;

replace

regex同样提供了替换功能。将替换功能和子表达式结合起来,可以实现字符串的格式化功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
std::cout<<"test regex replace usage:\n";
try
{
std::string format = "$2-$5";
std::regex phone_regex(phone_pattern);
std::string ori_phone = "yubo: (020)85776452";
std::cout<<"formated phone: "<<std::regex_replace(ori_phone, phone_regex, format) <<std::endl;
}
catch (std::regex_error e)
{
std::cout<<e.what()<<'\t'<<e.code()<<std::endl;
}
std::cout<<"test regex replace usage done.\n"<<std::endl;

其中format字符串中$2和$5就分别表示第二个子表达式和第5个子表达式。

输出

整个测试程序的输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
test regex simple usage:
yubo1911@163.com
test regex simple usage done.

test regex icase:
cpp file: regex.cpp
cpp file: template.CPP
cpp file: class.hPP
test regex icase done.

test regex iterator usage:
yubo1911@163.com
yubo@gmail.com
guoyubo@gmail.com
test regex iterator usage done.

test regex sub_match usage:
010-82224567
(020)62334567
(021) 22346543
0357-4223456
test regex sub_match usage done.

test regex replace usage:
formated phone: yubo: 020-85776452
test regex replace usage done.

总结

  1. C++11种提供了regex模块,需要g++-4.9以上才能完整支持。
  2. regex默认采用ECMA-262标准,和浏览器中使用的一样。
  3. regex提供了查找、匹配、迭代器、子表达式、替换等常用用法。

完整代码详见regex.cpp

转载请注明出处: http://blog.guoyb.com/2016/09/10/cpp11-9/

欢迎使用微信扫描下方二维码,关注我的微信公众号TechTalking,技术·生活·思考:
后端技术小黑屋

评论