云计算百科
云计算领域专业知识百科平台

【STL】正则表达式(C++)


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:默认语法,语法规则与 JavaScript、.NET 的正则高度一致;
  • basic:POSIX 基础正则(BRE);
  • extended:POSIX 扩展正则(ERE);
  • awk:在扩展正则基础上,新增了更多不可见字符的转义写法;
  • grep:基于基础正则,额外支持换行符 \\n 分隔多个多选匹配分支;
  • egrep:基于扩展正则,同样允许换行符划分匹配分支;
  • 程序默认采用 ECMAScript 语法;一次配置只能选定其中一种语法,不可混用多种语法标准。

    1.1.2 配套修饰标识(Flag)

    可搭配语法,追加任意数量的标识来修改正则引擎工作方式:

    • icase:匹配过程忽略字母大小写;
    • nosubs:忽略括号内的捕获分组,不存储分组匹配到的内容;
    • optimize:优化匹配运行速度,但正则编译构造阶段耗时会上升;
    • collate:依照系统本地区域的字符排序规则解析字符区间(例如 [a-z]);

    语法标识与行为标识可以自由组合;若只填写行为标识、未指定语法,依旧默认使用 ECMAScript 语法;

    1.1.3 补充说明

  • 日常开发绝大多数场景直接使用默认 ECMAScript 语法即可,上手成本最低;
  • POSIX 系列语法(basic / extended / grep / egrep)多用于对接 Linux 命令行正则使用习惯;
  • 多个 flag 之间使用按位或运算符 | 拼接组合;
  • 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)\\1 写法非法,无可用捕获组。
  • 格式转义字符:\\f 换页符、\\n 换行、\\r 回车、\\t 水平制表符、\\v 垂直制表符。
  • 零宽断言(环视)
    • 正向先行断言 (?=子表达式):校验后方文本符合规则,但不会消耗匹配指针;(?=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 语法专属扩展单元

  • 补充转义符:\\ 反斜杠、\\a 告警音、\\b 退格键,外加通用的 \\f\\n\\r\\t\\v。
  • 八进制转义 \\ooo:1~3位八进制数字,解析为对应 ASCII 字符。
  • 1.2.4 补充要点

  • C++ regex 默认采用 ECMAScript 语法,功能最全面,日常开发首选;
  • BRE/grep POSIX 语法需要对括号添加反斜杠转义,书写繁琐;
  • 零宽断言只做位置校验,不会占用字符,是正则匹配里高频实用语法;
  • 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;

    重复限定还有以下形式:

  • {min}(basic/grep 中为 \\{min\\}):等价 {min,min},精确匹配 min 次
  • {min,}(basic/grep 中为 \\{min,\\}):至少匹配 min 次,无上限
  • *:等价 {0,无上限}
  • 示例:

    • 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 重点)

  • 语法差异大坑(C++ 必看)
    • regex_constants::basic / grep(POSIX BRE):+ ? 不原生支持,大括号必须转义 \\{ \\};
    • 默认模式 ECMAScript、extended、awk、egrep:直接使用 + ? {m,n},无需转义;
  • 贪婪 vs 非贪婪
    • 仅 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)

    本段讲解正则的分支选择符 | :

  • 除 basic、grep 模式以外,使用竖线 | 分隔多个拼接表达式,命中任意一条分支即可匹配成功。
  • 分支存在匹配优先级差异:ECMAScript 采用最先命中优先;其余语法选择最长字符串优先。
  • grep、egrep 还支持换行符 \\n 充当分支分隔符。
  • 1.5.1 概述

    除 basic 与 grep 语法之外,所有正则语法都允许:一段拼接式正则后面添加竖线 | ,再接另一段拼接正则。 可以通过该方式拼接任意多条分支表达式。最终的正则只要目标字符串能够匹配其中任意一条分支,整体就匹配成功。

    当有多条分支都可以适配目标文本时:

    • ECMAScript:返回最先成功匹配的分支结果;
    • 其余正则语法:挑选能够匹配出最长子串的分支;

    示例:ab|cd 可以匹配 ab、cd;无法匹配 abd、acd; 在 grep 和 egrep 模式中,换行符 \\n 同样可以用来分隔多个分支选项;

    1.5.2 核心要点

  • BRE(basic / grep)特殊限制
    • POSIX-基础正则不直接支持裸 | ,需要转义 \\| 才能启用分支功能;
  • | 优先级最低
    • 运算优先级:量词 > 拼接 > 或分支
      • ab|cd = (ab)|(cd),而非 a(b|c)d
  • 两种分支策略区别
    • ECMAScript(C++ regex 默认模式):从左往右扫描,抓到第一个匹配项就直接停止;
    • ERE / awk / egrep:全部分支对比,选取匹配长度最大的结果。
  • grep 独有特性
    • 可以换行分隔分支,等价于 a\\nb 等同于 a|b。
  • 1.6 子表达式(subexpression)

    该段介绍正则中子表达式(subexpression)的定义区分:basic、grep 语法里子表达式代表表达式拼接;其余正则语法中,子表达式指由 | 构成的分支选择单元。

    在 basic 和 grep 正则语法中,一个子表达式就是一段拼接组合的表达式。 其余类型的正则语法下,子表达式为分支选择(由 | 分隔的多选结构)。

    解析

  • basic(BRE)/grep
    • BRE 基础正则优先级结构:拼接为基础单元,\\| 转义之后才代表或运算,因此原生子单元是字符拼接。
  • ECMAScript、extended、awk、egrep
    • 语法将 | 分支当作一层完整子表达式,拼接只是分支内部的组合。
      • 举个例子:ab|cd 整体就是一条子表达式,内部由 ab、cd 两个拼接单元组成;
  • 2 语法总览

    这份对照表罗列 C++ 标准库六种正则语法(basic、extended、ECMAScript、grep、egrep、awk)的功能差异,覆盖分支选择、锚点、捕获组、量词、各类转义字符、零宽断言、非贪婪匹配等语法特性;ECMAScript(C-regex 默认模式)支持的高级功能最为齐全,basic、grep 基础正则大量符号需要添加反斜杠转义。

    下表汇总各类正则语法所具备的功能:

    语法功能basicextendedECMAScriptgrepegrepawk
    | 充当分支分隔符 ✅️ ✅️ ✅️ ✅️
    \\n 充当分支分隔符 ✅️ ✅️
    锚点符号 ✅️ ✅️ ✅️ ✅️ ✅️ ✅️
    反向引用 ✅️ ✅️ ✅️
    方括号字符集 ✅️ ✅️ ✅️ ✅️ ✅️ ✅️
    () 捕获分组 ✅️ ✅️ ✅️ ✅️
    \\(\\)转义捕获分组 ✅️ ✅️
    控制字符转义序列 ✅️
    d/s/w 类字符转义 ✅️
    文件格式转义 ✅️ ✅️
    十六进制转义序列 ✅️
    普通字符转义(原样匹配) ✅️ ✅️ ✅️ ✅️ ✅️ ✅️
    否定型零宽断言 ✅️
    单词否定边界断言 ✅️
    非捕获分组 ✅️
    非贪婪量词 ✅️
    八进制转义序列 ✅️
    普通文本字符 ✅️ ✅️ ✅️ ✅️ ✅️ ✅️
    正向零宽断言 ✅️
    {} 量词写法 ✅️ ✅️ ✅️ ✅️
    \\{\\} 转义形式量词 ✅️ ✅️
    * 通用量词 ✅️ ✅️ ✅️ ✅️ ✅️ ✅️
    ?、+ 量词 ✅️ ✅️ ✅️ ✅️
    unicode 转义序列 ✅️
    通配符 . ✅️ ✅️ ✅️ ✅️ ✅️ ✅️
    单词边界断言 ✅️

    关键点解析

  • basic-BRE、grep
    • 属于老旧基础正则,()、{}、| 必须写成 \\(\\)、\\{\\}、\\| 才会生效,原生不支持非贪婪、断言、非捕获组等高级语法。
  • extended-ERE、egrep、awk
    • 符号不需要反斜杠转义;缺少断言、非贪婪匹配、Unicode 转义,awk 独有八进制转义。
  • ECMAScript(C++ std::regex 默认模式)
    • 功能最完整:支持断言、单词边界、非贪婪量词、非捕获组、各类进制转义,日常开发优先选用该模式。
  • grep/egrep 独有:
    • 换行符可以代替竖线 | 做分支分隔。
  • 3 语义详细信息

    3.1 位置锚点

    锚点用于匹配目标字符串里的位置,而非字符。脱字符 ^ 匹配字符串开头,美元符号 $ 匹配字符串末尾。

    注释:

    • 锚点不会消耗文本字符,只判定当前所处位置;
    • ^ 起始锚点、$ 结尾锚点为正则最基础的边界约束符号;

    3.2 反向引用

    反向引用由反斜杠加上十进制数字 N 构成,它会匹配第 N 个捕获分组内存储的文本内容。数字 N 不能超过该反向引用前面已经出现过的捕获分组总数。

    在 basic 与 grep 语法中,仅读取反斜杠后的单个十进制数字作为分组编号;而 ECMAScript 会读取紧随反斜杠之后全部连续的数字。 因此 basic、grep 最多只支持编号 1-9 的反向引用,即便表达式存在超过九个捕获组。 ECMAScript 则没有位数上限。

    示例解析:

  • ((a+)(b+))(c+)\\3
    • 能够匹配字符串 aabbbcbbb。\\3 引用第三个捕获组 (b+) 保存的内容;无法匹配 aabbbcbb。
  • (a)\\2
    • 属于非法写法,前面仅有 1 个捕获组,不存在二号分组。
  • (b(((((((((a))))))))))\\10
    • basic 模式:\\1 代表一号捕获组 (外层(b…)),末尾字符 0 当作普通字面字符;
    • ECMAScript:\\10 整体视作十号捕获组,匹配最内层包裹 a 的分组;
  • 核心要点

  • 反向引用不匹配固定字符,而是复用之前括号捕获到的文本;
  • POSIX-basic/grep:仅支持 \\1~\\9;
  • ECMAScript 可识别多位数编号,支持 \\10、\\11 等深层捕获组;
  • 3.3 方括号表达式

    方括号表达式用于定义一组字符与排序元素。

    • 当表达式以 ^ 开头时,只要当前目标字符不在该字符集合内,匹配就成功。
    • 不带脱字符时,只要目标字符能够命中集合当中任意一个元素,即可匹配成功。

    集合内的字符可以由多种元素自由组合定义:独立字符、字符区间、字符类、等价类以及排序符号。

    关键解析

  • [abc]:匹配 a、b、c 当中任意一个字符;
  • [^abc]:匹配除 a、b、c 以外的任意字符;
  • 内部支持 – 区间写法 [a-z]、内置字符类等多种写法;
  • 3.4 捕获组

    捕获组将括号内的内容标记成一个整体单元,同时保存与之匹配到的目标子串。 每一个捕获组拥有一个数字编号:按照从左往右统计左圆括号的顺序进行编号。 该正则实现环境下,捕获组数量上限为31个。

    示例说明:

  • ab+
    • 可以匹配字符串 abb,无法匹配 abab;加号只作用于字符 b。
  • (ab)+
    • 加号作用于整个 ab 单元,能够重复匹配,命中 abab,不能匹配 abb。
  • ((a+)(b+))(c+)
    • 匹配 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 小写字母
    print 可打印字符:字母、数字、标点、空格
    punct 标点符号
    space 空白字符
    upper 大写字母
    xdigit 十六进制字符 0-9,a-f,A-F
    d 等价于 digit(数字)
    s 等价于 space(空白)
    w 等价于 alnum(字母数字)

    核心要点:

  • 字符类只能放在方括号 [] 里面使用,示例:[[:digit:]];
  • d/s/w 属于 ECMAScript 风格简写别名;
  • 字符集合受系统本地语言环境(locale)的影响;
  • 3.6 字符区间

    方括号表达式中的字符区间,会把区间内所有字符加入方括号的字符集合。

    • 书写格式:起始字符 – 终止字符
    • 区间包含编码数值大于等于起始字符、小于等于终止字符的全部字符;
    • 区间结果受平台字符编码影响;
    • 当短横线 – 位于方括号开头、末尾,或是区间起止位置异常时,- 仅代表它自身字面字符。

    示例解读:

  • [0-7]
    • 匹配字符集合 {0,1,2,3,4,5,6,7},只能匹配数字,无法匹配字母 a
  • ASCII 编码环境下 [h-k]
    • 包含 h、i、j、k
  • EBCDIC 编码环境下 [h-k]
    • 编码从 0x88(h) 到 0x92(k),中间夹带多个不可见控制字符,区间字符远多于 h-k 四个字母。
  • [-0-24]
    • 集合包含短横线 – 、0、1、2、4;这里 – 在最开头,作为普通符号。
  • [0-2-]
    • 匹配 0、1、2 和短横线 –
  • ASCII 环境 [+ – -]
    • 区间从 +(ASCII 43) 到 – (ASCII 45),包含 +、,、-
  • 补充规则:开启地区语言环境后,字符区间不再依靠原始编码值,而是根据当前区域的字符排序规则判定区间范围。

    关键要点:

  • – 只有放在两个有效字符中间时,才充当区间连接符;
  • ASCII、EBCDIC 等编码不同,相同字符区间得到的字符集合不一样;
  • 本地化(locale)模式下区间遵从语言排序规则,而非字节编码;
  • 3.7 排序元素

    排序元素是被视作单个字符的多字符序列。

    • 排序元素多用于部分本地化环境,把多字符字符组合当成整体匹配。

    3.8 排序符号

    方括号表达式内的排序符号,可以将一个排序元素加入该表达式定义的字符集合。

    • 书写格式:[.排序元素.]
    • [. .] 专属 POSIX 正则,ECMAScript 引擎一般不支持该写法。

    3.9 控制转义序列

    控制转义序列由反斜杠、字母 c,再加大小写英文字母构成;它匹配该字母对应的 ASCII 控制字符。

    • 例如:\\ci 等价于十六进制 \\x09(制表符),因为快捷键 Ctrl+I 的 ASCII 编码就是 0x09
    • \\cx 属于 ECMAScript 风格的转义语法,用来快捷输入各类不可见的控制字符。

    3.10 DSW 字符转义

    dsw 字符转义是字符类的短名称,如下表所示:

    转义序列等效命名字符类默认匹配范围(ASCII)
    \\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 风格)正则没有该功能;
  • transform_primary 作用
    • 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
  • ECMAScript 模式特殊规则
    • \\b 在 ECMAScript 里是单词边界断言,不再代表退格字符;
    • 没有响铃 \\a;
    • \\\\ 仅用来转义出字面反斜杠,归类为普通标识转义;
  • POSIX 系列正则(basic / extended / awk 等)
    • 完整支持上面全套 C-风格控制字符转义;
  • 3.13 十六进制转义序列

    十六进制转义序列由一个反斜杠、字母 x,再加上两位十六进制数字(0-9、a-f、A-F)构成。它匹配目标字符串中,编码值等于这两位十六进制数所代表数值的那个字符。

    • 例如,使用 ASCII 编码时,\\x41 可以匹配字符 A

    要点说明:

  • 格式:\\xHH,HH 必须恰好 2 位十六进制;
  • 作用:通过字符的十六进制 ASCII 码直接指定要匹配的字符;
  • 引擎支持:C++ std::regex 里,只有 ECMAScript 语法模式支持该转义;basic / extended(POSIX)默认不识别 \\x;
  • 易错点
    • \\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 转义)外,其余所有字符都可使用字面转义

    要点说明

  • 本质作用:\\元字符 → 普通字符,取消元字符特殊语义
  • 名称:Identity escape,标准叫法 恒等转义 / 字面转义
  • 跨模式坑点
    • 同样写 \\+,在 basic-POSIX 可能报错,basic 里 + 本身不是元字符,不能随便加反斜杠转义;ECMAScript 几乎可以随便反斜杠转义符号;
  • C++ regex 默认模式是 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

    要点总结

  • ^ 只有位于 [ 之后第一位才代表取反,其余地方就是普通脱字符
  • POSIX 系列语法(basic / extended / grep / egrep / awk):] 放在括号最前面不需要转义
  • ECMAScript(C++正则默认模式):] 在 [] 里面必须转义 \\],否则语法出错
  • 3.16 负向断言

    负向断言匹配不满足括号内规则的位置。它不会消耗目标字符串里的任何字符。 示例:(!aa)(a*)

    • 可以匹配字符串 “a”,捕获组 1 拿到子串 “a”;
    • 不能匹配 “aa”、“aaa”;
    • 文档里 (!aa) 是 ECMAScript 风格负先行断言 (?!aa) 的简写写法;

    核心:

  • 零宽度:断言本身只检查条件,不吞掉字符,匹配位置不会向后移动;
  • 负先行含义(?!aa):当前位置后面不能紧跟着 aa;
  • 拆解例子 (?!aa)(a*)
    • (?!aa):检查当前位置之后,不是两个连续 a
    • (a*):然后捕获尽可能多的 a
    • “a”:当前位置后面只有 1 个 a,满足断言,成功匹配
    • “aa”:开头后面就是 aa,负断言失败,整体无法匹配
  • 注:

    • 只有 ECMAScript 语法支持 (?!) 负先行断言;
    • POSIX (basic / extended / grep / awk) 语法不支持零宽断言;

    3.17 负向单词边界断言(Negative word boundary assert)

    当目标字符串的当前位置不在单词边界处时,断言匹配成功。

    核心:

  • 属于零宽度断言,只判断位置,不消耗任何字符;
  • 单词边界 \\b:一边是单词字符 [a-zA-Z0-9_]、另一边是非单词字符
  • 负单词边界元字符:\\B
    • \\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)(?:b)*(c)
    • (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”

    要点解析

  • 贪婪(greedy)
    • 默认行为,+、*、? 、{m,n} 都是贪婪,尽可能多吃字符;
    • (a+)(a*b) 贪婪:a+ 拿走尽可能多 a → aaa,剩下 b 交给第二组;
  • 非贪婪(non-greedy/lazy)
    • 在量词后面加 ? 开启:+?、*?、??、{m,n}?
    • 改成非贪婪写法:(a+?)(a*b)
    • a+? 先拿最少 1 个 a,剩下 aab 交给第二组。
  • 语法支持(C++ std::regex)
    • ✅️ECMAScript:完整支持非贪婪 +?、*?、??、{m,n}?
    • ❌️basic / extended / grep / egrep / awk:没有非贪婪量词
  • 3.20 八进制转义序列(Octal escape sequence)

    八进制转义序列由一个反斜杠,后面紧跟 1-3 位八进制数字 0-7 组成。它可以匹配目标字符串中,等于该八进制数值的字符。 如果全部数字都是0,则该转义序列无效;

    • 示例:在 ASCII 编码下,\\101 匹配字符 A

    要点说明

  • 语法:\\ + 1~3 个数字(0-7)
    • \\0:❌️全零,非法
    • \\65:✅️1~3位八进制
    • \\101:✅️1~3位八进制
  • 匹配规则:把八进制数转为十进制,取对应 ASCII 字符
    • \\101 = 八进制 101 = 十进制 65 = ‘A’
  • 位数上限:最多读取 3 位八进制数字,超过就不再解析
  • 语法支持:C++ 正则仅 ECMAScript 模式支持八进制转义;POSIX(basic / extended) 不支持
  • 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) 判断

    核心要点

  • 一个字符是不是元字符,取决于你选择哪一种正则语法,不同模式特殊字符列表不一样
  • basic / grep(BRE)很多符号是上下文元字符,不在特定位置就变回普通字符,这是 POSIX-BRE 最容易踩坑的地方
  • 三种匹配模式
    • 默认:精确二进制编码相等
    • 不区分大小写:调用 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,光标留在原地
  • 匹配逻辑拆解第一个例子 (?=aa)(a*),目标串 “aaaa”
    • 在起始位置,先检查后面是不是 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++代码小提醒
    • C++字符串本身反斜杠需要转义,正则里写 \\u0041,C++源码字符串必须写成 \\\\u0041
  • 3.24 通配符(Wildcard character,. 点号)

    通配符 . 可以匹配目标字符串里除换行符以外的任意单个字符。

    要点说明

  • 基础规则
    • . ≠ 真正意义上的 “所有字符”,默认不能匹配 \\n 换行;
      • 例:正则 a.b
        • “aab”:✅️匹配
        • “axb”:✅️匹配
        • “a\\nb”:❌️默认情况下匹配失败
  • C++ std::regex 开启点号匹配换行
    • ECMAScript 语法下,设置标志 regex_constants::dotall,. 就可以包含换行符 \\n
  • 不同正则模式均支持 .
    • basic / extended / ECMAScript / grep / egrep / awk 全部支持点通配符
  • 如果需要字面 .
    • 必须转义 \\.,否则会被当成通配符
  • 3.25 单词边界(Word boundary,\\b)

    单词边界出现在下面四种位置之一:

  • 当前位置是目标字符串开头,并且首字符属于单词字符 A-Z a-z 0-9 _
  • 当前位置在目标字符串末尾之后,并且字符串最后一个字符是单词字符
  • 当前字符是单词字符,前一个字符不是单词字符
  • 当前字符不是单词字符,前一个字符是单词字符
  • 要点详解

  • 零宽度断言
    • \\b 只匹配位置,不消耗任何字符,不会吞掉字符串里的字符。
  • 单词字符定义
    • [A-Z a-z 0-9 _]
    • 下划线 _ 属于单词字符,这点很容易踩坑;
  • 边界本质:单词字符 ↔ 非单词字符 的交界处
    • abc def:c 和空格之间、空格和 d 之间都存在单词边界
    • hello_world:中间 _ 两边没有边界,因为下划线属于单词字符
  • 支持情况
    • 仅 ECMAScript 模式支持 \\b / \\B,POSIX(basic / extended / grep …)不提供单词边界。
  • 反义:\\B
    • 非单词边界,匹配不是单词边界的位置。
    • 示例:正则 \\bcat\\b
      • "cat"✅️
      • "my cat."✅️
      • "category"❌️(cat 后面紧跟着字母,无边界)
  • 3.26 单词边界断言(Word boundary assert,\\b)

    当目标字符串的当前位置正好处于一个单词边界之后时,单词边界断言匹配成功。

    要点解析

  • 零宽度
    • \\b 是位置断言,不消耗任何字符,只检测当前光标所在位置是不是单词边界。
  • 单词边界判断标准(回顾)
    • 单词字符:A-Z a-z 0-9 _
    • 边界发生在:
      • 字符串开头 + 后面是单词字符
      • 字符串结尾 + 前面是单词字符
      • 单词字符 ↔ 非单词字符的交界处
  • \\b 和 \\B
    • \\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:查找成功,匹配最靠前的前三个字符。

    如果在同一个起始位置可以匹配出多个不同子串,有两种选择策略:

  • 优先匹配(first-match):分支 | 从左向右尝试,一旦左边分支匹配成功,直接返回,不再测试右侧分支。
  • 最长匹配(longest-match):在所有可行的子匹配里面选出长度最长的那一个;长度相同则优先选择先找到的。
  • 举例:正则 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)

  • regex_match → 完整匹配
    • 要求正则覆盖全部输入字符串,等价于自动隐式加上 ^…$
  • // "abcd" 不能匹配 "bcd"
    regex_match("abcd", sm, regex("bcd"))false

  • regex_search → 子串搜索
    • 在字符串任意位置查找,找到最左侧匹配
  • 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,n∈0~9
    • 取第 n 个捕获组的内容,一位编号
    • $1 = 第一个括号分组
  • $nn,nn∈10~99
    • 两位编号捕获组
    • $10 = 读取第 10 号捕获组
  • C++ 使用注意事项

  • POSIX(basic / extended…)不支持$& $ $' $1 这类替换语法,只有 ECMAScript 模式可以用
  • C++ 普通双引号字符串,$ 不需要转义;反斜杠才需要转义;
  • 如果字面需要输出一个美元符号 $,ECMAScript 替换串使用 $$
  • 赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【STL】正则表达式(C++)
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!