修改后的 ECMAScript 正则表达式语法
此页面描述了当使用设置为 ECMAScript
(默认值) 的 syntax_option_type
构造 std::basic_regex 时所使用的正则表达式语法。有关其他受支持的正则表达式语法,请参见 syntax_option_type
。
C++ 中的 ECMAScript
3 正则表达式语法是 ECMA-262 语法,并带有以下 (仅 C++ ) 标记的修改。
内容 |
[[编辑]] 概述
修改后的正则表达式语法主要是 ECMAScript RegExp 语法,并在 ClassAtom 下对区域设置进行了 POSIX 类型的扩展。对相等性检查和数字解析进行了一些澄清。对于这里的许多示例,您可以在浏览器控制台中尝试此等效代码
function match(s, re) { return s.match(new RegExp(re)); }
标准中的“规范性引用”指定了 ECMAScript 3。我们在此处链接到 ECMAScript 5.1 规范,因为它是一个与 ECMAScript 3 只有细微差别的版本,并且它也具有 HTML 版本。有关方言功能的概述,请参见 MDN JavaScript RegExp 指南。
[[编辑]] 备选项
正则表达式模式是一个或多个 备选项 的序列,由析取运算符 |
分隔 (换句话说,析取运算符的优先级最低)。
模式 :
- 析取
析取 :
- 备选项
- 备选项
|
析取
该模式首先尝试跳过 析取,然后匹配左侧的 备选项,后跟正则表达式的其余部分 (在析取之后)。
如果失败,它将尝试跳过左侧的 备选项,然后匹配右侧的 析取 (后跟正则表达式的其余部分)。
如果左侧的 备选项,右侧的 析取 和正则表达式的其余部分都具有选择点,则在继续左侧 备选项 中的下一个选择之前,将尝试表达式其余部分中的所有选择。如果左侧 备选项 中的选择已用尽,则将尝试右侧 析取 而不是左侧 备选项。
跳过的 备选项 内的任何捕获括号都会产生空子匹配。
#include <cstddef> #include <iostream> #include <regex> #include <string> void show_matches(const std::string& in, const std::string& re) { std::smatch m; std::regex_search(in, m, std::regex(re)); if (!m.empty()) { std::cout << "input=[" << in << "], regex=[" << re << "]\n " "prefix=[" << m.prefix() << "]\n smatch: "; for (std::size_t n = 0; n < m.size(); ++n) std::cout << "m[" << n << "]=[" << m[n] << "] "; std::cout << "\n suffix=[" << m.suffix() << "]\n"; } else std::cout << "input=[" << in << "], regex=[" << re << "]: NO MATCH\n"; } int main() { show_matches("abcdef", "abc|def"); show_matches("abc", "ab|abc"); // left Alternative matched first // Match of the input against the left Alternative (a) followed // by the remained of the regex (c|bc) succeeds, which results // in m[1]="a" and m[4]="bc". // The skipped Alternatives (ab) and (c) leave their submatches // m[3] and m[5] empty. show_matches("abc", "((a)|(ab))((c)|(bc))"); }
输出
input=[abcdef], regex=[abc|def] prefix=[] smatch: m[0]=[abc] suffix=[def] input=[abc], regex=[ab|abc] prefix=[] smatch: m[0]=[ab] suffix=[c] input=[abc], regex=[((a)|(ab))((c)|(bc))] prefix=[] smatch: m[0]=[abc] m[1]=[a] m[2]=[a] m[3]=[] m[4]=[bc] m[5]=[] m[6]=[bc] suffix=[]
[[编辑]] 项
每个 备选项 要么为空,要么是 项 的序列 (项 之间没有分隔符)
备选项 :
- [空]
- 备选项 项
空的 备选项 始终匹配,并且不消耗任何输入。
连续的 项 尝试同时匹配输入的连续部分。
如果左侧的 备选项,右侧的 项 和正则表达式的其余部分都具有选择点,则在继续右侧 项 中的下一个选择之前,将尝试表达式的其余部分中的所有选择,并且在继续左侧 备选项 中的下一个选择之前,将尝试右侧 项 中的所有选择。
#include <cstddef> #include <iostream> #include <regex> #include <string> void show_matches(const std::string& in, const std::string& re) { std::smatch m; std::regex_search(in, m, std::regex(re)); if (!m.empty()) { std::cout << "input=[" << in << "], regex=[" << re << "]\n " "prefix=[" << m.prefix() << "]\n smatch: "; for (std::size_t n = 0; n < m.size(); ++n) std::cout << "m[" << n << "]=[" << m[n] << "] "; std::cout << "\n suffix=[" << m.suffix() << "]\n"; } else std::cout << "input=[" << in << "], regex=[" << re << "]: NO MATCH\n"; } int main() { show_matches("abcdef", ""); // empty regex is a single empty Alternative show_matches("abc", "abc|"); // left Alternative matched first show_matches("abc", "|abc"); // left Alternative matched first, leaving abc unmatched }
输出
input=[abcdef], regex=[] prefix=[] smatch: m[0]=[] suffix=[abcdef] input=[abc], regex=[abc|] prefix=[] smatch: m[0]=[abc] suffix=[] input=[abc], regex=[|abc] prefix=[] smatch: m[0]=[] suffix=[abc]
[[编辑]] 量词
- 每个 项 要么是 断言 (见下文),要么是 原子 (见下文),要么是紧随其后的 量词 的 原子
项 :
- 断言
- 原子
- 原子 量词
每个 量词 要么是贪婪量词 (仅由一个 量词前缀 组成),要么是非贪婪量词 (由一个 量词前缀 后跟问号 ?
组成)。
量词 :
- 量词前缀
- 量词前缀
?
每个 量词前缀 确定两个数字:最小重复次数和最大重复次数,如下所示
量词前缀 | 最小 | 最大 |
---|---|---|
*
|
零 | 无穷大 |
+
|
一 | 无穷大 |
?
|
零 | 一 |
{ 十进制数字 } |
十进制数字的值 | 十进制数字的值 |
{ 十进制数字 , } |
十进制数字的值 | 无穷大 |
{ 十进制数字 , 十进制数字 } |
逗号前的十进制数字的值 | 逗号后的十进制数字的值 |
各个 十进制数字 的值通过调用 std::regex_traits::value(仅 C++ ) 获取每个数字。
后跟 量词 的 原子 将重复 量词 指定的次数。量词 可以是非贪婪的,在这种情况下,原子 模式会重复尽可能少的次数,同时仍与正则表达式的其余部分匹配;也可以是贪婪的,在这种情况下,原子 模式会重复尽可能多的次数,同时仍与正则表达式的其余部分匹配。
重复的是 原子 模式,而不是它匹配的输入,因此 原子 的不同重复可以匹配不同的输入子字符串。
如果 原子 和正则表达式的其余部分都具有选择点,则首先使 原子 匹配尽可能多 (或尽可能少,如果为非贪婪) 的次数。在继续最后一次 原子 重复中的下一个选择之前,将尝试正则表达式其余部分中的所有选择。在继续倒数第二次 (n–1) 次 原子 重复中的下一个选择之前,将尝试最后一次 (第 n 次) 原子 重复中的所有选择;此时可能会发现现在可能进行更多或更少的 原子 重复;这些重复将被耗尽 (再次,从尽可能少或尽可能多开始),然后再继续 (n-1) 次 原子 重复中的下一个选择,依此类推。
每次重复 原子 时,都会清除 原子 的捕获 (请参见下面的 "(z)((a+)?(b+)?(c))*" 示例)
#include <cstddef> #include <iostream> #include <regex> #include <string> void show_matches(const std::string& in, const std::string& re) { std::smatch m; std::regex_search(in, m, std::regex(re)); if (!m.empty()) { std::cout << "input=[" << in << "], regex=[" << re << "]\n " "prefix=[" << m.prefix() << "]\n smatch: "; for (std::size_t n = 0; n < m.size(); ++n) std::cout << "m[" << n << "]=[" << m[n] << "] "; std::cout << "\n suffix=[" << m.suffix() << "]\n"; } else std::cout << "input=[" << in << "], regex=[" << re << "]: NO MATCH\n"; } int main() { // greedy match, repeats [a-z] 4 times show_matches("abcdefghi", "a[a-z]{2,4}"); // non-greedy match, repeats [a-z] 2 times show_matches("abcdefghi", "a[a-z]{2,4}?"); // Choice point ordering for quantifiers results in a match // with two repetitions, first matching the substring "aa", // second matching the substring "ba", leaving "ac" not matched // ("ba" appears in the capture clause m[1]) show_matches("aabaac", "(aa|aabaac|ba|b|c)*"); // Choice point ordering for quantifiers makes this regex // calculate the greatest common divisor between 10 and 15 // (the answer is 5, and it populates m[1] with "aaaaa") show_matches("aaaaaaaaaa,aaaaaaaaaaaaaaa", "^(a+)\\1*,\\1+$"); // the substring "bbb" does not appear in the capture clause m[4] // because it is cleared when the second repetition of the atom // (a+)?(b+)?(c) is matching the substring "ac" // NOTE: gcc gets this wrong - it does not correctly clear the // matches[4] capture group as required by ECMA-262 21.2.2.5.1, // and thus incorrectly captures "bbb" for that group. show_matches("zaacbbbcac", "(z)((a+)?(b+)?(c))*"); }
输出
input=[abcdefghi], regex=[a[a-z]{2,4}] prefix=[] smatch: m[0]=[abcde] suffix=[fghi] input=[abcdefghi], regex=[a[a-z]{2,4}?] prefix=[] smatch: m[0]=[abc] suffix=[defghi] input=[aabaac], regex=[(aa|aabaac|ba|b|c)*] prefix=[] smatch: m[0]=[aaba] m[1]=[ba] suffix=[ac] input=[aaaaaaaaaa,aaaaaaaaaaaaaaa], regex=[^(a+)\1*,\1+$] prefix=[] smatch: m[0]=[aaaaaaaaaa,aaaaaaaaaaaaaaa] m[1]=[aaaaa] suffix=[] input=[zaacbbbcac], regex=[(z)((a+)?(b+)?(c))*] prefix=[] smatch: m[0]=[zaacbbbcac] m[1]=[z] m[2]=[ac] m[3]=[a] m[4]=[] m[5]=[c] suffix=[]
[[编辑]] 断言
断言 匹配条件,而不是输入字符串的子字符串。它们永远不会消耗输入中的任何字符。每个 断言 都是以下之一
断言 :
-
^
-
$
-
\
b
-
\
B
-
(
?
=
析取)
-
(
?
!
析取)
断言 ^
(行首) 匹配
断言 $
(行尾) 匹配
在上面的两个断言和下面的原子 .
中,行终止符 是以下四个字符之一:U+000A
(\n
或换行符),U+000D
(\r
或回车符),U+2028
(行分隔符) 或 U+2029
(段落分隔符)
断言 \b
(单词边界) 匹配
断言 \B
(负单词边界) 匹配除以下各项以外的所有内容
断言 (
?
=
析取 )
(零宽度正向先行断言) 如果 析取 将在当前位置匹配输入,则匹配
断言 (
?
!
析取 )
(零宽度负向先行断言) 如果 析取 将 *不* 在当前位置匹配输入,则匹配。
对于前瞻断言,在匹配 析取 时,位置不会在匹配正则表达式的其余部分之前前进。 此外,如果 析取 可以通过多种方式在当前位置匹配,则仅尝试第一个。
ECMAScript 禁止回溯到先行析取中,这会影响从正则表达式的其余部分到正向先行断言的反向引用的行为 (请参见下面的示例)。 从正则表达式的其余部分到负向先行断言的反向引用始终是未定义的 (因为先行析取必须失败才能继续)。
注意:先行断言可用于在多个正则表达式之间创建逻辑与 (请参见下面的示例)。
#include <cstddef> #include <iostream> #include <regex> #include <string> void show_matches(const std::string& in, const std::string& re) { std::smatch m; std::regex_search(in, m, std::regex(re)); if (!m.empty()) { std::cout << "input=[" << in << "], regex=[" << re << "]\n " "prefix=[" << m.prefix() << "]\n smatch: "; for (std::size_t n = 0; n < m.size(); ++n) std::cout << "m[" << n << "]=[" << m[n] << "] "; std::cout << "\n suffix=[" << m.suffix() << "]\n"; } else std::cout << "input=[" << in << "], regex=[" << re << "]: NO MATCH\n"; } int main() { // matches the a at the end of input show_matches("aaa", "a$"); // matches the o at the end of the first word show_matches("moo goo gai pan", "o\\b"); // the lookahead matches the empty string immediately after the first b // this populates m[1] with "aaa" although m[0] is empty show_matches("baaabac", "(?=(a+))"); // because backtracking into lookaheads is prohibited, // this matches aba rather than aaaba show_matches("baaabac", "(?=(a+))a*b\\1"); // logical AND via lookahead: this password matches IF it contains // at least one lowercase letter // AND at least one uppercase letter // AND at least one punctuation character // AND be at least 6 characters long show_matches("abcdef", "(?=.*[[:lower:]])(?=.*[[:upper:]])(?=.*[[:punct:]]).{6,}"); show_matches("aB,def", "(?=.*[[:lower:]])(?=.*[[:upper:]])(?=.*[[:punct:]]).{6,}"); }
输出
input=[aaa], regex=[a$] prefix=[aa] smatch: m[0]=[a] suffix=[] input=[moo goo gai pan], regex=[o\b] prefix=[mo] smatch: m[0]=[o] suffix=[ goo gai pan] input=[baaabac], regex=[(?=(a+))] prefix=[b] smatch: m[0]=[] m[1]=[aaa] suffix=[aaabac] input=[baaabac], regex=[(?=(a+))a*b\1] prefix=[baa] smatch: m[0]=[aba] m[1]=[a] suffix=[c] input=[abcdef], regex=[(?=.*[[:lower:]])(?=.*[[:upper:]])(?=.*[[:punct:]]).{6,}]: NO MATCH input=[aB,def], regex=[(?=.*[[:lower:]])(?=.*[[:upper:]])(?=.*[[:punct:]]).{6,}] prefix=[] smatch: m[0]=[aB,def] suffix=[]
[[编辑]] 原子
原子 可以是以下之一
原子 :
- 模式字符
-
.
-
\
原子转义 - 字符类
-
(
析取)
-
(
?
:
析取)
其中 原子转义 :
- 十进制转义
- 字符转义
- 字符类转义
不同类型的原子评估方式不同。
[[编辑]] 子表达式
原子 (
析取 )
是一个标记的子表达式:它执行 析取,并将 析取 消耗的输入子字符串的副本存储在子匹配数组中,索引对应于标记子表达式的左括号 (
在整个正则表达式中到目前为止遇到的次数。
除了在 std::match_results 中返回外,捕获的子匹配可以作为反向引用 (\1
,\2
,...) 访问,并且可以在正则表达式中引用。请注意,std::regex_replace 使用 $
而不是 \
作为反向引用 ($1
,$2
,...),方式与 String.prototype.replace
(ECMA-262,第 15.5.4.11 节) 相同。
原子 (
?
:
析取 )
(非标记子表达式) 仅评估 析取,而不将其结果存储在子匹配中。这纯粹是词法分组。
本节不完整 原因:没有示例 |
[[编辑]] 反向引用
十进制转义 :
- 十进制整数字面量 [先行 ∉ 十进制数字]
如果 \
后跟一个十进制数字 N
,其第一位不是 0
,则转义序列被视为反向引用。值 N
通过在每个数字上调用 std::regex_traits::value(仅 C++ ) 并使用以 10 为基数的算术组合其结果来获得。如果 N
大于整个正则表达式中左捕获括号的总数,则会发生错误。
当反向引用 \N
作为 原子 出现时,它匹配与当前存储在子匹配数组的第 N 个元素中的子字符串相同的子字符串。
十进制转义 \0
不是反向引用:它是一个字符转义,表示 NUL 字符。它后面不能跟十进制数字。
如上所述,请注意 std::regex_replace 使用 $
而不是 \
作为反向引用 ($1
,$2
,...)。
本节不完整 原因:没有示例 |
[[编辑]] 单字符匹配
原子 .
匹配并消耗输入字符串中的任何一个字符,除了 行终止符 (U+000D
,U+000A
,U+2029
或 U+2028
)
原子 模式字符,其中 模式字符 是任何 源字符,但 *不* 包括字符 ^ $ \ . * + ? ( ) [ ] { } |
,如果输入字符等于此 模式字符,则匹配并消耗输入中的一个字符。
此匹配以及所有其他单字符匹配的相等性定义如下
每个 原子,由转义字符 \
后跟 字符转义 以及特殊的十进制转义 \0
组成,如果输入字符等于 字符转义 表示的字符,则匹配并消耗输入中的一个字符。以下字符转义序列被识别
字符转义 :
- 控制转义
-
c
控制字母 - 十六进制转义序列
- Unicode 转义序列
- 标识转义
在此,控制转义 是以下五个字符之一:f n r t v
控制转义 | 代码单元 | 名称 |
---|---|---|
f
|
U+000C | 换页 |
n
|
U+000A | 换行 |
r
|
U+000D | 回车 |
t
|
U+0009 | 水平制表符 |
v
|
U+000B | 垂直制表符 |
控制字母 是任何小写或大写 ASCII 字母,此字符转义匹配的字符的代码单元等于 控制字母 的代码单元的值除以 32 的余数。 例如,\cD
和 \cd
都匹配代码单元 U+0004
(EOT),因为 'D' 是 U+0044
并且 0x44 % 32 == 4,而 'd' 是 U+0064
并且 0x64 % 32 == 4。
十六进制转义序列 是字母 x
,后跟正好两个 十六进制数字 (其中 十六进制数字 是 0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F
之一)。 此字符转义匹配的字符的代码单元等于两位十六进制数的数值。
Unicode 转义序列 是字母 u
,后跟正好四个 十六进制数字。 此字符转义匹配的字符的代码单元等于此四位十六进制数的数值。 如果该值不适合此 std::basic_regex 的 CharT
,则会抛出 std::regex_error 异常 (仅 C++ )。
标识转义 可以是任何非字母数字字符:例如,另一个反斜杠。 它按原样匹配字符。
#include <cstddef> #include <iostream> #include <regex> #include <string> void show_matches(const std::wstring& in, const std::wstring& re) { std::wsmatch m; std::regex_search(in, m, std::wregex(re)); if (!m.empty()) { std::wcout << L"input=[" << in << L"], regex=[" << re << L"]\n " L"prefix=[" << m.prefix() << L"]\n wsmatch: "; for (std::size_t n = 0; n < m.size(); ++n) std::wcout << L"m[" << n << L"]=[" << m[n] << L"] "; std::wcout << L"\n suffix=[" << m.suffix() << L"]\n"; } else std::wcout << L"input=[" << in << "], regex=[" << re << L"]: NO MATCH\n"; } int main() { // Most escapes are similar to C++, save for metacharacters. You will have to // double-escape or use raw strings on the slashes though. show_matches(L"C++\\", LR"(C\+\+\\)"); // Escape sequences and NUL. std::wstring s(L"ab\xff\0cd", 5); show_matches(s, L"(\\0|\\u00ff)"); // No matching for non-BMP Unicode is defined, because ECMAScript uses UTF-16 // atoms. Whether this emoji banana matches can be platform dependent: // These need to be wide-strings! show_matches(L"\U0001f34c", L"[\\u0000-\\ufffe]+"); }
可能的输出
input=[C++\], regex=[C\+\+\\] prefix=[] wsmatch: m[0]=[C++\] suffix=[] input=[ab?c], regex=[(\0{{!}}\u00ff)] prefix=[ab] wsmatch: m[0]=[?] m[1]=[?] suffix=[c] input=[?], regex=[[\u0000-\ufffe]+]: NO MATCH
[[编辑]] 字符类
原子可以表示字符类,也就是说,如果字符属于预定义的字符组之一,它将匹配并消耗一个字符。
可以通过字符类转义引入字符类
原子 :
-
\
字符类转义
或直接
原子 :
- 字符类
字符类转义是某些常用字符类的简写形式,如下所示
字符类转义 | 类名表达式(仅 C++ ) | 含义 |
---|---|---|
d
|
[[:digit:]]
|
数字 |
D
|
[^[:digit:]]
|
非数字 |
s
|
[[:space:]]
|
空白字符 |
S
|
[^[:space:]]
|
非空白字符 |
w
|
[_[:alnum:]]
|
字母数字字符和字符 _ |
W
|
[^_[:alnum:]]
|
字母数字或 _ 以外的字符 |
字符类 是用括号括起来的 类范围 序列,可以选择以否定运算符 ^
开头。 如果它以 ^
开头,则此 原子 匹配 *不* 在由所有 类范围 的并集表示的字符集中的任何字符。 否则,此 原子 匹配 *是* 在由所有 类范围 的并集表示的字符集中的任何字符。
字符类 :
-
[
[
先行 ∉ {^
}] 类范围]
-
[
^
类范围]
类范围 :
- [空]
- 非空类范围
非空类范围 :
- 类原子
- 类原子 非空无破折号类范围
- 类原子 - 类原子 类范围
如果非空类范围具有 类原子 - 类原子
的形式,则它匹配以下范围内的任何字符:(仅 C++ )
第一个 类原子 必须匹配单个排序元素 c1
,第二个 类原子 必须匹配单个排序元素 c2
。 要测试输入字符 c
是否由此范围匹配,请执行以下步骤
transformed c1 <= transformed c && transformed c <= transformed c2
,则字符 c
匹配如果字符 -
是以下情况,则将其视为字面量
- 类范围 的第一个或最后一个字符
- 破折号分隔的范围规范的开头或结尾类原子
- 紧跟在破折号分隔的范围规范之后。
- 用反斜杠转义为 字符转义
非空无破折号类范围 :
- 类原子
- 无破折号类原子 非空无破折号类范围
- 无破折号类原子 - 类原子 类范围
类原子 :
-
-
- 无破折号类原子
- 扩展类原子(仅 C++ )
- 排序元素类原子(仅 C++ )
- 等价类原子(仅 C++ )
无破折号类原子 :
- 源字符,但不是
\ 或 ] 或 -
之一 -
\
类转义
每个 无破折号类原子 表示一个字符 - 源字符 按原样或按以下方式转义
类转义 :
- 十进制转义
-
b
- 字符转义
- 字符类转义
特殊的 类转义 \b
生成一个字符集,该字符集匹配代码单元 U+0008 (退格符)。 在 字符类 之外,它是单词边界 断言。
在 字符类 内使用 \B
和使用任何反向引用 (零以外的 十进制转义) 都是错误的。
字符 -
和 ]
在某些情况下可能需要转义,以便被视为原子。 在 字符类 之外具有特殊含义的其他字符,例如 *
或 ?
,不需要转义。
本节不完整 原因:没有示例 |
[[编辑]] 基于 POSIX 的字符类
这些字符类是 ECMAScript 语法的扩展,等效于 POSIX 正则表达式中找到的字符类。
扩展类原子(仅 C++ ) :
-
[:
类名:]
表示命名字符类 类名 的所有成员字符。 仅当 std::regex_traits::lookup_classname 为此名称返回非零值时,该名称才有效。 如 std::regex_traits::lookup_classname 中所述,保证可以识别以下名称:alnum, alpha, blank, cntrl, digit, graph, lower, print, punct, space, upper, xdigit, d, s, w
。 其他名称可以由系统提供的区域设置 (例如日语中的 jdigit
或 jkanji
) 提供,或者作为用户定义的扩展实现。
排序元素类原子(仅 C++ ) :
-
[.
类名.]
表示命名的排序元素,它可能表示单个字符或字符序列,这些字符或字符序列在嵌入的区域设置下作为单个单元进行排序,例如捷克语中的 [.tilde.]
或 [.ch.]
。 仅当 std::regex_traits::lookup_collatename 不是空字符串时,该名称才有效。
当使用 std::regex_constants::collate 时,排序元素始终可以用作范围的端点 (例如匈牙利语中的 [[.dz.]-g]
)。
等价类原子(仅 C++ ) :
-
[=
类名=]
表示与命名排序元素属于同一等价类的所有字符,也就是说,所有主排序键与排序元素 类名 的主排序键相同的字符。 仅当 std::regex_traits::lookup_collatename 对于该名称不是空字符串,并且 std::regex_traits::transform_primary 为 std::regex_traits::lookup_collatename 的调用结果返回的值不是空字符串时,该名称才有效。
主排序键是一种忽略大小写、重音或特定于区域设置的调整的键;例如,[[=a=]]
匹配以下任何字符: a, À, Á, Â, Ã, Ä, Å, A, à, á, â, ã, ä 和 å。
类名(仅限 C++) :
- 类名字符
- 类名字符 类名
类名字符(仅限 C++) :
- 源字符,但不是
. = :
中的任何一个
本节不完整 原因:没有示例 |