正则表达式

正则表达式是一个强大的文本匹配工具

正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特殊字符 ,及这些特殊字符的组合,组成一个"规则字符串",这个"规则字符串"用来表达对字符串的一种过滤逻辑

正则表达式基本语法

学习正则表达式跳转参考

图形化展示正则表达式匹配流程

转义字符

所有的ASCII码都可以用 \ 加数字(一般是8进制数字)来表示.而C中定义了一些字母前加”"来表示常见的哪些不能显示的ASCII字符,如 \0, \t, \n 等.
如:

  • 表示ASCII -> 大写字母M(ASCII十进制值为77)
    • 用8进制表示为: \0115
    • 用十六进制表示: \x4d
  • C语言中字符的转义
    • \a 响铃
    • \b 退格
    • \r 回车
    • \n 换行
    • \\ 代表一个反斜杠

正则表达式中使用的一些具备特殊含义的特殊字符(若要表示原本的字符则要加转义字符在前面)

? + { } | ( ) [ ]

转义字符 描述 正则表达式 示例
\a 与报警 (bell) 符 \u0007 匹配 \a 匹配 “Warning!” + ‘\u0007’ 中的 “\u0007”
\b 在字符类中,与退格键 \u0008 匹配 [\b]{3,} 匹配 “\b\b\b\b” 中的 “\b\b\b\b”
\t 与制表符 \u0009 匹配 (\w+)\t 匹配 “Name\tAddr\t” 中的 “Name\t” 和 “Addr\t”
\r 与回车符 \u000D 匹配,(\r 与换行符 \n 不是等效的) \r\n(\w+) 匹配 “\r\nHello\nWorld.” 中的 “\r\nHello”
\v 与垂直制表符 \u000B 匹配 [\v]{2,} 匹配 “\v\v\v” 中的 “\v\v\v”
\f 与换页符 \u000C 匹配 [\f]{2,} 匹配 “\f\f\f” 中的 “\f\f\f”
\n 与换行符 \u000A 匹配 \r\n(\w+) 匹配 “\r\nHello\nWorld.” 中的 “\r\nHello”
\e 与转义符 \u001B 匹配 \e 匹配 “\x001B” 中的 “\x001B”
\nnn 使用八进制形式指定一个字符(nnn 由二到三位数字组成) \w\040\w 匹配 “a bc d” 中的 “a b” 和 “c d”
\xnn 使用十六进制形式指定字符(nn 由两位数字组成) \w\x20\w 匹配 “a bc d” 中的 “a b” 和 “c d”
\cX\cx 匹配 X 或 x 指定的 ASCII 字符,其中 X 或 x 是控件字符的字母 \cC 匹配 “Ctrl” 中的 “C”
\unnnn 使用十六进制形式匹配一个 Unicode 字符(nnnn 表示一个四位数) \w\u0020\w 匹配 “a bc d” 中的 “a b” 和 “c d”
\ 在后面带有不识别的转义字符时,与该字符匹配 \d+[+-x*]\d+\d+[+-x*\d+ 匹配 “(2+2) * 39” 中的 “2+2” 和 “39”

字符类

字符类 描述 正则表达式 示例
[character_group] 匹配 character_group 中的任何一个字符,默认情况下 character_group 中的字符区分大小写 [mn] 匹配 “mat” 中的 “m”,”moon” 中的 “m” 和 “n”
[^character_group] 匹配不在 character_group 中的任何单个字符,默认情况下 character_group 中的字符区分大小写 [^aei] 匹配 “avail” 中的 “v” 和 “l”
[first-last] 字符范围,匹配与从 first 到 last 范围内的任何单个字符 [b-d] [b-d]irds 可以匹配 Birds、 Cirds、 Dirds
. 通配符:匹配任何字符,若要匹配句号(. 或 \u002E),则必须在该字符前面加上转义符 (.) a.e 匹配 “have” 中的 “ave”, “mate” 中的 “ate”
\p{name} 匹配 name 中指定的 Unicode 字符 \p{Lu} 匹配 “City Lights” 中的 “C” 和 “L”
\P{name} 匹配不在 name 中指定的 Unicode 字符 \P{Lu} 匹配 “City” 中的 “i”、 “t” 和 “y”
\w 匹配字母、数字、下划线 \w 匹配 “Room#1” 中的 “R”、 “o”、 “m” 和 “1”
\W 匹配字母、数字或下划线以外的字符 \W 匹配 “Room#1” 中的 “#”
\s 匹配任何空白字符(包括换行符) \w\s 匹配 “ID A1.3” 中的 “D “
\S 匹配任何非空白字符(包括换行符) \s\S 匹配 “int __ctr” 中的 “ _”
\d 匹配任何十进制数字 \d 匹配 “4 = IV” 中的 “4”
\D 匹配不是十进制数的任意字符 \D 匹配 “4 = IV” 中的 “ “、 “=”、 “ “、 “I” 和 “V”

定位符

断言 描述 正则表达式 示例
^ 从字符串的开始位置进行匹配 ^\d{3} 匹配 “567-777-“ 中的 “567”
$ 从字符串的结尾位置进行匹配 -\d{4}$ 匹配 “8-12-2012” 中的 “-2012”
\A 匹配字符串的开始位置 \A\w{4} 匹配 “Code-007-“ 中的 “Code”
\Z 匹配字符串的结尾位置或字符串结尾换行之前的位置 -\d{3}\Z 匹配 “Bond-901-007” 中的 “-007”
\z 匹配字符串的结尾位置 -\d{3}\z 匹配 “-901-333” 中的 “-333”
\G 匹配上一个匹配结束的位置 \G(\d) 匹配 (1)(3)(5)[7](9) 中的 “(1)”、 “(3)” 和 “(5)”
\b 匹配一个单词的开始或结束的位置 er\b 匹配”never”中的”er”,但不能匹配”verb”中的”er”。
\B 匹配一个单词的中间位置 er\B 匹配”verb”中的”er”,但不能匹配”never”中的”er”。

分组构造

分组构造描述了正则表达式的子表达式,并捕获输入字符串的子字符串

分组构造 描述 正则表达式 示例
(subexpression) 捕获匹配的子表达式并将其分配到一个从零开始的序号中 (\w)\1 “deep” 中的 “ee”
(?subexpression) 将匹配的子表达式捕获到一个命名组中 (?< double>\w)\k< double> “deep” 中的 “ee”
(?<name1 -name2>subexpression) 定义平衡组 (((?’Open’()[^()])+((?’Close-Open’))[^()])+)*(?(Open)(?!))$ “3+2^((1-3)(3-1))” 中的 “((1-3)(3-1))”
(?: subexpression) 定义非捕获组 Write(?:Line)? “Console.WriteLine()” 中的 “WriteLine”
(?imnsx-imnsx:subexpression) 应用或禁用 subexpression 中指定的选项 A\d{2}(?i:\w+)\b “A12xl A12XL a12xl” 中的 “A12xl” 和 “A12XL”
(?= subexpression) 零宽度正预测先行断言 \w+(?=.) “He is. The dog ran. The sun is out.” 中的 “is”、 “ran” 和 “out”
(?! subexpression) 零宽度负预测先行断言 \b(?!un)\w+\b “unsure sure unity used” 中的 “sure” 和 “used”
(?<=subexpression) 零宽度正回顾后发断言 (?<=19)\d{2}\b “1851 1999 1950 1905 2003” 中的 “99”、”50”和 “05”
(? 零宽度负回顾后发断言 (? “Hi woman Hi man” 中的 “man”
(?> subexpression) 非回溯(也称为”贪婪”)子表达式 [13579](?>A+B+) “1ABB 3ABBC 5AB 5AC” 中的 “1ABB”、 “3ABB” 和 “5AB”

限定符

表示匹配数量的字符

限定符用来指定在字符串中必须存在某个元素(可以是字符、组或字符类)才能匹配成功

限定符 描述 正则表达式 示例
* 匹配上一个元素零次或多次 \d*.\d “.0”、 “19.9”、 “219.9”
+ 匹配上一个元素一次或多次 “be+” 匹配 “been” 中的 “bee”, “bent” 中的 “be”
? 匹配上一个元素零次或一次 “rai?n” “ran”、 “rain”
{n} 匹配上一个元素 n 次 “,\d{3}” 匹配 “1,043.6” 中的 “,043”, “9,876,543,210” 中的 “,876”、 “,543” 和 “,210”
{n, } 匹配上一个元素至少 n 次 “\d{2,}” “166”、 “29”、 “1930”
{n, m} 匹配上一个元素至少 n 次,但不多于 m 次 “\d{3,5}” 匹配 “166”, “17668”, “193024” 中的 “19302”
*? 匹配上一个元素零次或多次,但次数尽可能少 \d*?.\d “.0”、 “19.9”、 “219.9”
+? 匹配上一个元素一次或多次,但次数尽可能少 “be+?” 匹配 “been” 中的 “be”, “bent” 中的 “be”
?? 匹配上一个元素零次或一次,但次数尽可能少 “rai??n” “ran”、 “rain”
{n}? 匹配前导元素恰好 n 次 “,\d{3}?” 匹配 “1,043.6” 中的 “,043”, “9,876,543,210” 中的 “,876”、 “,543” 和 “,210”
{n,}? 匹配上一个元素至少 n 次,但次数尽可能少 “\d{2,}?” “166”、 “29” 和 “1930”
{n, m}? 匹配上一个元素的次数介于 n 和 m 之间,但次数尽可能少 “\d{3,5}?” 匹配 “166”, “17668”, “193024” 中的 “193” 和 “024”
  • ? 前面修饰的子表达式出现0次或者1次

    ? 号同时作为懒惰匹配的标识符.如:<.*?>表示前面的.*为懒惰匹配,细节讲解参考下方

  • * 前面修饰的子表达式出现0次或者多次

  • + 前面修饰的子表达式出现1次或者多次

  • {n,m} 前面修饰的子表达式出现n次到m次({n,}为n到无穷次,{n}则表示为出现n次)

实例: abc?d可以匹配abdabcd

所有限定符默认为贪婪匹配,? 才能标识懒惰匹配, <.*?> 针对 <1>123<2> 文本.匹配的是 <1><2>;若是 <.*> 则匹配为 <1>123<2>,为贪婪匹配

贪婪与懒惰匹配

默认为贪婪匹配,手动使用?设置懒惰匹配如下:

代码/语法 说明
*? 重复任意次,但尽可能少重复
+? 重复1次或更多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复
{n,}? 重复n次以上,但尽可能少重复

界定限定符作用的子表达式范围

  • 直接为单个字符 作用于单个字符
  • (字符串) 作用于字符串,()里边的东西代表一个整体
  • [字符序列] 字符组:作用于字符序列标识的多个字符之一 如[abc]匹配a,也匹配b [字符1字符2字符3]相当于(字符1|字符2|字符3) 也可以作用为[a-zA-Z0-9]指的范围区间的任意一个字符
  • [^字符序列] 匹配除了列出的字符序列以外的字符,正和上面相反

实例:

  • [^\x00-\xff] 匹配所有全角符号,包含全角标点和汉字韩文日文等等
  • [一-龥]匹配所有汉字

或运算符:| 或符号

元字符

元字符:正则表达式中预先定义好了的一系列常用的字符类型

数字,空白符,单词开头,结尾

多大数元字符以反斜杠开头

  • \d 数字字符
  • \w 单词字符:包含英文,数字和下划线
  • \s 空白符:tab和换行符
  • . 不包含换行符之外的任意字符
  • \D 非数字字符
  • \W 非单词字符
  • \S 非空白字符
  • \b 表示单词边界,可以用于前或后,如:/b单词/b,这样可以防止匹配多余字符:单词的开头或结尾 单词与符号之前的边界
  • \B 非单词的边界 符号与符号 单词与单词的边界

限定位置的字符

两个: ^ $

  • ^ 匹配行首 ^a 只匹配行首的a
    • ^hello:要求第一个字符必须是h,后面必须是ello
    • ^(hello):要求必须以hello开头的hello hellohellohello 只匹配第一个 hello
  • $ 匹配行尾 a$ 只匹配行尾的a

反向引用构造

反向引用构造允许先前匹配的子表达式随后在相同的正则表达式中进行标识。下表列出了常用的反向引用构造:

反向引用构造 描述 正则表达式 示例
\number 反向引用,匹配编号子表达式的值 (\w)\1 匹配 “seek” 中的 “ee”
\k 命名反向引用,匹配命名表达式的值 (?< char>\w)\k< char> 匹配 “seek” 中的 “ee”

备用构造

备用构造用于修改正则表达式以启用 either/or 匹配。下表列出了常用的备用构造:

备用构造 描述 正则表达式 示例
| 匹配以竖线` `字符分隔的任何一个元素 th(e|is|at)
(?( expression )yes | no) 如果正则表达式模式由 expression 匹配指定,则匹配 yes 部分;否则匹配可选的 no 部分,expression 被解释为零宽度断言 (?(A)A\d{2}\b|\b\d{3}\b) 匹配 “A10 C103 910” 中的 “A10” 和 “910”
(?( name )yes | no ) 如果 name 或已命名或已编号的捕获组具有匹配,则匹配 yes 部分;否则匹配可选的 no 部分。 (?< quoted>”)?(?(quoted).+?”|\S+\s) 匹配 “Dogs.jpg “Yiska playing.jpg”” 中的 Dogs.jpg 和 “Yiska playing.jpg”

替换

替换是替换模式中使用的正则表达式。下表列出了用于替换的字符:

字符 描述 模式 替换模式 输入字符串 结果字符串
$number 替换按组 number 匹配的子字符串 \b(\w+)(\s)(\w+)\b $3$2$1 “one two” “two one”
${name} 替换按命名组 name 匹配的子字符串 \b(?< word1>\w+)(\s)(?< word2>\w+)\b ${word2} ${word1} “one two” “two one”
$$ 替换字符”$” \b(\d+)\s?USD $$$1 “103 USD” “$103”
$& 替换整个匹配项的一个副本 ($(\d(.+\d+)?){1}) **$& “$1.30” $1.30
$` 替换匹配前的输入字符串的所有文本 B+ $` “AABBCC” “AAAACC”
$’ 替换匹配后的输入字符串的所有文本 B+ $’ “AABBCC” “AACCCC”
$+ 替换最后捕获的组 B+(C+) $+ “AABBCCDD” AACCDD
$_ 替换整个输入字符串 B+ $_ “AABBCC” “AAAABBCCCC”

杂项构造

下表列出了各种杂项构造:

构造 描述 示例
(?imnsx-imnsx) 在模式中间对诸如不区分大小写这样的选项进行设置或禁用 \bA(?i)b\w+\b 匹配 “ABA Able Act” 中的 “ABA” 和 “Able”
(?#注释) 内联注释。该注释在第一个右括号处终止 \bA(?# 匹配以 A 开头的单词 )\w+\b
# [行尾] 该注释以非转义的 # 开头,并继续到行的结尾 (?x)\bA\w+\b# 匹配以 A 开头的单词

进阶概念

高级概念:

捕获组,断言,递归,平衡组

捕获组

捕获组就是把正则表达式中子表达式匹配的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用。当然,这种引用既可以是在正则表达式内部,也可以是在正则表达式外部。

捕获组有两种形式,一种是普通捕获组,另一种是命名捕获组,通常所说的捕获组指的是普通捕获组。语法如下:

  • 普通捕获组:(Expression)
  • 命名捕获组:(?<name>Expression)

普通捕获组在大多数支持正则表达式的语言或工具中都是支持的,而命名捕获组目前只有.NET、PHP、Python等部分语言支持,据说Java会在7.0中提供对这一特性的支持。上面给出的命名捕获组的语法是.NET中的语法,另外在.NET中使用(?’name’Expression)与使用(?<name>Expression)是等价的。在PHP和Python中命名捕获组语法为:(?P<name>Expression)

编号规则

普通捕获组编号规则

如果没有显式为捕获组命名,即没有使用命名捕获组,那么需要按数字顺序来访问所有捕获组。在只有普通捕获组的情况下,捕获组的编号是按照“(”出现的顺序,从左到右,从1开始进行编号的。

image-20231126110121764

需要说明的是,编号为0的捕获组,指的是正则表达式整体

命名捕获组编号规则

命名捕获组通过显式命名,可以通过组名方便的访问到指定的组,而不需要去一个个的数编号,同时避免了在正则表达式扩展过程中,捕获组的增加或减少对引用结果导致的不可控。

不过容易忽略的是,命名捕获组也参与了编号的,在只有命名捕获组的情况下,捕获组的编号也是按照“(”出现的顺序,从左到右,从1开始进行编号的。

image-20231126110329139

普通捕获组与命名捕获组混合编号规则

当一个正则表达式中,普通捕获组与命名捕获组混合出现时,捕获组的编号规则稍显复杂。对于其中的命名捕获组,随时都可以通过组名进行访问,而对于普通捕获组,则只能通过确定其编号后进行访问。

混合方式的捕获组编号,首先按照普通捕获组中“(”出现的先后顺序,从左到右,从1开始进行编号,当普通捕获组编号完成后,再按命名捕获组中“(”出现的先后顺序,从左到右,接着普通捕获组的编号值继续进行编号。

也就是先忽略命名捕获组,对普通捕获组进行编号,当普通捕获组完成编号后,再对命名捕获组进行编号。

image-20231126110517528

捕获组的引用

对捕获组的引用一般有以下几种:

  1. 正则表达式中,对前面捕获组捕获的内容进行引用,称为反向引用
  2. 正则表达式中,(?(name)yes|no)的条件判断结构
  3. 在程序中,对捕获组捕获内容的引用

反向引用

捕获组捕获到的内容,不仅可以在正则表达式外部通过程序进行引用,也可以在正则表达式内部进行引用,这种引用方式就是反向引用。

反向引用的作用通常是用来查找或限定重复,限定指定标识配对出现等等。

对于普通捕获组和命名捕获组的引用,语法如下:

  • 普通捕获组反向引用\k<number>,通常简写为\number
  • 命名捕获组反向引用\k<name>或者\k'name'

普通捕获组反向引用中number是十进制的数字,即捕获组的编号;命名捕获组反向引用中的name为命名捕获组的组名

简单案例

常用正则表达式查询

  • 用户名: ^[a-zA-Z0-9_-]{3,16}$
  • 时间(小时:分钟 24小时制): ((1|0?)[0-9]|2[0-3]):([0-5][0-9])
  • IP地址: ((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)

C++中使用正则表达式

头文件 #include <regex>

regex 表示有一个正则表达式类,比如:regex pattern("(.{3})(.{2})_(\d{4})!")
regex_match 全文匹配,要求整个字符串符合正则表达式的匹配规则。用来判断一个字符串和一个正则表达式是否模式匹配,返回一个 bool 值,true 为匹配,false 为不匹配。匹配的含义是目标字符串必须完全和正则表达式相匹配,不能有多余的字符,如果需要部分匹配则应使用regex_search
regex_search 搜索匹配,根据正则表达式来搜索字符串中是否存在符合规则的子字符串。 能和正则表达式相匹配就返回true
regex_replace 替换匹配,即可以将符合匹配规则的子字符串替换为其他字符串。要求输入一个正则表达式,以及一个用于替换匹配子字符串的格式化字符串。这个格式化字符串可以通过转义序列引用匹配子字符串中的部分内容
sregex_iterator 迭代器适配器,调用regex_search来遍历一个string中所有匹配的子串
smatch/match_results 容器类,保存在string中搜索的结果。如果匹配成功,这些函数将成功匹配的相关信息保存在给定的smatch对象中。比如:smatch results;将匹配结果存放在results中,另一种存储方法声明:match_results<string::const_iterator> result