boost正则
Regex
头文件: "boost/regex.hpp"
正则表达式常用于对输入数据的格式进行验证。应用软件通常要求输入符合某种结构。考虑一个应用软件,它要求输入一定要符合如下格式,"3个数字, 一个单词, 任意字符, 2个数字或字符串"N/A," 一个空格, 然后重复第一个单词." 手工编写代码来验证这个输入既沉闷又容易出错,而且这些格式还很可能会改变;在你弄明白之前,可能就需要支持其它的格式,你精心编写的分析器可能就需要修改并重新调试。让我们写出一个可以验证这个输入的正则表达式。首先,我们需要一个匹配3个数字的表达式。对于数字,我们应该使用一个特别的缩写,\d。要表示它被重复3次,需要一个称为bounds operator的特定重复,它用花括号括起来。把这两个合起来,就是我们的正则表达式的开始部分了。
boost::regex reg("\\d{3}");
注意,我们需要在转义字符(\)之前加一个转义字符,即在我们的字符串中,缩写 \d 变成了 \\d 。这是因为编译器会把第一个\当成转义字符扔掉;我们需要对\进行转义,这样\才可以出现在我们的正则表达式中。
接下来,我们需要定义一个单词的方法,即定义一个字符序列,该序列结束于一个非字母字符。有不只一种方法可以实现它,我们将使用字符类别(也称为字符集)和范围这两个正则表达式的特性来做。字符类别即一个用方括号括起来的表达式。例如,一个匹配字符a, b, 和 c中任一个的字符类别表示为:[abc]. 如果用范围来表示同样的东西,我们要写:[a-c]. 要写一个包含所有字母的字符类型,我们可能会有点发疯,如果要把它写成: [abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ], 但不用这样;我们可以用范围来表示:[a-zA-Z]. 要注意的是,象这样使用范围要依赖于当前所用的locale,如果正则表达式的 basic_regex::collate 标志被打开。使用以上工具以及重复符 +, 它表示前面的表达式可以重复,但至少重复一次,我们现在可以表示一个单词了。
boost::regex reg("[a-zA-Z]+");
以上正则表达式可以工作,但由于经常要表示一个单词,所以有一个更简单的方法:\w. 这个符号匹配所有单词,不仅是ASCII的单词,因此它不仅更短,而且也更适用于国际化的环境。接下来的字符是一个任意字符,我们已经知道要用点来表示。
boost::regex reg(".");
再接下来是 2个数字或字符串 "N/A." 为了匹配它,我们需要用到一个称为选择的特性。选择即是匹配两个或更多子表达式中的任意一个,每种选择之间用 | 分隔开。就象这样:
boost::regex reg("(\\d{2}|N/A)");
注意,这个表达式被圆括号括了起来,以确保整个表达式被看作为两个选择。在正则表达式中增加一个空格是很简单的;用缩写\s. 把以上每一样东西合并起来,就得到了以下表达式:
boost::regex reg("\\d{3}[a-zA-Z]+.(\\d{2}|N/A)\\s");
现在事情变得有点复杂了。我们需要某种方法,来验证接下来的输入数据中的单词是否匹配第一个单词(即那个我们用表达式[a-zA-Z]+所捕获的单词)。关键是要使用后向引用(back reference),即对前面的子表达式的引用。为了可以引用表达式 [a-zA-Z]+, 我们必须先把它用圆括号括起来。这使得表达式([a-zA-Z]+)成为我们的正则表达式中的第一个子表达式,我们就可以用索引1来建立一个后向引用了。
这样,我们就得到了整个正则表达式,用于表示"3个数字, 一个单词, 任意字符, 2个数字或字符串"N/A," 一个空格, 然后重复第一个单词.":
boost::regex reg("\\d{3}([a-zA-Z]+).(\\d{2}|N/A)\\s\\1");
现在我们来看一下另一个Boost.Regex算法, regex_search. 与 regex_match不同的是,regex_search不要求整个输入数据完全匹配,则仅要求部分数据可以匹配。作为说明,考虑一个程序员的问题,他可能在他的程序中有一至两次忘记了调用 delete 。虽然他知道这个简单的测试可能没什么意义,他还是决定计算一下new 和 delete出现的次数,看看数字是否符合。这个正则表达式很简单;我们有两个选择,new 和 delete.
boost::regex reg("(new)|(delete)");
有两个原因我们要把子表达式用括号括起来:一个是为了表明我们的选择是两个组。另一个原因是我们想在调用regex_search时引用这些子表达式,这样我们就可以判断是哪一个选择被匹配了。我们使用regex_search的一个重载,它接受一个match_results类型的参数。当 regex_search 执行匹配时,它通过一个match_results类型的对象报告匹配的子表达式。类模板 match_results 使用一个输入序列所用的迭代器类型来参数化。
template <class Iterator,
class
Allocator=std::allocator<sub_match<Iterator> >
class match_results;
typedef match_results<const char*>
cmatch;
typedef match_results<const wchar_t>
wcmatch;
typedef match_results<std::string::const_iterator>
smatch;
typedef match_results<std::wstring::const_iterator>
wsmatch;
我们将使用 std::string, 所以要留意 typedef smatch, 它是 match_results<std::string::const_iterator>的缩写。如果 regex_search 返回 true, 传递给该函数的 match_results 引用将包含匹配的子表达式结果。在 match_results里,用已索引的sub_match来表示正则表达式中的每个子表达式。我们来看一下我们如何帮助这位困惑的程序员来计算对new 和 delete的调用。
boost::regex
reg("(new)|(delete)");
boost::smatch m;
std::string
s=
"Calls to new must be followed by delete. \
Calling
simply new results in a leak!";
if
(boost::regex_search(s,m,reg)) {
// Did new match?
if
(m[1].matched)
std::cout << "The expression (new)
matched!\n";
if (m[2].matched)
std::cout <<
"The expression (delete) matched!\n";
}
以上程序在输入字符串中查找 new 或 delete, 并报告哪一个先被找到。通过传递一个类型 smatch 的对象给 regex_search, 我们可以得知算法如何执行成功的细节。我们的表达式中有两个子表达式,因此我们可以通过match_results的索引1得到子表达式 new . 这样我们得到一个 sub_match实例,它有一个Boolean成员,matched, 告诉我们这个子表达式是否参与了匹配。因此,对于上例的输入,运行结果将输出"The expression (new) matched!\n". 现在,你还有一些工作要做。你需要继续把正则表达式应用于输入的剩余部分,为此,你要使用另外一个 regex_search的重载,它接受两个迭代器,指示出要查找的字符序列。因为 std::string 是一个容器,它提供了迭代器。现在,在每一次匹配时,你必须把指示范围起始点的迭代器更新为上一次匹配的结束点。最后,增加两个变量来记录 new 和 delete的次数。以下是完整的程序:
#include
<iostream>
#include <string>
#include
"boost/regex.hpp"
int main() {
// "new"
and "delete" 出现的次数是否一样?
boost::regex reg("(new)|(delete)");
boost::smatch m;
std::string s=
"Calls to new must be followed by delete.
\
Calling simply new results in a leak!";
int
new_counter=0;
int delete_counter=0;
std::string::const_iterator it=s.begin();
std::string::const_iterator end=s.end();
while
(boost::regex_search(it,end,m,reg)) {
// 是
new 还是
delete?
m[1].matched ? ++new_counter : ++delete_counter;
it=m[0].second;
}
if (new_counter!=delete_counter)
std::cout << "Leak detected!\n";
else
std::cout << "Seems ok...\n";
}
用户常见的误解
我所见到的与Boost.Regex相关的最常见的问题与regex_match的语义有关。人们很容易忘记必须使regex_match的所有输入匹配给定的正则表达式。因此,用户常以为以下代码会返回 true.
boost::regex
reg("\\d*");
bool b=boost::regex_match("17 is
prime",reg);
无疑这个调用永远不会得到成功的匹配。只有所有输入被 regex_match 匹配才可以返回 true!几乎所有的用户都会问为什么 regex_search 不是这样而 regex_match 是。
boost::regex
reg("\\d*");
bool b=boost::regex_search("17 is
prime",reg);
这次肯定返回 true. 值得注意的是,你可以用一些特定的缓冲操作符来让 regex_search 象 regex_match 那样运行。\A 匹配缓冲的起始点,而 \Z 匹配缓冲的结束点,因此如果你把 \A放在正则表达式的开始,把 \Z放在最后,你就可以让 regex_search象 regex_match那样使用,即必须匹配所有输入。以下正则表达式要求所有输入被匹配掉,而不管你使用的是 regex_match或是 regex_search.
boost::regex reg("\\A\\d*\\Z");
请记住,这并不表示可以无需使用 regex_match;相反,它可以清晰地表明我们刚才说到的语义,即必须匹配所有输入。
关于重复和贪婪
另一个容易混淆的地方是关于重复的贪婪。有些重复,如 +和 *,是贪婪的。即是说,它们会消耗掉尽可能多的输入。以下正则表达式并不罕见,它用于在一个贪婪的重复后捕获两个数字。
boost::regex reg("(.*)(\\d{2})");
这个正则表达式是对的,但它可能不能匹配你想要的子表达式!表达式 .*会吞掉所有东西而后续的子表达式将不能匹配。以下是示范这个行为的一个例子:
int
main() {
boost::regex reg("(.*)(\\d{2})");
boost::cmatch m;
const char* text = "Note that I'm 31 years
old, not 32.";
if(boost::regex_search(text,m, reg)) {
if (m[1].matched)
std::cout << "(.*) matched: "
<< m[1].str() << '\n';
if (m[2].matched)
std::cout << "Found the age: " << m[2] <<
'\n';
}
}
在这个程序中,我们使用了match_results的另一个特化版本,即类型 cmatch. 它就是 match_results<const char*>的typedef, 之所以我们必须用它而不是用之前用过的 smatch,是因为我们现在是用一个字符序列调用 regex_search而不是用类型 std::string来调用。你期望这个程序的运行结果是什么?通常,一个刚开始使用正则表达式的用户会首先想到 m[1].matched和 m[2].matched都为 true, 且第二个子表达式的结果会是 "31". 接着在认识到贪婪的重复所带来的效果后,即重复会尽可能消耗输入,用户会想到只有第一个子表达式是 true,即 .*成功地吞掉了所有的输入。最后,新用户得到了下结论,两个子表达式都被匹配,但第二个表达式匹配的是最后一个可能的序列。即第一个子表达式匹配的是 "Note that I'm 31 years old, not" 而第二个匹配 "32".
那么,如果你想使用重复并匹配另一个子表达式的第一次出现,该怎么办?要使用非贪婪的重复。在重复符后加一个 ?,重复就变为非贪婪的了。这意味着该表达式会尝试发现最短的匹配可能而不再阻止表达式的剩余部分进行匹配。因此,要让前面的正则表达式正确工作,我们需要把它改为这样。
boost::regex reg("(.*?)(\\d{2})");
如果我们用这个正则表达式来修改程序,那么 m[1].matched和 m[2].matched都会为 true. 表达式 .*?只消耗最少可能的输入,即它将在第一个字符 3处停止,因为这就是表达式要成功匹配所需要的。因此,第一个子表达式会匹配 "Note that I'm" 而第二个匹配 "31".
boost::regex
reg1("\\d{5}");
boost::regex
reg2("\\d{2,4}");
boost::regex reg3("\\d{2,}");
第一个正则表达式匹配5个数字。第二个匹配 2个, 3个, 或者 4个数字。第三个匹配2个或更多个数字,没有上限。
boost::regex reg("[^13579]");
它包含一个非字符类别,匹配任意不是奇数数字的字符。看一下以下这个小程序,试着给出程序的输出。
int
main() {
boost::regex reg4("[^13579]");
std::string s="0123456789";
boost::sregex_iterator
it(s.begin(),s.end(),reg4);
boost::sregex_iterator end;
while (it!=end)
std::cout << *it++;
}
错的正则表达式
一个错的正则表达式就是一个不遵守规则的正则表达式。例如,你可能忘了一个右括号,这样正则表达式引擎将无法成功编译这个正则表达式。这时,将抛出一个 bad_expression 类型的异常。正如我前面提到的,这个异常的名字将会在下一版本的Boost.Regex中被修改,还有在即将加入Library Technical Report的版本中也是。异常类型 bad_expression 将被更名为 regex_error.
如果你的应用程序中的正则表达式全都是硬编码的,你可能不用处理错误表达式,但如果你是接受了用户的输入来作为正则表达式,你就必须准备进行错误处理。这里有一个程序,提示用户输入一个正则表达式,接着输入一个用来对正则表达式进行匹配的字符串。由用户进行输入时,总是有可能会导致无效的输入。
int
main() {
std::cout << "Enter a regular
expression:\n";
std::string s;
std::getline(std::cin,
s);
try {
boost::regex reg(s);
std::cout <<
"Enter a string to be matched:\n";
std::getline(std::cin,s);
if (boost::regex_match(s,reg))
std::cout << "That's right!\n";
else
std::cout << "No, sorry, that doesn't match.\n";
}
catch(const boost::bad_expression& e) {
std::cout
<<
"That's not a valid regular expression!
(Error: " <<
e.what() << ")
Exiting...\n";
}
}
为了保护应用程序和用户,一个 try/catch 块用于处理构造时抛出 boost::regex 的情形,这时会打印一个提示信息,而程序会温和地退出。用这个程序来测试,我们开始时输入一些合理的数据。
Enter
a regular expression:
\d{5}
Enter a string to be
matched:
12345
That's right!
现在,给一些错误的数据,试着输入一个错误的正则表达式。
Enter
a regular expression:
(\w*))
That's not a valid regular
expression! (Error: Unmatched ( or \() Exiting...