C++11:正则表达式

1、介绍

正则表达式是一种描述字符序列的方法,下面介绍C++11正则表达式库(RE)库的使用。

其包含的组件如下:

regex  //表示一个正则表达式的类
regex_match  //将一个字符与一个正则表达式匹配
regex_search  //寻找第一个与正则表达式匹配的子序列
regex_replace  //使用给定格式替换一个正则表达式
sregex_iterator  //迭代器适配器,调用regex_search来遍历一个string中所有匹配的字串
smatch  //容器类,保存在string中搜索的结果
ssub_match  //string中匹配的子表达式的结果

2、正则表达式的使用

从一个例子开始,查找违反拼写规则:i除非在c之后,否则必须在e之前的单词。

    string pattern("[^c]ei");
    pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";
    regex r(pattern);
    smatch ret;
    string reg_test = "receipt freind theif receive";
    if (regex_search(reg_test, ret, r))
        cout << "find the str: " <<ret.str() << endl;

首先声明我们需要使用的pattern,即正则表达式;然后定义regex 和smatch ,调用regex_search函数查找对应的字符串中满足pattern的字串。然后调用smatch.str()输出。C++11中的正则表达式默认使用的正则表达式语法是ECMAScript。

下面列出regex常用的一些选项:

regex r(re)   //re可以是一个正则表达式,一个string,一个表示字符范围的迭代器对,一个指向空字符结尾的字符数组的指针,一个字符指针和一个计数器或者是一个花括号包围的字符列表
regex r(re, f)  //f是指出对象如何处理的标志
r = re  //将r中正则表达式替换为re
r.assign(re, f);  //与运算符 = 效果相同
r.mark_count();  //r中子表达式的数目
r.flags();  //返回r的标志集合

下面是标志集合:

icase  忽略大小写
nosubs  不保存匹配的子表达式
optimize  执行速度优先于构造速度
ECMAScript  使用ECMA-262语法
basic  使用POSIX基本的正则表达式语法
extended  使用POSIX扩张的正则表达式语法
awk  使用POSIX版本的awk语言的语法
grep  使用POSIX版本的grep语言的语法
grep  使用POSIX版本的egrep语言的语法

接下来再看一个例子,查找后缀为.cpp .cc. cxx的文件:

    array<string, 5> as5{"1.cpp", "sdja.txt", "qwei91.cxx", "sdauhd.c", "asdjh.cc"};
    regex r1("([[:alnum:]]+)\\.(cpp|cxx|cc)$", regex::icase); //在第一个表达式中加了()就变成了子表达式
    smatch ret1;
    for (auto e : as5)
    {
        if(regex_search(e, ret1, r1))
            cout << "find the cpp\\cxx\\cc file: " << ret1.str() << endl;
    }

在编写正则表达式的时候如果写错了,比如缺少括号这样的错误,在编译阶段是不会报错的,因为正则表达式本身相当于一个简单语言设计的“程序”。所以如果正则表达式写错了,只会在运行阶段,抛出异常。C++11中定义的正则表达式异常有13个,用到的时候再去查吧。

3、匹配与Regex迭代器类型

在使用正则表达式匹配字符串的时候,我们可以使用sregex_iterator来获得所有的匹配。

下面是sregex_iterator的操作:

sregex_iterator it(b,e,r); //遍历迭代器b和e表示的string,它调用sregex_search(b,e,r)将it定位到输入中第一个匹配的位置
sregex_iterator end;  //尾后迭代器
*it //返回一个smatch对象的引用
it->  //指向smatch对象的指针
++it  //自加
it++
it1 == it2;  //如果两个sregex_iterator都是尾后迭代器,则它们相等。两个非尾后迭代器是从相同的输入序列和regex对象构造,则它们相等
it1 != it2; 

当我们将一个sregex_iterator绑定到一个string和一个regex对象时,迭代器自动定位到给定string中第一个匹配位置。

下面是一个使用sregex_iterator的例子:

    //使用regex_iterator,输出所有的匹配结果,并且输出上下文
    string pattern1("[^c]ei");
    pattern1 = "[[:alpha:]]*" + pattern1 + "[[:alpha:]]*";
    regex r2(pattern1, regex::icase);
    string reg_test = "receipt freind theif receive";
    for (sregex_iterator it(reg_test.begin(), reg_test.end(), r2), 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;
    }

上面的例子中使用sregex_iterator输出所有匹配的结果,并且使用prefix()和suffix()输出匹配位置的上下文。

下面是smatch的常用操作:

m.ready();  //如果已经通过调用regex_serach或者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(...); 
m.length(n);  //第n个匹配的子表达式的大小,0表示整个匹配
m.position(n);  //第n个子表达式距离序列开始的距离
m.str(n);  //第n个子表达式匹配的string
m[n];  //第n个匹配的子表达式的ssub_match对象
m.begin(), m.end();  //表示匹配范围的迭代器
m.cbegin(), m.cend();  //表示匹配范围的常迭代器

4、使用匹配的子表达式

字符串使用正则表达式匹配之后有许多的子表达式,常用于进行数据验证或者格式转换。

看下面的例子:

bool phone_valid(const smatch& m)
{
    //如果区号前有一个左括号
    if (m[1].matched)
        //return m[3].matched && (m[4].matched == 0 || m[4].str() == " ");
        return m[3].matched;
    else
        //return !m[3].matched && m[4].str() == m[6].str();
        return !m[3].matched;
}
    //使用正则表达式检查电话号码是否合法
    string reg_phone = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ]?)(\\d{4})";
    regex r3(reg_phone);
    //smatch m;
    string reg_s = "0201234567, 020.1234567, 020-1234567, 020-123456789, (020)-1234567, (020).1234567, (020) 1234567, (020)-12345678910, (0201234567, 020)1234567, 12345678910, 020s1234567, 020 s123456, 020/1234657, +86 1234567, +86.1234567, +86-1234567";
    for (sregex_iterator it(reg_s.begin(), reg_s.end(), r3), end_it; it != end_it; ++it)
    {
        if(phone_valid(*it))
            cout << "valid: " << it->str() << endl;
        else
            cout << "not valid: " << it->str() << endl;
    }

使用regex_replace,这个对象可以用于替换字符串中的特定字符。

m.format(dest, fmt, mft)
m.format(fmt, mft);

regex_replace(dest, seq, r, fmt, mft);
regex_replace(seq, r, fmt, mft);

上面的几个操作是regex_replace的常用操作,光看命名就很好理解。

下面用一个例子直接展示regex_replace的使用:

    string reg_phone = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ]?)(\\d{4})";
    string reg_replace_test = "(654) 123-7856";
    regex reg_replace(reg_phone);
    string regex_replace_fmt = "$2-$5-$7";
    cout << regex_replace(reg_replace_test, reg_replace, regex_replace_fmt) << endl;

上述例子将电话的格式转换成用 - 连接的形式。

标准库中定义了标志来直到如何处理正则表达式,在替换过程中也是一样的,定义了标志来控制匹配或格式的标志。这些标志可以传递给函数regex_search或regex_match或是类smatch的fomat成员。这些值定义在命名空间std::regex_constants中。

match_default   等价于format_default
match_not_bol  不将首字符作为行首处理
match_not_eol  不将尾字符作为行首处理
match_not_bow  不将首字符作为单词首处理
match_not_eow  不将尾字符作为单词尾处理
match_any   如果存在多个匹配,则返回任意一个匹配
match_not_null  不匹配任何空序列
match_continuous  匹配必须从输入的首字符开始
match_prev_avail  输入序列包含第一个匹配之前的内容
format_default  用ECMAScript规则替换字符串
format_sed   用POSIX sed规则替换字符串
format_no_copy  不输出输入序列中未匹配的部分
format_first_only  只替换子表达式的第一次出现的

如修改上面的替换例子:

    string reg_phone = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ]?)(\\d{4})";
    string reg_replace_test = "(654) 123-7856";
    regex reg_replace(reg_phone);
    string regex_replace_fmt = "$2-$5-$7";
    cout << regex_replace(reg_replace_test, reg_replace, regex_replace_fmt, format_no_copy ) << endl;

这样只会输出regex_replace拷贝的文本,而不会输出整个输入序列。

正则表达式的使用建议

(1)避免创建不必要的正则表达式,正则表达式的编译是一个非常慢的操作,运行时开销也比较大。
(2)正则表达式的的类型要与输入的类型匹配,如输入类型是string,使用正则表达式的类为regex, smatch等;如输入类型为const char,使用正则表达式的类为:regex,cmatch等;如输入类型为wstring,使用正则表达式的类为:wregex,wsmatch等;如输入类型为const wchar_t,使用正则表达式的类为:wregex,wcmatch等;

总结

C++11新标准引入了正则表达式库,在C++代码里面可以直接使用标准库的正则表达式工具,对于字符串的处理非常方便。但是因为正则表达式的开销比较大,并且在编译阶段是不会报错的,所以应该避免不必要的使用以及做好异常处理。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容