正则表达式(regular expression)是一种描述字符序列的方法,是一种及其强大的计算工具。C++正则表达式库(RE库)定义在头文件regex中,包含多个组件:
regex | 表示有一个正则表达式的类,即regex类表示一个正则表达式 |
regex_match | 将一个字符序列与一个正则表达式匹配 |
regex_search | 寻求第一个与正则表达式匹配的子序列 |
regex_replace | 使用给定格式替换一个正则表达式 |
sregex_iterator | 迭代器适配器,调用regex_search来遍历一个string中所有匹配的子串 |
smatch | 容器类,保存在string中搜索的结果 |
ssub_match | string中匹配的子表达式的结果 |
注意:这些操作返回bool值,指出是否找到匹配 | |
(seq, m, r, mft)
(seq, r, mft ) | 在字符序列seq中查找regex对象r中的正则表达式。seq可以是一个string、表示范围的一对迭代器以及一个指向空字符结尾的字符数组的指针
m是一个match对象,用来保存匹配结果的细节。m和seq必须具有兼容的类型
mft是一个可选的regex_constants::match_flag_type值。 |
使用正则表达式库
查找违反拼写规则“i除非在c之后,否则必须在e之前”的单词示例:
//查找不在字符c之后的字符串ei
string pattern("[^c]ei");
pattern = "[[:alpha:]]" + pattern + "[[:alpha:]]*";
regex r(pattern); //构造一个用于查找模式的regex
smatch results;
string test_str = "receipt freind theif receive";
if( regex_search(text_str, results, r))
cout << results.str() << endl;
我们首先定义了一个string来保存希望查找的正则表达式。正则表达式[^c]表明我们希望匹配任一不是'c'的字符,而[^c]ei指出我们想要匹配这种字符后接ie的字符串。此模式描述的字符串恰好包含三个字符。我们想要包含此模式的单词的完整内容。为了与整个单词匹配,我们还需要一个正则表达式与这个三字母模式之前和之后的字母匹配。
这个正则表达式包含0个或多个字母后接我们的三字母的模式,然后再接0个或多个额外的字母。默认情况下,regex使用的正则表达式语言是ECMAScript。在ECMAScript中,模式[[:alpha:]]匹配任一字母,符号+和*分别表示我们希望“1个或多个”或“0个或多个”匹配。因此[[::alpha:]]*将匹配0个或多个字母。
指定regex对象的选项
当我们定义一个regex或是对一个regex调用assign为其赋予新值时,可以指定一些标志来影响regex如何操作。这些标志控制regex对象的处理过程。对于指出编写正则表达式所用语言的6个标志,我们必须设置其中一个,且只能设置一个。默认情况下,ECMAScript标志被设置,从而regex会使用ECMA-262规范,这也是很多Web浏览器所用的正则表达式语言。
regex r(re) regex r(re, f) | re表示一个正则表达式,它可以是一个string、一个表示字符范围的迭代对、一个指向空字符结尾的字符数组的指针、一个字符指针和一个计数器或是一个花括号包围的字符列表。 f是指出对象如何处理的标志。f通过下面列出的值来设置。如果未指定f,默认值为ECMAScript。 |
r1 = re | 将r1中的正则表达式替换成re。 re表示一个正则表达式,它可以是另一个regex对象、一个string、一个指向空字符结尾的字符数组的指针或是一个花括号包围的字符列表。 |
r1.assign(re, f) | 与使用赋值运算符(=)效果相同 |
r.mark_count() | r中子表达式的数目 |
r.flags() | 返回r的标志集 |
注:构造函数和赋值操作也可能抛出类型为regex_error的异常。 |
定义在regex和regex_constants::synatax_option_type中 | |
icase | 在匹配过程中忽略大小写 |
nosubs | 不保存匹配的子表达式 |
optimize | 执行速度优先于构造速度 |
ECMAScript | 使用ECMA-262指定的语法 |
basic | 使用POSIX基本的正则表达式语法 |
extended | 使用POSIX扩展的正则表达式语法 |
awk | 使用POSIX版本的awk语言的语法 |
grep | 使用POSIX版本的grep的语法 |
egrep | 使用POSIX版本的egrep的语法 |
一个正则表达式来识别扩展名的示例:
//1个或多个字母或数字字符后面接一个'.'再接"cpp"或"cxx"或"cc"
regex r("([[:alnum:]+)\\.(cpp|cxx|cc)$", regex::icase);
smatch results;
string filename;
while( cin >> filename)
if( regex_search(filename, results, r))
cout << results.str() << endl;
在正则表达式语言中,字符点(.)通常匹配任意字符。与C++一样,可以在字符之前放置一个反斜线\来去掉其特殊含义。由于反斜线\也是C++中的一个特殊字符,我们在字符串常量中必须连续使用两个反斜线。
一个正则表达式的语法是否正确是在运行时解析的。如果我们编写的正则表达式存在错误,则在运行时标准库会抛出一二类型为regex_error的异常。类似标准库异常类型,regex_error有一个描述发生什么错误的what操作,和一个返回错误类型对应的数值编码的code成员。
try{
regex r("([[:alnum:]+)\\.(cpp|cxx|cc)$", regex::icase);
}catch (regex_error e)
{
cout << e.what() << "\ncode:" << e.code() << endl;
}
定义在regex和regex_constants::error_type中 | |
error_collate | 无效的元素校对请求 |
error_ctype | 无效的字符类 |
error_escape | 无效的转义字符或无效的尾置转义 |
error_backref | 无效的向后引用 |
error_brack | 不匹配的方括号( '[' 或 ']' ) |
error_paren | 不匹配的小括号( '(' 或 ')' ) |
error_brace | 不匹配的花括号( '{' 或 '}' ) |
error_badbrace | {}中无效的范围 |
error_range | 无效的字符范围(如[z-a]) |
error_space | 内存不足,无法处理此正则表达式 |
error_badrepeat | 重复字符(*、?、+或{ )之前没有有效的正则表达式 |
error_complexity | 要求的匹配过于复杂 |
error_stack | 栈空间不足,无法处理匹配 |
一个正则表达式所表示的“程序”是在运行时而非编译时编译的。正则表达式的编译是一个非常慢的操作,特别是在你使用了扩展的正则表达式语法或复杂的正则表达式时。
如果输入序列类型 | 则使用正则表达式类 |
string | regex、smatch、ssub_match和sregex_iterator |
const char* | regex、cmatch、csub_match和cregex_iterator |
wstring | wregex、wsmatch、wssub_match和wsregex_iterator |
const wchar_t * | wregex、wcmatch、wcsub_match和wcregex_iterator |
cmatch results;
if( regex_search("myfile.cc", results, r))
cout << results.str() << endl;
匹配和Regex迭代器类型
我们可以使用sregex_iterator来获取所有匹配。regex迭代器是一种迭代器适配器,被绑定到一个输入序列和一个regex对象上。
这些操作也适用于cregex_iterator、wsregex_iterator和wcregex_iterator | |
sregex_iterator it(b, e, r); | 一个sregex_iterator,遍历迭代器b和e表示的string。它调用sregex_search(b, e, r)将it定位到输入中第一个匹配的位置 |
sregex_iterator end; | sregex_iterator的尾后迭代器 |
*it it-> | 根据最后一个调用regex_search的结果,返回一个smatch对象的引用或一个指向smatch对象的指针 |
++it it++ | 从输入序列当前匹配位置开始调用regex_search。前置版本返回递增后迭代器;后置版本返回旧值 |
it1 == it2 it1 != it2 | 如果两个sregex_iterator都是尾后迭代器,则它们相等。 两个非尾后迭代器是从相同输入序列和regex对象构造,则他们相等 |
当我们将一个sregex_iterator绑定到一个string和一个regex对象时,迭代器自动定位到给定string中第一个匹配位置。
//查找不在字符c之后的字符串ei
string pattern("[^c]ei");
pattern = "[[:alpha:]]" + pattern + "[[:alpha:]]*";
regex r(pattern, regex::icase); //构造一个用于查找模式的regex
for( sregex_iterator it(file.begin(), file.end(), r), end_it;
it != end_it; ++it)
cout << it->str() << endl;
匹配类型有两个名为prefix和suffix的成员,分别返回表示输入序列中当前匹配之前和之后部分的ssub_match对象。一个ssub_match对象有两个名为str和length的成员,分别返回匹配的string和该string的大小
for( sregex_iterator it(file.begin(), file.end(), r), end_it;
it != end_it; ++it)
{
auto pos = it->prefix().length();
pos = pos > 40 ? pos - 40 : 0;
cout << it->prefix().str().substr(pos)
<< "\n\t\t" << it->str() << " <<<\n"
<< it->suffix().str().substr(0, 40)
<< endl;
}
这些操作也适用于cmatch、wsmatch、wcmatch和对于的csub_match、wssub_match、和wcsub_match | |
m.ready() | 如果已经通过调用regex_search或regex_match设置了m,则返回true。否则返回false。 如果ready返回false,则对m进行操作是未定义的 |
m.size() | 如果匹配失败,返回0;否则返回最近一次匹配的正则表达式中子表达式的数目 |
m.empty() | 若m.size()为0,则返回true |
m.prefix() | 一个ssub_match对象,表示当前匹配之前的序列 |
m.suffix() | 一个ssub_match对象,表示当前匹配之后的部分 |
m.format(...) | 后面讲 |
在接受一个索引的操作中,n的默认值为0且必须小于m.size() 第一个子匹配(索引为0)表示整个匹配 | |
m.length(n) | 第n个匹配的子表达式的大小 |
m.postiong(n) | 第n个子表达式距序列开始的距离 |
m.str(n) | 第n个子表达式匹配的string |
m[n] | 对应第n个子表达式的ssub_match对象 |
m.begin(),m.end() m.cbegin(),m.cend() | 表示m中sub_match元素范围的迭代器。 |
使用子表达式
正则表达式中的模式通常包含1个或多个子表达式(subexpression)。一个子表达式是模式的一部分,本身也具有意义。正则表达式语法通常用括号来表示子表达式。
regex r("([[:alnum:]+)\\.(cpp|cxx|cc)$", regex::icase);
这个模式保护两个括号括起来的子表达式:
- ([[:alnum:]]+),匹配1个或多个字符的序列
- (cpp|cxx|cc),匹配文件扩展名
可以通过修改使输出语句只打印文件名:
if( regex_search(filename, results, r))
cout << results.str(1) << endl;
打印str(1),即与第一个子表达式匹配的部分。
匹配对象除了提供匹配整体的相关信息外,还提供访问模式中每个子表达式的能力。子匹配是按位置来访问的。第一个子匹配位置为0,表示整个模式对应的匹配,随后是每个子表达式对应的匹配。
子表达式的一个常见用途是验证必须匹配特定格式的数据。下面介绍一下ECMAScript正则表达式语言的一些特性:
- \{d}表示单个数字,而\{d}{n}则表示一个n个数字的序列。(如:\{d}{3}匹配三个数字的序列)
- 在方括号中的字符集合表示匹配这些字符中的任意一个。(如:[-. ]匹配一个短横线或一个点或一个空格。注意,点再括号中没有特殊含义)
- 后接'?'的组建是可选的。(如:\{d}{3}[-. ]?\{d}{4}匹配序列是开始是三个数字,后接一个可选的短横线或点或空格,然后是四个数字。此模式可以匹配555-0132或555.0132或555 0132或5550132)
- 类似C++,ECMAScript使用反斜线\表示一个字符本身而不是其特殊含义。由于我们的模式包括括号,而括号是ECMAScript中的特殊字符,因此必须使用 \( 和 \) 来表示括号是我们模式的一部分而不是特殊字符。
由于反斜线\是C++的特殊字符,在模式中每次出现\的地方,必须用一个额外的反斜线来告知是反斜线而不是特殊字符。即我们用\\{d}{3}来表示正则表达式\{d}{3}。
string phone =
"(\\()?\\{d}{3}(\\))?([-. ])?(\\{d}{3})([-. ])?(\\{d}{4})";
regex r(phone);
smatch m;
string s;
while(getline(cin, s))
{
for( sregex_iterator it(s.begin(), s.end(), r), end_it;
it != end_it; ++it)
{
if(valid(*it))
cout << "valid:" << it->str() << endl;
else
cout << "not valid:" << it->str() << endl;
}
}
上述代码是匹配美国电话号码的模式示例,:
- (\\()? :表示区号部分可选的左括号
- \\{d}{3} :表示区号
- (\\))? :表示区号部分可选的右括号
- ([-. ])? :表示区号部分可选的分隔符
- (\\{d}{3}) :表示号码的下三位数字
- ([-. ])? :表示可选的分隔符
- (\\{d}{4}) :表示号码的最后四位数字
子匹配操作
我们将使用下表描述的子匹配操作来编写valid函数。上述代码的pattern有7个子表达式。每个smatch对象会包含8个ssub_match元素。如果一个子表达式是完整匹配的一部分,则其对应的ssub_match对象的matched成员是true
注意:这些操作适用于ssub_match、csub_match、wssub_match和wcsub_match | |
matched | 一个public bool数据成员,指出此ssub_match是否匹配了 |
first second | public数据成员,指向匹配序列首元素和尾后位置的迭代器。 如果未匹配,则first和second是相等的 |
length() | 匹配的大小。如果matched为false,则返回0 |
str() | 返回一个包含输入中匹配部分的string。 如果matched为false,返回空string |
s == ssub | 将ssub_match对象ssub转化为string对象s。等价于 s = ssub.str()。转换运算符不是explicit的 |
bool valid(const smatch &m)
{
//如果区号前有一个左括号,则区号后面有一个右括号,且之后紧跟剩余号码或一个空格
if(m[1].matched)
return m[3].matched &&
(m[4].matched == 0 || m[4].str() == " ");
//否则,区号后面不能有由括号,且后两个组成部分间的分隔符必须匹配
else
return !m[3].matched &&
m[4].str() == m[6].str();
}
使用regex_replace
正则表达式不仅用在我们希望查找一个给定序列的时候,还用在当我们想将找到序列替换成另一个序列的时候。此时,可以调用regex_replace。它接受一个输入字符序列和一个regex对象,还接受一个描述我们想要输出形式的字符串。
m.format(dest, fmt, mft) m.format(fmt, mft) | 使用格式字符串 fmt 生成格式化输出。匹配在m中,可选的match_flag_type标志在mft中。 第一个版本写入迭代器dest指向的目的位置并接受 fmt 参数,可以是一个string,也可以是表示字符数组范围的一对指针。 第二个版本返回一个string,保存输出,并接受 fmt 参数,可以是一个string,也可以是一个指向空字符结尾的字符数组指针。 mft的默认值为format_default |
regex_replace(dest, seq, r, fmt ,mft) regex_replace( seq, r, fmt , mft) | 遍历seq,用regex_search查找和regex对象r匹配的子串。使用格式字符串fmt和可选match_flag_type标志来生成输出 第一个版本写入迭代器dest指向的目的位置并接受一对迭代器seq表示范围。 第二个版本返回一个string,保存输出,且seq即可以是一个string,也可以是一个指向空字符结尾的字符数组指针。 在所有情况下, fmt即可以是一个string,也可以是一个指向空字符结尾的字符数组指针。mft的默认值为match_default |
替换字符串由我们想要的字符组合与匹配的子串对于的子表达式而组成。我们用一个符号$后跟子表达式的索引号来表示一个特定的子表达式:
string phone =
"(\\()?\\{d}{3}(\\))?([-. ])?(\\{d}{3})([-. ])?(\\{d}{4})";
string fmt = "$2.$5.$7";
regex r(phone);
string number = "(908) 555-1800";
cout << regex_replace(number, r, fmt) << endl;
"result: 908.555.1800"
int main()
{
string phone =
"(\\()?\\{d}{3}(\\))?([-. ])?(\\{d}{3})([-. ])?(\\{d}{4})";
regex r(phone);
smatch m;
string s;
string fmt = "$2.$5.$7";
while( getline(cin, s))
cout << regex_replace(s, r, fmt) << endl;
return 0;
}
标准库定义了用来在替换过程中控制匹配或格式的标志,这些标志可以传递给函数regex_search或regex_match或是类smatch的format成员。
匹配和格式化标志的类型为match_flag_type。这些值都定义在名为regex_constants的命名空间里。类似bind的placeholders,regex_constants也定已在命名空间std中的命名空间:
using namespace std::regex_contants::format_no_copy;
using namespace std::regex_contants;
定义在regex_constants::match_flag_type中 | |
match_default | 等价于format_default |
match_not_bol | 不将首字符作为行首处理 |
match_not_eol | 不将尾字符作为行尾处理 |
match_not_bow | 不将首字符作为单词首处理 |
match_not_eow | 不将首字符作为单词尾处理 |
match_any | 如果存在多于1个匹配,则可返回任意一个匹配 |
match_not_null | 不匹配任何空序列 |
match_continuous | 匹配必须从输入的首字符开始 |
match_prev_avail | 输入序列包含第一个匹配之前的内容 |
format_default | 用ECMAScript规则替换字符串 |
format_sed | 用POSIXsed规则替换字符串 |
format_no_copy | 不输出输入序列中未匹配的部分 |
format_first_only | 只替换子表达式第一次出现 |
string fmt2 = "$2.$5.$7";
cout << regex_replace(s, r, fmt2, format_no_copy) << endl;