zoukankan      html  css  js  c++  java
  • 使用Boost的Regex库

    boost::regex类为C++提供了完整的正则表达式支持,并且已被接收为C++0x标准库。它同时也在Boost库中扮演着极重要的角色,不少Boost子库都需要它的支持,有不少人甚至就是为了它才下载使用Boost的。

    注意使用Boost.Regex需要预先编译,完整编译请参考我之前的linux平台上编译安装boost库

    1.概述
    Boost.Regex手里有七种武器和两大法宝
    其中的七种武器是:

    regex_match 函数
    regex_search 函数
    regex_replace 函数
    regex_format 函数
    regex_grep 函数
    regex_split 函数
    RegEx 类
    每种武器都又有诸多变化(每个函数都分别以C字符串类型、std::string类型、迭代器类型作为参数重载),不过后面四种武器因年久失修已不建议使用.
    两大法宝是:

    regex_iterator 迭代器
    regex_token_iterator 迭代器
    这两大法宝是整个Boost.Regex的灵魂,用熟它们以后那是“摘花飞叶即可伤人”啊~~

    boost::regex的默认正则表达式语法是perl语法
        boost::regex支持perl regular表达式、POSIX-Extended regular表达式和POSIX-Basic Regular表达式,但默认的表达式语法是perl语法,如果要使用其余两种语法需要在构造表达式的时候明确指定。

       例如,下面两种方法效果相同
       // e1 is a case sensitive Perl regular expression:
      // since Perl is the default option there''s no need to explicitly specify the syntax used here:
    boost::regex e1(my_expression);
    // e2 a case insensitive Perl regular expression:
            boost::regex e2(my_expression, boost::regex::perl|boost::regex::icase);

    perl正则表达式语法可参见《perl语言入门》第7、8、9章或boost的文档

    boost::regex对unicode编码的支持
        boost::regex使用ICU来实现对unicode及unicode变种的支持,这需要在编译boost的时候指出是否使用ICU以及ICU所在的目录。否则编译出来的boost::regex不支持unicode编码。其中boost::wregex支持unicode编码的搜索,如果要搜索UTF-8、UTF-16、UFT-32编码的字符串,则要用boost::u32regex。注意boost::wregex只能支持unicode编码,不能支持uft编码。

    搜索时如何忽略大小写
        如果要在搜索时忽略大小写(即大小写不敏感),则要用到表达式选项boost::regex::icase,例如: boost::regex e2(my_expression, boost::regex::perl|boost::regex::icase);

    模板类:
        basic_regex   用来保存一个“正则表达式”的类。
        sub_match     继承于pair<Iterator,Iterator>迭代器组,用来表示匹配的一个结果。
        match_results  sub_match的容器,用来表示一次搜索或匹配算法的所有结果,类似于vector<sub_match>。
    算法:
       regex_match   匹配算法,判断一个正则表达式(参数 e)是否匹配整个字符序列 str. 它主要用于验证文本。注意,这个正则表达式必须匹配被分析串的全部,否则函数返回 false. 如果整个序列被成功匹配,regex_match 返回 True.
       regex_search   查找算法,类似于 regex_match, 但它不要求整个字符序列完全匹配。你可以用 regex_search 来查找输入中的一个子序列,该子序列匹配正则表达式 e.
       regex_replace   替换算法,在整个字符序列中查找正则表达式e的所有匹配。这个算法每次成功匹配后,就根据参数fmt对匹配字符串进行格式化。缺省情况下,不匹配的文本不会被修改,即文本会被输出但没有改变。
    迭代器:
        regex_iterator  枚举一个字符串中所有匹配的字串,regex_iterator的结果相当于match_results。
       regex_token_iterator 枚举一个字符串中所有匹配的字串,regex_iterator的结果相当于sub_match。
    详述

    basic_regex

    template <class charT, class traits = regex_traits<charT>, class Allocator = std::allocator<charT>  >

    class basic_regex;

    typedef basic_regex<char> regex;

    typedef basic_regex<wchar_t> wregex;

    很明显,charT是正则式的字符类型,regex和wregex是basic_regex的两个特化。
    注意,正则式的字符类型要和需要匹配的字符串的字符类型相同。例如:不能在regex_find算法中分别使用string和wregex最为参数,要么是string和regex,要么是wstring和wregex。
    构造函数:
    basic_regex re
    产生空的正则式
    basic_regex re(str)
    正则式为str,str可以为basic_string,也可以是0结尾的char*字符串。
    Basic_regex re(re2)
    拷贝构造。
    basic_regex re(str,flag)
    正则式为str,使用flag语法选项,flag是一组常量的组合。例如:icase可以使正则式匹配忽略大小写。
    basic_regex re(beg,end)
    使用迭代器构造正则式。可以是basic_string的迭代器,也可以是const char*。
    basic_regex re(beg,end,flag)
    使用迭代器构造正则式,flag是语法选项。

    常用的语法选项:
    regex_constants::normal
    默认的语法。符合EMCAScript,JavaScript中的正则式。
    regex_constants::icase
    匹配的时候忽略大小写。
    regex_constants::nosubs
    不把匹配的子串保存进match_results结构。
    regex_constants::collate
    对于[a-b]的匹配,考虑地区

    语法选项通过或运算来结合。在basic_regex中这些语法选项也进行了定义,所以可以写成regex::normal,这要比regex_constants少打好几个字母了吧!J
    assign成员函数:
    re.assign(re2)
    复制一个正则式
    re.assign(str)
    正则式为str。
    re.assign(str, flag)
    正则式为str,使用flag语法选项,flag是一组常量的组合。
    re.assign(beg, end)
    使用迭代器构造正则式。
    re.assign(beg, end, flag)
    使用迭代器构造正则式,flag是语法选项。

    其实basic_regex很多用法和basic_string很像,因为正则表达式也是个字符串嘛!
    迭代器:
    regex::iterator it
    常迭代器类型,即const_iterator
    re.begin()
    返回的是常迭代器哦!const_iterator
    re.end()
    没有逆向迭代器。

    例如:copy(re.begin(), re.end(), ostream_iterator<char>(cout));
    其他:
    re.size()
    正则表达式长度,即str的长度。
    re.max_size()
    正则表达式的最大长度。
    re.empty()
    长度是否为0
    re.mark_count()
    返回正则式的组数,一般情况下为小括号对数+1。在boost.regex中使用小括号分组,详情请看下面的算法详解。
    re.flags()
    返回语法选项。
    cout<<re
    正则式的流输出,相当于上面示例的copy算法。
    swap
    成员函数,全局函数都有
    re.imbue(loc)
    设置local为loc,返回原来的local
    re.getloc()
    得到当前local
    ==,!=,<,<=,>,>=
    比较运算符重载

    sub_match
    sub_match是一个迭代器组,表示正则式中的一个匹配。

    template <class BidirectionalIterator>

    class sub_match : public std::pair<BidirectionalIterator, BidirectionalIterator>;

    boost没有提供sub_match的任何特化,因为我们不会显示的声明一个sub_match变量。sub_match是作为match_results的元素用的。比如:match_results的operator[]和迭代器返回的就是一个特化的sub_match。
    唯一的成员变量:
    bool matched  是否匹配。
    成员函数:
    length()
    返回长度,即两个迭代器之间的距离。
    operator basic_string< value_type>()
    隐式的basic_string转换。
    str()
    显式的basic_string转换。

    还有就是一大堆的比较操作符的重载了,这里就不多说了。
    match_results
    match_results相当于sub_match的容器,用于表示正则式算法的返回结果。

    template <class BidirectionalIterator,

              class Allocator = allocator<sub_match<BidirectionalIterator> >

    class match_results;

    typedef match_results<const char*> cmatch;

    typedef match_results<const wchar_t*> wcmatch;

    typedef match_results<string::const_iterator> smatch;

    typedef match_results<wstring::const_iterator> wsmatch;

    声明很简单,有四个特化可以直接使用,不过要注意string和char*字符串使用的match_results是不同的。
    成员函数:
    m.size()
    容量。
    m.max_size()
    最大容量。
    m.empty()
    容量是否为0。
    m[n]
    第n个元素,即sub_match
    m.prefix()
    返回代表前缀的sub_match,前缀指字符串的开头到第一个匹配的开头。
    m.suffix()
    返回代表后缀的sub_match,后缀之最后一个匹配的结尾到字符串的结尾。
    m.length(n)
    返回第n个元素的长度,即m[n].size()。
    m.position(n)
    返回第n个元素的位置。
    cout<<m
    流输出,输出整个匹配,相当于cout<<m[0]。因为第0个元素是整个匹配,详细情况请看下面的解释。
    m.format(fmtstr)
    使用格式化字符串,格式化结果,返回字符串
    m.format(fmtstr,flags)
    使用格式化字符串,格式化结果,返回字符串,flags是格式化选项。
    m.format(out,fmtstr)
    同上,但是使用输出迭代器输出结果。
    m.format(out.fmtstr,flags)
    同上,但是使用输出迭代器输出结果。

    迭代器:
    smatch::iterator
    迭代器,常迭代器
    smatch::const_iterator
    同上
    m.begin()
    返回常迭代器
    m.end()
    同上

    最后,说一个实例

    我处理一个文本

                实际值/-20.031,-1.896,-2.861,-1,0,0

    提取其中的数字

    regex exp("\s*实际值/(-?[0-9.]+),(-?[0-9.]+),(-?[0-9.]+),(-?[0-9.]+),(-?[0-9.]+),(-?[0-9.]+)$");

    大家看看还有没有更好的写法?

    regex_match

    regex_match算法用来测试一个字符串是否完全匹配正则式。让我们来看一下regex_match的使用:

    if (regex_match(str, m, re))

    {

        ...

    }

    str是一个字符串,可以是string,wstring,char *或者wchar_t *

    m是match_results,它通过引用传入参数,来保存匹配的结果,m要和str的类型匹配,可以是smatch,wsmatch,cmatch或wcmatch,用来分别对应string,wstring,char *或者wchar_t*的str。

    re就是正则表达式了,一般来说是regex或wregex。

    str,m,re的类型如下:

    str类型
    m类型
    re类型
    string
    smatch (match_results<string::const_iterator>)
    regex (basic_regex<char>)
    wstring
    wsmatch (match_results<wstring::const_iterator>)
    wregex (basic_regex<wchar_t>)
    char*
    cmatch (match_results<const char*>)
    regex (basic_regex<char>)
    wchar_t*
    wcmatch (match_results<const wchar_t*>)
    wregex (basic_regex<wchar_t>)

    函数的返回值表示字符串是否完全匹配正则表达式,当返回true的时候,m保存了匹配的结果;返回false,m未定义。

    下面让我们来看一下,当函数返回true的时候,m是怎么样的。

    m.size() == re.mark_count()

    还记得re.mark_count()返回的是什么吗?在上一篇中说的是re.mark_count()返回的时正则式的“组数”,并没有详细解释。这里我要详细解释一下。

    其实,这个“组数”在boost的regex中叫做sub-expression。sub-expression就是在正则式中使用小括号括起来的一部分,正则式本身是一个sub-expression,所以re.mark_count()等于小括号对数+1。

    m.prefix()和m.suffix()

    这两个返回的是sub_match类型(相当于一个迭代器组)。在regex_match算法中,这两个返回的sub_match都是空的,他们的值如下:(sub_match继承于pair,所以有first和second成员哦)

    m.prefix().first == str.begin()

    m.prefix().second == str.begin()

    m.prefix().matched == false

    m.suffix().first == str.end()

    m.suffix().second == str.end()

    m.suffix().matched == false

    因为regex_match是完全匹配,即整个字符串和正则式匹配,所以前缀和后缀都是空的。

    m[0]

    返回第0个匹配的,由于regex_match是完全匹配,所以

    m[0].first == str.begin()

    m[0].second == str.end()

    m[0].matched == true

    m[n] , n<m.size()

    返回第n个匹配的sub-expression。

    m[n].matched 表示第n个sub-expression是否在字符串中存在。整个regex匹配,但是sub_exp可能匹配的是空的,例如”(a*)”就有可以匹配空。

    m[n].first和m[n].second 表示匹配的范围。如果匹配空的话,都为str.end()。

    根据我的测试,m[1],m[2],...,m[n]的顺序是按照正则式的左小括号的顺序来的,例如对于正则式”((a)bc)d(efg)”,如果匹配了一个字符串的话(字符串只可能是”abcdefg”),则

    m[0] == “abcdefg”  (sub_match重载了==运算符使得可以和一个字符串比较)

    m[1] == “abc”

    m[2] == “a”

    m[3] == “efg”

    regex_match的其它用法

    regex_match(str,re)
    只测试是否匹配,不需要匹配的结果
    regex_match(beg,end,re)
    输入的是迭代器
    regex_match(beg,end,m,re)
    注意m的类型为match_results<iterator>
    regex_match(str,m,re,flag)
    flag是匹配选项,默认是的regex_constants::match_default

    regex_search

    regex_search的用法基本上和regex_match一样。

    if (regex_search(str, m, re))

    {

        ...

    }

    regex_search不要求str完全匹配re,只要str中的一个字串匹配re就可以了。所以,m.prefix()和m.suffix()不一定为空。

    regex_search是从左往右匹配,而且尽量匹配长的字串。

    2.示例代码

    先准备一个测试用的数据备用,如果各位有雅兴可以参考本站的另一篇文章《Google Testing》使用Google Testing框架来做这个实验,花一样时间学两样啊~~

    #include <iostream>
    #include <boost/regex.hpp>
    using namespace std;
    int main(int argc, char* argv[])
    { //( 1 ) (( 3 ) 2 )(( 5 )4)( 6 )

        //(\w+)://((\w+\.)*\w+)((/\w*)*)(/\w+\.\w+)?

        //^协议://网址(x.x...x)/路径(n个\字串)/网页文件(xxx.xxx)

        const char *szReg = "(\\w+)://((\\w+\\.)*\\w+)((/\\w*)*)(/\\w+\\.\\w+)?";
        const char *szStr = "http://www.cppprog.com/2009/0112/48.html";
        //练习代码...

        cin.get(); //暂停

    }

    2.1 字符串匹配
    要确定一行字符串是否与指定的正则表达式匹配,使用regex_match。
    下面这个代码可以验证szStr字串(定义在上面)是否与szReg匹配。

    { //字符串匹配
        boost::regex reg( szReg );
        bool r=boost::regex_match( szStr , reg);
        assert(r); //是否匹配
    }

    boost::regex的构造函数中还可以加入标记参数用于指定它的行为,如:
    //指定使用perl语法(默认),忽略大小写。
    boost::regex reg1( szReg, boost::regex::perl|boost::regex::icase );
    //指定使用POSIX扩展语法(其实也差不多)
    boost::regex reg2( szReg, boost::regex::extended );

    下面这个代码不仅验证是否匹配,而且可以从中提取出正则表达式括号对应的子串。
    { //提取子串

        boost::cmatch mat;
        boost::regex reg( szStr );
        bool r=boost::regex_match( szStr, mat, reg);
        if(r) //如果匹配成功
        {
            //显示所有子串
            for(boost::cmatch::iterator itr=mat.begin(); itr!=mat.end(); ++itr)
            {
                // 指向子串对应首位置 指向子串对应尾位置 子串内容
                cout << itr->first-szStr << ' ' << itr->second-szStr << ' ' << *itr << endl;
            }
        }
        //也可直接取指定位置信息
        if(mat[4].matched) cout << "Path is" << mat[4] << endl;
    }

    其中,boost::cmatch是一个针对C字符串的特化版本,它还有另三位兄弟,如下:
    typedef match_results<const char*> cmatch;
    typedef match_results<std::string::const_iterator> smatch;
    typedef match_results<const wchar_t*> wcmatch;
    typedef match_results<std::wstring::const_iterator> wsmatch;

    可以把match_results看成是一个sub_match的容器,同时它还提供了format方法来代替regex_format函数。
    一个sub_match就是一个子串,它从std::pair<BidiIterator, BidiIterator>继承而来,这个迭代器pair里的first和second分别指向了这个子串开始和结尾所在位置。同时,sub_match又提供了str(),length()方法来返回整个子串。

    2.2 查找字符串
    regex_match只验证是否完全匹配,如果想从一大串字符串里找出匹配的一小段字符串(比如从网页文件里找超链接),这时就要使用regex_search了。

    下面这段代码从szStr中找数字

    { //查找

        boost::cmatch mat;
        boost::regex reg( "\\d+" ); //查找字符串里的数字

        if(boost::regex_search(szStr, mat, reg))
        {
            cout << "searched:" << mat[0] << endl;
        }
    }

    2.3  替换
    regex_replace提供了简便的方法来部分替换源字符串正则表达式中,使用$1~$9(或\1~\9)表示第几个子串,$&表示整个串,$`表示第一个串,$'表示最后未处理的串。
    { //替换1,把上面的HTTP的URL转成FTP的
        boost::regex reg( szReg );
        string s = boost::regex_replace( string(szStr), reg, "ftp://$2$5");
        cout << "ftp site:"<< s << endl;
    }

    正则表达式中,使用(?1~?9新字串)表示把第几个子串替换成新字串
    { //替换2,使用format_all参数把<>&全部转换成网页字符

        string s1 = "(<)|(>)|(&)";
        string s2 = "(?1&lt;)(?2&gt;)(?3&amp;)";
        boost::regex reg( s1 );
        string s = boost::regex_replace( string("cout << a&b << endl;"), reg, s2, boost::match_default | boost::format_all);
        cout << "HTML:"<< s << endl;
    }

    2.4  使用regex_iterator查找
       对应于C字符串和C++字符串以及宽字符,regex_iterator同样也有四个特化:

        typedef regex_iterator<const char*> cregex_iterator;
        typedef regex_iterator<std::string::const_iterator> sregex_iterator;
        typedef regex_iterator<const wchar_t*> wcregex_iterator;
        typedef regex_iterator<std::wstring::const_iterator> wsregex_iterator;
        这个迭代器的value_type定义是一个match_results。

    { //使用迭代器找出所有数字

        boost::regex reg( "\\d+" ); //查找字符串里的数字

        boost::cregex_iterator itrBegin(szStr, szStr+strlen(szStr), reg);
        boost::cregex_iterator itrEnd;
        for(boost::cregex_iterator itr=itrBegin; itr!=itrEnd; ++itr)
        {
                // 指向子串对应首位置 指向子串对应尾位置 子串内容

                cout << (*itr)[0].first-szStr << ' ' << (*itr)[0].second-szStr << ' ' << *itr << endl;
        }
    }

    Boost.Regex也提供了make_regex_iterator函数简化regex_iterator的构造,如上面的itrBegin可以写成:

    itrBegin = make_regex_iterator(szStr,reg);

    2.5  使用regex_token_iterator拆分字符串
    它同样也有四个特化,形式和上面类似,就不再写一遍骗篇幅了。这个迭代器的value_type定义是一个sub_match。

    { //使用迭代器拆分字符串

        boost::regex reg("/"); //按/符拆分字符串

        boost::cregex_token_iterator itrBegin(szStr, szStr+strlen(szStr), reg,-1);
        boost::cregex_token_iterator itrEnd;
        for(boost::cregex_token_iterator itr=itrBegin; itr!=itrEnd; ++itr)
        {
            cout << *itr << endl;
        }
    }

    Boost.Regex也提供了make_regex_token_iterator函数简化regex_token_iterator的构造,最后的那个参数-1表示以reg为分隔标志拆分字符串,如果不是-1则表示取第几个子串,并且可以使用数组来表示同时要取几个子串,例如:
    { //使用迭代器拆分字符串2

        boost::regex reg("(.)/(.)"); //取/的前一字符和后一字符(这个字符串形象貌似有点邪恶-_-)

        int subs[] = {1,2}; // 第一子串和第二子串

        boost::cregex_token_iterator itrBegin = make_regex_token_iterator(szStr,reg,subs); //使用-1参数时拆分,使用其它数字时表示取第几个子串,可使用数组取多个串

        boost::cregex_token_iterator itrEnd;
        for(boost::cregex_token_iterator itr=itrBegin; itr!=itrEnd; ++itr)
        {
            cout << *itr << endl;
        }
    }

    2.6 完整测试代码

    #include <iostream>
    #include <boost/regex.hpp>
    using namespace std;
    int main(int argc, char* argv[])
    { //( 1 ) (( 3 ) 2 )(( 5 )4)( 6 )

        //(\w+)://((\w+\.)*\w+)((/\w*)*)(/\w+\.\w+)?

        //^协议://网址(x.x...x)/路径(n个\字串)/网页文件(xxx.xxx)

        const char *szReg = "(\\w+)://((\\w+\\.)*\\w+)((/\\w*)*)(/\\w+\\.\\w+)?";
        const char *szStr = "http://www.cppprog.com/2009/0112/48.html";
        { //字符串匹配

            boost::regex reg( szReg );
            bool r=boost::regex_match( szStr , reg);
            assert(r);
        }
        { //提取子串

            boost::cmatch mat;
            boost::regex reg( szReg );
            bool r=boost::regex_match( szStr, mat, reg);
            if(r) //如果匹配成功

            {
                //显示所有子串

                for(boost::cmatch::iterator itr=mat.begin(); itr!=mat.end(); ++itr)
                {
                    // 指向子串对应首位置 指向子串对应尾位置 子串内容

                    cout << itr->first-szStr << ' ' << itr->second-szStr << ' ' << *itr << endl;
                }
            }
            //也可直接取指定位置信息

            if(mat[4].matched) cout << "Path is" << mat[4] << endl;
        }
        { //查找

            boost::cmatch mat;
            boost::regex reg( "\\d+" ); //查找字符串里的数字

            if(boost::regex_search(szStr, mat, reg))
            {
                cout << "searched:" << mat[0] << endl;
            }
        }
        { //替换

            boost::regex reg( szReg );
            string s = boost::regex_replace( string(szStr), reg, "ftp://$2$5");
            cout << "ftp site:"<< s << endl;
        }
        { //替换2,把<>&转换成网页字符

            string s1 = "(<)|(>)|(&)";
            string s2 = "(?1&lt;)(?2&gt;)(?3&amp;)";
            boost::regex reg( s1 );
            string s = boost::regex_replace( string("cout << a&b << endl;"), reg, s2, boost::match_default | boost::format_all);
            cout << "HTML:"<< s << endl;
        }
        { //使用迭代器找出所有数字

            boost::regex reg( "\\d+" ); //查找字符串里的数字

            boost::cregex_iterator itrBegin = make_regex_iterator(szStr,reg); //(szStr, szStr+strlen(szStr), reg);

            boost::cregex_iterator itrEnd;
            for(boost::cregex_iterator itr=itrBegin; itr!=itrEnd; ++itr)
            {
                    // 指向子串对应首位置 指向子串对应尾位置 子串内容

                    cout << (*itr)[0].first-szStr << ' ' << (*itr)[0].second-szStr << ' ' << *itr << endl;
            }
        }
        { //使用迭代器拆分字符串

            boost::regex reg("/"); //按/符拆分字符串

            boost::cregex_token_iterator itrBegin = make_regex_token_iterator(szStr,reg,-1); //使用-1参数时拆分,使用其它数字时表示取第几个子串,可使用数组取多个串

            boost::cregex_token_iterator itrEnd;
            for(boost::cregex_token_iterator itr=itrBegin; itr!=itrEnd; ++itr)
            {
                cout << *itr << endl;
            }
        }
        { //使用迭代器拆分字符串2

            boost::regex reg("(.)/(.)"); //取/的前一字符和后一字符(这个字符串形象貌似有点邪恶-_-)

            int subs[] = {1,2}; // 第一子串和第二子串

            boost::cregex_token_iterator itrBegin = make_regex_token_iterator(szStr,reg,subs); //使用-1参数时拆分,使用其它数字时表示取第几个子串,可使用数组取多个串

            boost::cregex_token_iterator itrEnd;
            for(boost::cregex_token_iterator itr=itrBegin; itr!=itrEnd; ++itr)
            {
                cout << *itr << endl;
            }
        }
        cin.get();
        return 0;
    }

  • 相关阅读:
    poj2987 Firing
    poj3469 Dual Core CPU
    poj3281 Dining
    poj1149 PIGS
    Java基础知识强化之集合框架笔记50:Map集合之Map集合的概述和特点
    Java基础知识强化之网络编程笔记09:TCP之客户端键盘录入服务器写到文本文件中
    Java基础知识强化之IO流笔记35:InputStreamReader(Reader字符流的子类)2种read数据方式
    Java基础知识强化之IO流笔记34:OutputStreamWriter(Writer字符流的子类)5种write数据方式
    Java基础知识强化之IO流笔记33:转换流之InputStreamReader的使用
    Java基础知识强化之IO流笔记32:转换流之OutputStreamWriter的使用
  • 原文地址:https://www.cnblogs.com/hcfalan/p/1892274.html
Copyright © 2011-2022 走看看