Boost C++ 库

世界上最受尊敬和设计最精良的 C++ 库项目之一。 Herb SutterAndrei AlexandrescuC++ 编码标准

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

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

什么是 xpressive?

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

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

Hello, world!

理论说够了。让我们来看一下 xpressive 风格的Hello World

#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,这与您习惯的有所不同。“sregex”中的“s”代表“string”,表示此 regex 可用于在std::string对象中查找模式。我稍后将详细讨论这种区别及其影响。

注意正则表达式对象的初始化方式

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

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

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

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

正如您所见,静态 regexes 的语法明显不同于标准的 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>。如果您只使用静态 regexes,可以通过仅包含xpressive_static.hpp来提高编译时间。同样,如果您只打算使用动态 regexes,可以包含xpressive_dynamic.hpp

如果您还想在静态 regexes 中使用语义操作或自定义断言,则需要另外包含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。要使regex_match()成功,整个字符串必须从头到尾匹配 regex。如果您向regex_match()提供match_results<>,它会将找到的任何标记的子表达式写入其中。

regex_search()

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

regex_replace()

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

regex_iterator<>

一个符合 STL 的迭代器,可以轻松查找字符串中与 regex 匹配的所有位置。取消引用regex_iterator<>会返回一个match_results<>。递增regex_iterator<>会查找下一个匹配项。

regex_token_iterator<>

regex_iterator<>类似,不同之处在于取消引用regex_token_iterator<>会返回一个字符串。默认情况下,它将返回 regex 匹配的整个子字符串,但它可以配置为一次返回任何或所有标记的子表达式,甚至返回字符串中匹配 regex 的部分。

regex_compiler<>

一个用于basic_regex<>对象的工厂。它将一个字符串“编译”成一个正则表达式。您通常不必直接处理regex_compiler<>,因为basic_regex<>类有一个工厂方法,该方法在内部使用regex_compiler<>。但是,如果您需要执行一些高级操作,例如使用不同的std::locale创建basic_regex<>对象,则需要显式使用regex_compiler<>


现在您对 xpressive 提供的工具有所了解,您可以回答以下两个问题来选择适合您的工具:

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

了解您的迭代器类型

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

表 43.2. xpressive 类型别名与迭代器类型

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++ 中嵌入一个专门用于模式匹配的迷你语言。这些“静态 regexes”比基于字符串的同类产品有许多优势。特别是,静态 regexes:

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

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

构造和赋值

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

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

赋值工作类似。

字符和字符串字面量

在静态 regexes 中,字符和字符串字面量匹配它们本身。例如,在上面的 regex 中,'$''.'分别匹配字符'$''.'。不要被$.在 Perl 中是元字符的事实所迷惑。在 xpressive 中,字面量始终代表它们本身。

在使用静态 regexes 中的字面量时,您必须注意至少一个操作数不是字面量。例如,以下是无效的 regexes:

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

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

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

顺序和交替

您可能已经注意到,静态 regexes 中的子表达式必须由顺序运算符>>分隔。您可以将此运算符读作“后面跟着”。

// 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 标签的 regex:

"<(\\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。有关如何自定义 regex 行为的更多信息,请参阅关于本地化和 Regex Traits的部分。

静态 xpressive 语法速查表

下表列出了熟悉的 regex 构造及其在静态 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)

between n and m 次,非贪婪匹配。

^

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

(?>stuff)

keep(stuff)

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

(?=stuff)

before(stuff)

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

(?!stuff)

~before(stuff)

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

(?<=stuff)

after(stuff)

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

(?<!stuff)

~after(stuff)

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

(?P<name>stuff)

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

创建命名捕获。

(?P=name)

mark_tag name(n);
...
名称

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



概述

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

构造与赋值

有两种方法可以创建动态正则表达式:使用 basic_regex<>::compile() 函数或使用 regex_compiler<> 类模板。如果您想要默认的 locale,请使用 basic_regex<>::compile()。如果您需要指定不同的 locale,请使用 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 imbue 一个 regex_compiler<> 对象后,该 regex_compiler<> 编译的所有正则表达式对象都将使用该 locale。例如:

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<> 具有类型为 BidirectionalIteratorfirstsecond 数据成员。它们是此 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)


caution 结果失效 caution

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

正则表达式不仅适用于搜索文本;它们也擅长处理文本。最常见的文本处理任务之一是搜索和替换。xpressive 提供了 regex_replace() 算法用于搜索和替换。

regex_replace()

使用 regex_replace() 执行搜索和替换很简单。您只需要输入序列、一个 regex 对象以及一个格式字符串或格式化器对象。 regex_replace() 算法有几个版本。有些接受像 std::string 这样的双向容器作为输入序列,并将结果返回到同一类型的新容器中。有些接受以 null 结尾的字符串作为输入,并返回一个 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 格式序列

当您没有使用上述格式标志之一指定替换字符串方言时,您将获得 ECMA-262(ECMAScript 标准)定义的方言。下表显示了在 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,而 flagsregex_constants::match_flag_type 的值。

表 43.11. 格式化器签名

格式化器调用

返回类型

语义

fmt(what)

字符范围(例如 std::string)或 null 结尾的字符串

由 regex 匹配的字符串将被格式化器返回的字符串替换。

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<> 是文本处理世界的“吉恩苏刀”。它切!它削!本节介绍如何使用高度可配置的 regex_token_iterator<> 来分割输入序列。

概述

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

如您所见,regex_token_iterator<> 可以做很多事情。这使得描述起来很困难,但一些示例应该能使其清晰。

示例 1:简单分词

此示例使用 regex_token_iterator<> 将序列分割成一系列由单词组成的 token。

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<> 将序列分割成一系列由单词组成的 token,但它使用 regex 作为分隔符。当我们向 regex_token_iterator<> 构造函数传递 -1 作为最后一个参数时,它指示 token 迭代器将匹配 regex 的部分视为 token。

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<> 将包含一堆日期的序列分割成一系列仅包含年份的 token。当我们向 regex_token_iterator<> 构造函数传递一个正整数 N 作为最后一个参数时,它指示 token 迭代器仅将每次匹配的第 N 个标记子表达式视为 token。

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,...} 时,它指示 token 迭代器将每次匹配的第 I 个、第 J 个等标记子表达式视为 token。

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() 查找下一个匹配。此时,过程重复——token 迭代器取第二个子匹配的值,然后是第一个,依此类推。

概述

对于复杂的正则表达式,处理数字捕获可能很麻烦。计算左括号以确定要引用的捕获是不有趣的。更有趣的事实是,仅仅编辑一个正则表达式就可能导致捕获被分配一个新数字,从而使引用旧数字的代码失效。

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

动态命名捕获

对于动态正则表达式,xpressive 遵循其他流行 regex 引擎的命名捕获语法。您可以使用 "(?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)");

上述正则表达式的效果是找到第一个重复的字符。

一旦您使用带有命名捕获的 regex 执行了匹配或搜索操作,您就可以使用捕获的名称通过 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 等一样使用它,但名称更有意义。下面是使用静态 regex 的上述示例的外观:

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> 才能使用格式表达式。

概述

将 regex 表示为 C++ 表达式的一个关键优势是能够轻松地在 regex 内部引用其他 C++ 代码和数据。这使得其他正则表达式库无法实现编程惯用语。特别值得一提的是,一个 regex 可以引用另一个 regex,从而允许您用 regex 构建语法。本节介绍如何通过值和引用嵌入一个 regex 到另一个 regex 中,当 regex 对象引用其他 regex 时它们的行为,以及如何在成功解析后访问结果树。

按值嵌入 Regex

basic_regex<> 对象具有值语义。当一个 regex 对象出现在另一个 regex 定义的右侧时,它就像是按值嵌入了该 regex;也就是说,嵌套 regex 的副本被包含 regex 存储。在模式匹配期间,内部 regex 由外部 regex 调用。内部 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;

这一行创建了一个新 regex,该 regex 按值嵌入了旧 regex。然后,新 regex 被赋值回原始 regex。由于旧 regex 的副本是在右侧创建的,因此此操作的执行方式符合您的预期:新 regex 具有被单词开始和结束断言包围的旧 regex 的行为。

[Note] 注意

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

按引用嵌入 Regex

如果您希望能够构建递归正则表达式和上下文无关文法,按值嵌入 regex 是不够的。您需要能够使您的正则表达式自我引用。大多数正则表达式引擎不提供这种能力,但 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() 辅助函数使其成为可能。它允许一个 regex 对象被 按引用 嵌入到另一个 regex 对象中。由于右侧按引用持有 parentheses,因此将右侧分配回 parentheses 会创建一个循环,该循环将递归执行。

构建文法

一旦我们允许正则表达式中的自引用,魔鬼就跑出了瓶子,各种有趣的事情都成为可能。特别是,我们现在可以用正则表达式构建文法。让我们看一个教科书上的文法示例:不起眼的计算器。

sregex group, factor, term, expression;

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

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

让我们仔细看看这个正则表达式文法。请注意它是循环的:expressionterm 为基础实现,termfactor 为基础实现,factorgroup 为基础实现,groupexpression 为基础实现,从而闭合了循环。一般来说,定义循环文法的方法是前向声明 regex 对象,并按引用嵌入尚未初始化的正则表达式。在上面的文法中,只有一个地方我们需要引用一个尚未初始化的 regex 对象:group 的定义。在该位置,我们使用 by_ref() 按引用嵌入 expression。在所有其他地方,按值嵌入其他 regex 对象就足够了,因为它们已经初始化,并且它们的值不会改变。

[Tip] 提示

如果可能,请按值嵌入

一般来说,优先按值嵌入正则表达式而不是按引用。它涉及少一次间接寻址,使您的模式匹配速度稍快一些。此外,值语义更简单,并且可以使您的文法更容易理解。不要担心“复制”regex 的开销。每个 regex 对象都与其所有副本共享其实现。

动态 Regex 文法

使用 regex_compiler<>,您还可以构建由动态正则表达式组成的文法。您可以通过创建命名 regex 并按名称引用其他 regex 来做到这一点。每个 regex_compiler<> 实例都维护一个从名称到使用它创建的 regex 的映射。

您可以通过在 regex 前面加上 "(?$name=)" 来创建一个命名的动态 regex,其中 name 是 regex 的名称。您可以在另一个 regex 中使用 "(?$name)" 来引用一个命名的 regex。在另一个 regex 中引用命名 regex 时,它不必已存在,但必须在您使用该 regex 时存在。

下面是一个使用动态 regex 文法实现上面计算器示例的代码片段。

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;
}

与静态 regex 文法一样,嵌套的 regex 调用会创建嵌套的匹配结果(请参阅下面的“嵌套结果”)。结果是匹配字符串的完整解析树。与静态 regex 不同,动态 regex 始终按引用嵌入,而不是按值嵌入。

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

上面计算器示例引发了一系列非常复杂的内存管理问题。四个 regex 对象中的每一个都相互引用,有些是直接的,有些是间接的,有些是按值的,有些是按引用的。如果我们从一个函数返回其中一个,而让其他对象超出作用域呢?引用会怎么样?答案是 regex 对象是内部引用计数的,因此它们会在需要时保持其引用的 regex 对象。所以按值传递 regex 对象永远不是问题,即使它引用了已经超出作用域的其他 regex 对象。

那些处理过引用计数的人可能熟悉它的阿喀琉斯之踵:循环引用。如果 regex 对象是引用计数的,那么像计算器示例中创建的循环会怎么样?它们会泄漏吗?答案是否定的,它们不会泄漏。basic_regex<> 对象有一些棘手的引用跟踪代码,可以确保即使是循环的 regex 文法在最后一个外部引用消失时也会被清理。所以不要担心它。创建循环文法,按您想要的任意方式传递 regex 对象并复制它们。它快速而高效,并保证不会泄漏或导致悬空引用。

嵌套 Regex 和子匹配作用域

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

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

作者可能无意让内部 regex 覆盖外部 regex 写入的子匹配。当内部 regex 作为用户输入接受时,问题尤其严重。作者无法知道内部 regex 是否会覆盖子匹配向量。这显然是不可接受的。

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

嵌套结果

如果嵌套 regex 有自己的子匹配,那么在成功匹配后应该有一个访问它们的途径。实际上,确实有。在 regex_match()regex_search() 之后,match_results<> 结构的行为就像一个由嵌套结果组成的树的头部。match_results<> 类提供了一个 nested_results() 成员函数,它返回一个 match_results<> 结构的有序序列,代表嵌套 regex 的结果。嵌套结果的顺序与嵌套 regex 对象匹配的顺序相同。

以我们之前看到的匹配平衡、嵌套括号的 regex 为例。

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] 提示

请参阅 输出嵌套结果示例 部分中的定义。

过滤嵌套结果

有时一个 regex 会有几个嵌套的 regex 对象,而您想知道哪个结果对应于哪个 regex 对象。这时 basic_regex<>::regex_id()match_results<>::regex_id() 就派上用场了。在迭代嵌套结果时,您可以将结果的 regex id 与您感兴趣的 regex 对象的 id 进行比较。

为了让这件事更容易一些,xpressive 提供了一个谓词,可以轻松地只迭代与特定嵌套 regex 对应的结果。它被称为 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::make_filter_iterator()Boost.Iterator 中选择仅对应于特定嵌套 regex 的那些结果。此程序显示以下内容:

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] 注意

要将语义操作与静态 regex 一起使用,您必须 #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() 函数(在 <boost/ref.hpp> 中)与 Boost.Xpressive 的 ref() 函数(在 <boost/xpressive/regex_actions.hpp> 中)之间存在一个重要区别。第一个返回一个普通的 reference_wrapper<>,它在许多方面表现得像一个普通引用。相比之下,boost::xpressive::ref() 返回一个 惰性 引用,您可以在惰性执行的表达式中使用它。这就是为什么我们可以说 ref(result)[s1],即使 result 没有接受 s1operator[]

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

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

惰性操作执行

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

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

例如,考虑以下 regex,它每找到一个数字就递增一个计数器。

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() 中的子表达式被编译成一个独立的 regex 对象,匹配 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() 中。现在,每当此 regex 匹配一个数字时,该操作都会被排队,然后在我们尝试匹配 '!' 字符之前立即执行。在这种情况下,该操作会执行三次。

[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<>。这要求 map 和正则表达式一起定义,并在它们超出作用域之前使用。如果我们想定义一次正则表达式并用它来填充许多不同的 map 怎么办?我们宁愿将 map 传递给 regex_match() 算法,而不是直接在正则表达式对象中嵌入对它的引用。我们可以做的是定义一个占位符,并在语义动作中使用它而不是 map 本身。稍后,当我们调用其中一个正则表达式算法时,我们可以将引用绑定到一个实际的 map 对象。下面的代码显示了如何操作。

// 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<> 变量。我们可以在语义动作中使用该占位符,就像它是一个 map 一样。然后,我们定义一个 match_results<> 结构,并通过“what.let( _map = result );”将实际 map 绑定到占位符。 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<>,该 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 正则表达式中。Map 的键是需要匹配的字符串,Map 的值是需要返回给您的语义动作的数据。Xpressive 属性,命名为 a1, a2, 直到 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

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

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

默认情况下,符号表匹配区分大小写,但可以通过将表达式包含在 icase() 中来使其不区分大小写。

属性

正则表达式最多可以使用九个属性。它们在 boost::xpressive 命名空间中命名为 a1, a2, ..., a9。属性类型与赋给它的 map 的第二个组件类型相同。可以通过在语义动作中使用 (a1 | default-value) 语法为属性指定默认值。

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

[Note] 注意

Xpressive 从 map 构建一个隐藏的三叉搜索树,以便可以快速搜索。如果定义了 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 对象充当正则表达式工厂。一旦它们被注入了区域信息,它们创建的每个正则表达式对象都将使用该区域信息。

对静态正则表达式使用自定义特性

如果您希望某个特定的静态正则表达式使用一套不同的特性,您可以使用特殊的 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 静态正则表达式,您不限于在字符序列中搜索模式。您可以搜索原始字节、整数或任何符合 Char Concept 的模式。 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<> 对象设为“静态常量”。

重复使用 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 必须具有平凡的默认构造函数、复制构造函数、赋值运算符和析构函数。此外,对于对象,必须满足以下要求; c 类型为 CharTc1c2 类型为 CharT const,并且 i 类型为 int

表 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 的类型和函数的特性类; uX 类型的一个对象; vconst X 类型的一个对象; pconst CharT* 类型的一个值; I1I2Input Iteratorscconst CharT 类型的一个值; sX::string_type 类型的一个对象; csconst X::string_type 类型的一个对象; bbool 类型的一个值; iint 类型的一个值; F1F2const CharT* 类型的值; locX::locale_type 类型的一个对象;而 chconst char 类型的一个对象。

表 43.15. 特性要求

表达式

返回类型

断言 / 说明
前提 / 后置条件

X::char_type

CharT

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

X::string_type

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

X::locale_type

实现定义

一个可复制构造的类型,表示由 traits 类使用的 locale。

X::char_class_type

实现定义

一个位掩码类型,表示特定的字符分类。此类型可以按位或(bitwise-or)组合多个值,以获得新的有效值。

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) 指定的字符序列转换为一个位掩码类型,该类型随后可以传递给 isctype。从 lookup_classname 返回的值可以安全地进行按位或(bitwise or)组合。如果字符序列不是 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。 -end note]

u.imbue(loc)

X::locale_type

将 locale loc 注入 u,并返回 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