C++正则表达式

Regex(正则表达式)

Regex的作用

  • 匹配:将整个输入匹配某个正则表达式
  • 查找:在字符串中进行查找
  • 切分:根据正则表达式对字符串进行切分
  • 替换:将与正则表达式吻合的子序列进行替换操作

Regex的匹配和查找

通过使用regex来定义一个正则表达式,如下:

regex reg1("<.*>.*</.*>");
复制代码

在这个正则表达式中,“.”表示除了"\n"以外的任何字符,“*”表示0次或者多次。因而上述正则表达式可以用来匹配html或xml格式的字符串。但上述正则表达式在匹配html或xml时,前后的标记可以不相同。为了达到前后标记相同的目的,需要使用组的概念。(...)用来定义捕获组,之后就可以借由正则表达式\1来再次指代它, 如下所示:

regex reg1("<(.*)>.*</\\1>");
复制代码

由于正则表达式也是个正常的字符序列,所以在使用“\1”时需要进行转义。当然在C++11之后,也可以通过使用raw string(原生字符串)的方式来取消字符串的转义,raw string以“R"(”开头,以“)"”结束。上述正则表达式可以写作:

regex reg2(R"(<(.*)>.*</\1>)");
复制代码

使用regex_match()来匹配字符串(完全匹配),用regex_search()来对字符串进行搜索(部分匹配),即

regex_search(data, regex(pattern));
复制代码

等价于

regex_match(data, regex("(.|\n)*" + pattern + "(.|\n)*"));
复制代码

其中(.|\n)*指任何数量和任何字符。

regex_match()和regex_search()的简单示例如下:

#include <regex>
#include <iostream>
using namespace std;

void out(bool b)
{
    cout << (b ? "found" : "not found") << endl;
}

int main()
{
    regex reg1("<.*>.*</.*>");
    bool found = regex_match("<tag>value</gat>", reg1);
    out(found);

    regex reg2("<(.*)>.*</\\1>");
    found = regex_match("<tag>value</tag>", reg2);
    out(found);

    found = regex_match("<tag>value</tag>", regex(R"(<(.*)>.*</\1>)"));
    out(found);
    cout << endl;

    found = regex_match("XML tag: <tag>value</tag>",
        regex("<(.*)>.*</\\1>"));
    out(found);

    found = regex_match("XML tag: <tag>value</tag>",
        regex(".*<(.*)>.*</\\1>.*"));
    out(found);

    found = regex_search("XML tag: <tag>value</tag>",
        regex("<(.*)>.*</\\1>"));
    out(found);

    found = regex_search("XML tag: <tag>value</tag>",
        regex(".*<(.*)>.*</\\1>.*"));
    out(found);

    system("pause");
}
复制代码

正则表达式匹配结果处理

regex_match()和regex_search()可以通过使用match_results对象来获得正则表达式匹配的细节。std::match_results是个模板类,必须依据其所处理的字符的迭代器类型而实例化。C++标准库提供了以下预定义的实例化实现:

  • smatch:针对string而设计的
  • cmatch:针对C-string(const char*)而设计的
  • wsmatch:针对wstring而设计的
  • wcmatch:针对wide C-string(const wchar_t*)而设计的

因而对于C++中的string类型必须使用smatch类型;而对于寻常的字符串常量则必须选用cmatch类型。

一般而言,match_result对象包含以下接口:

  • 一个sub_match对象m[0],表示匹配到的所有字符
  • prefix()接口,表示第一个匹配到的对象前的所有字符
  • suffix()接口,表示最后一个匹配到的对象后的所有字符
  • 对于任何捕获组,可以通过m[n]下标方式来进行访问
  • size()接口可以取得sub_match对象的个数(包括m[0])
  • str()接口可以以string形式取得字符
  • length()接口可以取得字符数量

sub_match对象派生自pair<>,其first成员是第一个字符的位置,second成员是最末字符的下一位置。

str()、length()、position()接口既可以不带参数获取match_result对象整体的值,也可以带参数来获取第n个匹配到的子串的信息。此外,match_result还可以通过begin()、cbegin()、end()、cend()来进行迭代访问。

不管是在regex_match()还是在regex_search()使用match_results对象,其所提供的接口是一样的,唯一的区别是regex_match()中prefix和suffix总是为空。

match_results使用示例如下:

void regex_match_result()
{
    string data = "XML tag: <tag-name>the value</tag-name>.";
    cout << "data:		" << data << "\n\n";

    smatch m;
    bool found = regex_search(data, m, regex("<(.*)>(.*)</(\\1)>"));

    cout << "m.empty():		" << boolalpha << m.empty() << endl;
    cout << "m.size():		" << m.size() << endl;
    if (found)
    {
        cout << "m.str():		" << m.str() << endl;
        cout << "m.length()		" << m.length() << endl;
        cout << "m.position()		" << m.position() << endl;
        cout << "m.prefix().str():	" << m.prefix().str() << endl;
        cout << "m.suffix().str():	" << m.suffix().str() << endl;
        cout << endl;

        for (int i = 0; i < m.size(); ++i)
        {
            cout << "m[" << i << "].str():		" << m[i].str() << endl;
            cout << "m.str(" << i << "):		" << m.str(i) << endl;
            cout << "m.position(" << i << "):		" << m.position(i) << endl;
        }
        cout << endl;

        cout << "matches:" << endl;
        for (auto pos = m.begin(); pos != m.end(); ++pos)
        {
            cout << " " << *pos << " ";
            cout << "(length: " << pos->length() << ")" << endl;
        }
    }
}
复制代码

拥有了正则表达式所有匹配结果的一切信息后,可以通过给regex_search()传入字符串区间来搜索整个字符串中所有匹配的子串,示例如下:

void regex_search_test()
{
    string data = "<person>\n"
        " <first>Nico</first>\n"
        " <last>Josuttis</last>\n"
        "</person>\n";
    regex reg("<(.*)>(.*)</(\\1)>");

    auto pos = data.cbegin();
    auto end = data.cend();
    smatch m;
    for (; regex_search(pos, end, m, reg); pos = m.suffix().first)		// m.suffix().first等同于m[0].second
    {
        cout << "match:	" << m.str() << endl;
        cout << "tag:	" << m[1].str() << endl;
        cout << "value:	" << m.str(2) << endl;
    }
}
复制代码

Regex Iterator

为了逐一迭代正则查找的所有匹配结果,可以使用regex iterator。模板类regex_iterator<>对于string和C-string也有着实例化的实现,分别以s、c、ws和wc开头。regex iterator的定义方式如下:

sregex_iterator pos(data.cbegin(), data.cend(), reg);
sregex_iterator end;
复制代码

第一个初始化了一个regex iterator,用来迭代data,找出符合正则规则reg的匹配。第二个为其默认构造,定义出了一个指向末尾下一位置的迭代器。regex iterator是一个双向迭代器,可以使用++、--操作进行前向和后向移动。示例如下:

void regex_iterator_test()
{
    string data = "<person>\n"
        " <first>Nico</first>\n"
        " <last>Josuttis</last>\n"
        "</person>\n";
    regex reg("<(.*)>(.*)</(\\1)>");

    sregex_iterator pos(data.cbegin(), data.cend(), reg);
    sregex_iterator end;
    for (; pos != end; ++pos)
    {
        cout << "match:	" << pos->str() << endl;
        cout << "tag:	" << pos->str(1) << endl;
        cout << "value:	" << pos->str(2) << endl;
    }
}
复制代码

Regex Token Iterator

regex_token_iterator<>是用来处理字符串中子序列之前的内容的,可以用来将string拆分为一个个词汇单元或者用以正则表达式为分隔符来分割string。regex_token_iterator<>有着针对string和C-string实现的实例化,分另带有s、c、ws和wc前缀。

在初始化regex_token_iterator<>时,除了可以传入给它字符串的起点和终点,以及一个正则表达式外,还可以指明一列数值,用来表示语汇化过程中的元素:

  • -1表示对每一个匹配的正则表达式之前的子序列感兴趣
  • 0表示对每一个匹配的正则表达式感兴趣
  • 任何其它数字n表示对正则表达式中第n个匹配结果感兴趣

regex_token_iterator允许以下列各种方式指明所在意的token:

  • 单一整数值
  • 一个整数值初值列
  • 一个由整数组成的vector
  • 一个由整数组成的array

regex_token_iterator示例如下:

void regex_token_iterator_test()
{
    string data = "<person>\n"
        " <first>Nico</first>\n"
        " <last>Josuttis</last>\n"
        "</person>\n";
    regex reg("<(.*)>(.*)</(\\1)>");

    sregex_token_iterator pos(data.cbegin(), data.cend(), reg, { 0, 2 });
    sregex_token_iterator end;
    for (; pos != end; ++pos)
    {
        cout << "match:	" << pos->str() << endl;
    }
    cout << endl;

    string names = "nico, jim, helmut, paul, tim, john paul, rita";
    regex sep("[ \t\n]*[,;.][ \t\n]*");		//被, ; .和空格分隔
    sregex_token_iterator p(names.cbegin(), names.cend(), sep, -1);
    sregex_token_iterator e;
    for (; p != e; ++p)
    {
        cout << "name;	" << p->str() << endl;
    }
}
复制代码

Regex Replacement

regex_replace()用于将匹配到的正则表达式字符序列替换为另一个字符序列,它的替换规则如下:

默认的Patternsed Pattern意义
$&&matched pattern
$n\n第n个捕获组
$‘匹配到结果的前缀
$’匹配到结果的后缀
$$字符$

示例如下:

void regex_replace_test()
{
    string data = "<person>\n"
        " <first>Nico</first>\n"
        " <last>Josuttis</last>\n"
        "</person>\n";
    regex reg("<(.*)>(.*)</(\\1)>");

    cout << regex_replace(data, reg, "<$1 value=\"$2\"/>") << endl;

    string reg2; 
    regex_replace(back_inserter(reg2),
        data.begin(), data.end(),
        reg,
        "<$1 value=\"$2\"/>",
        regex_constants::format_no_copy | regex_constants::format_first_only);
    cout << reg2 << endl;
}
复制代码

Regex Flag

Regex中还有一些常量可作为regex构造函数或regex的各个函数的最后一个可有可无的参数,这些常量若被传入则会影响regex接口的行为,下表列举了Regex的常量:

regex常量意义
Regex Grammar
ECMAScript使用ECMAScript文法(默认)
basic使用POSIX的basic regular expression文法
extended使用POSIX的extended regular expression文法
awk使用UNIX工具awk的文法
grep使用UNIX工具grep的文法
egrep使用UNIX工具egrep的文法
其它Creation Flag
icase忽略大小写
nosubs不将子序列存储于匹配结果中
optimize优化matching速度,然后才考虑regex的创建速度
collate形式为[a-b]的字符区间会受locale影响
Algorithm Flag
match_not_null不匹配空序列
match_not_bol第一个字符不匹配beginning-of-line
match_not_eol最末字符不匹配end-of-line
match_not_bow第一个字符不匹配beginning-of-word
match_not_eow最末字符不匹配end-of-word
match_continuous只方式图匹配“从第一个字符开始”的子序列
match_any如果多于一个匹配,任何匹配皆可接受
match_prev_avail第一字符的前一位置是个有效位置
Replacement Flag
format_default使用默认的ECMAScript替换语法
format_sed使用UNIX工具sed的替换语法
format_first_only只替换第一个匹配
format_no_copy对于未获匹配的字符,不予以复制

Regex ECMAScript文法

以下表格列举了ECMAScript文法中常见的Regex表达式:

表达式意义
.\n以外的任何字符
[...]...字符中任何一个
[^...]...字符以外的任何一个
[[:charclass:]]指定字符类charclass中的一个
\n, \t, \f, \r, \v一个换行、tab、form feed、carriage return、vertical tab
\xhh, \uhhh一个16进制字符或Unicode字符
\d, \D, \s, \S, \w, \W某字符类中的一个字符
*前一字符或群组任意次数
?前一字符或群组可有可无
+前一字符或群组至少一次
{n}前一字符或群组n次
{n,}前一字符或群组至少n次
{n,m}前一字符或群组至少n次,至多m次
...|...在|之前或之后的pattern
(...)设定分组
\1, \2, \3, ...第n个组
\b一个正字词边界(字词的起点或终点)
\B一个负字词办界(字词的非起点或非终点)
^一行的起点
$一行的终点

下表展示了字符类及其效果

字符类缩写转义效果
[[:alnum:]]一个字母或数字,相当于[[:alpha:][:digit:]]
[[:alpha:]]一个字母
[[:blank:]]一个空格或tab
[[:cntrl:]]一个控制符
[[:digit:]][[:d:]]\d一个数字
\D一个非数字,相当于[[1]]
[[:graph:]]一个可打印非空白字符,相当于[[:alnum:][:punct:]]
[[:lower:]]一个小写字母
[[:print:]]一个可打印字符,包括空白符
[[:punct:]]一个标点号字符
[[:space:]][[:s:]]\s一个空白符
\S一个非空白字符
[[:upper:]]一个大写字母
[[:xdigit:]]一个十六进制数
[[:w:]]\w一个字母、数字或下划线
\W不是字母、数字或下划线

示例:

[_[:alpha:]][_[:alnum:]]*           //C++标识符
(.|\n)*                             //任意个任何字符
[123]?[0-9]\.1?[0-9]\.20[0-9]{2}    //2000之后的一个日期复制代码

  1. :digit: ↩︎

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值