std::regex ECMAScript 语法完整参考
C++ 中
std::regex默认使用的正则语法(std::regex_constants::ECMAScript)。该语法基于 ECMAScript 3(ECMA-262 第 3 版),并在此基础上做了少量修改与限制。
目录
- 一、语法选择与标志
- 二、核心文法解析
- 三、元素详解
- [1. 字符类 Character Classes](#1. 字符类 Character Classes)
- [2. 量词 Quantifiers](#2. 量词 Quantifiers)
- [3. 断言 Assertions](#3. 断言 Assertions)
- [4. 分组与反向引用](#4. 分组与反向引用)
- [5. 转义序列](#5. 转义序列)
- 四、括号表达式细节
- 五、匹配语义
- 六、替换格式
- [七、C++ 特有的修改与限制](#七、C++ 特有的修改与限制)
- 八、一页速查表
- 九、参考资料
一、语法选择与标志
cpp
#include <regex>
using namespace std::regex_constants;
可选语法(syntax_option_type)
| 取值 | 说明 |
|---|---|
ECMAScript |
默认。最接近 JavaScript / .NET 语言的语法 |
basic |
POSIX 基本正则(BRE) |
extended |
POSIX 扩展正则(ERE) |
awk |
extended + 更多非打印字符转义 |
grep |
basic + 允许 \n 分隔分支 |
egrep |
extended + 允许 \n 分隔分支 |
一次只能指定一种语法。
可叠加的标志
| 标志 | 作用 |
|---|---|
icase |
忽略大小写 |
nosubs |
忽略标记匹配(括号内的表达式),不保存替换内容 |
optimize |
让匹配更快,代价是构造时间可能更长 |
collate |
使用区域敏感的排序序列(如 [a-z] 按 locale 解释) |
可以叠加零个或多个标志。若只指定标志而不指定语法,默认
ECMAScript。
cpp
std::regex re1(R"(\d+)", std::regex::ECMAScript);
std::regex re2(R"(abc)", std::regex::icase | std::regex::ECMAScript);
std::regex re3(R"(abc)"); // 默认即 ECMAScript
二、核心文法解析
一个正则表达式文法由多个可选项(Alternative)组成,各项之间用 析取运算符 | 分隔。
匹配优先级
| 的优先级最低 。匹配时引擎会优先尝试匹配左侧的可选项;只有左侧失败,才会尝试右侧。
cpp
b|bc // 在 "abcd" 中 search
// → 匹配 "b"(左项先成功,不再尝试右项 "bc")
这就是 ECMAScript 的**「首次匹配」**语义,与 POSIX 的「最长匹配」不同。详见 五、匹配语义。
捕获行为
若左侧可选项匹配成功,则右侧可选项中定义的捕获组会产生「空子匹配」(empty submatches)。
cpp
(a)|(b) // 匹配 "a" 时:
// 捕获组 1 = "a"
// 捕获组 2 = 空子匹配(matched = false)
C++ 中通过 std::smatch 检查:
cpp
std::string s = "a";
std::smatch m;
std::regex re(R"((a)|(b))");
if (std::regex_search(s, m, re)) {
m[1].matched; // true, m[1] = "a"
m[2].matched; // false ← 空子匹配
m[2].str(); // ""(但 matched 为 false)
}
三、元素详解
1. 字符类 Character Classes
用于匹配一个字符集合中的任意一个字符。
| 语法 | 含义 |
|---|---|
[abc] |
匹配 a、b、c 中的任意一个字符 |
[^abc] |
否定字符类,匹配除 a、b、c 之外任意字符 |
[a-z] |
匹配 a 到 z 范围内的任意小写字母 |
\d、\D |
匹配数字 / 非数字(C++ 中为区域敏感) |
\s、\S |
匹配空白字符 / 非空白字符(C++ 中为区域敏感) |
\w、\W |
匹配单词字符 / 非单词字符(C++ 中为区域敏感) |
[[:alpha:]] |
POSIX 字符类,匹配字母 |
[=elt=] |
等价类,匹配与 elt 排序等价的字符 |
dsw 快捷转义与具名类的对应关系:
| 转义 | 等价具名类 | 默认类 |
|---|---|---|
\d |
[[:d:]] |
[[:digit:]] |
\D |
[^[:d:]] |
[^[:digit:]] |
\s |
[[:s:]] |
[[:space:]] |
\S |
[^[:s:]] |
[^[:space:]] |
\w |
[[:w:]] |
[a-zA-Z0-9_](ASCII) |
\W |
[^[:w:]] |
[^a-zA-Z0-9_](ASCII) |
POSIX 具名类([:name:])默认支持:
| 名称 | 含义 |
|---|---|
alnum |
大小写字母 + 数字 |
alpha |
大小写字母 |
blank |
空格或制表符 |
cntrl |
控制字符 |
digit |
数字 |
graph |
字母 + 数字 + 标点 |
lower / upper |
小写 / 大写字母 |
print |
字母 + 数字 + 标点 + 空格 |
punct |
标点 |
space |
空格 |
xdigit |
十六进制数字 |
d / s / w |
分别同 digit / space / alnum |
⚠️
\w默认只认 ASCII 字符,匹配中文等 Unicode 字母需要自己写字符类。
通配符 .
cpp
. // 匹配目标序列中除换行(\n)以外的任意字符
普通字符(需转义的特殊字符)
ECMAScript 中有特殊含义的字符固定为这 13 个:
^ $ \ . * + ? ( ) [ ] { } |
标识转义(Identity Escape)
反斜杠 + 单个字符,匹配该字符本身,用于去掉特殊含义。
cpp
a* // 匹配 "aaa"
a\* // 匹配字面量 "a*",不匹配 "aaa"
各语法允许标识转义的字符集不同:
| 语法 | 允许标识转义的字符 |
|---|---|
basic / grep |
( ) { } . [ \ * ^ $ |
extended / egrep |
`( ) { . [ \ * ^ $ + ? |
awk |
上述 + " / |
| ECMAScript | 除「可构成标识符的字符」外全部 ------ 字母、数字、$、_、Unicode 转义序列不能被标识转义 |
2. 量词 Quantifiers
用于指定前一个元素重复的次数。
| 语法 | 含义 |
|---|---|
* |
匹配零次或多次(等价 {0,}) |
+ |
匹配一次或多次(等价 {1,}) |
? |
匹配零次或一次(等价 {0,1}) |
{n} |
恰好匹配 n 次 |
{n,} |
至少匹配 n 次 |
{n,m} |
匹配 n 到 m 次 |
贪婪与非贪婪
默认情况下量词是贪婪的 ------ 会尽可能多地匹配字符。
cpp
(a+)(a*b) // 目标 "aaab"
// 贪婪: 组1="aaa", 组2="b"
在量词后加 ? 可变为非贪婪模式:
cpp
(a+?)(a*b) // 目标 "aaab"
// 非贪婪:组1="a", 组2="aab"
可用于量词的元素 :除 (?=、(?!、^、$ 之外的所有元素。
以下示例均按整串匹配 (
std::regex_match)语义理解。
cpp
a{2,3} // 匹配 "aa" / "aaa",不匹配 "a" / "aaaa"
a{2} // 只匹配 "aa"
a{2,} // 匹配 "aa" 及以上
ab+ // 整串匹配 "abb",不匹配 "abab"
(ab)+ // 不匹配 "abb",匹配 "abab"
⚠️ 注意区分
regex_match与regex_search。
regex_search只要求存在子序列匹配即可,因此用regex_search时
ab+能在"abab"中找到"ab",(ab)+能在"abb"中找到"ab"。详见 五、匹配语义。
3. 断言 Assertions
断言不消耗字符,只用于检查某个条件是否成立(零宽匹配)。
| 语法 | 含义 |
|---|---|
^ |
匹配输入的开头 |
$ |
匹配输入的结尾 |
\b |
匹配单词边界 |
(?=...) |
正向先行断言 :要求右侧能匹配 ... |
(?!...) |
负向先行断言 :要求右侧不能匹配 ... |
(?<=...) |
正向后行断言 :要求左侧能匹配 ... |
(?<!...) |
负向后行断言 :要求左侧不能匹配 ... |
⚠️ 后行断言(
(?<=)/(?<!))在 C++ 标准中不属于 ECMAScript 文法,std::regex不支持。上表列出是为了与 JavaScript 对照 ------ 现代 JS 已支持后行断言,C++ 没有跟进。
单词边界出现的四种情况:
- 当前字符在序列开头,且是单词字符(
A-Za-z0-9_) - 当前位置越过序列结尾,且最后一个字符是单词字符
- 当前字符是单词字符,前一个不是
- 当前字符不是单词字符,前一个是
示例:
cpp
(?=a)a // 匹配 "a"
(?!a)a // 不匹配 "a"
a\b. // 匹配 "a~",不匹配 "ab"
a\B. // 匹配 "ab",不匹配 "a~"
(?!aa)(a*) // 匹配 "a"(组1="a"),不匹配 "aa" / "aaa"
(?=aa)(a*) // 匹配 "aaaa",组1="aaaa"
⚠️
\b在 ECMAScript 下是单词边界断言,不是退格符。退格符的写法见 [5. 转义序列](#5. 转义序列)。
4. 分组与反向引用
| 语法 | 含义 |
|---|---|
(...) |
捕获组,将子模式分组并捕获匹配的文本 |
(?:...) |
非捕获组,只分组,不捕获文本 |
\n |
反向引用,引用第 n 个捕获组匹配到的文本 |
捕获组编号:由左括号的出现顺序决定(数到该左括号为止的开括号个数)。
cpp
((a+)(b+))(c+)
// 组1 = "aabbb"
// 组2 = "aa"
// 组3 = "bbb"
// 组4 = "c"
反向引用的编号解析差异(重要):
| 语法 | N 的确定方式 | 上限 |
|---|---|---|
basic / grep |
只取反斜杠后的一位十进制数字 | N ≤ 9 |
| ECMAScript | 取反斜杠后连续的所有十进制数字 | 无上限 |
cpp
((a+)(b+))(c+)\3 // 匹配 "aabbbcbbb"
// \3 引用组3 的内容 "(b+)"
(a)\2 // 非法(不存在组2)
(b(((((((((a))))))))))\10
// ECMAScript:\10 = 第 10 个捕获组(最内层那个)
// basic: \1 = 第 1 个捕获组,后面的 0 是普通字符 '0'
非捕获组与反向引用:
cpp
(?:a) // 合法,但不会产生捕获组
(?:a)\1 // 非法 ------ 不存在第 1 个捕获组
⚠️ C++ 实现限制:捕获组最多 31 个。
分组对量词的影响:
以下按整串匹配理解。
cpp
ab+ // 整串匹配 "abb"(+ 只作用于 b)
(ab)+ // 整串匹配 "abab"(+ 作用于整个 "ab")
5. 转义序列
| 类型 | 形式 | 含义 |
|---|---|---|
| 标识转义 | \k |
匹配字符 k 本身 |
| 文件格式转义 | \f \n \r \t \v |
换页 / 换行 / 回车 / 水平制表 / 垂直制表 |
| 十六进制转义 | \xhh |
两位十六进制数表示一个字符 |
| Unicode 转义 | \uhhhh |
四位十六进制数表示一个字符 |
| 控制字符转义 | \ck |
匹配 k 命名的控制字符(k 为 a-z / A-Z) |
⚠️ ECMAScript 下 \a 与 \b 不作文件格式转义:
\b是单词边界断言\a不允许 (basic/awk里才表示响铃)
示例:
cpp
"\ci" // 匹配 "\x09"(Ctrl+I = 0x09)
"\x41" // ASCII 下匹配 "a"
"\u0041" // ASCII 下匹配 "a"
\.
\. \* \+ \? \( \) \[ \] \{ \} \| \^ \$ \\
C++ 中的字符串字面量陷阱:
正则里的 \ 在 C++ 字符串字面量中本身需要转义,否则编译器会先吃掉一层。建议一律用原始字符串字面量(Raw String Literal):
cpp
std::regex bad(R"(\d+)"); // ✅ 正确:正则收到 \d+
std::regex wrong("\\d+"); // ✅ 正确但易错
std::regex oops("\d+"); // ❌ 危险:\d 不是合法转义序列,行为未定义
四、括号表达式细节
[expr] 内可包含以下构成的任意组合:
| 构成 | 形式 | 说明 |
|---|---|---|
| 单个字符 | abc |
将该字符加入集合 |
| 字符范围 | ch1-ch2 |
将闭区间 [ch1, ch2] 内的字符加入集合 |
| 字符类 | [:name:] |
将具名类中的字符加入集合 |
| 等价类 | [=elt=] |
将与 elt 等价的排序元素加入集合 |
| 排序符号 | [.elt.] |
将排序元素 elt 加入集合 |
] 的处理 ------ ECMAScript 与其他语法的分水岭
cpp
// 其他语法:位于开头的 ] 表示自身
[]abc] // 匹配 a b c ]
[^]abc] // 匹配除 a b c ] 外的字符
// ECMAScript:必须转义
[]a // 匹配 "a"(空括号表达式 + 普通字符 a)
[\]abc] // 匹配 a b c ]
^ 的位置
仅当位于括号表达式开头时表示取反,其他位置表示自身。
cpp
[abc] // a 或 b 或 c
[^abc] // 非 a b c
[a^bc] // a、b、c 或 ^
- 的位置
在开头、结尾、或作为某个范围的首尾字符时,表示自身。
cpp
[0-7] // { 0,1,2,3,4,5,6,7 }
[-0-24] // { -, 0, 1, 2, 4 }
[0-2-] // { 0, 1, 2, - }
[+--] // { +, ',', - } (ASCII)
范围依赖平台字符编码
cpp
[h-k]
// ASCII:{ h, i, j, k }
// EBCDIC:{ h, i, \x8A, ..., \x90, j, k }(因为 h=0x88, k=0x92)
使用
collate标志时,范围内的字符由 locale 的排序规则决定。
五、匹配语义
regex_match vs regex_search
| 要求 | 示例 | |
|---|---|---|
std::regex_match |
整个正则匹配整个目标序列 | bcd 不匹配 "abcd",也不匹配 "bcde" |
std::regex_search |
目标中存在子序列匹配即可 | bcd 能匹配 "abcd"、"bcde" |
cpp
bcd 在 "abcd" 中 search → 匹配后三个字符
bcd 在 "bcde" 中 search → 匹配前三个字符
bcd 在 "bcdbcd" 中 search → 匹配前三个字符(最左)
首次匹配 vs 最长匹配
同一位置存在多个匹配时的选择策略:
| 策略 | 规则 |
|---|---|
| 首次匹配(First Match) | ECMAScript 使用 。取正则匹配过程中先找到的那个 |
| 最长匹配(Longest Match) | 取该位置最长的子序列;长度相同时取先找到的 |
cpp
b|bc 在 "abcd" 中 search
// ECMAScript(首次匹配)→ "b"
// 最长匹配 → "bc"
部分匹配 Partial Match
匹配到达目标序列末尾但未到达正则末尾,也算成功。
cpp
ab 部分匹配 "a" ✅
ab 部分匹配 "ac" ❌
- 部分匹配成功 后,向目标追加字符可能导致后续部分匹配失败
- 部分匹配失败 后,向目标追加字符不可能导致后续部分匹配成功
cpp
std::regex re("ab");
std::smatch m;
std::regex_search(s, m, re, std::regex_constants::match_partial);
六、替换格式
std::regex_replace 的替换串中可使用以下格式:
| ECMAScript 规则 | sed 规则 | 替换为 |
|---|---|---|
$& |
& |
整个匹配 [match[0].first, match[0].second) |
$$ |
--- | 字面量 $ |
| --- | \& |
字面量 & |
| ``$``` | --- | 匹配之前的文本(前缀) |
$' |
--- | 匹配之后的文本(后缀) |
$n |
\n |
第 n 个捕获组(n = 0--9) |
| --- | \\n |
字面量 \ |
$nn |
--- | 第 nn 个捕获组(nn = 10--99) |
cpp
std::string s = "2026-09-18";
std::regex re(R"((\d{4})-(\d{2})-(\d{2}))");
// 换成 年/月/日
std::string out = std::regex_replace(s, re, "$1/$2/$3"); // "2026/09/18"
七、C++ 特有的修改与限制
C++ 的 ECMAScript 文法基于 ECMAScript 3,并做了以下修改:
1. POSIX 类型扩展
在字符类(character class)内部引入了对本地环境(locale)的 POSIX 类型扩展。
cpp
[[:alpha:]] // 匹配字母(POSIX 具名类)
[[:digit:]] // 匹配数字
[[:space:]] // 匹配空白
2. 区域敏感
\d、\s、\w 等字符类在 C++ 中是区域敏感(locale-sensitive)的。
也就是说,它们的行为可能随
std::locale设置而改变,与 JavaScript 的固定 ASCII 行为不同。
3. 不支持的特性
C++ 标准库的 std::regex 不支持以下语法:
| 不支持的语法 | 说明 | JS 是否支持 |
|---|---|---|
(?<=...) |
后行断言 | ✅ 现代 JS 支持 |
(?<!...) |
否定后行断言 | ✅ 现代 JS 支持 |
(?<name>...) |
具名捕获组 | ✅ |
\k<name> |
具名反向引用 | ✅ |
\p{...} |
Unicode 属性类 | ✅ |
\Q...\E |
引用块 | ❌(PCRE 支持) |
x++ / x*+ |
占有量词 | ❌(PCRE/Java 支持) |
(?(cond)...) |
条件表达式 | ❌(PCRE 支持) |
4. 语法选项互斥
ECMAScript 是默认文法;在 basic、extended、awk、grep、egrep 等选项中最多只能选择一种。
5. 其他行为差异
| 项目 | C++ std::regex | JavaScript |
|---|---|---|
^ / $ |
只匹配整个序列的开头 / 结尾 | 配合 m 标志可匹配行首行尾 |
. |
不匹配换行 \n |
配合 s 标志可匹配换行 |
\w \d \s |
区域敏感 | 固定 ASCII |
| 捕获组上限 | 31 个 | 无硬性上限 |
| 匹配策略 | 首次匹配 | 首次匹配 |
| 性能 | 较差(无 DFA 优化) | 引擎优化较好 |
⚠️ 性能提示 :
std::regex在多数实现中性能显著低于 PCRE / RE2 / JavaScript 引擎。长文本、复杂正则、循环调用场景建议改用
RE2、
PCRE2或 C++17 的std::regex替代品(如
CTRE编译期正则)。
八、一页速查表
锚点 ^ $ 只匹配整个序列的首尾
普通字符 a . . 不匹配换行
字符集合 [...] [^...] ECMAScript 下 ] 必须写成 \]
字符类 \d \D \s \S \w \W 区域敏感
POSIX 类 [[:alpha:]] 等 仅括号表达式内可用
分组 (...) (?:...)
反向引用 \n \nn 无上限;捕获组最多 31 个
先行断言 (?=...) (?!...) 无后行断言
边界 \b \B \b 是边界,不是退格
转义 \xhh \uhhhh \ck \f \n \r \t \v
量词 * + ? {n} {n,} {n,m}
非贪婪 量词后加 ?
分支 a|b 首次匹配,非最长匹配
替换 $& $$ $1 $2 $nn $` $'
常用代码骨架
cpp
#include <regex>
#include <string>
#include <iostream>
int main() {
std::string text = "订单号 A1234,金额 56.78 元";
// 1) 搜索
std::regex re(R"([A-Z](\d+))");
std::smatch m;
if (std::regex_search(text, m, re)) {
std::cout << "整个匹配: " << m.str() << '\n'; // A1234
std::cout << "捕获组 1: " << m[1].str() << '\n'; // 1234
std::cout << "位置: " << m.position() << '\n';
}
// 2) 完全匹配
std::regex re_full(R"(\d{4}-\d{2}-\d{2})");
std::cout << std::boolalpha
<< std::regex_match("2026-09-18", re_full) << '\n'; // true
// 3) 替换
std::regex re_date(R"((\d{4})-(\d{2})-(\d{2}))");
std::cout << std::regex_replace("2026-09-18", re_date, "$1/$2/$3") << '\n';
// 4) 全局遍历
std::regex re_num(R"(\d+(\.\d+)?)");
auto begin = std::sregex_iterator(text.begin(), text.end(), re_num);
auto end = std::sregex_iterator();
for (auto it = begin; it != end; ++it) {
std::cout << "找到: " << it->str() << '\n';
}
}
九、参考资料
- ECMA-262 标准(ECMAScript 3 起定义了正则文法)
- Regular Expressions (C++) --- Microsoft Learn(C++ std 库文法对照,最完整的实现级说明)
- MDN RegExp Guide(JavaScript 侧语法对照)
- cppreference: std::regex(若可访问)
说明 :本参考以 C++ 标准库(
std::basic_regex的 ECMAScript 文法)为准。撰写时 cppreference 全站返回 403(Cloudflare 验证页),故以微软官方 std 库文档为主要依据,
该文档描述的即为同一套
std::regexECMAScript 行为,并在实现细节上更细(捕获组上限、
]转义差异、标识转义白名单等)。