C++标准库原生支持多种正则表达式语法规范,本篇内容主要讲解在使用正则功能时可供选择的各类语法体系差异。
目录
- 1 正则表达式语法规则
-
- 1.1 概述
-
- 1.1.1 概述
- 1.1.2 配套修饰标识(Flag)
- 1.1.3 补充说明
- 1.2 语法单元(Element)
-
- 1.2.1 所有正则语法通用基础单元
- 1.2.2 ECMAScript(C++默认正则语法)额外支持的单元
- 1.2.3 awk 语法专属扩展单元
- 1.2.4 补充要点
- 1.3 重复(Repetition)
-
- 1.3.1 概述
- 1.3.2 关键知识点(C++ std::regex 重点)
- 1.4 连接(Concatenation)
-
- 1.4.1 概述
- 1.4.2 补充要点
- 1.5 分支选择(Alternation)
-
- 1.5.1 概述
- 1.5.2 核心要点
- 1.6 子表达式(subexpression)
- 2 语法总览
- 3 语义详细信息
-
- 3.1 位置锚点
- 3.2 反向引用
- 3.3 方括号表达式
- 3.4 捕获组
- 3.5 字符类
- 3.6 字符区间
- 3.7 排序元素
- 3.8 排序符号
- 3.9 控制转义序列
- 3.10 DSW 字符转义
- 3.11 等价类
- 3.12 文件格式转义字符
- 3.13 十六进制转义序列
- 3.14 字面转义(恒等转义 / Identity-escape)
- 3.15 普通字符(Individual character)
- 3.16 负向断言
- 3.17 负向单词边界断言(Negative word boundary assert)
- 3.18 非捕获组(Non-capture group)
- 3.19 非贪婪重复
- 3.20 八进制转义序列(Octal escape sequence)
- 3.21 普通字符(Ordinary character)
- 3.22 正向断言(Positive assert)
- 3.23 Unicode 转义序列(Unicode escape sequence)
- 3.24 通配符(Wildcard character,. 点号)
- 3.25 单词边界(Word boundary,\\b)
- 3.26 单词边界断言(Word boundary assert,\\b)
- 4 匹配与搜索
- 5 格式标志 / 替换文本占位符(ECMAScript 替换规则)
1 正则表达式语法规则
1.1 概述
1.1.1 概述
开发者通过枚举类型 std::regex_constants::syntax_option_type 选定正则所采用的语法风格,标准库在 std::regex_constants 中提供了 6 套主流语法规范:
程序默认采用 ECMAScript 语法;一次配置只能选定其中一种语法,不可混用多种语法标准。
1.1.2 配套修饰标识(Flag)
可搭配语法,追加任意数量的标识来修改正则引擎工作方式:
- icase:匹配过程忽略字母大小写;
- nosubs:忽略括号内的捕获分组,不存储分组匹配到的内容;
- optimize:优化匹配运行速度,但正则编译构造阶段耗时会上升;
- collate:依照系统本地区域的字符排序规则解析字符区间(例如 [a-z]);
语法标识与行为标识可以自由组合;若只填写行为标识、未指定语法,依旧默认使用 ECMAScript 语法;
1.1.3 补充说明
1.2 语法单元(Element)
正则表达式的组成单元分为以下通用类型,同时 ECMAScript、awk 语法各自扩展了专属语法单元。
1.2.1 所有正则语法通用基础单元
普通字符
- 匹配目标文本中完全相同的单个字符。例:a 只能匹配字符 a,无法匹配大写 B、小写 b 、c。
通配符 .
- 匹配除换行符以外的任意单个字符;例:. 可匹配 a、B、b、c 任意字符。
方括号表达式 [expr]、[^expr]
- [expr]:匹配目标字符 / 排序元素出现在 expr 定义集合内的内容;
- [\\^expr]:匹配目标字符 / 排序元素不在 expr 集合中的内容;
- 方括号内部 expr 可组合书写以下规则:
- 单个字面字符:将该字符加入匹配集合;
- 字符区间 ch1-ch2:匹配闭区间 [ch1,ch2] 内所有字符,如 [b-z] 匹配 b、c,不匹配 a、B;
- 标准字符类 [:名称:]:例如 [:lower:] 匹配所有小写字母 a/b/c,不会匹配大写 B;
- 等价类 [=elt=]:匹配所有与 elt 等价的排序字符;
- 排序符号 [.elt.]:将 elt 这个排序单元加入匹配集合;
锚点符号
- ^ 匹配整个字符串的起始位置;
- $ 匹配整个字符串的末尾位置;
捕获分组
- ECMAScript、extended、egrep、awk:写法(子表达式)
- basic、grep 旧式 POSIX 语法:写法 \\(子表达式\\)
- 匹配括号内规则对应的字符片段,同时保存匹配结果至分组编号。示例:(a) 匹配字符 a,并将内容存入 1 号捕获组。
本义转义 \\k
- 匹配字符 k 本身,用来对点号、括号等元字符进行转义;
反向引用(ECMAScript、basic、grep 支持)
- 格式 \\数字,复用第 N 个捕获组已经匹配到的文本;
- 示例:(a)\\1,第一个分组捕获 a,\\1 再次匹配 a,整体匹配字符串 aa。
1.2.2 ECMAScript(C++默认正则语法)额外支持的单元
- 正向先行断言 (?=子表达式):校验后方文本符合规则,但不会消耗匹配指针;(?=a)a 可以匹配字符串 a;
- 负向先行断言 (?!子表达式):校验后方文本不符合规则;(?!a)a 无法匹配 a。
- \\xhh:两位十六进制表示一个字符;
- \\uhhhh:四位十六进制表示一个 Unicode 字符;
- \\ck 匹配对应控制字符;
- \\b:匹配单词边界位置;
- \\B:匹配非单词边界;
- 例:a\\b. 匹配 a~,不匹配 ab;a\\B. 匹配 ab,不匹配 a~;
- \\d \\D (数字/非数字)、\\s \\S (空白/非空白)、\\w \\W(单词字符/非单词字符)。
1.2.3 awk 语法专属扩展单元
1.2.4 补充要点
1.3 重复(Repetition)
本段介绍正则表达式量词(重复限定 Repetition)规则:
- 除零宽断言、锚点之外的任意正则元素后都可以附加重复次数限定符 {min,max};
- 区分 POSIX BRE(basic/grep)与其他语法(ECMAScript、ERE、awk、egrep)支持的简写符号 * + ?;
- * 等价 {0,无穷},+ 等价 {1,无穷},? 等价 {0,1};
ECMAScript 语法支持在量词后追加 ? 实现非贪婪匹配。
1.3.1 概述
- 除正向断言、反向断言、锚点之外的任意元素,后方都可以跟随重复次数限定;
- 最通用的重复写法为 {min,max};在 basic、grep(BRE 基础正则)语法中需要写成 \\{min,max\\}。
- 被该量词修饰的元素,匹配最少 min 次、最多 max 次连续出现;
示例:a{2,3} 匹配 aa、aaa;不匹配 a、aaaa;
重复限定还有以下形式:
示例:
- a{2}:只匹配 aa,不匹配 a、aaa;
- a{2,}:匹配 aa、aaa…,不匹配单个 a;
- a*:匹配空字符串、a、aa…;
除 basic、grep 以外的所有正则语法,额外支持两种简写量词:
- ? 等效于 {0,1};
- + 等效于 {1,无上限};
示例:
- a?:匹配空字符串、a、不匹配 aa;
- a+:匹配 a、aa…,不能匹配空字符串;
在 ECMAScript 模式下,所有量词后面均可追加 ?,代表非贪婪(惰性)匹配。
1.3.2 关键知识点(C++ std::regex 重点)
- regex_constants::basic / grep(POSIX BRE):+ ? 不原生支持,大括号必须转义 \\{ \\};
- 默认模式 ECMAScript、extended、awk、egrep:直接使用 + ? {m,n},无需转义;
- 仅 ECMAScript 语法支持 .*?、a{2,3}? 这类惰性写法;POSIX ERE/BRE 不支持非贪婪量词。
- ✅️:普通字符、.、括号分组、字符集[];
- ❌️:^ $ 锚点、\\b、前后向断言(不能对零宽修饰重复)
量词对照表:
| X{n} | X 恰好出现 n 次 |
| X{n,} | X 至少 n 次 |
| X{n,m} | X n~m 次 |
| X* | X 0次或多次 |
| X+ | X 1次或多次 |
| X? | X 0次或1次 |
| X+? | ECMAScript:最少匹配(非贪婪) |
1.4 连接(Concatenation)
本节讲解正则表达式的连接规则: 多个正则元素(可携带重复量词)直接相邻书写,构成拼接表达式。匹配逻辑要求目标文本按顺序依次匹配每一个元素。
- 示例:a{2,3}b 匹配 2~3 个 a 后面紧跟 1 个 b;
1.4.1 概述
正则表达式元素(无论是否带有重复量词)可以相互拼接,组成更长的正则表达式。拼接后的表达式,匹配的目标字符串必须依次拼接各个独立元素所匹配的内容。
- 例如:a{2,3}b 能够匹配 “aab”、“aaab”,但无法匹配 “ab”、“aaaab”。
1.4.2 补充要点
- 正则中不需要任何运算符,元素紧邻即代表顺序拼接;
- ab = a 后面紧跟 b,这是正则最基础的隐式运算。
- 重复量词 → 拼接(序列)→ 选择符 |
- 示例:ab|cd 等价 (ab)|(cd),不是 a(b|c)d。
- a+b?[0-9]:一个或多个 a,后面可选 1 个 b,最后跟上一位数字。
1.5 分支选择(Alternation)
本段讲解正则的分支选择符 | :
1.5.1 概述
除 basic 与 grep 语法之外,所有正则语法都允许:一段拼接式正则后面添加竖线 | ,再接另一段拼接正则。 可以通过该方式拼接任意多条分支表达式。最终的正则只要目标字符串能够匹配其中任意一条分支,整体就匹配成功。
当有多条分支都可以适配目标文本时:
- ECMAScript:返回最先成功匹配的分支结果;
- 其余正则语法:挑选能够匹配出最长子串的分支;
示例:ab|cd 可以匹配 ab、cd;无法匹配 abd、acd; 在 grep 和 egrep 模式中,换行符 \\n 同样可以用来分隔多个分支选项;
1.5.2 核心要点
- POSIX-基础正则不直接支持裸 | ,需要转义 \\| 才能启用分支功能;
- 运算优先级:量词 > 拼接 > 或分支
- ab|cd = (ab)|(cd),而非 a(b|c)d
- ECMAScript(C++ regex 默认模式):从左往右扫描,抓到第一个匹配项就直接停止;
- ERE / awk / egrep:全部分支对比,选取匹配长度最大的结果。
- 可以换行分隔分支,等价于 a\\nb 等同于 a|b。
1.6 子表达式(subexpression)
该段介绍正则中子表达式(subexpression)的定义区分:basic、grep 语法里子表达式代表表达式拼接;其余正则语法中,子表达式指由 | 构成的分支选择单元。
在 basic 和 grep 正则语法中,一个子表达式就是一段拼接组合的表达式。 其余类型的正则语法下,子表达式为分支选择(由 | 分隔的多选结构)。
解析
- BRE 基础正则优先级结构:拼接为基础单元,\\| 转义之后才代表或运算,因此原生子单元是字符拼接。
- 语法将 | 分支当作一层完整子表达式,拼接只是分支内部的组合。
- 举个例子:ab|cd 整体就是一条子表达式,内部由 ab、cd 两个拼接单元组成;
2 语法总览
这份对照表罗列 C++ 标准库六种正则语法(basic、extended、ECMAScript、grep、egrep、awk)的功能差异,覆盖分支选择、锚点、捕获组、量词、各类转义字符、零宽断言、非贪婪匹配等语法特性;ECMAScript(C-regex 默认模式)支持的高级功能最为齐全,basic、grep 基础正则大量符号需要添加反斜杠转义。
下表汇总各类正则语法所具备的功能:
| | 充当分支分隔符 | ✅️ | ✅️ | ✅️ | ✅️ | ||
| \\n 充当分支分隔符 | ✅️ | ✅️ | ||||
| 锚点符号 | ✅️ | ✅️ | ✅️ | ✅️ | ✅️ | ✅️ |
| 反向引用 | ✅️ | ✅️ | ✅️ | |||
| 方括号字符集 | ✅️ | ✅️ | ✅️ | ✅️ | ✅️ | ✅️ |
| () 捕获分组 | ✅️ | ✅️ | ✅️ | ✅️ | ||
| \\(\\)转义捕获分组 | ✅️ | ✅️ | ||||
| 控制字符转义序列 | ✅️ | |||||
| d/s/w 类字符转义 | ✅️ | |||||
| 文件格式转义 | ✅️ | ✅️ | ||||
| 十六进制转义序列 | ✅️ | |||||
| 普通字符转义(原样匹配) | ✅️ | ✅️ | ✅️ | ✅️ | ✅️ | ✅️ |
| 否定型零宽断言 | ✅️ | |||||
| 单词否定边界断言 | ✅️ | |||||
| 非捕获分组 | ✅️ | |||||
| 非贪婪量词 | ✅️ | |||||
| 八进制转义序列 | ✅️ | |||||
| 普通文本字符 | ✅️ | ✅️ | ✅️ | ✅️ | ✅️ | ✅️ |
| 正向零宽断言 | ✅️ | |||||
| {} 量词写法 | ✅️ | ✅️ | ✅️ | ✅️ | ||
| \\{\\} 转义形式量词 | ✅️ | ✅️ | ||||
| * 通用量词 | ✅️ | ✅️ | ✅️ | ✅️ | ✅️ | ✅️ |
| ?、+ 量词 | ✅️ | ✅️ | ✅️ | ✅️ | ||
| unicode 转义序列 | ✅️ | |||||
| 通配符 . | ✅️ | ✅️ | ✅️ | ✅️ | ✅️ | ✅️ |
| 单词边界断言 | ✅️ |
关键点解析
- 属于老旧基础正则,()、{}、| 必须写成 \\(\\)、\\{\\}、\\| 才会生效,原生不支持非贪婪、断言、非捕获组等高级语法。
- 符号不需要反斜杠转义;缺少断言、非贪婪匹配、Unicode 转义,awk 独有八进制转义。
- 功能最完整:支持断言、单词边界、非贪婪量词、非捕获组、各类进制转义,日常开发优先选用该模式。
- 换行符可以代替竖线 | 做分支分隔。
3 语义详细信息
3.1 位置锚点
锚点用于匹配目标字符串里的位置,而非字符。脱字符 ^ 匹配字符串开头,美元符号 $ 匹配字符串末尾。
注释:
- 锚点不会消耗文本字符,只判定当前所处位置;
- ^ 起始锚点、$ 结尾锚点为正则最基础的边界约束符号;
3.2 反向引用
反向引用由反斜杠加上十进制数字 N 构成,它会匹配第 N 个捕获分组内存储的文本内容。数字 N 不能超过该反向引用前面已经出现过的捕获分组总数。
在 basic 与 grep 语法中,仅读取反斜杠后的单个十进制数字作为分组编号;而 ECMAScript 会读取紧随反斜杠之后全部连续的数字。 因此 basic、grep 最多只支持编号 1-9 的反向引用,即便表达式存在超过九个捕获组。 ECMAScript 则没有位数上限。
示例解析:
- 能够匹配字符串 aabbbcbbb。\\3 引用第三个捕获组 (b+) 保存的内容;无法匹配 aabbbcbb。
- 属于非法写法,前面仅有 1 个捕获组,不存在二号分组。
- basic 模式:\\1 代表一号捕获组 (外层(b…)),末尾字符 0 当作普通字面字符;
- ECMAScript:\\10 整体视作十号捕获组,匹配最内层包裹 a 的分组;
核心要点
3.3 方括号表达式
方括号表达式用于定义一组字符与排序元素。
- 当表达式以 ^ 开头时,只要当前目标字符不在该字符集合内,匹配就成功。
- 不带脱字符时,只要目标字符能够命中集合当中任意一个元素,即可匹配成功。
集合内的字符可以由多种元素自由组合定义:独立字符、字符区间、字符类、等价类以及排序符号。
关键解析
3.4 捕获组
捕获组将括号内的内容标记成一个整体单元,同时保存与之匹配到的目标子串。 每一个捕获组拥有一个数字编号:按照从左往右统计左圆括号的顺序进行编号。 该正则实现环境下,捕获组数量上限为31个。
示例说明:
- 可以匹配字符串 abb,无法匹配 abab;加号只作用于字符 b。
- 加号作用于整个 ab 单元,能够重复匹配,命中 abab,不能匹配 abb。
- 匹配 aabbbc,各组对应结果:
- 1号捕获组:aabbb
- 2号捕获组:aa
- 3号捕获组:bbb
- 4号捕获组:c
核心要点
3.5 字符类
方括号表达式内可以使用字符类,它会把该命名类别下全部字符归入方括号所定义的字符集合。字符类语法格式:[:类名:]。 底层实现依靠 traits.lookup_classname 获取类别编号;再通过 traits.isctype(字符,编号) 判断该字符是否属于当前字符类。
标准 regex_traits 模板支持下面所有内置字符类:
| alnum | 大小写字母 + 数字 |
| alpha | 大小写字母 |
| blank | 空格、制表符 |
| cntrl | 格式控制转义字符 |
| digit | 数字字符 |
| graph | 字母、数字、标点(不含空格) |
| lower | 小写字母 |
| 可打印字符:字母、数字、标点、空格 | |
| punct | 标点符号 |
| space | 空白字符 |
| upper | 大写字母 |
| xdigit | 十六进制字符 0-9,a-f,A-F |
| d | 等价于 digit(数字) |
| s | 等价于 space(空白) |
| w | 等价于 alnum(字母数字) |
核心要点:
3.6 字符区间
方括号表达式中的字符区间,会把区间内所有字符加入方括号的字符集合。
- 书写格式:起始字符 – 终止字符
- 区间包含编码数值大于等于起始字符、小于等于终止字符的全部字符;
- 区间结果受平台字符编码影响;
- 当短横线 – 位于方括号开头、末尾,或是区间起止位置异常时,- 仅代表它自身字面字符。
示例解读:
- 匹配字符集合 {0,1,2,3,4,5,6,7},只能匹配数字,无法匹配字母 a
- 包含 h、i、j、k
- 编码从 0x88(h) 到 0x92(k),中间夹带多个不可见控制字符,区间字符远多于 h-k 四个字母。
- 集合包含短横线 – 、0、1、2、4;这里 – 在最开头,作为普通符号。
- 匹配 0、1、2 和短横线 –
- 区间从 +(ASCII 43) 到 – (ASCII 45),包含 +、,、-
补充规则:开启地区语言环境后,字符区间不再依靠原始编码值,而是根据当前区域的字符排序规则判定区间范围。
关键要点:
3.7 排序元素
排序元素是被视作单个字符的多字符序列。
- 排序元素多用于部分本地化环境,把多字符字符组合当成整体匹配。
3.8 排序符号
方括号表达式内的排序符号,可以将一个排序元素加入该表达式定义的字符集合。
- 书写格式:[.排序元素.]
- [. .] 专属 POSIX 正则,ECMAScript 引擎一般不支持该写法。
3.9 控制转义序列
控制转义序列由反斜杠、字母 c,再加大小写英文字母构成;它匹配该字母对应的 ASCII 控制字符。
- 例如:\\ci 等价于十六进制 \\x09(制表符),因为快捷键 Ctrl+I 的 ASCII 编码就是 0x09
- \\cx 属于 ECMAScript 风格的转义语法,用来快捷输入各类不可见的控制字符。
3.10 DSW 字符转义
dsw 字符转义是字符类的短名称,如下表所示:
| \\d | [[:d:]] | 数字 [[:digit:]] |
| \\D | [^[:d:]] | 非数字 [^[:digit:]] |
| \\s | [[:s:]] | 空白字符 [[:space:]] |
| \\S | [^[:s:]] | 非空白字符 [^[:space:]] |
| \\w | [[:w:]] | 字母、数字、下划线 [a-zA-Z0-9_] |
| \\W | [^[:w:]] | 非单词字符 [^a-zA-Z0-9] |
3.11 等价类
等价类是 POSIX 正则方括号表达式 [] 当中的本地化匹配语法,写法:[=元素=]。 它可以把所有等价的字符、排序元素一并归入匹配集合,多用于忽略重音、大小写这类变体字符。
方括号表达式内的等价类,会把所有和定义元素等价的普通字符、排序元素,全部加入括号表达式的可匹配字符集合。
- 等价类语法格式:[=排序元素=]
- 底层判断逻辑:两个排序元素 elt1、elt2,经过 traits.transform_primary 转换之后结果相同,则二者视为等价。
说明:
- 仅 POSIX 基础 / 扩展正则(basic、extended)支持;
- ECMAScript(JavaScript 风格)正则没有该功能;
- C++ 正则特征类的该函数会剥离字符修饰信息:去掉字母重音符号、大小写标记,只保留字符基础本体。
- 例如 á、a、À 经过转换之后主键一致,[=a=] 就能够全部匹配。
- [.name.]:排序符号,用来录入多字符排序元素
- [:digit:]:内置命名字符类
- [=a=]:等价类,匹配所有字形变体字符
- 适配多国语言、带重音的外文文本模糊匹配,普通英文开发几乎用不到。
3.12 文件格式转义字符
文件格式转义就是 C语言常见的字符转义序列:\\\\、\\a、\\b、\\f、\\n、\\r、\\t、\\v。 各自含义依次为:反斜杠、响铃、退格、换页、换行、回车、水平制表符、垂直制表符。 在 ECMAScript 正则语法中,\\a 和 \\b 不被当作文件格式转义;反斜杠 \\\\ 虽然可用,但它属于标识转义,不属于文件格式转义。
| \\\\ | 字面反斜杠 |
| \\a | 蜂鸣响铃 |
| \\b | 退格键 |
| \\f | 换页符 |
| \\n | 换行 |
| \\r | 回车 |
| \\t | 水平 Tab |
| \\v | 垂直 Tab |
- \\b 在 ECMAScript 里是单词边界断言,不再代表退格字符;
- 没有响铃 \\a;
- \\\\ 仅用来转义出字面反斜杠,归类为普通标识转义;
- 完整支持上面全套 C-风格控制字符转义;
3.13 十六进制转义序列
十六进制转义序列由一个反斜杠、字母 x,再加上两位十六进制数字(0-9、a-f、A-F)构成。它匹配目标字符串中,编码值等于这两位十六进制数所代表数值的那个字符。
- 例如,使用 ASCII 编码时,\\x41 可以匹配字符 A
要点说明:
- \\x41 → A
- \\x61 → a
- 不能只写 1 位:\\xA 是非法写法,必须写成 \\x0A
3.14 字面转义(恒等转义 / Identity-escape)
字面转义由反斜杠 + 单个字符组成,直接匹配该字符本身。 当某个字符在正则里拥有特殊含义时,就需要使用字面转义;加上反斜杠之后,该字符会失去特殊功能,变回普通字符。
- a* 可以匹配 “aaa”,不能匹配 “a*”
- a\\* 不能匹配 “aaa”,可以匹配 “a*”
可以做字面转义的字符集合,随正则语法模式不同而变化:
| basic、grep | { ( ) { } . [ \\ * ^ $ } |
| extended、egrep | { ( ) { . [ \\ * ^ $ + ? | } |
| awk | 在 extended 基础上额外支持 " / |
| ECMAScript | 除标识符字符(字母、数字 、$、_、Unicode 转义)外,其余所有字符都可使用字面转义 |
要点说明
- 同样写 \\+,在 basic-POSIX 可能报错,basic 里 + 本身不是元字符,不能随便加反斜杠转义;ECMAScript 几乎可以随便反斜杠转义符号;
3.15 普通字符(Individual character)
方括号表达式里可以直接书写单个字符,该字符就会被加入方括号定义的字符集合。 ^ 放在方括号非开头的位置时,仅代表字符本身,不充当取反元字符。 示例:
- [abc]:可以匹配 a、b、c,不能匹配 d
- [^abc]:取反,可以匹配 d,不能匹配 a、b、c
- [a^bc]:^不是首字符,作为普通字符,可匹配 a、b、c、^,不能匹配 d
除 ECMAScript 以外的全部正则语法: 如果 ] 紧跟在左括号 [ 后面、或者紧跟在开头取反符 ^ 之后,] 被当作普通字面字符,而不是方括号的结束标记。 示例:
- []a:非法,缺少用来闭合的 ]
- []abc]:集合包含 ]、a、b、c
- [^]abc]:取反集合,匹配除 ]、a、b、c 以外字符
ECMAScript 语法规则不一样: 方括号内部想要表示字面的 ]、必须转义写成 ] 示例:
- []a:方括号内部为空集合,只能匹配 a
- [\\]abc]:集合包含 ]、a、b、c
要点总结
3.16 负向断言
负向断言匹配不满足括号内规则的位置。它不会消耗目标字符串里的任何字符。 示例:(!aa)(a*)
- 可以匹配字符串 “a”,捕获组 1 拿到子串 “a”;
- 不能匹配 “aa”、“aaa”;
- 文档里 (!aa) 是 ECMAScript 风格负先行断言 (?!aa) 的简写写法;
核心:
- (?!aa):检查当前位置之后,不是两个连续 a
- (a*):然后捕获尽可能多的 a
- “a”:当前位置后面只有 1 个 a,满足断言,成功匹配
- “aa”:开头后面就是 aa,负断言失败,整体无法匹配
注:
- 只有 ECMAScript 语法支持 (?!) 负先行断言;
- POSIX (basic / extended / grep / awk) 语法不支持零宽断言;
3.17 负向单词边界断言(Negative word boundary assert)
当目标字符串的当前位置不在单词边界处时,断言匹配成功。
核心:
- \\b = 是单词边界
- \\B = 不是单词边界
示例:字符串 apple
- a 前面:边界 → \\b 成立,\\B 失败
- a 和 p 之间:两个都是单词字符 → 不是边界,\\B 匹配成功
C++ regex 限制 仅 ECMAScript 模式支持 \\b / \\B 单词边界,POSIX 语法不提供单词边界断言。
3.18 非捕获组(Non-capture group)
非捕获组可以把括号内整体当成一个逻辑单元,但是不会分配编号、不保存匹配到的文本。
示例:(a)(?:b)*(c) 匹配字符串 “abbc”
- 捕获组 1:a
- 捕获组 2:c
- (?:b)* :只用来分组、支持量词 * 重复,不会生成捕获编号。
核心
- 改变运算优先级
- 可以直接后面加量词 * + ? {m,n}
- 不占用捕获编号
- 匹配结果不会存到 match 分组里
- 轻微节省内存、提升一点性能
- (a):捕获组 #1,保存 a
- (?:b)*:把 b 打包,可以重复零次或多次,不产生分组
- (c):捕获组 #2,保存 c
C++ regex 语法支持
- 只有 ECMAScript 模式支持 (?:…) 非捕获组;
- POSIX(basic / extended / grep / awk)没有非捕获组语法;
3.19 非贪婪重复
非贪婪量词会匹配目标字符串中满足条件的最短子串;贪婪量词则匹配满足条件的最长子串。 举个例子:(a+)(a*b) 可以匹配字符串 “aaab”。
- 使用非贪婪模式时:捕获组 1 匹配 “a”,捕获组 2 匹配 “aab”
- 使用贪婪模式时:捕获组 1 匹配 “aaa”,捕获组 2 匹配 “b”
要点解析
- 默认行为,+、*、? 、{m,n} 都是贪婪,尽可能多吃字符;
- (a+)(a*b) 贪婪:a+ 拿走尽可能多 a → aaa,剩下 b 交给第二组;
- 在量词后面加 ? 开启:+?、*?、??、{m,n}?
- 改成非贪婪写法:(a+?)(a*b)
- a+? 先拿最少 1 个 a,剩下 aab 交给第二组。
- ✅️ECMAScript:完整支持非贪婪 +?、*?、??、{m,n}?
- ❌️basic / extended / grep / egrep / awk:没有非贪婪量词
3.20 八进制转义序列(Octal escape sequence)
八进制转义序列由一个反斜杠,后面紧跟 1-3 位八进制数字 0-7 组成。它可以匹配目标字符串中,等于该八进制数值的字符。 如果全部数字都是0,则该转义序列无效;
- 示例:在 ASCII 编码下,\\101 匹配字符 A
要点说明
- \\0:❌️全零,非法
- \\65:✅️1~3位八进制
- \\101:✅️1~3位八进制
- \\101 = 八进制 101 = 十进制 65 = ‘A’
3.21 普通字符(Ordinary character)
普通字符是在当前正则语法规则下、不具备特殊含义的任意有效字符。
- 在 ECMAScript 模式中,以下字符拥有特殊语义:
- ^ $ \\ . * + ? ( ) [ ] { } |
- 在 basic、grep 模式中,以下字符永久拥有特殊语义:
- . [ \\
- 同时在 basic、grep 模式里,下面这些字符仅在特定上下文才有特殊含义:
- *:绝大多数场景有特殊含义;只有位于正则开头、^ 之后、捕获组开头、捕获组内 ^ 之后时,*作为普通字符
- ^:仅当它位于整个正则表达式的第一个字符时才是锚点元字符
- $:仅当它位于整个正则表达式的最后一个字符时才是锚点元字符
- 在 extended、egrep、awk 模式中,永久特殊字符:
- . [ \\ ( * + ? { |
- extended / egrep / awk 模式下,上下文敏感字符:
- ):只有前面存在对应的 ( 时才有分组右括号含义
- ^:仅在正则首位作为行开头锚点
- $:仅在正则末尾作为行结尾锚点
普通字符会直接匹配目标字符串中完全一样的字符:
- 默认区分大小写:两个字符编码值相等才算匹配成功
- 忽略大小写匹配:通过 traits.translate_nocase(ch0) == traits.translate_nocase(ch1) 判断
- 区域语言(locale)敏感匹配:通过 traits.translate(ch0) == traits.translate(ch1) 判断
核心要点
- 默认:精确二进制编码相等
- 不区分大小写:调用 translate_nocase 转换后比较
- 本地化匹配:调用 translate,受当前 locale 影响
3.22 正向断言(Positive assert)
正向断言可以匹配它内部的子模式,但不会消耗目标字符串中的任何字符。 示例:
- (=aa)(a*) 可以匹配 “aaaa”,捕获组 1 匹配 “aaaa”。
- (aa)(a*) 可以匹配 “aaaa”,捕获组 1 匹配开头的 “aa”,捕获组 2 匹配末尾的 “aa”。
- (=aa)(a)|(a) 可以匹配 “a”:正向断言匹配失败,捕获组 1 得到空串,捕获组 2 匹配 “a”。
- 该表达式也可以匹配 “aa”:捕获组 1 匹配 “aa”,捕获组 2 得到空串。
要点说明
- 断言只检查位置后面能不能匹配上,匹配成功之后,正则光标的位置不会向后移动,后面的模式仍然从当前位置开始匹配。
- 普通分组 (aa):吃掉两个字符,光标前进 2 位;
- 正向断言 (=aa):只做检测,光标原地不动;
- ⚠️微软文档这里用 (=pattern) 表示正向先行断言
- 标准 ECMAScript 写法是 (?=pattern),C++ std::regex ECMAScript 模式支持 (?=…)
- (aa):消耗字符,匹配到的 “aa” 存入捕获组,光标往后走
- (?=aa):不消耗字符,仅校验后面是不是 aa,光标留在原地
- 在起始位置,先检查后面是不是 aa → 通过(断言成功),光标不动
- 接着执行 a*,从当前位置尽可能吃掉所有 a,拿到 aaaa
- 只有 ECMAScript 语法支持正向 / 负向断言;basic / extended / grep / egrep / awk 没有断言功能。
3.23 Unicode 转义序列(Unicode escape sequence)
Unicode 转义序列格式:反斜杠 \\ + 字母 u + 4 位十六进制数字 0-9 a-f A-F。 它匹配目标字符串中码点等于这 4 位十六进制数值的字符。 示例:ASCII 编码环境下,\\u0041 匹配字符 “A”
要点详解
- \\uXXXX
- XXXX必须是恰好 4 个十六进制字符,范围 0000 – FFFF,只能表示 BMP 平面(U+0000~U+FFFF)。
- ⚠️:std::regex ECMAScript 模式里,\\u 不支持 \\uXXXXXXXX 8位格式(补充平面字符)
- 把 4 位十六进制转为 Unicode 码点,匹配对应字符
- \\u0041 → 码点 U+0041 → 'A'
- \\u4E2D → U+4E2D → 汉字 中
- ✅️ECMAScript:支持 \\uXXXX
- ❌️basic / extended / grep / egrep / awk:不支持 Unicode 转义
- C++字符串本身反斜杠需要转义,正则里写 \\u0041,C++源码字符串必须写成 \\\\u0041
3.24 通配符(Wildcard character,. 点号)
通配符 . 可以匹配目标字符串里除换行符以外的任意单个字符。
要点说明
- . ≠ 真正意义上的 “所有字符”,默认不能匹配 \\n 换行;
- 例:正则 a.b
- “aab”:✅️匹配
- “axb”:✅️匹配
- “a\\nb”:❌️默认情况下匹配失败
- 例:正则 a.b
- ECMAScript 语法下,设置标志 regex_constants::dotall,. 就可以包含换行符 \\n
- basic / extended / ECMAScript / grep / egrep / awk 全部支持点通配符
- 必须转义 \\.,否则会被当成通配符
3.25 单词边界(Word boundary,\\b)
单词边界出现在下面四种位置之一:
要点详解
- \\b 只匹配位置,不消耗任何字符,不会吞掉字符串里的字符。
- [A-Z a-z 0-9 _]
- 下划线 _ 属于单词字符,这点很容易踩坑;
- abc def:c 和空格之间、空格和 d 之间都存在单词边界
- hello_world:中间 _ 两边没有边界,因为下划线属于单词字符
- 仅 ECMAScript 模式支持 \\b / \\B,POSIX(basic / extended / grep …)不提供单词边界。
- 非单词边界,匹配不是单词边界的位置。
- 示例:正则 \\bcat\\b
- "cat"✅️
- "my cat."✅️
- "category"❌️(cat 后面紧跟着字母,无边界)
3.26 单词边界断言(Word boundary assert,\\b)
当目标字符串的当前位置正好处于一个单词边界之后时,单词边界断言匹配成功。
要点解析
- \\b 是位置断言,不消耗任何字符,只检测当前光标所在位置是不是单词边界。
- 单词字符:A-Z a-z 0-9 _
- 边界发生在:
- 字符串开头 + 后面是单词字符
- 字符串结尾 + 前面是单词字符
- 单词字符 ↔ 非单词字符的交界处
- \\b:匹配单词边界位置
- \\B:匹配非单词边界位置(边界不成立的位置)
- 仅 ECMAScript 语法支持 \\b / \\B;
- basic、extended、grep、egrep、awk 没有单词边界;
- 字符串:test,abc
- 位置在 t 前面、t-e 之间、e-s 之间、s-t 之间、t 和 , 之间、, 和 a 之间、a-b 之间、b-c 之间、c 末尾
- 其中单词边界:^t、t,、,a、c$
4 匹配与搜索
完整匹配(match):正则表达式必须匹配整个目标字符串才算匹配成功。
- 例如正则 bcd,可以匹配 “bcd”;不能匹配 “abcd”,也不能匹配 “bcde”。
搜索查找(search):只要目标字符串中某处存在一段子串能够匹配正则,查找就成功。查找通常优先找到最靠左的匹配项。 示例:
- 在 “bcd” 中搜索 bcd:查找成功,匹配整个字符串。
- 在 “abcd” 中搜索 bcd:查找成功,匹配最后三个字符。
- 在 “bcde” 中搜索 bcd:查找成功,匹配开头三个字符。
- 在 “bcdbcd” 中搜索 bcd:查找成功,匹配最靠前的前三个字符。
如果在同一个起始位置可以匹配出多个不同子串,有两种选择策略:
举例:正则 b|bc,目标串 “abcd”
- first-match(ECMAScript):先匹配 b 成功,直接返回子串 “b”,不会再尝试 bc
- longest-match(POSIX-extended / egrep / awk):候选 “b”、“bc”,选择更长的 “bc”
部分匹配(partial match):即使正则还没有解析完毕,只要已经走到目标字符串末尾且中途没有匹配失败,部分匹配就视为成功。 因此:
- 部分匹配成功之后,如果给目标字符串追加字符,后续再次做部分匹配有可能失败;
- 如果当前部分匹配已经失败,就算再追加字符,后续也不可能变成部分匹配成功;
例子:开启部分匹配模式时,ab 可以部分匹配 “a”;不能部分匹配 “ac”。
核心要点(C++ std::regex)
- 要求正则覆盖全部输入字符串,等价于自动隐式加上 ^…$
// "abcd" 不能匹配 "bcd"
regex_match("abcd", sm, regex("bcd")) → false
- 在字符串任意位置查找,找到最左侧匹配
regex_search("abcd", sm, regex("bcd")) → true
- ECMAScript:First-match 优先匹配,左边分支命中即停止
- POSIX extended / egrep / awk:Longest-match 最长优先
- C++通过 match_partial 标志实现,用于流式、截断字符串场景,普通正则很少用到。
5 格式标志 / 替换文本占位符(ECMAScript 替换规则)
仅 ECMAScript 语法支持下面这些 $ 开头的替换占位符,用于 regex_replace 的替换字符串
| $& | 整个正则完整匹配到的子串(对应第 0 号捕获组) |
| $` | 匹配项前面的全部文本(前缀) |
| $’ | 匹配项后面的全部文本(后缀) |
| $n | 编号 0-9 的捕获组,n 是 1 位数字 |
| $nn | 编号10-99 的捕获组,nn 是两位数字 |
各个占位符解释
- 保存本次匹配到的完整字符串,等价于 match[0]
- 例:把 abc 替换成 [$&] → [abc]
- 匹配子串左边、还没被匹配的前缀内容;
- 原串:123abc456,匹配 abc;
- $` = “123”
- 匹配子串右边的后缀内容
- 原串:123abc456,匹配 abc;
- $’ = “456”
- 取第 n 个捕获组的内容,一位编号
- $1 = 第一个括号分组
- 两位编号捕获组
- $10 = 读取第 10 号捕获组
C++ 使用注意事项
网硕互联帮助中心






评论前必须登录!
注册