Boost库学习笔记(一)
一 正则表达式(regex库)
正则表达式是什么?
正则表达式使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。
正则表达式通常被用来检索、替换那些符合某个模式的文本,格式化文本。(文本处理)
支持正则表达式的语种:perl,java,.net,php,awk,sed
C++语言本身并不提供正则表达式支持,但是boost库中regex库使得C++支持正则表达式,并且改进了有效输入的健壮性。Regex库将正则表达式并入C++程序,包含了perl,grep,emacs等常见工具的几种不同的语法。Boost.regex填补了C++标准库对于正则表达式的需要。
Regex
头文件支持:boost/regex.hpp
正则表达式是被封装为一个类型basic_regex的对象。
源码分析:
namespace boost
{
template<class charT, class traits = regex_traits<charT> >
classbasic_regex;
typedefbasic_regex<char> regex;
typedefbasic_regex<wchar_t> wregex;
}
namespace boost
{
template<class charT, class traits =regex_traits<charT> >
classbasic_regex {
public:
typedef charT value_type;
typedef implementation-specific const_iterator;
typedef const_iterator iterator;
typedef charT& reference;
typedef const charT& const_reference;
typedef std::ptrdiff_t difference_type;
typedef std::size_t size_type;
typedef regex_constants::syntax_option_type flag_type;
typedeftypename traits::locale_type locale_type;
explicitbasic_regex ();
explicitbasic_regex(const charT* p, flag_type f= regex_constants::normal);
basic_regex(constcharT* p1, const charT* p2,
flag_typef = regex_constants::normal);
basic_regex(constcharT* p, size_type len, flag_type f);
basic_regex(constbasic_regex&);
template<class ST, class SA>
explicitbasic_regex(const basic_string<charT, ST, SA>& p,
flag_typef = regex_constants::normal);
template<class InputIterator>
basic_regex(InputIteratorfirst, InputIterator last,
flag_typef = regex_constants::normal);
size_typesize() const;
size_typemax_size() const;
boolempty() const;
unsignedmark_count()const;
};
typedefbasic_regex<char> regex;
typedefbasic_regex<wchar_t> wregex;
}
explicit basic_regex(const charT* p, flag_type f =regex_constants::normal);
这个构造函数接受一个包含正则表达式的字符序列,还有一个参数用于指定使用正则表达式时的选项,例如是否忽略大小写。如果p中的正则表达式无效,则抛出一个 bad_expression 或 regex_error 的异常。注意这两个异常其实是同一个东西;在写这本书之时,尚未改变当前使用的名字 bad_expression ,但下一个版本的Boost.Regex将会使用 regex_error.
bool empty() const;
这个成员函数是一个谓词,当basic_regex实例没有包含一个有效的正则表达式时返回 true ,即它被赋予一个空的字符序列时。
unsigned mark_count()const;
mark_count 返回regex中带标记子表达式的数量。带标记子表达式是指正则表达式中用圆括号括起来的部分。匹配这个子表达式的文本可以通过调用某个正则表达式算法而获得。
typedef basic_regex<char> regex;
typedef basic_regex<wchar_t> wregex;
不要使用类型 basic_regex来定义变量,你应该使用这两个typedef中的一个。这两个类型,regex 和 wregex, 是两种字符类型的缩写,就如 string 和 wstring 是
basic_string<char> 和basic_string<wchar_t>的缩写一样。这种相似性是不一样的,某种程度上,regex 是一个特定类型的字符串的容器。
一般常用的函数
算法regex_match判断给定的正则表达式是否匹配由一对双向迭代器给出的全部字符序列,算法定义如下,这个函数的主要用处是数据输入验证。
template <class BidirectionalIterator,class Allocator, class charT, class traits>
bool regex_match(BidirectionalIteratorfirst, BidirectionalIterator last,
match_results<BidirectionalIterator,Allocator>& m, const basic_regex <charT, traits>&e,match_flag_type flags = match_default);
判断正则表达式e是否精确匹配全部的字符序列[first, last),参数flags(参见match_flag_type)用来控制表达式如何匹配字符序列。如果存在这样的匹配,则返回true,否则返回false。
如果对于N字符长度表达式匹配的复杂度超过O(N2),或者表达式匹配时发生栈空间溢出(如果Boost.Regex设置为递归模式),或者匹配器耗尽了所允许申请的内存(如果Boost.Regex设置为非递归模式)时,会扔出std::runtime_error异常。
regex_match 判断一个正则表达式(参数 e)是否匹配整个字符序列str. 它主要用于验证文本。注意,这个正则表达式必须匹配被分析串的全部,否则函数返回 false. 如果整个序列被成功匹配,regex_match 返回 True.
template <class BidirectionalIterator,class charT, class traits>
bool regex_match(BidirectionalIteratorfirst, BidirectionalIterator last,const basic_regex <charT, traits>&e,match_flag_type flags = match_default);
template <class charT, class Allocator,class traits>
bool regex_match(const charT* str,match_results<const charT*, Allocator>& m,const basic_regex<charT, traits>& e,match_flag_type flags = match_default);
template <class ST, class SA, classAllocator, class charT, class traits>
bool regex_match(constbasic_string<charT, ST, SA>& s,match_results<typenamebasic_string<charT, ST, SA>::const_iterator, Allocator>& m, constbasic_regex <charT, traits>& e,match_flag_type flags =match_default);
template <class charT, class traits>
bool regex_match(const charT* str,constbasic_regex <charT, traits>& e,match_flag_type flags =match_default);
template <class ST, class SA, classcharT, class traits>
bool regex_match(constbasic_string<charT, ST, SA>& s,const basic_regex <charT,traits>& e,match_flag_type flags = match_default);
算法regex_search在由一对双向迭代器指定的范围内搜索给定的正则表达式。算法使用很多启发式的方法仅检查当前位置可能存在的匹配,以减少搜索时间。算法定义如下:
template <class BidirectionalIterator,classAllocator, class charT, class traits>
bool regex_search(BidirectionalIteratorfirst, BidirectionalIterator last,
match_results<BidirectionalIterator,Allocator>& m,const basic_regex<charT, traits>& e,
match_flag_type flags = match_default);
template <class ST, class SA, classAllocator, class charT, class traits>
bool regex_search(constbasic_string<charT, ST, SA>& s, match_results< typenamebasic_string<charT, ST,SA>::const_iterator, Allocator>& m, constbasic_regex<charT, traits>& e, match_flag_type flags =match_default);
template<class charT, class Allocator,class traits>
bool regex_search(const charT* str,match_results<const charT*, Allocator>& m,
const basic_regex<charT, traits>&e, match_flag_type flags = match_default);
template <class BidirectionalIterator,class charT, class traits>
bool regex_search(BidirectionalIteratorfirst, BidirectionalIterator last,const basic_regex<charT, traits>&e, match_flag_type flags = match_default);
template <class charT, class traits>
bool regex_search(const charT* str, constbasic_regex<charT, traits>& e, match_flag_type flags =match_default);
template<class ST, class SA, classcharT, class traits>
bool regex_search(constbasic_string<charT, ST, SA>& s,const basic_regex<charT,traits>& e,
match_flag_type flags = match_default);
template <class BidirectionalIterator,class Allocator, class charT, class traits>
bool regex_search(
BidirectionalIterator first,
BidirectionalIterator last,
match_results<BidirectionalIterator,Allocator>& m,
const basic_regex<charT, traits>&e,
match_flag_type flags = match_default);
判断在[first,last)内是否存在某个子序列匹配正则表达式e,参数flags用来控制表达式如何匹配字符序列。如果存在这样的序列则返回true,否则返回false。
regex_search 类似于 regex_match, 但它不要求整个字符序列完全匹配。你可以用 regex_search 来查找输入中的一个子序列,该子序列匹配正则表达式 e.
算法regex_replace在字符串搜索正则表达式的所有匹配:对于每个匹配,调用match_results<>::format来格式化字符串并将结果送到输出迭代器中。 如果flags参数没有设置标签format_no_copy,那么没有匹配的文本部分将被原封不动地拷贝到输出。 如果设置了标签format_first_only,那么只有第一个匹配被替换,而不是所有匹配的地方。
template <class OutputIterator, classBidirectionalIterator, class traits, class charT>
OutputIterator regex_replace(
OutputIterator out,
BidirectionalIterator first,
BidirectionalIterator last,
const basic_regex<charT, traits>&e,
const basic_string<charT>& fmt,
match_flag_type flags = match_default);
template <class traits, class charT>
basic_string<charT> regex_replace(
const basic_string<charT>& s,
const basic_regex<charT, traits>&e,
const basic_string<charT>& fmt,
match_flag_type flags = match_default);
template <class OutputIterator, classBidirectionalIterator, class traits, class charT>
OutputIterator regex_replace(
OutputIterator out,
BidirectionalIterator first,
BidirectionalIterator last,
const basic_regex<charT, traits>&e,
const basic_string<charT>& fmt,
match_flag_type flags = match_default);
遍历表达式e在序列[first,last)中的所有匹配,对于每个匹配,用匹配的字符串和格式化字符串fmt合并的结果替换,并将结果字符串拷贝到out。
如果在flags中设置了标签format_no_copy,那么未匹配的文本部分不被拷贝到输出。
如果设置了标签format_first_only,那么只有e第一个出现的地方被替换。
格式化字符串fmt如何被解释,以及搜索匹配的原则,都由设置在flags的标签决定:参见match_flag_type。
regex_replace 在整个字符序列中查找正则表达式e的所有匹配。这个算法每次成功匹配后,就根据参数fmt对匹配字符串进行格式化。缺省情况下,不匹配的文本不会被修改,即文本会被输出但没有改变。
这三个算法都有几个不同的重载形式:一个接受 const charT* (charT 为字符类型), 另一个接受 const basic_string<charT>&, 还有一个重载接受两个双向迭代器作为输入参数。
代码示例应用
使用regexes 和 regex_match 来进行数据验证。
验证输入
正则表达式常用于对输入数据的格式进行验证。应用软件通常要求输入符合某种结构。考虑一个应用软件,它要求输入一定要符合如下格式,"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::regexreg("\\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::regexreg("\\d{3}([a-zA-Z]+).(\\d{2}|N/A)\\s\\1");
程序示例
#include <iostream>
#include <string>
#include <boost/regex.hpp>
using namespace std;
using namespace boost;
int main()
{
//匹配一个字符串顺序是3个数字+1个单词+一些字符+2个数字(或者"N/A")+一个空格+第一个单词重复
regexreg1("\\d{3}([a-zA-Z]+).(\\d{2}|N/A)\\s\\1");
stringstr1("123Hello N/A Hello");
stringstr2("123Hello 12 hello");
if(true==regex_match(str1,reg1))
{
cout<<"字符串"<<str1<<"匹配成功!"<<endl;
}
else
{
cout<<"字符串"<<str1<<"匹配失败!"<<endl;
}
if(true==regex_match(str2,reg1))
{
cout<<"字符串"<<str2<<"匹配成功!"<<endl;
}
else
{
cout<<"字符串"<<str2<<"匹配失败!"<<endl;
}
return0;
}
查找
现在我们来看一下另一个Boost.Regex算法, regex_search. 与 regex_match 不同的是,regex_search 不要求整个输入数据完全匹配,则仅要求部分数据可以匹配。作为说明,考虑一个程序员的问题,他可能在他的程序中有一至两次忘记了调用 delete 。虽然他知道这个简单的测试可能没什么意义,他还是决定计算一下new 和delete出现的次数,看看数字是否符合。这个正则表达式很简单;我们有两个选择,new 和 delete.
boost::regexreg("(new)|(delete)");
有两个原因我们要把子表达式用括号括起来:一个是为了表明我们的选择是两个组。另一个原因是我们想在调用regex_search时引用这些子表达式,这样我们就可以判断是哪一个选择被匹配了。我们使用regex_search的一个重载,它接受一个match_results类型的参数。当 regex_search 执行匹配时,它通过一个match_results类型的对象报告匹配的子表达式。类模板 match_results 使用一个输入序列所用的迭代器类型来参数化。
template <classBidirectionalIterator,class Allocator =std::allocator<sub_match<BidirectionalIterator> >
class match_results;
typedef match_results<constchar*> cmatch;
typedef match_results<constwchar_t*> wcmatch;
typedefmatch_results<string::const_iterator> smatch;
typedefmatch_results<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的调用。
regexreg1("(new)|(delete)");
smatchm;
strings="calls to new must be followed by delete.\calling simply new results ina leak!";
if(regex_search(s,m,reg1)) //查找先被找到的
{
if(m[1].matched)
{
cout<<"Theexpression (new) matched !"<<endl;
}
if(m[2].matched)
{
cout<<"Theexpression (delete) matched !"<<endl;
}
}
以上程序在输入字符串中查找 new 或 delete, 并报告哪一个先被找到。通过传递一个类型 smatch 的对象给 regex_search, 我们可以得知算法如何执行成功的细节。我们的表达式中有两个子表达式,因此我们可以通过match_results的索引1得到子表达式 new . 这样我们得到一个 sub_match实例,它有一个Boolean成员,matched, 告诉我们这个子表达式是否参与了匹配。因此,对于上例的输入,运行结果将输出"The expression (new) matched!\n". 现在,你还有一些工作要做。你需要继续把正则表达式应用于输入的剩余部分,为此,你要使用另外一个 regex_search的重载,它接受两个迭代器,指示出要查找的字符序列。因为 std::string 是一个容器,它提供了迭代器。现在,在每一次匹配时,你必须把指示范围起始点的迭代器更新为上一次匹配的结束点。最后,增加两个变量来记录 new 和 delete的次数。
regexreg1("(new)|(delete)");
smatch m;
string s="calls to new must be followed bydelete.\calling simply new results in a leak!";
string s1="calls to new must be followed bydelete!";
intnewcount=0;
intdeletecount=0;
string::const_iteratorit=s.begin();
string::const_iteratorend=s.end();
while(regex_search(it,end,m,reg1))
{
m[1].matched?++newcount:++deletecount;
it=m[0].second;
}
if(newcount!=deletecount)
{
cout<<"Leakdetected!"<<endl;
}
else
{
cout<<"seemsok..."<<endl;
}
newcount=0;
deletecount=0;
it=s1.begin();
end=s1.end();
while(regex_search(it,end,m,reg1))
{
m[1].matched?++newcount:++deletecount;
it=m[0].second;
}
if(newcount!=deletecount)
{
cout<<"Leakdetected!"<<endl;
}
else
{
cout<<"seemsok..."<<endl;
}
这个程序总是把迭代器 it 设置为 m[0].second。match_results[0] 返回对匹配整个正则表达式的子匹配的引用,因此我们可以确认这个匹配的结束点就是下次运行regex_search的起始点。运行这个程序将输出"Leak detected!", 因为这里有两次 new, 而只有一次 delete.当然,一个变量也可能在两个地方删除,还有可能调用 new[] 和 delete[], 等等。
替换
Regex算法家族中的第三个算法是regex_replace. 顾名思义,它是用于执行文本替换的。它在整个输入数据中进行搜索,查找正则表达式的所有匹配。对于表达式的每一个匹配,该算法调用 match_results::format 并输入结果到一个传入函数的输出迭代器。
在本章的介绍部分,我给出了一个例子,将英式拼法的 colour 替换为美式拼法 color. 不使用正则表达式来进行这个拼写更改会非常乏味,也很容易出错。问题是可能存在不同的大小写,而且会有很多单词被影响,如colourize. 要正确地解决这个问题,我们需要把正则表达式分为三个子表达式。
boost::regex reg("(Colo)(u)(r)",
boost::regex::icase|boost::regex::perl);
我们将要去掉的字母u独立开,为了在所有匹配中可以很容易地删掉它。另外,注意到这个正则表达式是大小写无关的,我们要把格式标志 boost::regex::icase 传给regex 的构造函数。你还要传递你想要设置的其它标志。设置标志时一个常见的错误就是忽略了regex缺省打开的那些标志,如果你没有设置这些标志,它们不会打开,你必须设置所有你要打开的标志。
调用 regex_replace时,我们要以参数方式提供一个格式化字符串。该格式化字符串决定如何进行替换。在这个格式化字符串中,你可以引用匹配的子表达式,这正是我们想要的。你想保留第一个和第三个匹配的子表达式,而去掉第二个(u)。表达式 $N表示匹配的子表达式, N 为子表达式索引。因此我们的格式化串应该是 "$1$3", 表示替换文本为第一个和第三个子表达式。通过引用匹配的子表达式,我们可以保留匹配文本中的所有大小写,而如果我们用字符串来作替换文本则不能
做到这一点。以下是解决这个问题的完整程序。
regexreg1("(Colo)(u)(r)",regex::icase|regex::perl);
string str1("Colour,colours,color,colourize");
cout<<"替换之前str1="<<str1<<endl;
str1=regex_replace(str1,reg1,"$1$3");
cout<<"替换之后str1="<<str1<<endl;