I - 正则表达式概述
从 C++11 起,标准库增加了正则表达式 (Regular Expression) — std::regex
,包含在头文件 <regex> 中。
描述字符序列的方法,用于字符(串)的模式/模糊匹配。
用途 ,一般只有两种用途:
- 查找 (是否包含,找出来)
std::regex_match(seq, r, mft); // (seq, m, r, mft)
std::regex_search(seq, r, mft); // (seq, m, r, mft)
标识 | 释义 |
---|---|
seq | sequence 表示目标序列 |
r | regex 表示模式,即正则表达式 |
m | match 对象,即 std::match_results 匹配到结果数组 |
mft | match flag type 匹配标识选项位掩码类型 (bitmask type) |
- 替换(替换内容,替换格式)
std::regex_replace(seq, r, fmt, mft);
标识 | 释义 |
---|---|
seq | sequence 目标序列 |
r | regex 表示模式,即正则表达式 |
fmt | 正则表达式替换格式字符串,准确语法依赖于 flags 的值 |
mft | match flag type 匹配标识选项位掩码类型 (bitmask type) |
II - 内容
正则表达式各语言间通用,ECMAScript 规范,在不同语言间,有可能伴随很小的差异。主要包含三种内容:
-
- 匹配字符
\w\d\s
- 匹配字符
-
- 匹配数量
+*?{n}
- 匹配数量
-
- 内置字符特殊用途
^$[]
- 内置字符特殊用途
2.1 - 匹配字符
基础字符
字符 | 说明 |
---|---|
\w | 匹配字符或数字或下划线 |
\W | 匹配除了字符/数字/下划线以外的字符 |
\d | 匹配数字 |
\D | 匹配任意非数字的字符 |
\s | 匹配任意空白符 |
\S | 匹配任意非空白符的字符 |
字母大小写表示匹配相反的内容
空白符:比如换行符 ' \n ', 制表符 ' \t ' 等, 空白符表
空白符 描述 ' ' space (SPC) '\t' horizontal tab (TAB) '\n' newline (LF) line feed '\v' vertical tab (VT) '\f' form feed (FF) '\r' carriage return (CR) 比如有些地方看到的 CRLF 就是说 Windows 的换行符 \r\n , Carriage Return Line Feed
LF 即 Linux 的换行符 \n , Line Feed
内置通用字符簇
字符簇 | 说明 |
---|---|
[[:alpha:]] | 任何字母 |
[[:digit:]] | 任何数字 |
[[:alnum:]] | 任何字母和数字 |
[[:xdigit:]] | 任何 16 进制字符,相当于 [0-9a-fA-F] (中括号稍后解释) |
[[:space:]] | 任何空白符 |
[[:upper:]] | 任何大写字母 |
[[:lower:]] | 任何小写字母 |
[[:punct:]] | 任何标点符号 |
注意:alpha 不为希腊字母 α 。
alpha = alphabet 字母表,26 个字母,也就是全部的字母
alnum = alphanumeric 含有数字和字母的,有些库提供接口如
isAlphanumeric() // 表示字符串或序列是否由数字和字母组成
punct = punctuation 标点
2.2 - 匹配数量/次数
字符 | 说明 |
---|---|
+ | 一次或多次,即 >= 1 次 |
* | 零次或多次,即 >= 0 次 |
? | 零次或一次,是或否 |
{n} | 确定的 n 次 |
{n, m} | n 到 m 次,如果 { n, } 则表示大于等于 n 次,也就是从 n 到无限大,m 为无限大 |
2.3 - 特殊字符的用途
字符 | 说明 |
---|---|
. | 匹配任何除了 换行或行终止符 (line terminators LF, CR, LS, PS) 之外的任何单个字符 |
^ | 匹配字符串开始的位置 |
$ | 匹配字符串结尾的位置 |
[] | 匹配范围,例:[a-z] 小写字母 |
[^] | 匹配反向范围,例:[\^a-z] 所有小写字母意外的字符 |
| | 两个匹配条件进行逻辑或运算 |
() | 匹配子表达式 例 (him|her) 匹配 him 或 her |
LS (line separator):行分隔符,Unicode 字符 2028
PS (paragraph separator):段落分隔符,Unicode 字符 2029
此两者均为不可见字符。
示例:匹配一个 C++ 源文件
std::regex r("[[:alnum:]]+\\.(cpp|cxx|cc)$");
通常一个 C++ 的源文件,形式为 字母数字加 .
加后缀名 cpp/cxx/cc 结尾。$
即是表示字符串结尾。由于 .
有特殊用途所以表示一个点字符则需要加上 \
,而反斜杠在 C++ 中为转义字符,所以表示一个反斜杠需要使用双反斜杠 \\
,因此正则表达式表示匹配一个点字符为:\\.
III - 使用
3.1 - 正则表达式有不止一种写法
像 [0-9]
代表的含意与 \d
就是完全等价的,表示一个数字字符
再者如 [a-z0-9A-Z_]
也完全等同于 \w
表示一个字母/数字/下划线字符。
3.2 - 内置通用字符簇特殊用法
内置通用字符簇可以添加字符,也可以反向匹配,如:
[^[:digit:]]
等价于 \D
,所有非数字字符[^[:space:]]
等价于 \S
,所有非空白符[_[:alnum:]]
等价于 \w
,字母/数字/下划线字符[^_[:alnum:]]
等价于 \W
, 所有非字母/数字/下划线字符
3.3 - 元字符使用
\b
并不匹配字符,而是匹配字符位置,与 ^
和 $
类似。
例如匹配字符两端加上 \b
即可做到整词匹配 (whole word),如 \bword\b
只会匹配到 word,而不会匹配到如 sword 或 words 等。
示例:匹配一个 IP 地址
std::regex ip("\\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\."
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\."
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\."
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b");
首尾的 \\b
表示整词匹配,\\.
表示匹配 IP 地址中的点符号, ()
表示一个子表达式。
子表达式中分三部分,
- 第一部分 - 可以为 250 到 255
- 第二部分 - 200 到 249
- 第三部分 -
?
表示存在或不存在,[01]?
表示百位的 0 或 1 或者不存在,有时写 IP 时可不写为 0 的高位数 如 247.231.3.19 。 后置的[0-9]?
可与前面的[0-9]
结合表示两位数字,或者不存在,使用前者表示单个数字。
3.4 - 零宽断言
(Lookahead and Lookbehind Zero-Length Assertions)
用于查找在某些内容 (但并不包括这些内容) 之前或之后的内容,也就是说它们像 \b
, ^
, $
那样用于指定一个位置,这个位置应该满足一定的条件 (即断言) ,因此它们也被称为零宽断言。
(?=exp)
称为零宽度正预测先行断言,它断言自身出现的位置的后面能匹配表达式 exp。比如 \b\w+(?=s\b)
,匹配以 s 结尾的单词前面的部分(除了 s 以外的部分),如查找句子 There are some books, some fruits and some cups。时,它会匹配 book, fruit 和 cup。
(?<=exp)
称为零宽度正回顾后发断言,它断言自身出现的位置的前面能匹配表达式 exp 。如 (?<=\bre)\w+\b
会匹配以 re 开头的单词后面的部分 (除了 re 以外的部分),例如在查找 redo an action 时,它匹配 do 。
以下两种为确保不存在的断言,类似于前面讲到的反向匹配,可结合 [a-z]
和 [^a-z]
来理解。
(?!exp)
称为零宽度负预测先行断言,断言此位置的后面不能匹配表达式 exp 。例如:\d{3}(?!\d)
匹配三位数字,而且这三位数字的后面不能是数字;\b((?!abc)\w)+\b
匹配不包含连续字符串abc的单词。
(?<!exp)
称为零宽度负回顾后发断言,用来断言此位置的前面不能匹配表达式 exp 。(?<![a-z])\d{7}
匹配前面不是小写字母的七位数字。
速记:
符号| 内容
:-|:-?=
|正预测?<=
|正回顾?!
|负预测?<!
|负回顾
=
表示相同,即匹配, !
表示取反,即反向匹配,无 <
为预测,有 <
为回顾
3.5 - 宽字符匹配
正则表达式提供一套宽字符类型的操作
std::wregex;
std::wregex_iterator;
3.6 - 代码示例
格式转换示例
string phone = "(\\()?(\\d{3})(\\)?)([-. ])?(\\d{3})([-. ])?(\\d{4})";
regex r(phone);
smatch m;
string s = "morgan (609) 555-0132 201.555.0175 800.555.0000";
string fmt = "$2.$5.$7";// 将号码格式改为 ddd.ddd.dddd
cout << regex_replace(s, r, fmt) << endl;
cout << regex_replace(s, r, fmt, regex_constants::format_no_copy) << endl;
输出
morgan 609.555.0132 201.555.0175 800.555.0000
609.555.0132 201.555.0175 800.555.0000
- 代码中正则表达式表示 7 个子表达式
"(\\()?(\\d{3})(\\)?)([-. ])?(\\d{3})([-. ])?(\\d{4})"
(\\()?
左括号,是否存在(\\d{3})
三位数字(\\)?)
右括号,是否存在([-. ])?
减符号,点符号,空格,以及子表达式是否存在(\\d{3})
三位数字([-. ])?
减符号,点符号,空格,以及子表达式是否存在,同表达式 4(\\d{4})
四位数字
其中代码中变量 fmt 值中的
$2.$5.$7
分别代表第 2 个表达式,第 5 个表达式,第 7 个表达式,中间使用点符号分开。
第 0 个表达式为全集,超过第 7 个表达式即为空。regex_constants::format_no_copy
表示不输出序列中未匹配的部分
3.7 - 注意事项
正则表达式不为 C++ 的一部分,所以无法做到编译时检查,可能会出现运行时构造错误,不符合正则表达式语法的情况。
在大型项目中使用正则表达式时,可以先写一个小程序的 demo ,用于检测正则表达式是否语法正确,避免大型程序需要较长的运行时间才会构建正则表达式对象,在遇到不符合语法导致抛出异常,反复修改至正常运行,从而浪费了较多时间。
示例代码
try {
regex r("[[:alnum:]+\\.(cpp|cxx|cc)$", regex::icase);
} catch (regex_error & e) {
cout << e.what() << "\ncode:" << e.code() << endl;
}
此种情况下会输出
regex_error(error_brack):
The expression contained mismatched [ and ].
code: 4
代码中 regex::icase
表示忽略字母大小写
IV - 参考链接
- 正则表达式30分钟入门教程
http://help.locoy.com/Document/Learn_Regex_For_30_Minutes.htm cppreference
https://en.cppreference.com/w/cpp/regexRegular-Expressions.info
https://www.regular-expressions.info/
https://www.regular-expressions.info/lookaround.html