C++编程语言:标准库:第37章——正则表达式(Bjarne Stroustrup)

第 37章 正则表达式(Regular Expressions)

目录

[37.1 正则表达式(规范表达式)(Regular Expressions)](#37.1 正则表达式(规范表达式)(Regular Expressions))

[37.1.1 正则表达式相关符号(Regular Express Notation)](#37.1.1 正则表达式相关符号(Regular Express Notation))

[37.2 regex](#37.2 regex)

[37.2.1 匹配结果(Match Results)](#37.2.1 匹配结果(Match Results))

[37.2.2 格式化(Formatting)](#37.2.2 格式化(Formatting))

[37.3 正则表达式函数](#37.3 正则表达式函数)

[37.3.1 regex_match()](#37.3.1 regex_match())

[37.3.2 regex_search()](#37.3.2 regex_search())

[37.3.3 regex_replace()](#37.3.3 regex_replace())

[37.4 正则表达式迭代器](#37.4 正则表达式迭代器)

[37.4.1 regex_iterator](#37.4.1 regex_iterator)

[37.4.2 regex_token_iterator](#37.4.2 regex_token_iterator)

[37.5 regex_traits](#37.5 regex_traits)

[37.6 建议](#37.6 建议)


37.1 正则表达式(规范表达式)(Regular Expressions)

在 **<regex>**中,标准库提供了正则表达式:

regex_match():将正则表达式与已知长度的字符串进行匹配。

regex_search():在任意长度的数据流中搜索与正则表达式匹配的字符串。

regex_replace():在任意长度的数据流中搜索与正则表达式匹配的字符串并进行替换。

regex_iterator:迭代匹配项和子匹配项。

regex_token_iterator:迭代不匹配项。

regex_search() 的结果是匹配的集合,通常表示为 smatch

void use()

{

ifstream in("file.txt"); // input file

if (!in) cerr << "no file\n";

regex pat {R"(\w{2}\s\d{5}(−\d{4})?)"}; // U.S. postal code pattern

int lineno = 0;

for (string line; getline(in,line);) {

++lineno;

smatch matches; // matched strings go here

if (regex_search(line , matches, pat)) {

cout << lineno << ": " << matches[0] << '\n'; // the complete match

if (1<matches.siz e() && matches[1].matched)

cout << "\t: " << matches[1] << '\n';// submatch

}

}

}

此函数读取一个文件,查找美国邮政编码,例如 TX77845DC 20500-0001smatch 类型是正则表达式结果的容器 。其中,matches[0] 表示整个模式,matches[1] 表示可选的四位子模式。我使用了原始字符串(§7.3.2.1),它特别适合正则表达式,因为正则表达式通常包含大量反斜杠。如果我使用传统的字符串,则模式定义如下:

regex pat {"\\w{2}\\s \\d{5}(−\\d{4})?"}; // U.S. postal code pattern

正则表达式的语法和语义经过精心设计,以便能够将其编译成状态机从而高效执行[Cox,2007]。regex 类型在运行时执行此编译

37.1.1 正则表达式相关符号(Regular Express Notation)

regex 库可以识别正则表达式的多种表示法变体(见第 37.2 节)。这里,我首先介绍默认使用的表示法,它是 ECMAScript(通常称为 JavaScript)中使用的 ECMA 标准的一种变体。

正则表达式的语法基于具有特殊意义的字符:

|--------|--------------------------|
| 正则表达式特殊字符 ||
| . | 任意单字符(通配符(wildcard)) |
| [ | 字符类开始 |
| ] | 字符类结束 |
| { | 开始计数 |
| } | 结束计数 |
| ( | 分组开始 |
| ) | 分组结束 |
| \ | 下一个字符具有特别的意义(转义字符) |
| * | 0个或以上 |
| + | 1个或以上 |
| | 可选(0个或1个) |
| | | 替代对象(或运算) |
| ˆ | 匹配行之开始;取反操作 |
| $ | 匹配行之结束 |

例如,我们可以指定一行,以零个或多个 A 开头,后跟一个或多个 B ,然后是一个可选的C,如下所示:

ˆ A B+C?$

匹配的例子:

AAAAAAAAAAAABBBBBBBBBC

BC

B

不匹配的例子:

AAAAA // B

AAAABC // 开头出现空白

AABBCC // 太多 C

如果模式的一部分被括在括号中,则该部分被视为子模式(可以从 smatch 中单独提取)

通过添加后缀,模式可以是可选的也可以是重复的(默认是1次):

|------------|--------------------------|
| 重复 ||
| { n } | 恰好 n 次 |
| { n, } | n或以上次数 |
| {n,m} | 至少n 次且至多m次 |
| | 0 或以上次数,即 { 0, } |
| + | 1 或以上次数,即 { 1, } |
| ? | 可选(0次或1次),即 (0,1) |

例如:

A{3}B{2,4}C

匹配的例子:

AAABBC

AAABBB

不匹配的例子:

AABBC // 太少 A

AAABC // 太少 B

AAABBBBBCCC // 太多 B

在任何重复符号后添加后缀**?** 会使模式匹配器变成"惰性 (lazy)"或"非贪婪 (non-greedy)"。也就是说,在查找模式时,它会查找最短匹配,而不是最长匹配在默认情况下,模式匹配器始终查找最长匹配(类似于 C++ 的 Max Munch 规则;§10.3)。考虑:

ababab

模式 (ab) 匹配所有 ababab 。然而,(ab) ? 只匹配第一个 ab

最常见的字符分类有名称:

|------------|-------------------|
| 字符分类 ||
| alnum | 任意字母数字字符 |
| alpha | 任意字母字符 |
| blank | 任意非行分隔符的空白字符 |
| cntrl | 任意控制字符 |
| d | 任意十进制数 |
| digit | 任意十进制数 |
| graph | 任意图形字符 |
| lower | 任意小写字符 |
| print | 任意可打印字符 |
| punct | 任何标点符号字符 |
| s | 任意空白字符 |
| space | 任意空白字符 |
| upper | 任意大写字符 |
| w | 任意词字符(字母数字字符加下划线) |
| xdigit | 何意16进制数字符 |

支持几种字条类缩写:

|---------|----------------------------------------|---------------------------------|
| 缩写的字符类 |||
| \d | 一个十进制数 | [[:digit:]] |
| \s | 一个空白(空白,跳格,等等) | [[:space:]] |
| \w | 一个字母(a-z )或数字(0-9 )或下划线(_) | [_[:alnum:]] |
| \D | 非 \d | [ ˆ [:digit:]] |
| \S | 非 \s | [ ˆ [:space:]] |
| \W | 非 \w | [ ˆ _[:alnum:]] |

此外,支持正则表达式的语言通常提供:

|---------|-----------|--------------------------------|
| 非标准但(常用的)缩写字符类 |||
| \l | 一个小写字符 | [[:lower:]] |
| \u | 一个大写字符 | [[:upper:]] |
| \L | 非 \l | [ ˆ [:lower:]] |
| \U | 非 \u | [ ˆ [:upper:]] |

为了实现完全可移植性,请使用字符类名称而不是这些缩写。

例如,考虑编写一个描述 C++ 标识符的模式:一个下划线或字母,后跟一个可能为空的字母、数字或下划线序列。为了说明其中的微妙之处,我列举了一些错误的尝试:

[:alpha:][:alnum:] // : 来自集合" :alph "的字符,后跟 ...

[[:alpha:]][[:alnum:]] // : 不接受下划线 ('_' 不是字母 )

([[:alpha:]]|_)[[:alnum:]] // : 下划线也不是 alnum 的一部分

([[:alpha:]]|)([[:alnum:]]|) // , 但笨拙

[[:alpha:]][[:alnum:]] // : 在字符类中包含下划线

[[:alpha:]][[:alnum:]] // 也可

[_[:alpha:]]\w // \w 等价于 [_[:alnum:]]

最后,这里有一个函数,它使用**regex_match()**的最简单版本(§37.3.1)来测试一个字符串是否是一个标识符:

bool is_identifier(const string& s)

{

regex pat {"[_[:alpha:]]\\w "};

return regex_match(s,pat);

}

注意,为了在普通字符串字面量(literal)中包含反斜杠需要使用两个反斜杠通常,反斜杠也可以表示特殊字符

|-------------|--------------------------|
| 特殊字符**(§iso.2.14.3, §6.2.3.2)** ||
| \n | 换行 |
| \t | 跳格(制表符) |
| \\ | 一个反斜杠 |
| \xhh | 使用两个十六进制数字表示的 Unicode 字符 |
| \uhhhh | 使用四个十六进制数字表示的 Unicode 字符 |

为了增加混淆的机会,提供了反斜杠的另外两种逻辑上不同的用法:

|---------|-------------------------|
| 特殊字符**(§iso.28.5.2, §37.2.2)** ||
| \b | 一个词的第一个或最后一个字符 |
| \B | 非 \b |
| \i | 按这种模式第 i 个sub_match |

使用原始字符串字面量(raw string literals)可以缓解许多特殊字符的问题。例如:

bool is_identifier(const string& s)

{

regex pat {R"([_[:alpha:]]\w )"};

return regex_match(s,pat);

}

以下是一些模式示例:

Ax // A, Ax, Axxxx

Ax+ //Ax, Axxx Not A

\d ?\d //1-2, 12 Not 1--2

\w{2} \d{4,5} // Ab-1234, XX-54321, 22-5432 Digits are in \w

(\d :)?(\d+) // 12:3, 1:23, 123, :123 Not 123:

(bs|BS) // bs, BS Not bS

[aeiouy] // a, o, u An English vowel, not x

[ ˆ aeiouy] // x, k Not an English vowel, not e

[a ˆ eiouy] // a, ˆ, o, u An English vowel or ˆ

一个可能由sub_match 表示的group (一种子模式)由括号分隔。如果需要括号,但又不用于定义子模式,请使用 (? 而不是普通的 (。例如:

(\s|:|,) (\d ) // 后接一个数的空白 , 冒号 , / 逗号

假设我们对数前的字符(可能是分隔符)不感兴趣,我们可以写为:

(?\s|:|,) (\d ) // 后接一个数的空白 , 冒号 , / 逗号

这将使正则表达式引擎不必存储第一个字符:(? 变体只有一个子模式。

|------------------------------------------------|----------------------------------------------|
| 正则表达式分组示例 ||
| \d \s\w+ | 无分组(子模式) |
| (\d )\s(\w+) | 两个分组 |
| (\d )(\s(\w+))+ | 两个分组(分组不嵌套) |
| (\s \w )+ | 一个组,但包含一个或多个子模式; 只有最后一个子模式会保存为 sub_match |
| <(. ?)>(. ?)</\1> | 三个分组;\1表示"与第 1 组相同" |

最后一个模式对于解析 XML 非常有用。它可以查找标签/标签结束标记。请注意,我使用了非贪婪匹配(惰性匹配),.∗? 作为标签和结束标签之间的子模式。如果我使用普通的**.∗**,则此输入会导致问题:

Always look for the <b>bright</b> side of <b>life</b>.

对第一个子模式进行贪婪匹配会将第一个 < 与最后一个**>** 匹配。对第二个子模式进行贪婪匹配会将第一个**<b>** 与最后一个**</b>** 匹配。这两种行为都是正确的,但不太可能是程序员想要的。

可以使用选项(§37.2)改变正则表达式符号的细节。例如,如果使用 regex_constants::grep ,则 a?x:y 是一个由五个普通字符组成的序列,因为**?** 在 grep中不表示"可选"。

有关正则表达式的更详尽介绍,请参阅 [Friedl,1997]。

37.2 regex

正则表达式是由字符序列(例如 string ) 构成的匹配引擎(通常是状态机)

template<class C, class traits = regex_traits<C>>

class basic_regex {

public:

using value_type = C;

using traits_type = traits;

using string_type = typename traits::string_type;

using flag_type = regex_constants::syntax_option_type;

using locale_type = typename traits::locale_type;

˜ basic_regex(); // not virtual; basic_regex is not meant to be used as a base class

// ...

};

regex_traits见 §37.5 。

string 一样,regex使用 char 版本的别名:

using regex = basic_regex<char>;

正则表达式模式的含义由regex_constantsregex 中定义相同的 syntax_option_type 常量控制:

|----------------|--------------------------------------------------------|
| basic_regex<C,Tr> 成员**(** syntax_option_type , §iso.28.5.1) ||
| icase | 匹配时不区分大小写 |
| nosubs | 匹配结果中不存储任何子表达式匹配 |
| optimize | 优先考虑快速匹配而不是快速正则表达式对象构造 |
| collate | 形式为 [a b] 的字符范围是局部敏感的 |
| ECMAScript | 正则表达式语法是 ECMAScript 在 ECMA-262 中使用的语法(略有修改;§iso.28.13) |
| basic | 正则表达式语法是 POSIX 中基本正则表达式所使用的语法 |
| extended | 正则表达式语法是 POSIX 中的扩展正则表达式所使用的语法 |
| awk | 正则表达式语法是 POSIX awk 使用的语法 |
| grep | 正则表达式语法是 POSIX grep 使用的语法 |
| egrep | 正则表达式语法是 POSIX egrep 使用的语法 |

除非有充分的理由,否则请使用默认值。充分的理由包括大量现有的正则表达式都采用了非默认的表示法。

regex 对象可以由string或类似的字符序列构造:

|--------------------------------|------------------------------------------------------------------------------------------------------------------|
| basic_regex<C,Tr> 构造函数**(§iso.28.8.2)** ||
| basic_regex r {}; | 默认构造函数:一个空模式; 标志设置为regex_constants::ECMAScript |
| basic_regex r {x,flags}; | x 可以是 basic_regexstring 、C 风格字符串或一个带有 flags 定义的符号的 initializer_list<value_type>;explicit |
| basic_regex r {x}; | basic_regex{x,reg ex_constants::ECMAScript} ; explicit |
| basic_regex r {p,n,flags}; | 使用flags 定义的符号,从 [p:p+n)中的字符构造 r |
| basic_regex r {p,n}; | basic_regex{p,n,reg ex_constants::ECMAScript} |
| basic_regex r {b,e ,flags} | 使用
flags
定义的符号,根据**[b:e)** 中的字符构造r |
| basic_regex r {b,e}; | basic_regex{b,e ,regex_constants::ECMAScript} |

regex 的主要用途贯穿搜索、匹配和替换功能(§37.3),但正则表达式本身也有一些操作:

|----------------------------|------------------------------------------------------------------------------------------------|
| basic_regex<C,Tr> 操作**(§iso.28.8)** ||
| r=x | 复制赋值:x 可以是 basic_regex ,C风格字符串,basic_string ,或者initializer_list<value_type> |
| r=move(r2) | 移动赋值 |
| r=r.assign(r2) | 复制或移动 |
| r=r.assign(x,flags) | 复制或移动;将 r 的标志设置为标志 x 可以是 basic_string 、C 样式字符串或 initializer_list<value_type> |
| r=r.assign(x) | r=r.assign(x,reg ex_constants::ECMAScript) |
| r=r.assign(p,n,flags) | 将 r 的模式设置为 [p:p+n) ,并将 r 的标志设置为 flags |
| r=r.assign(b,e ,flags) | 将r 的模式设置为**[b:e)** ,将 r 的标志设置为flags |
| r=r.assign(b,e) | r=r.assign(b,e ,regex_constants::ECMAScript) |
| n=r.mark_count() | nr 中标记子表达式的数量 |
| x=r.flags() | xr的标志 |
| loc2=r.imbue(loc) | r 获取语言环境 loc loc2r之前的语言环境 |
| loc=r.g etloc() | locr的语言环境 |
| r.swap(r2) | 交换rr2的值 |

你可以通过调用 getloc() 来确定localeregex ,并通过**flags()**了解所使用的标志,但遗憾的是,没有(标准)方法来读取其模式。如果您需要输出模式,请保留用于初始化的字符串的副本。例如:

regex pat1 {R"(\w+\d )"}; // 无法输出 pat1 中的模式

string s {R"(\w+\d )"};

regex pat2 {s};

cout << s << '\n'; // pat2 中的模式

37.2.1 匹配结果(Match Results)

正则表达式匹配的结果收集于match_results 对象中,该对象包含一个或多个 sub_match对象:

template<class Bi>

class sub_match : public pair<Bi,Bi> {

public:

using value_type = typename iterator_traits<Bi>::value_type;

using difference_type = typename iterator_traits<Bi>::difference_type;

using iterator = Bi;

using string_type = basic_string<value_type>;

bool matched; // true if *this contains a match

// ...

};

Bi 必须是双向迭代器 (§33.1.2)。sub_match可以看作是一对迭代器,指向被匹配的字符串。

|----------------------|-------------------------------------------------------------------------------------|
| sub_match<Bi>操作 ||
| sub_match sm {}; | 默认构造函数:一个空序列;constexpr |
| n=sm.length() | n 是匹配的字符数 |
| s=sm | 将 sub_match 隐式转换为 basic_strings 是包含匹配字符的 basic_string |
| s=sm.str() | s 是包含匹配字符的 basic_string |
| x=sm.compare(x) | 字典顺序比较:sm.str().compare(x); x 可以是 sub_matchbasic_string 或 C 风格字符串 |
| x==y | x 等于
y
吗?xy 可以是 sub_matchbasic_string |
| x!=y | !(x==y) |
| x<y | x 按字典顺序位于y 之前 |
| x>y | y<x |
| x<=y | !(x>y) |
| x>=y | !(x<y) |
| sm.matched | 如果sm 包含匹配项,则为 true ;否则为 false |

例如:

regex pat ("<(. ?)>(. ?)</(. ?)>");

string s = "Always look for the <b> bright </b> side of <b> death </b>";

if (regex_search(s1,m,p2))

if (m[1]==m[3]) cout << "match\n";

输出是 match

一个match_results 是一个sub_match的容器:

template<class Bi, class A = allocator<sub_match<Bi>>

class match_results {

public:

using value_type = sub_match<Bi>;

using const_reference = const value_type&;

using reference = const_reference;

using const_iterator = /* implementation-defined */;

using iterator = const_iterator;

using difference_type = typename iterator_traits<Bi>::difference_type;

using size_type = typename allocator_traits<A>::size_type;

using allocator_type = A;

using char_type = typename iterator_traits<Bi>::value_type;

using string_type = basic_string<char_type>;

˜match_results(); // not virtual

// ...

};

Bi 必须是双向迭代器(§33.1.2)。

basic_stringbasic_ostream 一样,为最常见的match_results提供了一些标准别名:

using cmatch = match_results<const char >; //C-style string

using wcmatch = match_results<const wchar_t >; //wide C-style string

using smatch = match_results<string::const_iterator>; // string

using wsmatch = match_results<wstring::const_iterator>; // wstring

match_results 提供对其匹配字符串、其 sub_matches以及匹配前后的字符的访问:

match_results提供了一组常规操作:

|-----------------------------|----------------------------------------------------------------------------------------------------------------------------------|
| regex<C,Tr> 匹配和子匹配(§iso.28.9, §iso.28.10) ||
| match_results m {}; | 默认构造函数:使用 allocator_type{} |
| match_results m {a}; | 使用分配器a;explicit |
| match_results m {m2}; | 复制和移动构造函数 |
| m2=m | 复制赋值 |
| m2=move(m) | 移动赋值 |
| m. ˜match_results() | 析构函数:释放所有资源 |
| m.ready() | m 是否完全匹配? |
| n=m.size() | n 1m 中子表达式的数量;如果没有匹配,则n==0 |
| n=m.max_size() | nm 的最大可能的sub_matches数量 |
| m.empty() | m.size()==0? |
| r=m[i] | rm 中第isub_match的常量引用; m[0]表示完全匹配; 如果 i>= size() m[i] 指向表示未匹配子表达式的sub_match |
| n=m.length(i) | n=m[i].length();m[i]的字符个数 |
| n=m.length() | n=m.length(0) |
| pos=m.position(i) | pos=m[i].first;m[i]的第一个字符 |
| pos=m.position() | pos=position(0) |
| s=m.str(i) | s=m[i].str();m[i]的字符串表示 |
| s=m.str() | s=m.str(0) |
| sm=m.prefix() | sms 是一个
sub_match
,表示输入字符串中匹配项之前未与
m
匹配的字符 |
| sm=m.suffix() | sms 一个
sub_match
,表示输入字符串中匹配结果之后未与m匹配的字符 |
| p=m.begin() | p 指向m 的第一个 sub_match |
| p=m.end() | p 指向m的最后一个子匹配项 |
| p=m.cbegin() | p 指向m 的第一个sub_match(const迭代器) |
| p=m.cend() | p 指向m 的最后一个子匹配项之后的子匹配项(const 迭代器**)** |
| a=m.get_allocator() | am的分配器 |
| m.swap(m2) | 交换 mm2 的状态 |
| m==m2 | mm2sub_matches值是否相等? |
| m!=m2 | !(m==m2) |

我们可以通过对 regex_match 添加下标来访问sub_match ,例如 m[i] 。如果下标 i 指向一个不存在的sub_match ,则 m[i] 的结果表示该sub_match不匹配。例如:

void test()

{

regex pat ("(AAAA)(BBB)?");

string s = "AAAA";

smatch m;

regex_search(s,m,pat);

cout << boolalpha;

cout << m[0].matched << '\n'; // true: we found a match

cout << m[1].matched << '\n'; // true: there was a first sub_match

cout << m[2].matched << '\n'; // false: no second sub_match

cout << m[3].matched << '\n'; // false: there couldn't be a third sub_match for pat

}

37.2.2 格式化(Formatting)

regex_replace() 中,格式化是使用 **format()**函数完成的:

|----------------------------------|---------------------------------------------------------------------------------------|
| regex<C,Tr> 格式化(§iso.28.10.5) 格式由match_flag_type选项控制 ||
| out=m.format(out,b,e ,flags) | 将**[b:e)** 复制到out m中的子匹配替换格式字符 |
| out=m.format(out,b,e) | out=m.format(out,b,e ,regex_constants::format_default) |
| out=m.format(out,fmt,flags) | out=m.format(out,begin(fmt),end(fmt),flags); fmt 可以是basic_string或 C 风格字符串 |
| out=m.format(out,fmt) | out=m.format(out,fmt,regex_constants::format_default) |
| s=m.format(fmt,flags) | 将s 构造为fmt的副本; 用m中的子匹配替换格式字符; fmt 可以是basic_string或 C 风格字符串 |
| s=m.format(fmt) | s=m.format(fmt,reg ex_constants::format_default) |

格式可以包含格式字符:

|-------------|------------------------|
| 格式替换符号 ||
| \&** | 匹配 | | ** ' | 前缀 |
| ** **'** | 后缀 | | **i | 第i 个子匹配,例如**1** | | **ii** | 第ii 个子匹配,例如**12** | | **** | 不匹配,****字符 |

有关示例,请参阅§37.3.3。

format() 完成的格式化细节由一组选项(标志)控制:

|-----------------------|-----------------------------------------------|
| regex<C,Tr> 格式化操作(regex_constants::match_flag_type ; §iso.28.5.2) ||
| format_default | 使用ECMAScript (ECMA-262)规则(§iso.28.13) |
| format_sed | 使用POSIX sed符号 |
| format_no_copy | 仅复制匹配项 |
| format_first_only | 仅替换正则表达式的第一次出现 |

37.3 正则表达式函数

将正则表达式模式应用于数据的函数包括:regex_search() (用于在字符序列中搜索)、regex_match() (用于匹配固定长度的字符序列)以及regex_replace()(用于执行模式替换)。

匹配的细节由一组选项(标志)控制:

|----------------------|-------------------------------------|
| regex<C,Tr> 匹配选项**(** regex_constants::match_flag_type ; §iso.28.5.2) ||
| match_not_bol | 字符 ˆ 不被视为表示"行首" |
| match_not_eol | 字符 $ 不被视为表示"行尾" |
| match_not_bow | \b 与子序列 [first,first) 不匹配 |
| match_not_eow | \b 与子序列 [last,last)不匹配 |
| match_any | 如果有多个匹配可能,则任何匹配都是可以接受的 |
| match_not_null | 不匹配空序列 |
| match_continuous | 仅匹配从
first
开始的子序列 |
| match_prev_avail | −− first是有效的迭代器位置 |

37.3.1 regex_match()

要求得与已知长度的整个序列(例如一行文本)匹配的模式,请使用 regex_match()

|-----------------------------------|-----------------------------------------------------------------------------------------------------------|
| 正则表达式匹配(§iso.28.11.2) 匹配由match_flag_type 选项控制(§37.3) ||
| regex_match(b,e ,m,pat,flags) | 输入 [b:e) 是否与正则表达式模式pat匹配? 将结果放入 match_results m;使用选项标志 |
| regex_match(b,e ,m,pat) | regex_match(b,e ,m,pat,regex_constants::match_default) |
| regex_match(b,e ,pat,flags) | 输入 [b:e) 是否与正则表达式模式pat 匹配?使用选项标志 |
| regex_match(b,e ,pat) | regex_match(b,e ,pat,regex_constants::match_default) |
| regex_match(x,m,pat,flags) | 输入 x 是否与正则表达式模式 pat 匹配? x 可以是 basic_string或 C 语言风格的字符串; 将结果放入 match_results m中;使用选项标志 |
| regex_match(x,m,pat) | regex_match(x,m,pat,regex_constants::match_default) |
| regex_match(x,pat,flags) | 输入 x 是否与正则表达式模式 pat 匹配? x 可以是 basic_string或 C 语言风格的字符串;请使用选项标志 |
| regex_match(x,pat) | regex_match(x,pat,regex_constants::match_default) |

举个例子,考虑一个用于验证表格格式的简单程序。如果表格格式符合预期,程序会向 cout 写入"一切正常";如果不符合预期,程序会向cerr写入错误消息。表格由一系列行组成,每行包含四个以制表符分隔的字段,但第一行(标题行)可能只有三个字段。例如:

Class-----------Boys---------------Girls----------------Total

1a----------------12------------------- 15 -------------------27

1b ---------------16 -------------------14 -------------------30

Total ------------28 -------------------29 -------------------57

这些数字应该在水平和垂直方向上相加。

程序读取标题行,然后对每一行进行求和,直到最后一行标记为"总计":

int main()

{

ifstream in("table .txt"); // input file

if (!in) cerr << "no file\n";

string line; // input buffer

int lineno = 0;

regex header {R"( ˆ[\w ]+(\t[\w ]+) $)"}; //tab-separated words

regex row {R"( ˆ([\w ]+)(\t\d+)(\t\d+)(\t\d+)$)"}; // label followed by three tab-separated numbers

if (getline(in,line)) { // check and discard the header line

smatch matches;

if (!regex_match(line ,matches,header))

cerr << "no header\n";

}

int boys = 0; // running totals

int girls = 0;

while (getline(in,line)) {

++lineno;

smatch matches; //submatches go here

if (!regex_match(line ,matches,row))

cerr << "bad line: " << lineno << '\n';

int curr_boy = stoi(matches[2]); // for stoi() see § 36.3.5

int curr_girl = stoi(matches[3]);

int curr_total = stoi(matches[4]);

if (curr_boy+curr_girl != curr_total) cerr << "bad row sum \n";

if (matches[1]=="Total") { // last line

if (curr_boy != boys) cerr << "boys do not add up\n";

if (curr_girl != girls) cerr << "girls do not add up\n";

cout << "all is well\n";

return 0;

}

boys += curr_boy;

girls += curr_girl;

}

cerr << "didn't find total line\n")

return 1;

}

37.3.2 regex_search()

要在序列的一部分(例如文件)中查找模式,请使用 regex_search()

|------------------------------------|-----------------------------------------------------------------------------------------------------------|
| 正则表达式搜索(§iso.28.11.3) 匹配由match_flag_type 选项控制(§37.3) ||
| regex_search(b,e ,m,pat,flags) | 输入 [b:e) 是否包含正则表达式模式pat 的匹配项?将结果放入 match_results m;使用选项标志 |
| regex_search (b,e ,m,pat) | regex_search(b,e ,m,regex_constants::match_default) |
| regex_search (b,e ,pat,flags) | 输入 [b:e) 是否包含正则表达式模式 pat 的匹配项?请使用选项标志 |
| regex_search (b,e ,pat) | regex_search(b,e ,pat,regex_constants::match_default) |
| regex_search (x,m,pat,flags) | 输入 x 是否包含正则表达式模式pat的匹配项? x 可以是basic_string或 C 语言风格的字符串; 将结果放入 match_results m中;使用选项标志 |
| regex_search (x,m,pat) | regex_search(x,m,pat,regex_constants::match_default) |
| regex_search (x,pat,flags) | 输入 x 是否包含正则表达式模式 pat 的匹配项? x 可以是 basic_string或 C 语言风格的字符串;请使用选项标志 |
| regex_search (x,pat) | regex_search(x,pat,regex_constants::match_default) |

例如,我可以像这样查找我的名字的一些更常见的拼写错误:

regex pat {"[Ss]tro?u?v?p?stra?o?u?p?b?"};

smatch m;

for (string s; cin>>s; )

if (regex_search(s,m,pat))

if (m[0]!="stroustrup" && m[0]!="Stroustrup" )

cout << "Found: " << m[0] << '\n';

给定合适的输入,这将输出 Stroustrup的拼写错误,例如:

Found: strupstrup

Found: Strovstrup

Found: stroustrub

Found: Stroustrop

请注意,即使模式"隐藏"在其他字符中,regex_search() 也能找到它。例如,它会在 abstrustrubal 中找到 strustrub 。如果你想将模式与输入字符串中的每一个字符匹配,请使用regex_match(§37.3.1)。

37.3.3 regex_replace()

要对序列的一部分(例如文件)中的模式进行简单替换,请使用 regex_replace()

|-----------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 正则表达式替换(§iso.28.11.4) 匹配由match_flag_type 选项控制(§37.3) ||
| out=regex_replace(out,b,e ,pat,fmt,flags) | 将 [b:e) 复制到 out, 搜索正则表达式模式 pat; 当找到 pat 的匹配项时, 使用fmt 格式将其复制到 out, 由 flags控制; fmt 可以是basic_string或 C 风格字符串 |
| out=regex_replace(out,b,e ,pat,fmt) | out=reg ex_replace(out,b,e ,pat,fmt, regex_constants::match_defaults) |
| s=regex_replace(x,pat,fmt,flags) | 将 x 复制到 s ,搜索正则表达式模式 pat; 当找到 pat 的匹配项时,使用 fmt 格式将其复制到 s,由标志控制; x 可以是basic_string或 C 风格的字符串; fmt 可以是basic_string或 C 风格的字符串 |
| s=regex_replace(x,pat,fmt) | s=regex_replace(x,pat,fmt, regex_constants::match_defaults) |

复制格式是使用regexformat() (§37.2.2) 和**** 前缀符号来完成的,例如,**&** 表示匹配,$2 表示第二个子匹配。这里有一个小测试程序,它接受一个包含单词和数字对的字符串,并将它们输出为 {word,number},每行一个:

void test1()

{

string input {"x 1 y2 22 zaq 34567"};

regex pat {"(\w+)\s(\d+)"}; // word space number

string format {"{1,2}\n"};

cout << regex_replace(input,pat,format);

}

输出结果是:

{x,1}

{y2,22}

{zaq,34567}

请注意行首那些恼人的"虚假"空格。默认情况下,regex_match() 会将未匹配的字符复制到其输出中,因此 pat未匹配的两个空格会被打印出来。

为了消除这些空格,我们可以使用format_no_copy选项(§37.2.2):

cout << regex_replace(input,pat,format,reg ex_constants::format_no_copy);

现在,我们得到:

{x,1}

{y2,22}

{zaq,34567}

子匹配不必按顺序输出:

void test2()

{

string input {"x 1 y 2 z 3"};

regex pat {"(\w)\s(\d+)"}; // word space number

string format {"2: 1\n"};

cout << regex_replace(input,pat,format,reg ex_constants::format_no_copy);

}

我们得到:

1: x

22: y2

34567: zeq

37.4 正则表达式迭代器

regex_search() 函数允许我们在数据流中查找某个模式的单个匹配项。如果我们想查找并处理所有此类匹配项,该怎么办?如果数据被组织成易于识别的行或记录序列,我们可以迭代这些行或记录,并对每个行或记录使用regex_match() 。如果我们想对每个模式的匹配项执行简单的替换,可以使用regex_replace() 。如果我们想迭代字符序列,并对每个模式的匹配项执行操作,可以使用regex_iterator

37.4.1 regex_iterator

regex_iterator是一个双向迭代器,当迭代的时候,它在序列中搜索下一个模式匹配项:

template<class Bi,

class C = typename iterator_traits<Bi>::value_type ,

class Tr = typename regex_traits<C>::type>

class regex_iterator {

public:

using regex_type = basic_regex<C,Tr>;

using value_type = match_results<Bi>;

using difference_type = ptrdiff_t;

using pointer = const value_type ;

using reference = const value_type&;

using iterator_category = forward_iterator_tag;

// ...

}

regex_traits在§37.5 中有描述。

提供了常用的别名集:

using cregex_iterator = regex_iterator<const char >;

using wcregex_iterator = regex_iterator<const wchar_t >;

using sregex_iterator = regex_iterator<string::const_iterator>;

using wsregex_iterator = regex_iterator<wstring::const_iterator>;

regex_iterator提供了一组最小的迭代器操作:

|---------------------------------------|---------------------------------------------------------|
| regex_iterator<Bi,C,Tr> (§iso.28.12.1) ||
| regex_iterator p {}; | p 是序列尾 |
| regex_iterator p {b,e,pat,flags); | 遍历 [b:e) ,使用选项标志寻找pat 的匹配项 |
| regex_iterator p {b,e,pat); | p 用**{b,e,pat,regex_constants::match_default}** 初始化 |
| regex_iterator p {q}; | 复制构造函数(无移动构造函数) |
| p=q | 赋值构造函数(无移动赋值函数) |
| p==q | p 是否指向与q 相同的 sub_match? |
| p!=q | !(p==q) |
| c= p | c 是当前的sub_match |
| x=p −>m | x=( p).m |
| ++p | 使p 指向p 模式的下一个出现位置 |
| q=p++ | q=p , 然后 ++p |

regex_iterator 是双向迭代器,因此我们不能直接在 istream上进行迭代。

例如,我们可以输出string中所有以空格分隔的单词:

void test()

{

string input = "aa as; asd ++e ˆasdf asdfg";

regex pat {R"(\s+(\w+))"};

for (sreg ex_iterator p(input.begin(),input.end(),pat); p!=sregex_iterator{}; ++p)

cout << ( p)[1] << '\n';

}

输出:

as

asd

asdfg

注意,我们遗漏了第一个单词 aa ,因为它前面没有空格。如果我们将模式简化为 R"((\ew+))",我们得到

aa

as

asd

e

asdf

asdfg

你不能通过 regex_iterator 进行写入,并且 **regex_iterator{}**是唯一可能的序列尾。

37.4.2 regex_token_iterator

regex_token_iteratorregex_iterator 的适配器,它对找到的 match_resultssub_matches进行迭代:

template<class Bi,

class C = typename iterator_traits<Bi>::value_type ,

class Tr = typename regex_traits<C>::type>

class regex_token_iterator {

public:

using regex_type = basic_regex<C,Tr>;

using value_type = sub_match<Bi>;

using difference_type = ptrdiff_t;

using pointer = const value_type ;

using reference = const value_type&;

using iterator_category = forward_iterator_tag;

// ...

regex_traits在§37.5 中有描述。

常用的别名集如下:

using cregex_token_iterator = regex_token_iterator<const char >;

using wcregex_token_iterator = regex_token_iterator<const wchar_t >;

using sregex_token_iterator = regex_token_iterator<string::const_iterator>;

using wsregex_token_iterator = regex_token_iterator<wstring::const_iterator>;

regex_token_iterator提供了一组最小的迭代器操作:

|-----------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| regex_token_iterator (§iso.28.12.2) ||
| regex_token_iterator p {}; | p是序列尾 |
| regex_token_iterator p {b,e,pat,x,flags}; | x 列出要包含在迭代中的sub_matche 的索引,或者 0 ,表示"整个匹配",或者 -1 ,表示"将每个未匹配的字符序列表示为 sub_match"; x 可以是 intinitializer_list<int>constvector<int>&const int (&sub_match)[N] |
| regex_token_iterator p {b,e,pat,x}; | p 初始化为 {b,e,pat,x,regex_constants::match_default} |
| regex_token_iterator p {b,e,pat}; | p初始化为 {b,e,pat,0,regex_constants::match_default} |
| regex_token_iterator p {q}; | 复制构造函数(无移动构造函数) |
| p. ˜regex_token_iterator() | 析构函数:释放所有资源 |
| p=q | 复制赋值(无移动赋值) |
| p==q | p 是否指向与 q 相同的 sub_match? |
| p!=q | !(p==q) |
| c= p | c 是当前的sub_match |
| x=p −>m | x=( p).m |
| ++p | 让 p 指向 p模式的下一个出现位置 |
| q=p++ | q=p ++p |

x 参数列出了要包含在迭代中的sub_match 。例如(迭代匹配项13 )

void test1()

{

string input {"aa::bb cc::dd ee::ff"};

regex pat {R"((\w+)([[:punct:]]+)(\w+)\s )"};

sregex_token_iterator end {};

for (sreg ex_token_iterator p{input.begin(),input.end(),pat,{1,3}}; p!=end; ++p)

cout << p << '\n';

}

输出结果:

aa

bb

cc

dd

ee

ff

-1 选项基本上颠倒了报告匹配的策略,将每个不匹配的字符序列表示为一个 sub_match 。这通常被称为token 拆分(即将字符流拆分成 token ),因为当你的模式匹配 token 分隔符时,选项 -1 会保留 token。例如:

void test2()

{

string s {"1,2 , 3 ,4,5, 6 7"}; // input

regex pat {R"(\s ,\s )"}; // 用逗号作分隔符

copy(sreg ex_token_iterator{s.begin(),s.end(),pat, −1)},

sregex_token_iterator{},

ostream_iterator<string>{cout,"\n"});

}

输出:

1

2

3

4

5

6 7

这可以使用显式循环等效地编写:

void test3()

{

sregex_token_iterator end{};

for (sreg ex_token_iterator p {s.begin(),s.end(),pat, −1}; p!=end; ++p)

cout << p << '\n';

}

37.5 regex_traits

regex_traits<T> 表示regex实现者所需的字符类型、字符串类型和语言环境之间的对应关系:

template<class C>

struct regex_traits {

public:

using char_type = C;

using string_type = basic_string<char_type>;

using locale_type = locale;

using char_class_type = /* implementation-defined bitmask type */;

// ...

};

标准库提供了特化的 regex_traits<char>regex_traits<wchar_t>

|-------------------------------------|-----------------------------------------------------------------------|
| regex_traits<C> 操作(§iso.28.7) ||
| regex_traits tr {}; | 创建默认的 regex_trait<C> |
| n=length(p) | n 是 C 风格字符串 p 中的字符数; n=char_traits<C>::length(); static |
| c2=tr.translate(c) | c2=c,即无操作 |
| c2=tr.translate_nocase(c) | use_facet<ctype<C>>(getloc()).tolower(c); §39.4.5 |
| s=tr.transform(b,e) | sis 是一个可用于与其他字符串进行比较的字符串**[b:e)** § 39.4.1 |
| s=tr.transform_primary(b,e) | s 是一个字符串,可用于将**[b:e)与其他字符串进行比较;忽略大小写;§39.4.1 |
| s=tr.lookup_collatename(b,e) | s 是排序元素的
string** 名称(名为 [b:e)]或空字符串 |
| m=tr.lookup_classname(b,e ,ign) | m 是字符分类**[b:e)的分类掩码的字符串名称; 如果ign==true**,则忽略大小写 |
| m=tr.lookup_classname(b,e) | m=tr.lookup_classname(b,e ,false) |
| tr.isctype(c,m) | c 是否被归类为 mmclass_type |
| i=tr.value(c,b) | iiscb为底数表示的整数值; b 必须是8 1016 |
| loc2=tr.imbue(loc) | 将 tr 的本地化设置为 locloc2tr之前的本地化 |
| loc=tr.g etloc() | loctr的本地化 |

转换用于生成字符串,以便在模式匹配实现中进行快速比较。

分类名称是 §37.1.1 中列出的字符分类之一,例如 alphasxdigit

37.6 建议

1\] 大多数常规正则表达式用法都使用**regex**;§37.1。 \[2\] 正则表达式的表示法可以调整以符合各种标准;§37.1.1,§37.2。 \[3\] 默认的正则表达式表示法是 ECMAScript 的表示法;§37.1.1。 \[4\] 为了便于移植,请使用字符类表示法以避免使用非标准缩写;§37.1.1。 \[5\] 保持克制;正则表达式很容易变成只写语言;§37.1.1。 \[6\] 除了最简单的模式外,最好使用原始字符串字面量来表达所有模式;§37.1.1。 \[7\] 请注意,**\\i** 允许你根据前一个子模式来表达子模式;§37.1.1。 \[8\] 使用 **?**使模式变得"惰性"; §37.1.1,§37.2.1。 \[9\] **regex**可以使用 ECMAScript、POSIX、awk、grep 和 egrep 表示法;§37.2。 \[10\] 保留模式字符串的副本,以备需要输出;§37.2。 \[11\] 使用 **regex_search()** 查找字符流,使用 **regex_match()** 查找固定布局;§37.3.2,§37.3.1。 **内容来源:** **\<\\> 第4版,作者 Bjarne Stroustrup**

相关推荐
雷达学弱狗2 小时前
正则表达式,字符串的搜索与替换
正则表达式
良木林2 小时前
JS中正则表达式的运用
前端·javascript·正则表达式
雷达学弱狗3 小时前
正则表达式与转义符的区别。注意输入的东西经过了一次转义,一次正则表达式。\\转义是单斜杠\\在正则表达式也是单斜杠所以\\\\经过两道门才是字符单斜杠
正则表达式
青草地溪水旁3 小时前
C/C++中的可变参数 (Variadic Arguments)函数机制
c语言·c++·可变参数
A尘埃3 小时前
智能工单路由系统(Java)
java·开发语言·智能工单
汉克老师3 小时前
第十四届蓝桥杯青少组C++选拔赛[2023.2.12]第二部分编程题(1、求和)
c++·蓝桥杯·蓝桥杯c++·c++蓝桥杯
Source.Liu3 小时前
【Python基础】 13 Rust 与 Python 注释对比笔记
开发语言·笔记·python·rust
qq_195551693 小时前
代码随想录70期day3
开发语言·python
XXYBMOOO3 小时前
Qt UDP 通信类详解与实现
开发语言·网络·c++·qt·网络协议·ui·udp