Boost C++ 库

...世界上备受推崇和精心设计的 C++ 库项目之一。 Herb SutterAndrei AlexandrescuC++ 编码标准

用户指南 - Boost C++ 函数库
PrevUpHomeNext

本节介绍如何使用 xpressive 完成文本操作和解析任务。如果您正在寻找有关 xpressive 中特定组件的详细信息,请查看参考部分。

什么是 xpressive?

xpressive 是一个正则表达式模板库。正则表达式(regexes)可以写成在运行时动态解析的字符串(动态正则表达式),或者写成在编译时解析的表达式模板[30](静态正则表达式)。动态正则表达式的优点是它们可以在运行时作为用户输入接受或从初始化文件中读取。静态正则表达式有几个优点。由于它们是 C++ 表达式而不是字符串,因此可以在编译时进行语法检查。此外,它们可以自然地引用程序中其他地方的代码和数据,使您能够在正则表达式匹配中回调到您的代码。最后,由于它们是静态绑定的,编译器可以为静态正则表达式生成更快的代码。

xpressive 的双重性质是独特而强大的。静态 xpressive 有点像 Spirit 解析器框架。像 Spirit 一样,您可以使用表达式模板构建带有静态正则表达式的语法。(与 Spirit 不同,xpressive 执行穷尽回溯,尝试所有可能性来查找模式匹配。)动态 xpressive 有点像 Boost.Regex。事实上,xpressive 的接口应该对任何使用过 Boost.Regex 的人来说都很熟悉。xpressive 的创新之处在于允许您在同一个程序中,甚至在同一个表达式中混合和匹配静态和动态正则表达式!您可以将动态正则表达式嵌入到静态正则表达式中,或者反之,嵌入的正则表达式将完全参与搜索,根据需要回溯以使匹配成功。

你好,世界!

理论足够了。让我们看看 Hello World,xpressive 风格

#include <iostream>
#include <boost/xpressive/xpressive.hpp>

using namespace boost::xpressive;

int main()
{
    std::string hello( "hello world!" );

    sregex rex = sregex::compile( "(\\w+) (\\w+)!" );
    smatch what;

    if( regex_match( hello, what, rex ) )
    {
        std::cout << what[0] << '\n'; // whole match
        std::cout << what[1] << '\n'; // first capture
        std::cout << what[2] << '\n'; // second capture
    }

    return 0;
}

该程序输出如下:

hello world!
hello
world

您会注意到的第一件事是 xpressive 中的所有类型都存在于 boost::xpressive 命名空间中。

[Note] 注意

本文档中其余的大多数示例将省略 using namespace boost::xpressive; 指令。就假装它在那里。

接下来,您会注意到正则表达式对象的类型是 sregex。如果您熟悉 Boost.Regex,这与您习惯的不同。“s”在“sregex”中代表“string”,表示此正则表达式可用于在 std::string 对象中查找模式。我将在后面详细讨论这种差异及其含义。

注意正则表达式对象是如何初始化的

sregex rex = sregex::compile( "(\\w+) (\\w+)!" );

要从字符串创建正则表达式对象,您必须调用工厂方法,例如 basic_regex<>::compile()。这是 xpressive 与其他面向对象的正则表达式库不同的另一个领域。其他库鼓励您将正则表达式视为一种增强的字符串。在 xpressive 中,正则表达式不是字符串;它们是领域特定语言中的小程序。字符串只是该语言的一种表示。另一种表示是表达式模板。例如,上面的代码行等价于以下内容

sregex rex = (s1= +_w) >> ' ' >> (s2= +_w) >> '!';

这描述了相同的正则表达式,只是它使用了静态 xpressive 定义的领域特定嵌入式语言。

如您所见,静态正则表达式的语法与标准 Perl 语法明显不同。这是因为我们受到 C++ 语法的限制。最大的不同是使用 >> 表示“后面跟着”。例如,在 Perl 中,您可以将子表达式并排放置

abc

但在 C++ 中,子表达式之间必须有一个运算符分隔

a >> b >> c

在 Perl 中,括号 () 具有特殊含义。它们分组,但作为副作用,它们还会创建像 $1$2 这样的反向引用。在 C++ 中,无法重载括号以赋予它们副作用。为了达到相同的效果,我们使用特殊的 s1s2 等标记。赋值给其中一个以创建反向引用(在 xpressive 中称为子匹配)。

您还会注意到一个或多个重复运算符 + 已从后缀位置移到前缀位置。这是因为 C++ 没有后缀 + 运算符。所以

"\\w+"

与此相同

+_w

我们将在稍后介绍所有其他差异。

获取 xpressive

有两种方法可以获取 xpressive。第一种也是最简单的方法是下载最新版本的 Boost。只需访问 http://sf.net/projects/boost 并点击“下载”链接。

第二种方法是直接访问 Boost Subversion 存储库。只需访问 http://svn.boost.org/trac/boost/ 并按照那里的说明进行匿名 Subversion 访问。Boost Subversion 中的版本是不稳定的。

使用 xpressive 构建

Xpressive 是一个仅包含头文件的模板库,这意味着您无需更改构建脚本或链接到任何单独的 lib 文件即可使用它。您只需 #include <boost/xpressive/xpressive.hpp>。如果您只使用静态正则表达式,则可以通过只包含 xpressive_static.hpp 来缩短编译时间。同样,如果您只打算使用动态正则表达式,则可以包含 xpressive_dynamic.hpp

如果您还想在静态正则表达式中使用语义动作或自定义断言,则需要额外包含 regex_actions.hpp

要求

Xpressive 要求 Boost 版本为 1.34.1 或更高。

支持的编译器

目前,已知 Boost.Xpressive 在以下编译器上工作

  • Visual C++ 7.1 及更高版本
  • GNU C++ 3.4 及更高版本
  • Intel for Linux 8.1 及更高版本
  • Intel for Windows 10 及更高版本
  • tru64cxx 71 及更高版本
  • MinGW 3.4 及更高版本
  • HP C/aC++ A.06.14

请查看 Boost 回归测试结果页面上的最新测试结果。

[Note] 注意

请将任何问题、意见和错误报告发送至 eric <at> boost-consulting <dot> com。

您无需了解太多即可开始高效使用 xpressive。让我们从 xpressive 提供的类型和算法的简要介绍开始。

表 43.1. xpressive 的工具箱

工具

描述

basic_regex<>

包含一个已编译的正则表达式。basic_regex<> 是 xpressive 中最重要的类型。您使用 xpressive 所做的所有事情都将从创建 basic_regex<> 类型的对象开始。

match_results<>, sub_match<>

match_results<> 包含 regex_match()regex_search() 操作的结果。它类似于 sub_match<> 对象的向量。sub_match<> 对象包含一个标记的子表达式(在 Perl 中也称为反向引用)。它本质上只是表示标记子表达式的开头和结尾的一对迭代器。

regex_match()

检查字符串是否与正则表达式匹配。对于 regex_match() 要成功,整个字符串必须从头到尾与正则表达式匹配。如果您给 regex_match() 一个 match_results<>,它将写入其中找到的任何标记的子表达式。

regex_search()

在字符串中搜索与正则表达式匹配的子字符串。regex_search() 将尝试在字符串中的每个位置查找匹配项,从开头开始,并在找到匹配项或字符串耗尽时停止。与 regex_match() 一样,如果您给 regex_search() 一个 match_results<>,它将写入其中找到的任何标记的子表达式。

regex_replace()

给定输入字符串、正则表达式和替换字符串,regex_replace() 通过用替换字符串替换输入字符串中与正则表达式匹配的部分来构建新字符串。替换字符串可以包含对标记子表达式的引用。

regex_iterator<>

一个 STL 兼容的迭代器,可以轻松地查找字符串中所有与正则表达式匹配的位置。解引用 regex_iterator<> 返回一个 match_results<>。递增 regex_iterator<> 找到下一个匹配项。

regex_token_iterator<>

regex_iterator<> 类似,只是解引用 regex_token_iterator<> 返回一个字符串。默认情况下,它将返回正则表达式匹配的整个子字符串,但可以配置为一次返回任何或所有标记的子表达式,甚至返回匹配正则表达式的字符串部分。

regex_compiler<>

用于 basic_regex<> 对象的工厂。它将字符串“编译”成正则表达式。您通常无需直接处理 regex_compiler<>,因为 basic_regex<> 类有一个内部使用 regex_compiler<> 的工厂方法。但是,如果您需要做一些花哨的事情,例如使用不同的 std::locale 创建 basic_regex<> 对象,您将需要明确使用 regex_compiler<>


现在您已经了解了 xpressive 提供的工具,您可以通过回答以下两个问题来选择适合您的工具

  1. 您将使用什么迭代器类型来遍历数据?
  2. 您想对数据做什么

了解您的迭代器类型

xpressive 中的大多数类都是以迭代器类型为参数的模板。xpressive 定义了一些常见的 typedef,以使选择正确的类型变得更容易。您可以使用下表根据迭代器类型查找正确的类型。

表 43.2. xpressive Typedefs 与迭代器类型

std::string::const_iterator

char const *

std::wstring::const_iterator

wchar_t const *

basic_regex<>

sregex

cregex

wsregex

wcregex

match_results<>

smatch

cmatch

wsmatch

wcmatch

regex_compiler<>

sregex_compiler

cregex_compiler

wsregex_compiler

wcregex_compiler

regex_iterator<>

sregex_iterator

cregex_iterator

wsregex_iterator

wcregex_iterator

regex_token_iterator<>

sregex_token_iterator

cregex_token_iterator

wsregex_token_iterator

wcregex_token_iterator


您应该注意到系统化的命名约定。这些类型中的许多都是一起使用的,因此命名约定有助于您保持一致地使用它们。例如,如果您有一个 sregex,您也应该使用一个 smatch

如果您没有使用这四种迭代器类型中的任何一种,那么您可以直接使用模板并指定您的迭代器类型。

了解您的任务

您想查找一次模式吗?多次?搜索和替换?xpressive 拥有所有这些以及更多的工具。下面是一个快速参考


这些算法和类在参考部分中详细描述。

[Tip] 提示

尝试点击上表中的任务,查看使用 xpressive 解决特定任务的完整示例程序。

使用 xpressive 时,您要做的第一件事是创建一个 basic_regex<> 对象。本节将介绍 xpressive 支持的两种方言(静态和动态)中构建正则表达式的细节。

概述

xpressive 与其他 C/C++ 正则表达式库真正区别开来的功能是能够使用 C++ 表达式编写正则表达式。xpressive 通过运算符重载实现这一点,使用一种称为表达式模板的技术在 C++ 中嵌入一种专用于模式匹配的微语言。这些“静态正则表达式”比基于字符串的正则表达式有许多优点。特别是,静态正则表达式

  • 在编译时进行语法检查;它们永远不会在运行时因语法错误而失败。
  • 可以自然地引用其他 C++ 数据和代码,包括其他正则表达式,从而可以轻松地从正则表达式构建语法并绑定在正则表达式部分匹配时执行的用户定义操作。
  • 静态绑定,以实现更好的内联和优化。静态正则表达式不需要状态表、虚函数、字节码或在编译时无法解析的函数指针调用。
  • 不限于在字符串中搜索模式。例如,您可以声明一个静态正则表达式,用于在整数数组中查找模式。

由于我们使用 C++ 表达式来组合静态正则表达式,因此我们受到 C++ 合法表达式规则的限制。不幸的是,这意味着“经典”正则表达式语法不能总是清晰地映射到 C++。相反,我们映射正则表达式构造,选择合法的 C++ 新语法。

构造和赋值

您通过将静态正则表达式赋值给 basic_regex<> 类型的对象来创建它。例如,以下定义了一个可用于在 std::string 类型对象中查找模式的正则表达式

sregex re = '$' >> +_d >> '.' >> _d >> _d;

赋值也类似。

字符和字符串字面量

在静态正则表达式中,字符和字符串字面量匹配它们自身。例如,在上面的正则表达式中,'$''.' 分别匹配字符 '$''.'。不要被 $. 是 Perl 中的元字符这一事实所迷惑。在 xpressive 中,字面量始终表示它们自身。

在静态正则表达式中使用字面量时,您必须注意至少有一个操作数不是字面量。例如,以下不是有效的正则表达式

sregex re1 = 'a' >> 'b';         // ERROR!
sregex re2 = +'a';               // ERROR!

二元 >> 运算符的两个操作数都是字面量,一元 + 运算符的操作数也是字面量,因此这些语句将分别调用原生 C++ 二元右移和一元加运算符。这不是我们想要的。为了让运算符重载生效,至少一个操作数必须是用户定义类型。我们可以使用 xpressive 的 as_xpr() 辅助函数来“污染”表达式的正则表达式特性,强制运算符重载找到正确的运算符。上面两个正则表达式应该写成

sregex re1 = as_xpr('a') >> 'b'; // OK
sregex re2 = +as_xpr('a');       // OK

顺序和交替

正如您可能已经注意到的,静态正则表达式中的子表达式必须用序列运算符 >> 分隔。您可以将此运算符读作“后跟”。

// Match an 'a' followed by a digit
sregex re = 'a' >> _d;

交替与 Perl 中使用 | 运算符的方式相同。您可以将此运算符读作“或”。例如

// match a digit character or a word character one or more times
sregex re = +( _d | _w );

分组和捕获

在 Perl 中,括号 () 具有特殊含义。它们进行分组,但作为副作用,它们还会创建像 $1$2 这样的反向引用。在 C++ 中,括号只进行分组——无法赋予它们副作用。为了达到相同的效果,我们使用特殊的 s1s2 等标记。赋值给其中一个会创建反向引用。然后您可以在表达式的后面使用该反向引用,就像在 Perl 中使用 \1\2 一样。例如,考虑以下正则表达式,它查找匹配的 HTML 标签

"<(\\w+)>.*?</\\1>"

在静态 xpressive 中,这将是

'<' >> (s1= +_w) >> '>' >> -*_ >> "</" >> s1 >> '>'

请注意,您如何通过赋值给 s1 来捕获反向引用,然后在模式的后面使用 s1 来查找匹配的结束标签。

[Tip] 提示

不捕获反向引用的分组

在 xpressive 中,如果您只想分组而不捕获反向引用,您可以使用 () 而不使用 s1。这等同于 Perl 的 (?:) 非捕获分组构造。

不区分大小写和国际化

Perl 允许您使用 (?i:) 模式修饰符使正则表达式的一部分不区分大小写。xpressive 也有一个不区分大小写的模式修饰符,称为 icase。您可以按如下方式使用它

sregex re = "this" >> icase( "that" );

在这个正则表达式中,"this" 将被精确匹配,而 "that" 将不区分大小写地匹配。

不区分大小写的正则表达式引发了国际化问题:如何评估不区分大小写的字符比较?此外,许多字符类是区域设置特定的。digit 匹配哪些字符,alpha 匹配哪些字符?答案取决于正则表达式对象使用的 std::locale 对象。默认情况下,所有正则表达式对象都使用全局区域设置。您可以通过使用 imbue() 模式修饰符来覆盖默认值,如下所示

std::locale my_locale = /* initialize a std::locale object */;
sregex re = imbue( my_locale )( +alpha >> +digit );

这个正则表达式将根据 my_locale 评估 alphadigit。有关如何自定义正则表达式行为的更多信息,请参阅本地化和正则表达式特性部分。

静态 xpressive 语法速查表

下表列出了熟悉的正则表达式构造及其在静态 xpressive 中的等价物。

表 43.4. Perl 语法与静态 xpressive 语法

Perl

静态 xpressive

含义

.

_

任意字符(假设 Perl 的 /s 修饰符)。

ab

a >> b

子表达式 ab 的序列。

a|b

a | b

子表达式 ab 的交替。

(a)

(s1= a)

分组并捕获反向引用。

(?:a)

(a)

分组且不捕获反向引用。

\1

s1

先前捕获的反向引用。

a*

*a

零次或多次,贪婪。

a+

+a

一次或多次,贪婪。

a?

!a

零次或一次,贪婪。

a{n,m}

repeat<n,m>(a)

nm 次,贪婪。

a*?

-*a

零次或多次,非贪婪。

a+?

-+a

一次或多次,非贪婪。

a??

-!a

零次或一次,非贪婪。

a{n,m}?

-repeat<n,m>(a)

nm 次,非贪婪。

^

bos

序列开始断言。

$

eos

序列结束断言。

\b

_b

单词边界断言。

\B

~_b

非单词边界断言。

\n

_n

字面换行符。

.

~_n

除字面换行符之外的任何字符(不带 Perl 的 /s 修饰符)。

\r?\n|\r

_ln

逻辑换行符。

[^\r\n]

~_ln

任何不是逻辑换行符的单个字符。

\w

_w

一个单词字符,等同于 set[alnum | '_']。

\W

~_w

非单词字符,等同于 ~set[alnum | '_']。

\d

_d

一个数字字符。

\D

~_d

非数字字符。

\s

_s

一个空格字符。

\S

~_s

非空格字符。

[:alnum:]

alnum

一个字母数字字符。

[:alpha:]

alpha

一个字母字符。

[:blank:]

blank

一个水平空白字符。

[:cntrl:]

cntrl

一个控制字符。

[:digit:]

digit

一个数字字符。

[:graph:]

graph

一个可绘制字符。

[:lower:]

lower

一个小写字符。

[:print:]

print

一个可打印字符。

[:punct:]

punct

一个标点符号。

[:space:]

空格

一个空白字符。

[:upper:]

upper

一个大写字符。

[:xdigit:]

xdigit

一个十六进制数字字符。

[0-9]

range('0','9')

'0''9' 范围内的字符。

[abc]

as_xpr('a') | 'b' |'c'

字符 'a''b''c'

[abc]

(set= 'a','b','c')

同上

[0-9abc]

set[ range('0','9') | 'a' | 'b' | 'c' ]

字符 'a''b''c' 或在 '0''9' 范围内的字符。

[0-9abc]

set[ range('0','9') | (set= 'a','b','c') ]

同上

[^abc]

~(set= 'a','b','c')

不是字符 'a''b''c'

(?i:stuff)

icase(stuff)

不区分大小写地匹配内容

(?>stuff)

keep(stuff)

独立子表达式,匹配内容并关闭回溯。

(?=stuff)

before(stuff)

正向先行断言,如果在内容之前匹配,但匹配中不包含内容

(?!stuff)

~before(stuff)

负向先行断言,如果在内容之前不匹配。

(?<=stuff)

after(stuff)

正向后行断言,如果在内容之后匹配,但匹配中不包含内容。(内容必须是固定宽度的。)

(?<!stuff)

~after(stuff)

负向后行断言,如果在内容之后不匹配。(内容必须是固定宽度的。)

(?P<name>stuff)

mark_tag name(n);
...
(name= stuff)

创建命名捕获。

(?P=name)

mark_tag name(n);
...
名称

引用先前创建的命名捕获。



概述

静态正则表达式很棒,但有时您需要更动态的东西。想象一下您正在开发一个具有正则表达式搜索/替换功能的文本编辑器。您需要从最终用户那里接受正则表达式作为运行时输入。应该有一种方法可以将字符串解析为正则表达式。这就是 xpressive 动态正则表达式的用途。它们是从与静态正则表达式相同的核心组件构建的,但它们是后期绑定的,因此您可以在运行时指定它们。

构造和赋值

有两种方法可以创建动态正则表达式:使用 basic_regex<>::compile() 函数或使用 regex_compiler<> 类模板。如果您想要默认区域设置,请使用 basic_regex<>::compile()。如果您需要指定不同的区域设置,请使用 regex_compiler<>。在正则表达式语法部分,我们将看到 regex_compiler<> 的另一个用途。

这是使用 basic_regex<>::compile() 的示例

sregex re = sregex::compile( "this|that", regex_constants::icase );

这是使用 regex_compiler<> 的相同示例

sregex_compiler compiler;
sregex re = compiler.compile( "this|that", regex_constants::icase );

basic_regex<>::compile() 是根据 regex_compiler<> 实现的。

动态 xpressive 语法

由于动态语法不受有效 C++ 表达式规则的限制,我们可以自由使用熟悉的语法来编写动态正则表达式。因此,xpressive 用于动态正则表达式的语法遵循 John Maddock 提案的指导,该提案旨在将正则表达式添加到标准库中。它本质上是 ECMAScript 标准化的语法,并为了国际化进行了微小更改。

由于该语法已在其他地方详尽记录,我将简单地将您推荐给现有标准,而不是在此处重复规范。

国际化

与静态正则表达式一样,动态正则表达式通过允许您指定不同的 std::locale 来支持国际化。为此,您必须使用 regex_compiler<>regex_compiler<> 类有一个 imbue() 函数。在您用自定义 std::locale 浸润 regex_compiler<> 对象后,所有由该 regex_compiler<> 编译的正则表达式对象都将使用该区域设置。例如

std::locale my_locale = /* initialize your locale object here */;
sregex_compiler compiler;
compiler.imbue( my_locale );
sregex re = compiler.compile( "\\w+|\\d+" );

该正则表达式将在评估固有字符集 "\\w""\\d" 时使用 my_locale

概述

创建正则表达式对象后,您可以使用 regex_match()regex_search() 算法在字符串中查找模式。本页涵盖正则表达式匹配和搜索的基础知识。在所有情况下,如果您熟悉 Boost.Regex 库中的 regex_match()regex_search() 的工作方式,xpressive 的版本也以相同的方式工作。

查看字符串是否匹配正则表达式

regex_match() 算法检查正则表达式是否与给定输入匹配。

[Warning] 警告

regex_match() 算法只有在正则表达式从头到尾匹配整个输入时才会报告成功。如果正则表达式只匹配输入的一部分,regex_match() 将返回 false。如果您想在字符串中搜索正则表达式匹配的子字符串,请使用 regex_search() 算法。

输入可以是双向范围,例如 std::string,C 风格的空终止字符串或一对迭代器。在所有情况下,用于遍历输入序列的迭代器类型必须与用于声明正则表达式对象的迭代器类型匹配。(您可以使用快速入门中的表格来查找适合您的迭代器的正确正则表达式类型。)

cregex cre = +_w;  // this regex can match C-style strings
sregex sre = +_w;  // this regex can match std::strings

if( regex_match( "hello", cre ) )              // OK
    { /*...*/ }

if( regex_match( std::string("hello"), sre ) ) // OK
    { /*...*/ }

if( regex_match( "hello", sre ) )              // ERROR! iterator mis-match!
    { /*...*/ }

regex_match() 算法可选地接受 match_results<> 结构作为输出参数。如果给出,regex_match() 算法将用有关正则表达式的哪些部分与输入的哪些部分匹配的信息填充 match_results<> 结构。

cmatch what;
cregex cre = +(s1= _w);

// store the results of the regex_match in "what"
if( regex_match( "hello", what, cre ) )
{
    std::cout << what[1] << '\n'; // prints "o"
}

regex_match() 算法还可以选择接受 match_flag_type 位掩码。使用 match_flag_type,您可以控制匹配的某些方面。请参阅 match_flag_type 参考以获取标志及其含义的完整列表。

std::string str("hello");
sregex sre = bol >> +_w;

// match_not_bol means that "bol" should not match at [begin,begin)
if( regex_match( str.begin(), str.end(), sre, regex_constants::match_not_bol ) )
{
    // should never get here!!!
}

点击此处查看一个完整的示例程序,该程序展示了如何使用 regex_match()。并查看 regex_match() 参考以查看可用重载的完整列表。

搜索匹配的子字符串

当您想知道输入序列是否包含与正则表达式匹配的子序列时,使用 regex_search()regex_search() 将尝试在输入序列的开头匹配正则表达式,并在序列中向前扫描,直到找到匹配项或序列耗尽。

在所有其他方面,regex_search() 的行为与 regex_match() (参见上文)相同。特别是,它可以在双向范围(例如 std::string)、C 风格的空终止字符串或迭代器范围上操作。必须同样小心,以确保正则表达式的迭代器类型与输入序列的迭代器类型匹配。与 regex_match() 一样,您可以选择提供一个 match_results<> 结构来接收搜索结果,以及一个 match_flag_type 位掩码来控制匹配的评估方式。

点击此处查看一个完整的示例程序,该程序展示了如何使用 regex_search()。并查看 regex_search() 参考以查看可用重载的完整列表。

概述

有时,仅仅知道 regex_match()regex_search() 是否成功是不够的。如果您将 match_results<> 类型的对象传递给 regex_match()regex_search(),那么在算法成功完成后,match_results<> 将包含有关正则表达式的哪些部分与序列的哪些部分匹配的额外信息。在 Perl 中,这些子序列被称为反向引用,它们存储在变量 $1$2 等中。在 xpressive 中,它们是 sub_match<> 类型的对象,它们存储在 match_results<> 结构中,该结构充当 sub_match<> 对象的向量。

match_results

那么,您已将 match_results<> 对象传递给正则表达式算法,并且算法已成功完成。现在您想检查结果。您对 match_results<> 对象所做的大部分事情都是对其进行索引以访问其内部存储的 sub_match<> 对象,但您还可以使用 match_results<> 对象做一些其他事情。

下表显示了如何访问存储在名为 whatmatch_results<> 对象中的信息。

表 43.5. match_results<> 访问器

访问器

效果

what.size()

返回子匹配的数量,在成功匹配后总是大于零,因为完整匹配存储在第零个子匹配中。

what[n]

返回第n个子匹配。

what.length(n)

返回第n个子匹配的长度。与 what[n].length() 相同。

what.position(n)

返回第n个子匹配开始的输入序列中的偏移量。

what.str(n)

返回从第n个子匹配构造的 std::basic_string<>。与 what[n].str() 相同。

what.prefix()

返回一个 sub_match<> 对象,它表示从输入序列的开头到完整匹配开始的子序列。

what.suffix()

返回一个 sub_match<> 对象,它表示从完整匹配的末尾到输入序列末尾的子序列。

what.regex_id()

返回上次与此 match_results<> 对象一起使用的 basic_regex<> 对象的 regex_id


您可以使用 match_results<> 对象做更多事情,但我们将在讨论语法和嵌套匹配时涵盖这一点。

sub_match

当您索引 match_results<> 对象时,您会得到一个 sub_match<> 对象。sub_match<> 本质上是一对迭代器。它定义如下

template< class BidirectionalIterator >
struct sub_match
    : std::pair< BidirectionalIterator, BidirectionalIterator >
{
    bool matched;
    // ...
};

由于它公开继承自 std::pair<>sub_match<> 具有 BidirectionalIterator 类型的 firstsecond 数据成员。这些是此 sub_match<> 表示的子序列的开始和结束。sub_match<> 还具有一个布尔 matched 数据成员,如果此 sub_match<> 参与了完整匹配,则该成员为 true。

下表显示了您如何访问存储在名为 subsub_match<> 对象中的信息。

表 43.6. sub_match<> 访问器

访问器

效果

sub.length()

返回子匹配的长度。与 std::distance(sub.first,sub.second) 相同。

sub.str()

返回从子匹配构造的 std::basic_string<>。与 std::basic_string<char_type>(sub.first,sub.second) 相同。

sub.compare(str)

执行子匹配与 str 之间的字符串比较,其中 str 可以是 std::basic_string<>、C 风格的空终止字符串或另一个子匹配。与 sub.str().compare(str) 相同。


注意 结果失效 注意

结果作为迭代器存储在输入序列中。任何使输入序列失效的操作都将使匹配结果失效。例如,如果您匹配一个 std::string 对象,则结果仅在您下次调用该 std::string 对象的非 const 成员函数之前有效。在此之后,match_results<> 对象持有的结果将失效。请勿使用它们!

正则表达式不仅擅长搜索文本,它们还擅长操作文本。而最常见的文本操作任务之一就是搜索和替换。xpressive 提供了 regex_replace() 算法用于搜索和替换。

regex_replace()

使用 regex_replace() 执行搜索和替换很简单。您只需要一个输入序列、一个正则表达式对象和一个格式字符串或格式化器对象。有几个版本的 regex_replace() 算法。有些接受输入序列作为双向容器,例如 std::string,并在相同类型的新容器中返回结果。另一些接受输入作为空终止字符串,并返回 std::string。还有一些接受输入序列作为一对迭代器,并将结果写入输出迭代器。替换可以指定为带有格式序列的字符串或格式化器对象。下面是一些使用基于字符串的替换的简单示例。

std::string input("This is his face");
sregex re = as_xpr("his");                // find all occurrences of "his" ...
std::string format("her");                // ... and replace them with "her"

// use the version of regex_replace() that operates on strings
std::string output = regex_replace( input, re, format );
std::cout << output << '\n';

// use the version of regex_replace() that operates on iterators
std::ostream_iterator< char > out_iter( std::cout );
regex_replace( out_iter, input.begin(), input.end(), re, format );

上面的程序打印出以下内容

Ther is her face
Ther is her face

注意,所有 "his" 的出现都被替换为 "her"

点击此处查看一个完整的示例程序,该程序展示了如何使用 regex_replace()。并查看 regex_replace() 参考以查看可用重载的完整列表。

替换选项

regex_replace() 算法接受一个可选的位掩码参数来控制格式。位掩码的可能值是

表 43.7. 格式标志

标志

含义

format_default

识别 ECMA-262 格式序列(见下文)。

format_first_only

仅替换第一个匹配项,而不是全部。

format_no_copy

不将输入序列中与正则表达式不匹配的部分复制到输出序列。

format_literal

将格式字符串视为字面量;也就是说,不识别任何转义序列。

format_perl

识别 Perl 格式序列(见下文)。

format_sed

识别 sed 格式序列(见下文)。

format_all

除了 Perl 格式序列外,还识别一些 Boost 特定格式序列。


这些标志位于 xpressive::regex_constants 命名空间中。如果替换参数是函数对象而不是字符串,则忽略 format_literalformat_perlformat_sedformat_all 标志。

ECMA-262 格式序列

当您未指定带有上述格式标志之一的替换字符串方言时,您将获得由 ECMAScript 标准 ECMA-262 定义的方言。下表显示了在 ECMA-262 模式下识别的转义序列。

表 43.8. 格式转义序列

转义序列

含义

$1, $2, 等。

对应的子匹配

$&

完整匹配

$`

匹配前缀

$'

匹配后缀

$$

一个字面量 '$' 字符


任何其他以 '$' 开头的序列都只代表其自身。例如,如果格式字符串是 "$a",那么 "$a" 将被插入到输出序列中。

Sed 格式序列

当向 regex_replace() 指定 format_sed 标志时,会识别以下转义序列

表 43.9. Sed 格式转义序列

转义序列

含义

\1, \2, 等。

对应的子匹配

&

完整匹配

\a

一个字面量 '\a'

\e

一个字面量 char_type(27)

\f

一个字面量 '\f'

\n

一个字面量 '\n'

\r

一个字面量 '\r'

\t

一个字面量 '\t'

\v

一个字面量 '\v'

\xFF

一个字面量 char_type(0xFF),其中 F 是任意十六进制数字

\x{FFFF}

一个字面量 char_type(0xFFFF),其中 F 是任意十六进制数字

\cX

控制字符 X


Perl 格式序列

当向 regex_replace() 指定 format_perl 标志时,会识别以下转义序列

表 43.10. Perl 格式转义序列

转义序列

含义

$1, $2, 等。

对应的子匹配

$&

完整匹配

$`

匹配前缀

$'

匹配后缀

$$

一个字面量 '$' 字符

\a

一个字面量 '\a'

\e

一个字面量 char_type(27)

\f

一个字面量 '\f'

\n

一个字面量 '\n'

\r

一个字面量 '\r'

\t

一个字面量 '\t'

\v

一个字面量 '\v'

\xFF

一个字面量 char_type(0xFF),其中 F 是任意十六进制数字

\x{FFFF}

一个字面量 char_type(0xFFFF),其中 F 是任意十六进制数字

\cX

控制字符 X

\l

将下一个字符转换为小写

\L

将替换的其余部分转换为小写,直到下一个 \E

\u

将下一个字符转换为大写

\U

将替换的其余部分转换为大写,直到下一个 \E

\E

终止 \L\U

\1, \2, 等。

对应的子匹配

\g<name>

命名的反向引用 name


Boost 特有的格式序列

当向 regex_replace() 指定 format_all 标志时,识别的转义序列与上述 format_perl 的相同。此外,还识别以下形式的条件表达式

?Ntrue-expression:false-expression

其中 N 是一个十进制数字,表示一个子匹配。如果对应的子匹配参与了完整匹配,则替换为 true-expression。否则,替换为 false-expression。在此模式下,可以使用括号 () 进行分组。如果需要字面量括号,必须将其转义为 \(

格式化器对象

格式字符串并不总是能满足您所有的文本替换需求。考虑一个简单的例子,您想将输入字符串映射到输出字符串,就像您可能对环境变量所做的那样。为此,您将使用格式化器 对象,而不是格式 字符串。考虑以下代码,它查找形式为 "$(XYZ)" 的嵌入式环境变量,并通过在映射中查找环境变量来计算替换字符串。

#include <map>
#include <string>
#include <iostream>
#include <boost/xpressive/xpressive.hpp>
using namespace boost;
using namespace xpressive;

std::map<std::string, std::string> env;

std::string const &format_fun(smatch const &what)
{
    return env[what[1].str()];
}

int main()
{
    env["X"] = "this";
    env["Y"] = "that";

    std::string input("\"$(X)\" has the value \"$(Y)\"");

    // replace strings like "$(XYZ)" with the result of env["XYZ"]
    sregex envar = "$(" >> (s1 = +_w) >> ')';
    std::string output = regex_replace(input, envar, format_fun);
    std::cout << output << std::endl;
}

在这种情况下,我们使用函数 format_fun() 来即时计算替换字符串。它接受一个 match_results<> 对象,其中包含当前匹配的结果。format_fun() 使用第一个子匹配作为全局 env 映射的键。以上代码显示:

"this" has the value "that"

格式化器不必是普通函数。它也可以是类类型的对象。它也可以不返回字符串,而是接受一个输出迭代器,将替换内容写入其中。考虑以下代码,它在功能上与上述代码等效。

#include <map>
#include <string>
#include <iostream>
#include <boost/xpressive/xpressive.hpp>
using namespace boost;
using namespace xpressive;

struct formatter
{
    typedef std::map<std::string, std::string> env_map;
    env_map env;

    template<typename Out>
    Out operator()(smatch const &what, Out out) const
    {
        env_map::const_iterator where = env.find(what[1]);
        if(where != env.end())
        {
            std::string const &sub = where->second;
            out = std::copy(sub.begin(), sub.end(), out);
        }
        return out;
    }

};

int main()
{
    formatter fmt;
    fmt.env["X"] = "this";
    fmt.env["Y"] = "that";

    std::string input("\"$(X)\" has the value \"$(Y)\"");

    sregex envar = "$(" >> (s1 = +_w) >> ')';
    std::string output = regex_replace(input, envar, fmt);
    std::cout << output << std::endl;
}

格式化器必须是一个可调用对象(函数或函数对象),它具有下表中详述的三种可能签名之一。对于下表,fmt 是函数指针或函数对象,what 是一个 match_results<> 对象,out 是一个 OutputIterator,flags 是一个 regex_constants::match_flag_type 的值

表 43.11. 格式化器签名

格式化器调用

返回类型

语义

fmt(what)

字符范围 (例如 std::string) 或空终止字符串

正则表达式匹配的字符串被格式化器返回的字符串替换。

fmt(what, out)

OutputIterator

格式化器将替换字符串写入 out 并返回 out

fmt(what, out, flags)

OutputIterator

格式化器将替换字符串写入 out 并返回 outflags 参数是传递给 regex_replace() 算法的匹配标志的值。


格式化器表达式

除了格式 字符串 和格式化器 对象regex_replace() 还接受格式化器 表达式。格式化器表达式是一个生成字符串的 lambda 表达式。它使用与 语义动作 相同的语法,这将在后面介绍。上面使用 regex_replace() 替换环境变量字符串的示例,在这里使用格式化器表达式重复一遍。

#include <map>
#include <string>
#include <iostream>
#include <boost/xpressive/xpressive.hpp>
#include <boost/xpressive/regex_actions.hpp>
using namespace boost::xpressive;

int main()
{
    std::map<std::string, std::string> env;
    env["X"] = "this";
    env["Y"] = "that";

    std::string input("\"$(X)\" has the value \"$(Y)\"");

    sregex envar = "$(" >> (s1 = +_w) >> ')';
    std::string output = regex_replace(input, envar, ref(env)[s1]);
    std::cout << output << std::endl;
}

在上面,格式化器表达式是 ref(env)[s1]。这意味着使用第一个子匹配 s1 的值作为 env 映射的键。xpressive::ref() 的目的是使对 env 局部变量的引用具有 惰性,以便在知道用什么替换 s1 之前,索引操作被推迟。

regex_token_iterator<> 是文本操作领域的 Ginsu 刀。它切片!它切丁!本节描述如何使用高度可配置的 regex_token_iterator<> 来分割输入序列。

概述

您可以使用输入序列、正则表达式和一些可选的配置参数来初始化一个 regex_token_iterator<>regex_token_iterator<> 将使用 regex_search() 找到序列中正则表达式第一次匹配的位置。当解引用时,regex_token_iterator<> 返回一个 标记,形式为 std::basic_string<>。它返回哪个字符串取决于配置参数。默认情况下,它返回对应于完整匹配的字符串,但它也可以返回对应于特定标记子表达式的字符串,甚至返回序列中 匹配的部分。当您递增 regex_token_iterator<> 时,它将移动到下一个标记。哪个标记是下一个取决于配置参数。它可能只是当前匹配中不同的标记子表达式,也可能是下一个匹配的一部分或全部。或者它可能是不匹配的部分。

如您所见,regex_token_iterator<> 可以做很多事情。这使得它很难描述,但一些例子应该能说明问题。

示例 1: 简单分词

此示例使用 regex_token_iterator<> 将一个序列切割成一系列由单词组成的标记。

std::string input("This is his face");
sregex re = +_w;                      // find a word

// iterate over all the words in the input
sregex_token_iterator begin( input.begin(), input.end(), re ), end;

// write all the words to std::cout
std::ostream_iterator< std::string > out_iter( std::cout, "\n" );
std::copy( begin, end, out_iter );

此程序显示以下内容

This
is
his
face

示例 2: 简单分词,重新加载

此示例也使用 regex_token_iterator<> 将一个序列分割成一系列由单词组成的标记,但它使用正则表达式作为分隔符。当我们向 regex_token_iterator<> 构造函数传递一个 -1 作为最后一个参数时,它指示标记迭代器将输入中 匹配正则表达式的部分视为标记。

std::string input("This is his face");
sregex re = +_s;                      // find white space

// iterate over all non-white space in the input. Note the -1 below:
sregex_token_iterator begin( input.begin(), input.end(), re, -1 ), end;

// write all the words to std::cout
std::ostream_iterator< std::string > out_iter( std::cout, "\n" );
std::copy( begin, end, out_iter );

此程序显示以下内容

This
is
his
face

示例 3: 简单分词,革新

此示例也使用 regex_token_iterator<> 将包含一堆日期的序列分割成一系列只包含年份的标记。当我们向 regex_token_iterator<> 构造函数传递一个正整数 N 作为最后一个参数时,它指示标记迭代器只将每个匹配的第 N 个标记子表达式视为标记。

std::string input("01/02/2003 blahblah 04/23/1999 blahblah 11/13/1981");
sregex re = sregex::compile("(\\d{2})/(\\d{2})/(\\d{4})"); // find a date

// iterate over all the years in the input. Note the 3 below, corresponding to the 3rd sub-expression:
sregex_token_iterator begin( input.begin(), input.end(), re, 3 ), end;

// write all the words to std::cout
std::ostream_iterator< std::string > out_iter( std::cout, "\n" );
std::copy( begin, end, out_iter );

此程序显示以下内容

2003
1999
1981

示例 4: 不那么简单的分词

此示例与上一个类似,不同之处在于,该程序不是只对年份进行标记化,而是将日期、月份和年份转换为标记。当我们向 regex_token_iterator<> 构造函数传递一个整数数组 {I,J,...} 作为最后一个参数时,它指示标记迭代器将每个匹配的第 I 个、第 J 个等标记子表达式视为标记。

std::string input("01/02/2003 blahblah 04/23/1999 blahblah 11/13/1981");
sregex re = sregex::compile("(\\d{2})/(\\d{2})/(\\d{4})"); // find a date

// iterate over the days, months and years in the input
int const sub_matches[] = { 2, 1, 3 }; // day, month, year
sregex_token_iterator begin( input.begin(), input.end(), re, sub_matches ), end;

// write all the words to std::cout
std::ostream_iterator< std::string > out_iter( std::cout, "\n" );
std::copy( begin, end, out_iter );

此程序显示以下内容

02
01
2003
23
04
1999
13
11
1981

sub_matches 数组指示 regex_token_iterator<> 首先取第二个子匹配的值,然后是第一个子匹配,最后是第三个。再次递增迭代器会指示它再次使用 regex_search() 来查找下一个匹配。此时,该过程重复——标记迭代器取第二个子匹配的值,然后是第一个,依此类推。

概述

对于复杂的正则表达式,处理带编号的捕获可能很痛苦。数左括号来找出要引用的捕获一点都不好玩。更不好玩的是,仅仅编辑正则表达式就可能导致捕获被分配一个新编号,从而使引用旧编号的代码失效。

其他正则表达式引擎通过一种称为 命名捕获 的功能解决了这个问题。此功能允许您为捕获分配一个名称,并通过名称而不是编号引用捕获。Xpressive 也支持命名捕获,无论是在动态正则表达式还是静态正则表达式中。

动态命名捕获

对于动态正则表达式,xpressive 遵循其他流行的正则表达式引擎的命名捕获语法。您可以使用 "(?P<xxx>...)" 创建一个命名捕获,并使用 "(?P=xxx)" 引用该捕获。例如,下面是一个创建命名捕获并引用它的正则表达式

// Create a named capture called "char" that matches a single
// character and refer back to that capture by name.
sregex rx = sregex::compile("(?P<char>.)(?P=char)");

以上正则表达式的作用是查找第一个重复的字符。

一旦您使用带有命名捕获的正则表达式执行了匹配或搜索操作,您就可以通过捕获的名称使用 match_results<> 对象来访问命名捕获。

std::string str("tweet");
sregex rx = sregex::compile("(?P<char>.)(?P=char)");
smatch what;
if(regex_search(str, what, rx))
{
    std::cout << "char = " << what["char"] << std::endl;
}

以上代码显示

char = e

您还可以从替换字符串中引用命名捕获。其语法是 "\\g<xxx>"。下面是一些演示如何在字符串替换中使用命名捕获的代码。

std::string str("tweet");
sregex rx = sregex::compile("(?P<char>.)(?P=char)");
str = regex_replace(str, rx, "**\\g<char>**", regex_constants::format_perl);
std::cout << str << std::endl;

请注意,在使用命名捕获时,必须指定 format_perl。只有 perl 语法才识别 "\\g<xxx>" 语法。以上代码显示:

tw**e**t

静态命名捕获

如果您正在使用静态正则表达式,创建和使用命名捕获会更加容易。您可以使用 mark_tag 类型创建一个变量,您可以像 s1s2 和其他变量一样使用它,但具有更具意义的名称。下面是上述示例使用静态正则表达式的样子

mark_tag char_(1); // char_ is now a synonym for s1
sregex rx = (char_= _) >> char_;

匹配操作后,您可以使用 mark_tag 索引到 match_results<> 以访问命名捕获

std::string str("tweet");
mark_tag char_(1);
sregex rx = (char_= _) >> char_;
smatch what;
if(regex_search(str, what, rx))
{
    std::cout << what[char_] << std::endl;
}

以上代码显示

char = e

在通过 regex_replace() 进行字符串替换时,您可以使用命名捕获创建 格式表达式,如下所示

std::string str("tweet");
mark_tag char_(1);
sregex rx = (char_= _) >> char_;
str = regex_replace(str, rx, "**" + char_ + "**");
std::cout << str << std::endl;

以上代码显示

tw**e**t
[Note] 注意

您需要包含 <boost/xpressive/regex_actions.hpp> 才能使用格式表达式。

概述

将正则表达式表示为 C++ 表达式的一个主要好处是能够轻松地从正则表达式内部引用其他 C++ 代码和数据。这使得其他正则表达式库无法实现的编程范例成为可能。特别值得注意的是,一个正则表达式可以引用另一个正则表达式,从而允许您使用正则表达式构建语法。本节描述如何按值和按引用嵌入一个正则表达式到另一个正则表达式中,正则表达式对象在引用其他正则表达式时的行为,以及在成功解析后如何访问结果树。

按值嵌入正则表达式

basic_regex<> 对象具有值语义。当一个正则表达式对象出现在另一个正则表达式定义中的右侧时,就好像该正则表达式是按值嵌入的;也就是说,内部正则表达式的副本由外部正则表达式存储。内部正则表达式在模式匹配期间由外部正则表达式调用。内部正则表达式完全参与匹配,根据需要进行回溯以使匹配成功。

考虑一个具有正则表达式查找功能和整词选项的文本编辑器。您可以使用 xpressive 如下实现:

find_dialog dlg;
if( dialog_ok == dlg.do_modal() )
{
    std::string pattern = dlg.get_text();          // the pattern the user entered
    bool whole_word = dlg.whole_word.is_checked(); // did the user select the whole-word option?

    sregex re = sregex::compile( pattern );        // try to compile the pattern

    if( whole_word )
    {
        // wrap the regex in begin-word / end-word assertions
        re = bow >> re >> eow;
    }

    // ... use re ...
}

仔细看这一行

// wrap the regex in begin-word / end-word assertions
re = bow >> re >> eow;

此行创建一个新正则表达式,通过值嵌入旧正则表达式。然后,将新正则表达式重新赋值给原始正则表达式。由于在右侧创建了旧正则表达式的副本,因此其工作方式符合预期:新正则表达式具有包裹在单词开头和单词结尾断言中的旧正则表达式的行为。

[Note] 注意

请注意,re = bow >> re >> eow 定义递归正则表达式,因为正则表达式对象默认按值嵌入。下一节将展示如何通过按引用嵌入正则表达式来定义递归正则表达式。

按引用嵌入正则表达式

如果您想能够构建递归正则表达式和上下文无关语法,按值嵌入正则表达式是不够的。您需要能够使您的正则表达式自引用。大多数正则表达式引擎不提供这种功能,但 xpressive 提供了。

[Tip] 提示

那些理论计算机科学家会正确地指出,自引用正则表达式不是“正则的”,因此严格来说,xpressive 根本不是一个 正则 表达式引擎。但正如 Larry Wall 曾经说过:“[正则表达式]这个词已经随着我们模式匹配引擎的功能而发展,所以我不会试图在这里对抗语言上的必要性。”

考虑以下代码,它使用 by_ref() 助手来定义一个匹配平衡嵌套括号的递归正则表达式

sregex parentheses;
parentheses                          // A balanced set of parentheses ...
    = '('                            // is an opening parenthesis ...
        >>                           // followed by ...
         *(                          // zero or more ...
            keep( +~(set='(',')') )  // of a bunch of things that are not parentheses ...
          |                          // or ...
            by_ref(parentheses)      // a balanced set of parentheses
          )                          //   (ooh, recursion!) ...
        >>                           // followed by ...
      ')'                            // a closing parenthesis
    ;

匹配平衡嵌套标签是一项重要的文本处理任务,而“经典”正则表达式无法完成此任务。by_ref() 助手使其成为可能。它允许一个正则表达式对象 通过引用 嵌入到另一个正则表达式中。由于右侧按引用持有 parentheses,因此将右侧重新赋值给 parentheses 会创建一个循环,该循环将递归执行。

构建语法

一旦我们在正则表达式中允许自引用,潘多拉的盒子就被打开了,各种有趣的事情都成为可能。特别是,我们现在可以用正则表达式构建语法。让我们看看教科书上的语法示例:简单的计算器。

sregex group, factor, term, expression;

group       = '(' >> by_ref(expression) >> ')';
factor      = +_d | group;
term        = factor >> *(('*' >> factor) | ('/' >> factor));
expression  = term >> *(('+' >> term) | ('-' >> term));

上面定义的正则表达式 expression 对于正则表达式来说做了一些相当了不起的事情:它匹配数学表达式。例如,如果输入字符串是 "foo 9*(10+3) bar",此模式将匹配 "9*(10+3)"。它只匹配格式良好的数学表达式,其中括号平衡且中缀运算符各有两个参数。不要尝试使用任何其他正则表达式引擎!

让我们仔细看看这个正则表达式语法。请注意它是循环的:expression 是根据 term 实现的,term 是根据 factor 实现的,factor 是根据 group 实现的,而 group 又根据 expression 实现,从而形成闭环。通常,定义循环语法的方法是前向声明正则表达式对象,并通过引用嵌入尚未初始化的正则表达式。在上述语法中,只有一个地方需要引用尚未初始化的正则表达式对象:group 的定义。在该处,我们使用 by_ref() 按引用嵌入 expression。在所有其他地方,通过值嵌入其他正则表达式对象就足够了,因为它们已经初始化并且它们的值不会改变。

[Tip] 提示

如果可能,按值嵌入

一般来说,最好按值而非按引用嵌入正则表达式。这样做可以减少一次间接寻址,使您的模式匹配稍快一些。此外,值语义更简单,将使您的语法更容易理解。不必担心“复制”正则表达式的开销。每个正则表达式对象都与它的所有副本共享其实现。

动态正则表达式语法

使用 regex_compiler<>,您还可以使用动态正则表达式构建语法。您可以通过创建命名正则表达式,并通过名称引用其他正则表达式来实现。每个 regex_compiler<> 实例都保留了一个从名称到已创建的正则表达式的映射。

您可以通过在正则表达式前加上 "(?$name=)" 来创建一个命名动态正则表达式,其中 name 是正则表达式的名称。您可以使用 "(?$name)" 从另一个正则表达式中引用一个命名正则表达式。被引用的命名正则表达式在另一个正则表达式中被引用时不需要已经存在,但它必须在您使用该正则表达式时存在。

下面是一个代码片段,它使用动态正则表达式语法来实现上述计算器示例。

using namespace boost::xpressive;
using namespace regex_constants;

sregex expr;

{
     sregex_compiler compiler;
     syntax_option_type x = ignore_white_space;

            compiler.compile("(? $group  = ) \\( (? $expr ) \\) ", x);
            compiler.compile("(? $factor = ) \\d+ | (? $group ) ", x);
            compiler.compile("(? $term   = ) (? $factor )"
                             " ( \\* (? $factor ) | / (? $factor ) )* ", x);
     expr = compiler.compile("(? $expr   = ) (? $term )"
                             "   ( \\+ (? $term ) | - (? $term )   )* ", x);
}

std::string str("foo 9*(10+3) bar");
smatch what;

if(regex_search(str, what, expr))
{
     // This prints "9*(10+3)":
     std::cout << what[0] << std::endl;
}

与静态正则表达式语法一样,嵌套正则表达式调用会创建嵌套匹配结果(参见下面的 嵌套结果)。结果是一个完整的匹配字符串的解析树。与静态正则表达式不同,动态正则表达式总是按引用嵌入,而不是按值嵌入。

循环模式、复制和内存管理,天啊!

上述计算器示例引发了一些非常复杂的内存管理问题。四个正则表达式对象中的每一个都相互引用,有些是直接引用,有些是间接引用,有些是按值引用,有些是按引用引用。如果我们从一个函数返回其中一个,并让其他对象超出作用域,会发生什么?引用会变成什么样?答案是正则表达式对象在内部是引用计数的,因此只要它们需要被引用的正则表达式对象,它们就会使其保持活动状态。因此,按值传递正则表达式对象从来都不是问题,即使它引用了已经超出作用域的其他正则表达式对象。

那些处理过引用计数的人可能熟悉它的致命弱点:循环引用。如果正则表达式对象是引用计数的,那么像计算器示例中创建的循环会发生什么?它们会泄漏吗?答案是不会,它们不会泄漏。basic_regex<> 对象有一些巧妙的引用跟踪代码,可以确保即使是循环正则表达式语法,在最后一个外部引用消失时也会被清理。所以不用担心。创建循环语法,随意传递和复制你的正则表达式对象。它是快速高效的,并且保证不会泄漏或导致悬空引用。

嵌套正则表达式和子匹配作用域

嵌套正则表达式提出了子匹配作用域的问题。如果内部和外部正则表达式都写入和读取相同的子匹配向量,就会导致混乱。内部正则表达式会覆盖外部正则表达式写入的子匹配。例如,这会做什么?

sregex inner = sregex::compile( "(.)\\1" );
sregex outer = (s1= _) >> inner >> s1;

作者可能不希望内部正则表达式覆盖外部正则表达式写入的子匹配。当内部正则表达式作为用户输入被接受时,问题尤为突出。作者无法知道内部正则表达式是否会覆盖子匹配向量。这显然是不可接受的。

相反,实际情况是每次调用嵌套正则表达式都会获得自己的作用域。子匹配属于该作用域。也就是说,每个嵌套正则表达式调用都会获得自己的子匹配向量副本进行操作,因此内部正则表达式无法覆盖外部正则表达式的子匹配。因此,例如,上面定义的正则表达式 outer 将匹配 "ABBA",这正是它应该做的。

嵌套结果

如果嵌套正则表达式有自己的子匹配,那么在成功匹配后应该有一种方法来访问它们。实际上,是有的。在 regex_match()regex_search() 之后,match_results<> 结构的行为就像一个嵌套结果树的头部。match_results<> 类提供了一个 nested_results() 成员函数,它返回一个有序的 match_results<> 结构序列,表示嵌套正则表达式的结果。嵌套结果的顺序与嵌套正则表达式对象匹配的顺序相同。

以我们前面看到的用于匹配平衡嵌套括号的正则表达式为例

sregex parentheses;
parentheses = '(' >> *( keep( +~(set='(',')') ) | by_ref(parentheses) ) >> ')';

smatch what;
std::string str( "blah blah( a(b)c (c(e)f (g)h )i (j)6 )blah" );

if( regex_search( str, what, parentheses ) )
{
    // display the whole match
    std::cout << what[0] << '\n';

    // display the nested results
    std::for_each(
        what.nested_results().begin(),
        what.nested_results().end(),
        output_nested_results() );
}

此程序显示以下内容

( a(b)c (c(e)f (g)h )i (j)6 )
    (b)
    (c(e)f (g)h )
        (e)
        (g)
    (j)

在这里,您可以看到结果是如何嵌套的,并且它们是按照找到的顺序存储的。

[Tip] 提示

请参阅 示例 部分中 output_nested_results 的定义。

过滤嵌套结果

有时一个正则表达式会有多个嵌套的正则表达式对象,您想知道哪个结果对应哪个正则表达式对象。这时候 basic_regex<>::regex_id()match_results<>::regex_id() 就派上用场了。在遍历嵌套结果时,您可以将结果中的正则表达式 ID 与您感兴趣的正则表达式对象的 ID 进行比较。

为了使这更容易一些,xpressive 提供了一个谓词,可以轻松地迭代只对应于特定嵌套正则表达式的结果。它被称为 regex_id_filter_predicate,旨在与 Boost.Iterator 一起使用。您可以按如下方式使用它

sregex name = +alpha;
sregex integer = +_d;
sregex re = *( *_s >> ( name | integer ) );

smatch what;
std::string str( "marsha 123 jan 456 cindy 789" );

if( regex_match( str, what, re ) )
{
    smatch::nested_results_type::const_iterator begin = what.nested_results().begin();
    smatch::nested_results_type::const_iterator end   = what.nested_results().end();

    // declare filter predicates to select just the names or the integers
    sregex_id_filter_predicate name_id( name.regex_id() );
    sregex_id_filter_predicate integer_id( integer.regex_id() );

    // iterate over only the results from the name regex
    std::for_each(
        boost::make_filter_iterator( name_id, begin, end ),
        boost::make_filter_iterator( name_id, end, end ),
        output_result
        );

    std::cout << '\n';

    // iterate over only the results from the integer regex
    std::for_each(
        boost::make_filter_iterator( integer_id, begin, end ),
        boost::make_filter_iterator( integer_id, end, end ),
        output_result
        );
}

其中 output_results 是一个简单的函数,它接受一个 smatch 并显示完整匹配。请注意我们如何将 regex_id_filter_predicatebasic_regex<>::regex_id() 和来自 Boost.Iteratorboost::make_filter_iterator() 一起使用,以仅选择与特定嵌套正则表达式对应的那些结果。此程序显示以下内容:

marsha
jan
cindy
123
456
789

概述

想象一下您想解析一个输入字符串并从中构建一个 std::map<>。对于这样的事情,匹配正则表达式是不够的。您希望当正则表达式的某些部分匹配时 做一些事情。Xpressive 允许您将语义动作附加到静态正则表达式的某些部分。本节将向您展示如何操作。

语义动作

考虑以下代码,它使用 xpressive 的语义操作解析单词/整数对字符串,并将其填充到 std::map<> 中。下面将对其进行描述。

#include <string>
#include <iostream>
#include <boost/xpressive/xpressive.hpp>
#include <boost/xpressive/regex_actions.hpp>
using namespace boost::xpressive;

int main()
{
    std::map<std::string, int> result;
    std::string str("aaa=>1 bbb=>23 ccc=>456");

    // Match a word and an integer, separated by =>,
    // and then stuff the result into a std::map<>
    sregex pair = ( (s1= +_w) >> "=>" >> (s2= +_d) )
        [ ref(result)[s1] = as<int>(s2) ];

    // Match one or more word/integer pairs, separated
    // by whitespace.
    sregex rx = pair >> *(+_s >> pair);

    if(regex_match(str, rx))
    {
        std::cout << result["aaa"] << '\n';
        std::cout << result["bbb"] << '\n';
        std::cout << result["ccc"] << '\n';
    }

    return 0;
}

此程序打印以下内容

1
23
456

正则表达式 pair 有两部分:模式和动作。模式表示匹配一个单词,将其捕获到子匹配 1 中,以及一个整数,将其捕获到子匹配 2 中,两者之间用 "=>" 分隔。动作是方括号中的部分:[ ref(result)[s1] = as<int>(s2) ]。它表示获取子匹配 1 并用它来索引到 results 映射中,并将子匹配 2 转换为整数的结果赋值给它。

[Note] 注意

要将语义操作与静态正则表达式一起使用,您必须 #include <boost/xpressive/regex_actions.hpp>

这是如何工作的?就像静态正则表达式的其余部分一样,方括号之间的部分是一个表达式模板。它对动作进行编码,并在稍后执行。表达式 ref(result) 创建对 result 对象的惰性引用。更大的表达式 ref(result)[s1] 是一个惰性映射索引操作。稍后,当此动作执行时,s1 将被替换为第一个 sub_match<>。同样,当 as<int>(s2) 执行时,s2 将被替换为第二个 sub_match<>as<> 动作使用 Boost.Lexical_cast 将其参数转换为请求的类型。整个动作的效果是将一个新的单词/整数对插入到映射中。

[Note] 注意

<boost/ref.hpp> 中的函数 boost::ref()<boost/xpressive/regex_actions.hpp> 中的 boost::xpressive::ref() 之间存在一个重要区别。前者返回一个普通的 reference_wrapper<>,其在许多方面表现得像一个普通的引用。相比之下,boost::xpressive::ref() 返回一个 惰性 引用,您可以在惰性执行的表达式中使用它。这就是为什么我们可以说 ref(result)[s1],即使 result 没有接受 s1operator[]

除了子匹配占位符 s1s2 等之外,您还可以在动作中使用占位符 _ 来引用动作所附加的子表达式所匹配的字符串。例如,您可以使用以下正则表达式来匹配一串数字,将其解释为整数,并将结果赋值给局部变量

int i = 0;
// Here, _ refers back to all the
// characters matched by (+_d)
sregex rex = (+_d)[ ref(i) = as<int>(_) ];

惰性动作执行

将动作附加到正则表达式的一部分并执行匹配到底意味着什么?动作何时执行?如果动作是重复子表达式的一部分,动作是执行一次还是多次?如果子表达式最初匹配,但由于正则表达式的其余部分未能匹配而最终失败,动作是否会执行?

答案是,默认情况下,动作是 惰性 执行的。当一个子表达式匹配一个字符串时,它的动作与动作引用的任何子匹配的当前值一起被放入队列。如果匹配算法必须回溯,动作将根据需要从队列中弹出。只有在整个正则表达式成功匹配之后,动作才真正执行。它们会一次性执行,按照它们添加到队列中的顺序,作为 regex_match() 返回之前的最后一步。

例如,考虑以下正则表达式,它在找到数字时递增计数器。

int i = 0;
std::string str("1!2!3?");
// count the exciting digits, but not the
// questionable ones.
sregex rex = +( _d [ ++ref(i) ] >> '!' );
regex_search(str, rex);
assert( i == 2 );

动作 ++ref(i) 被排队三次:每个找到的数字一次。但它只 执行 两次:每个数字前面跟着一个 '!' 字符一次。当遇到 '?' 字符时,匹配算法回溯,从队列中删除最后一个动作。

即时动作执行

当您希望语义操作立即执行时,可以将包含该操作的子表达式包装在 keep() 中。keep() 会关闭其子表达式的回溯,但它也会导致子表达式排队的所有操作在 keep() 结束时执行。就好像 keep() 中的子表达式被编译成一个独立的正则表达式对象,并且匹配 keep() 就像单独调用 regex_search()。它匹配字符并执行操作,但从不回溯或回滚。例如,想象上述示例写成如下:

int i = 0;
std::string str("1!2!3?");
// count all the digits.
sregex rex = +( keep( _d [ ++ref(i) ] ) >> '!' );
regex_search(str, rex);
assert( i == 3 );

我们已经将子表达式 _d [ ++ref(i) ] 包装在 keep() 中。现在,每当这个正则表达式匹配一个数字时,动作就会被排队,然后立即执行,然后我们才尝试匹配一个 '!' 字符。在这种情况下,动作执行三次。

[Note] 注意

keep() 类似,before()after() 中的动作在它们的子表达式匹配时也会提前执行。

惰性函数

到目前为止,我们已经看到了如何编写由变量和运算符组成的语义动作。但是,如果您想从语义动作中调用函数呢?Xpressive 提供了一种实现此目的的机制。

第一步是定义一个函数对象类型。例如,这是一个函数对象类型,它在其参数上调用 push()

struct push_impl
{
    // Result type, needed for tr1::result_of
    typedef void result_type;

    template<typename Sequence, typename Value>
    void operator()(Sequence &seq, Value const &val) const
    {
        seq.push(val);
    }
};

下一步是使用 xpressive 的 function<> 模板来定义一个名为 push 的函数对象

// Global "push" function object.
function<push_impl>::type const push = {{}};

初始化看起来有点奇怪,但这是因为 push 正在进行静态初始化。这意味着它不需要在运行时构造。我们可以在语义操作中按如下方式使用 push

std::stack<int> ints;
// Match digits, cast them to an int
// and push it on the stack.
sregex rex = (+_d)[push(ref(ints), as<int>(_))];

您会注意到,这样做会导致成员函数调用看起来像普通函数调用。您可以选择以不同的方式编写语义操作,使其看起来更像成员函数调用

sregex rex = (+_d)[ref(ints)->*push(as<int>(_))];

Xpressive 识别 ->* 的使用,并将此表达式与上述表达式完全相同地处理。

当您的函数对象必须返回一个依赖于其参数的类型时,您可以使用 result<> 成员模板而不是 result_type typedef。例如,这是一个 first 函数对象,它返回 std::pair<>sub_match<>first 成员

// Function object that returns the
// first element of a pair.
struct first_impl
{
    template<typename Sig> struct result {};

    template<typename This, typename Pair>
    struct result<This(Pair)>
    {
        typedef typename remove_reference<Pair>
            ::type::first_type type;
    };

    template<typename Pair>
    typename Pair::first_type
    operator()(Pair const &p) const
    {
        return p.first;
    }
};

// OK, use as first(s1) to get the begin iterator
// of the sub-match referred to by s1.
function<first_impl>::type const first = {{}};

引用局部变量

正如我们在上面的示例中看到的,我们可以使用 xpressive::ref() 在动作中引用局部变量。任何此类变量都由正则表达式按引用持有,应注意避免让这些引用悬空。例如,在以下代码中,当 bad_voodoo() 返回时,对 i 的引用将悬空

sregex bad_voodoo()
{
    int i = 0;
    sregex rex = +( _d [ ++ref(i) ] >> '!' );
    // ERROR! rex refers by reference to a local
    // variable, which will dangle after bad_voodoo()
    // returns.
    return rex;
}

在编写语义操作时,您有责任确保所有引用都不会悬空。一种方法是使变量成为由正则表达式按值持有的共享指针。

sregex good_voodoo(boost::shared_ptr<int> pi)
{
    // Use val() to hold the shared_ptr by value:
    sregex rex = +( _d [ ++*val(pi) ] >> '!' );
    // OK, rex holds a reference count to the integer.
    return rex;
}

在上述代码中,我们使用 xpressive::val() 来按值持有共享指针。这通常不是必需的,因为动作中出现的局部变量默认是按值持有的,但在这种情况下是必需的。如果我们把动作写成 ++*pi,它会立即执行。这是因为 ++*pi 不是表达式模板,但 ++*val(pi) 是。

在语义动作中,将所有变量包装在 ref()val() 中可能会很繁琐。Xpressive 提供了 reference<>value<> 模板来简化操作。下表显示了等价关系

表 43.12. reference<> 和 value<>

这个 ...

... 等同于这个 ...

int i = 0;

sregex rex = +( _d [ ++ref(i) ] >> '!' );

int i = 0;
reference<int> ri(i);
sregex rex = +( _d [ ++ri ] >> '!' );

boost::shared_ptr<int> pi(new int(0));

sregex rex = +( _d [ ++*val(pi) ] >> '!' );

boost::shared_ptr<int> pi(new int(0));
value<boost::shared_ptr<int> > vpi(pi);
sregex rex = +( _d [ ++*vpi ] >> '!' );


如您所见,在使用 reference<> 时,您需要先声明一个局部变量,然后声明一个对其的 reference<>。这两步可以使用 local<> 合并为一步。

表 43.13. local<> vs. reference<>

这个 ...

... 等同于这个 ...

local<int> i(0);

sregex rex = +( _d [ ++i ] >> '!' );

int i = 0;
reference<int> ri(i);
sregex rex = +( _d [ ++ri ] >> '!' );


我们可以使用 local<> 将上述示例重写如下

local<int> i(0);
std::string str("1!2!3?");
// count the exciting digits, but not the
// questionable ones.
sregex rex = +( _d [ ++i ] >> '!' );
regex_search(str, rex);
assert( i.get() == 2 );

请注意,我们使用 local<>::get() 来访问局部变量的值。此外,请注意 local<> 可能会创建悬空引用,就像 reference<> 一样。

引用非局部变量

在本节开头,我们使用带语义操作的正则表达式解析单词/整数对字符串,并将其填充到 std::map<> 中。这要求映射和正则表达式一起定义并在它们超出作用域之前使用。如果我们想只定义一次正则表达式并用它来填充许多不同的映射呢?我们宁愿将映射传递给 regex_match() 算法,而不是直接将对其的引用嵌入到正则表达式对象中。我们可以做的是定义一个占位符,并在语义操作中使用它而不是映射本身。稍后,当我们调用其中一个正则表达式算法时,我们可以将引用绑定到实际的映射对象。以下代码展示了如何实现。

// Define a placeholder for a map object:
placeholder<std::map<std::string, int> > _map;

// Match a word and an integer, separated by =>,
// and then stuff the result into a std::map<>
sregex pair = ( (s1= +_w) >> "=>" >> (s2= +_d) )
    [ _map[s1] = as<int>(s2) ];

// Match one or more word/integer pairs, separated
// by whitespace.
sregex rx = pair >> *(+_s >> pair);

// The string to parse
std::string str("aaa=>1 bbb=>23 ccc=>456");

// Here is the actual map to fill in:
std::map<std::string, int> result;

// Bind the _map placeholder to the actual map
smatch what;
what.let( _map = result );

// Execute the match and fill in result map
if(regex_match(str, what, rx))
{
    std::cout << result["aaa"] << '\n';
    std::cout << result["bbb"] << '\n';
    std::cout << result["ccc"] << '\n';
}

此程序显示

1
23
456

我们在这里使用 placeholder<> 来定义 _map,它代表一个 std::map<> 变量。我们可以在语义操作中使用占位符,就像它是一个映射一样。然后,我们定义一个 match_results<> 结构,并通过 "what.let( _map = result );" 将实际的映射绑定到占位符。regex_match() 调用表现得就好像语义操作中的占位符已被替换为对 result 的引用一样。

[Note] 注意

语义动作中的占位符在运行时并不会 实际 被变量引用替换。在任何正则表达式算法执行期间,正则表达式对象都不会以任何方式被修改,因此它们可以安全地在多个线程中使用。

如果您使用 regex_iterator<>regex_token_iterator<>,则晚绑定动作参数的语法略有不同。正则表达式迭代器接受一个额外的构造函数参数来指定参数绑定。有一个 let() 函数,您可以使用它将变量绑定到其占位符。以下代码演示了如何操作。

// Define a placeholder for a map object:
placeholder<std::map<std::string, int> > _map;

// Match a word and an integer, separated by =>,
// and then stuff the result into a std::map<>
sregex pair = ( (s1= +_w) >> "=>" >> (s2= +_d) )
    [ _map[s1] = as<int>(s2) ];

// The string to parse
std::string str("aaa=>1 bbb=>23 ccc=>456");

// Here is the actual map to fill in:
std::map<std::string, int> result;

// Create a regex_iterator to find all the matches
sregex_iterator it(str.begin(), str.end(), pair, let(_map=result));
sregex_iterator end;

// step through all the matches, and fill in
// the result map
while(it != end)
    ++it;

std::cout << result["aaa"] << '\n';
std::cout << result["bbb"] << '\n';
std::cout << result["ccc"] << '\n';

此程序显示

1
23
456

用户定义断言

您可能已经熟悉正则表达式 断言。在 Perl 中,一些例子是 ^$ 断言,您可以使用它们分别匹配字符串的开头和结尾。Xpressive 允许您定义自己的断言。自定义断言是匹配成功所需的在匹配点必须为真的条件。您可以使用 xpressive 的 check() 函数检查自定义断言。

定义自定义断言有两种方法。最简单的是使用函数对象。假设您想确保子表达式匹配的子字符串长度为 3 或 6 个字符。以下结构定义了这样一个谓词

// A predicate that is true IFF a sub-match is
// either 3 or 6 characters long.
struct three_or_six
{
    bool operator()(ssub_match const &sub) const
    {
        return sub.length() == 3 || sub.length() == 6;
    }
};

您可以在正则表达式中按如下方式使用此谓词

// match words of 3 characters or 6 characters.
sregex rx = (bow >> +_w >> eow)[ check(three_or_six()) ] ;

上述正则表达式将查找长度为 3 或 6 个字符的整词。three_or_six 谓词接受一个 sub_match<>,它引用自定义断言所附加的子表达式所匹配的字符串部分。

[Note] 注意

自定义断言参与确定匹配是否成功。与延迟执行的动作不同,自定义断言在正则表达式引擎搜索匹配时会立即执行。

自定义断言也可以使用与语义操作相同的语法进行内联定义。下面是内联编写的相同自定义断言

// match words of 3 characters or 6 characters.
sregex rx = (bow >> +_w >> eow)[ check(length(_)==3 || length(_)==6) ] ;

在上述代码中,length() 是一个惰性函数,它调用其参数的 length() 成员函数,而 _ 是一个接收 sub_match 的占位符。

一旦您掌握了内联编写自定义断言的技巧,它们就会非常强大。例如,您可以编写一个只匹配有效日期的正则表达式(对于术语“有效”的某些适当宽松的定义)。

int const days_per_month[] =
    {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 31, 31};

mark_tag month(1), day(2);
// find a valid date of the form month/day/year.
sregex date =
    (
        // Month must be between 1 and 12 inclusive
        (month= _d >> !_d)     [ check(as<int>(_) >= 1
                                    && as<int>(_) <= 12) ]
    >>  '/'
        // Day must be between 1 and 31 inclusive
    >>  (day=   _d >> !_d)     [ check(as<int>(_) >= 1
                                    && as<int>(_) <= 31) ]
    >>  '/'
        // Only consider years between 1970 and 2038
    >>  (_d >> _d >> _d >> _d) [ check(as<int>(_) >= 1970
                                    && as<int>(_) <= 2038) ]
    )
    // Ensure the month actually has that many days!
    [ check( ref(days_per_month)[as<int>(month)-1] >= as<int>(day) ) ]
;

smatch what;
std::string str("99/99/9999 2/30/2006 2/28/2006");

if(regex_search(str, what, date))
{
    std::cout << what[0] << std::endl;
}

上面的程序打印出以下内容

2/28/2006

请注意内联自定义断言如何用于对月份、日期和年份的值进行范围检查。正则表达式不匹配 "99/99/9999""2/30/2006",因为它们不是有效日期。(没有第 99 个月,二月也没有 30 天。)

概述

符号表可以使用 std::map<> 构建到 xpressive 正则表达式中。映射键是待匹配的字符串,映射值是返回给您的语义操作的数据。Xpressive 属性,命名为 a1a2 直到 a9,持有与匹配键对应的 वैल्यू,以便在语义操作中使用。如果未找到符号,可以为属性指定默认值。

符号表

Xpressive 符号表只是一个 std::map<>,其中键是字符串类型,值可以是任何类型。例如,以下正则表达式匹配 map1 中的一个键,并将对应的值赋给属性 a1。然后,在语义操作中,它将存储在属性 a1 中的值赋给一个整数结果。

int result;
std::map<std::string, int> map1;
// ... (fill the map)
sregex rx = ( a1 = map1 ) [ ref(result) = a1 ];

考虑以下示例代码,它将数字名称翻译成整数。下面将对其进行描述。

#include <string>
#include <iostream>
#include <boost/xpressive/xpressive.hpp>
#include <boost/xpressive/regex_actions.hpp>
using namespace boost::xpressive;

int main()
{
    std::map<std::string, int> number_map;
    number_map["one"] = 1;
    number_map["two"] = 2;
    number_map["three"] = 3;
    // Match a string from number_map
    // and store the integer value in 'result'
    // if not found, store -1 in 'result'
    int result = 0;
    cregex rx = ((a1 = number_map ) | *_)
        [ ref(result) = (a1 | -1)];

    regex_match("three", rx);
    std::cout << result << '\n';
    regex_match("two", rx);
    std::cout << result << '\n';
    regex_match("stuff", rx);
    std::cout << result << '\n';
    return 0;
}

此程序打印以下内容

3
2
-1

程序首先构建一个数字映射,其中数字名称作为字符串键,对应的整数作为值。然后它使用属性 a1 来表示符号表查找的结果来构造一个静态正则表达式。在语义动作中,该属性被赋给一个整数变量 result。如果未找到符号,则将默认值 -1 赋给 result。通配符 *_ 确保即使未找到符号,正则表达式也能匹配。

此示例的更完整版本可以在 libs/xpressive/example/numbers.cpp 中找到[31]。它将数字名称翻译到“九亿九千九百九十九万九千九百九十九”,以及一些特殊数字名称,如“一打”。

符号表匹配默认区分大小写,但可以通过将表达式封装在 icase() 中使其不区分大小写。

属性

在正则表达式中最多可以使用九个属性。它们在 boost::xpressive 命名空间中被命名为 a1a2、...、a9。属性类型与其分配的映射的第二个组件相同。可以通过语法 (a1 | default-value) 在语义操作中指定属性的默认值。

属性具有适当的作用域,因此您可以做一些疯狂的事情,例如:( (a1=sym1) >> (a1=sym2)[ref(x)=a1] )[ref(y)=a1]。内部语义操作看到内部的 a1,外部语义操作看到外部的 a1。它们甚至可以具有不同的类型。

[Note] 注意

Xpressive 从映射中构建一个隐藏的三元搜索树,以便快速搜索。如果定义了 BOOST_DISABLE_THREADS,则隐藏的三元搜索树会“自调整”,因此在每次搜索后,它会根据先前搜索的频率重构自身以提高未来搜索的效率。

概述

将正则表达式与字符串匹配通常需要依赖于区域设置的信息。例如,不区分大小写的比较是如何执行的?区域设置敏感的行为被捕获在一个特性类中。xpressive 提供了三个特性类模板:cpp_regex_traits<>c_regex_traits<>null_regex_traits<>。第一个封装了 std::locale,第二个封装了全局 C 区域设置,第三个是一个存根特性类型,用于搜索非字符数据时。所有特性模板都符合 正则表达式特性概念

设置默认正则表达式特性

默认情况下,Xpressive 对所有模式都使用 cpp_regex_traits<>。这会导致所有正则表达式对象都使用全局的 std::locale。如果您在编译时定义了 BOOST_XPRESSIVE_USE_C_TRAITS,则 Xpressive 将默认使用 c_regex_traits<>

将自定义特性与动态正则表达式一起使用

要创建一个使用自定义特性对象的动态正则表达式,您必须使用 regex_compiler<>。基本步骤如下例所示:

// Declare a regex_compiler that uses the global C locale
regex_compiler<char const *, c_regex_traits<char> > crxcomp;
cregex crx = crxcomp.compile( "\\w+" );

// Declare a regex_compiler that uses a custom std::locale
std::locale loc = /* ... create a locale here ... */;
regex_compiler<char const *, cpp_regex_traits<char> > cpprxcomp(loc);
cregex cpprx = cpprxcomp.compile( "\\w+" );

regex_compiler 对象充当正则表达式工厂。一旦它们被赋予了一个 locale,它们创建的每个正则表达式对象都将使用该 locale。

将自定义特性与静态正则表达式一起使用

如果您希望某个特定的静态正则表达式使用不同的特性集,您可以使用特殊的 imbue() 模式修饰符。例如:

// Define a regex that uses the global C locale
c_regex_traits<char> ctraits;
sregex crx = imbue(ctraits)( +_w );

// Define a regex that uses a customized std::locale
std::locale loc = /* ... create a locale here ... */;
cpp_regex_traits<char> cpptraits(loc);
sregex cpprx1 = imbue(cpptraits)( +_w );

// A shorthand for above
sregex cpprx2 = imbue(loc)( +_w );

imbue() 模式修饰符必须包裹整个模式。只对静态正则表达式的一部分进行 imbue 是错误的。例如:

// ERROR! Cannot imbue() only part of a regex
sregex error = _w >> imbue(loc)( _w );

使用 null_regex_traits 搜索非字符数据

使用 xpressive 静态正则表达式,您不仅限于在字符序列中搜索模式。您可以在原始字节、整数或任何符合 字符概念 的数据中搜索模式。null_regex_traits<> 使其变得简单。它是 正则表达式特性概念 的一个存根实现。它不识别任何字符类,也不执行大小写敏感映射。

例如,使用 null_regex_traits<>,您可以编写一个静态正则表达式来在整数序列中查找模式,如下所示:

// some integral data to search
int const data[] = {0, 1, 2, 3, 4, 5, 6};

// create a null_regex_traits<> object for searching integers ...
null_regex_traits<int> nul;

// imbue a regex object with the null_regex_traits ...
basic_regex<int const *> rex = imbue(nul)(1 >> +((set= 2,3) | 4) >> 5);
match_results<int const *> what;

// search for the pattern in the array of integers ...
regex_search(data, data + 7, what, rex);

assert(what[0].matched);
assert(*what[0].first == 1);
assert(*what[0].second == 6);

通过这些提示和技巧,从 Xpressive 中榨取最大的性能。

编译模式一次并重复使用

编译正则表达式(动态或静态)比执行匹配或搜索的开销要 大得多。如果可能,最好将模式编译成 basic_regex<> 对象一次并重复使用,而不是反复重新创建它。

由于 basic_regex<> 对象不会被任何正则表达式算法修改,因此一旦它们的初始化(以及它们所属的任何语法的初始化)完成,它们就完全是线程安全的。重复使用模式最简单的方法是简单地将 basic_regex<> 对象设为“static const”。

重用 match_results<> 对象

match_results<> 对象会缓存动态分配的内存。因此,如果您需要执行多次正则表达式搜索,最好重用同一个 match_results<> 对象。

注意:match_results<> 对象不是线程安全的,因此不要在跨线程重用它们时做得太过火。

优先选择接受 match_results<> 对象的算法

这是上一条建议的推论。如果您正在进行多次搜索,您应该优先选择接受 match_results<> 对象的正则表达式算法,而不是不接受的,并且您应该每次都重用同一个 match_results<> 对象。如果您不提供 match_results<> 对象,将为您创建一个临时对象,并在算法返回时将其丢弃。对象中缓存的任何内存都将被释放,并且在下次需要时必须重新分配。

优先选择接受迭代器范围而不是空终止字符串的算法

Xpressive 提供了 regex_match()regex_search() 算法的重载,它们对 C 风格的空终止字符串进行操作。您应该优先选择接受迭代器范围的重载。当您将空终止字符串传递给正则表达式算法时,结束迭代器会通过调用 strlen 立即计算。如果您已经知道字符串的长度,您可以通过使用 [begin, end) 对调用正则表达式算法来避免这种开销。

使用静态正则表达式

平均而言,静态正则表达式比其动态对应项执行速度快约 10% 到 15%。熟悉静态正则表达式方言是值得的。

理解 syntax_option_type::optimize

optimize 标志告诉正则表达式编译器花额外的时间分析模式。这可以使某些模式执行得更快,但会增加编译模式的时间,并且通常会增加模式消耗的内存量。如果您打算重用模式,optimize 通常是一个不错的选择。如果您只使用模式一次,请不要使用 optimize

常见陷阱

请记住以下提示,以避免在使用 Xpressive 时遇到麻烦。

在单个线程上创建语法

使用静态正则表达式,您可以通过将正则表达式相互嵌套来创建语法。编译外部正则表达式时,外部和内部正则表达式对象,以及它们直接或间接引用的所有正则表达式对象都将被修改。因此,全局正则表达式对象参与语法是危险的。最好从单个线程构建正则表达式语法。一旦构建完成,生成的正则表达式语法可以从多个线程执行而不会出现问题。

警惕嵌套量词

这是许多正则表达式引擎共有的陷阱。某些模式可能导致性能呈指数级下降。这些模式通常涉及一个量化项嵌套在另一个量词中,例如 "(a*)*",尽管在许多情况下,问题更难发现。警惕具有嵌套量词的模式。

CharT 要求

如果类型 BidiIterT 用作 basic_regex<> 的模板参数,则 CharTiterator_traits<BidiIterT>::value_type。类型 CharT 必须具有简单的默认构造函数、复制构造函数、赋值运算符和析构函数。此外,对于对象;类型为 CharTc,类型为 CharT constc1c2,以及类型为 inti,必须满足以下要求:

表 43.14. CharT 要求

表达式

返回类型

断言 / 注释 / 前置条件 / 后置条件

CharT c

CharT

默认构造函数(必须是简单的)。

CharT c(c1)

CharT

复制构造函数(必须是简单的)。

c1 = c2

CharT

赋值运算符(必须是简单的)。

c1 == c2

bool

如果 c1 的值与 c2 相同,则为 true

c1 != c2

bool

如果 c1c2 不相等,则为 true

c1 < c2

bool

如果 c1 的值小于 c2,则为 true

c1 > c2

bool

如果 c1 的值大于 c2,则为 true

c1 <= c2

bool

如果 c1 小于或等于 c2,则为 true

c1 >= c2

bool

如果 c1 大于或等于 c2,则为 true

intmax_t i = c1

int

CharT 必须可转换为整数类型。

CharT c(i);

CharT

CharT 必须可从整数类型构造。


特性要求

在下表中,X 表示一个定义字符容器类型 CharT 的类型和函数的特性类;u 是类型 X 的对象;v 是类型 const X 的对象;p 是类型 const CharT* 的值;I1I2Input Iteratorsc 是类型 const CharT 的值;s 是类型 X::string_type 的对象;cs 是类型 const X::string_type 的对象;b 是类型 bool 的值;i 是类型 int 的值;F1F2 是类型 const CharT* 的值;loc 是类型 X::locale_type 的对象;ch 是类型 const char 的对象。

表 43.15. 特性要求

表达式

返回类型

断言 / 注释
前置 / 后置条件

X::char_type

CharT

在类模板 basic_regex<> 的实现中使用的字符容器类型。

X::string_type

std::basic_string<CharT>std::vector<CharT>

X::locale_type

实现定义

可复制构造的类型,表示特性类使用的 locale。

X::char_class_type

实现定义

表示特定字符分类的位掩码类型。此类型的多个值可以按位或运算以获得新的有效值。

X::hash(c)

unsigned char

生成一个介于 0UCHAR_MAX(含)之间的值。

v.widen(ch)

CharT

扩展指定的 char 并返回生成的 CharT

v.in_range(r1, r2, c)

bool

对于任何字符 r1r2,如果 r1 <= c && c <= r2,则返回 true。要求 r1 <= r2

v.in_range_nocase(r1, r2, c)

bool

对于字符 r1r2,如果存在某个字符 d 使得 v.translate_nocase(d) == v.translate_nocase(c) 并且 r1 <= d && d <= r2,则返回 true。要求 r1 <= r2

v.translate(c)

X::char_type

返回一个字符,使得对于任何被认为是与 c 等效的字符 d,都有 v.translate(c) == v.translate(d)

v.translate_nocase(c)

X::char_type

对于在不考虑大小写进行比较时被认为是与 c 等效的所有字符 C,都有 v.translate_nocase(c) == v.translate_nocase(C)

v.transform(F1, F2)

X::string_type

返回由迭代器范围 [F1, F2) 指定的字符序列的排序键,使得如果字符序列 [G1, G2) 在字符序列 [H1, H2) 之前排序,则 v.transform(G1, G2) < v.transform(H1, H2)

v.transform_primary(F1, F2)

X::string_type

返回由迭代器范围 [F1, F2) 指定的字符序列的排序键,使得如果在不考虑字符大小写的情况下,字符序列 [G1, G2) 在字符序列 [H1, H2) 之前排序,则 v.transform_primary(G1, G2) < v.transform_primary(H1, H2)

v.lookup_classname(F1, F2)

X::char_class_type

将迭代器范围 [F1,F2) 指定的字符序列转换为位掩码类型,该类型随后可以传递给 isctypelookup_classname 返回的值可以安全地进行按位或运算。如果字符序列不是 X 识别的字符类的名称,则返回 0。返回的值应与序列中字符的大小写无关。

v.lookup_collatename(F1, F2)

X::string_type

返回一个字符序列,该序列表示由迭代器范围 [F1, F2) 指定的整理元素。如果字符序列不是有效的整理元素,则返回空字符串。

v.isctype(c, v.lookup_classname(F1, F2))

bool

如果字符 c 是由迭代器范围 [F1, F2) 指定的字符类的成员,则返回 true,否则返回 false

v.value(c, i)

int

如果字符 c 在基数 i 中是有效数字,则返回数字 c 在基数 i 中表示的值;否则返回 -1
[注:i 的值只可能是 81016。 -注毕]

u.imbue(loc)

X::locale_type

u 注入 locale loc,返回 u 之前使用的 locale。

v.getloc()

X::locale_type

返回 v 当前使用的 locale。


致谢

本节改编自 Boost.Regex 文档中对应的页面以及将正则表达式添加到标准库的 提案

下面您可以找到六个完整的示例程序。

查看整个字符串是否匹配正则表达式

这是简介中的示例。为了您的方便,在此重现。

#include <iostream>
#include <boost/xpressive/xpressive.hpp>

using namespace boost::xpressive;

int main()
{
    std::string hello( "hello world!" );

    sregex rex = sregex::compile( "(\\w+) (\\w+)!" );
    smatch what;

    if( regex_match( hello, what, rex ) )
    {
        std::cout << what[0] << '\n'; // whole match
        std::cout << what[1] << '\n'; // first capture
        std::cout << what[2] << '\n'; // second capture
    }

    return 0;
}

该程序输出如下:

hello world!
hello
world


顶部

查看字符串是否包含匹配正则表达式的子字符串

在此示例中,请注意我们如何使用自定义 mark_tag 以使模式更具可读性。我们稍后可以使用 mark_tag 索引到 match_results<> 中。

#include <iostream>
#include <boost/xpressive/xpressive.hpp>

using namespace boost::xpressive;

int main()
{
    char const *str = "I was born on 5/30/1973 at 7am.";

    // define some custom mark_tags with names more meaningful than s1, s2, etc.
    mark_tag day(1), month(2), year(3), delim(4);

    // this regex finds a date
    cregex date = (month= repeat<1,2>(_d))           // find the month ...
               >> (delim= (set= '/','-'))            // followed by a delimiter ...
               >> (day=   repeat<1,2>(_d)) >> delim  // and a day followed by the same delimiter ...
               >> (year=  repeat<1,2>(_d >> _d));    // and the year.

    cmatch what;

    if( regex_search( str, what, date ) )
    {
        std::cout << what[0]     << '\n'; // whole match
        std::cout << what[day]   << '\n'; // the day
        std::cout << what[month] << '\n'; // the month
        std::cout << what[year]  << '\n'; // the year
        std::cout << what[delim] << '\n'; // the delimiter
    }

    return 0;
}

该程序输出如下:

5/30/1973
30
5
1973
/


顶部

替换所有匹配正则表达式的子字符串

以下程序在字符串中查找日期并使用伪 HTML 进行标记。

#include <iostream>
#include <boost/xpressive/xpressive.hpp>

using namespace boost::xpressive;

int main()
{
    std::string str( "I was born on 5/30/1973 at 7am." );

    // essentially the same regex as in the previous example, but using a dynamic regex
    sregex date = sregex::compile( "(\\d{1,2})([/-])(\\d{1,2})\\2((?:\\d{2}){1,2})" );

    // As in Perl, $& is a reference to the sub-string that matched the regex
    std::string format( "<date>$&</date>" );

    str = regex_replace( str, date, format );
    std::cout << str << '\n';

    return 0;
}

该程序输出如下:

I was born on <date>5/30/1973</date> at 7am.


顶部

查找所有匹配正则表达式的子字符串并逐一遍历它们

以下程序在宽字符字符串中查找单词。它使用 wsregex_iterator。请注意,解引用 wsregex_iterator 会产生一个 wsmatch 对象。

#include <iostream>
#include <boost/xpressive/xpressive.hpp>

using namespace boost::xpressive;

int main()
{
    std::wstring str( L"This is his face." );

    // find a whole word
    wsregex token = +alnum;

    wsregex_iterator cur( str.begin(), str.end(), token );
    wsregex_iterator end;

    for( ; cur != end; ++cur )
    {
        wsmatch const &what = *cur;
        std::wcout << what[0] << L'\n';
    }

    return 0;
}

该程序输出如下:

This
is
his
face


顶部

将字符串拆分为每个都匹配正则表达式的标记

以下程序在字符串中查找比赛时间,并首先显示分钟,然后显示秒。它使用 regex_token_iterator<>

#include <iostream>
#include <boost/xpressive/xpressive.hpp>

using namespace boost::xpressive;

int main()
{
    std::string str( "Eric: 4:40, Karl: 3:35, Francesca: 2:32" );

    // find a race time
    sregex time = sregex::compile( "(\\d):(\\d\\d)" );

    // for each match, the token iterator should first take the value of
    // the first marked sub-expression followed by the value of the second
    // marked sub-expression
    int const subs[] = { 1, 2 };

    sregex_token_iterator cur( str.begin(), str.end(), time, subs );
    sregex_token_iterator end;

    for( ; cur != end; ++cur )
    {
        std::cout << *cur << '\n';
    }

    return 0;
}

该程序输出如下:

4
40
3
35
2
32


顶部

使用正则表达式作为分隔符拆分字符串

以下程序处理一些用 HTML 标记的文本并剥离掉标记。它使用一个匹配 HTML 标签的正则表达式和一个 regex_token_iterator<>,该迭代器返回字符串中 匹配正则表达式的部分。

#include <iostream>
#include <boost/xpressive/xpressive.hpp>

using namespace boost::xpressive;

int main()
{
    std::string str( "Now <bold>is the time <i>for all good men</i> to come to the aid of their</bold> country." );

    // find a HTML tag
    sregex html = '<' >> optional('/') >> +_w >> '>';

    // the -1 below directs the token iterator to display the parts of
    // the string that did NOT match the regular expression.
    sregex_token_iterator cur( str.begin(), str.end(), html, -1 );
    sregex_token_iterator end;

    for( ; cur != end; ++cur )
    {
        std::cout << '{' << *cur << '}';
    }
    std::cout << '\n';

    return 0;
}

该程序输出如下:

{Now }{is the time }{for all good men}{ to come to the aid of their}{ country.}


顶部

显示嵌套结果树

这是一个辅助类,演示了如何显示嵌套结果树

// Displays nested results to std::cout with indenting
struct output_nested_results
{
    int tabs_;

    output_nested_results( int tabs = 0 )
        : tabs_( tabs )
    {
    }

    template< typename BidiIterT >
    void operator ()( match_results< BidiIterT > const &what ) const
    {
        // first, do some indenting
        typedef typename std::iterator_traits< BidiIterT >::value_type char_type;
        char_type space_ch = char_type(' ');
        std::fill_n( std::ostream_iterator<char_type>( std::cout ), tabs_ * 4, space_ch );

        // output the match
        std::cout << what[0] << '\n';

        // output any nested matches
        std::for_each(
            what.nested_results().begin(),
            what.nested_results().end(),
            output_nested_results( tabs_ + 1 ) );
    }
};

顶部



[31] 衷心感谢 David Jenkins 贡献此示例。


PrevUpHomeNext