服务器设计 之 【正则表达式及C++正则库的简介与使用】
目录
1.正则表达式
1.2.基础元字符:字符类的原子
1.3.字符集合与范围:自定义字符类
1.4.量词:重复次数控制
1.5.位置锚定:边界断言
1.6.分组与捕获
1.7.逻辑选择与修饰符
2.C++ 的正则表达式库 简介
2.1.std::basic_regex 及其别名
2.2.std::match_results 及其别名
2.3.std::sub_match 及其别名
2.4.std::regex_match
2.5. std::regex_search
2.6.std::regex_replace
2.7.std::regex_iterator
2.8.std::regex_token_iterator
2.9.标志
2.10.使用细节简介
1.正则表达式
- 正则表达式(Regular Expression,简称 regex 或 regexp)是一套用于描述、匹配和操作字符串的规则语言
- 它通过特定的字符组合定义搜索模式,应用于文本处理、数据验证、日志分析等场景中
1.2.基础元字符:字符类的原子
正则中,一些特殊符号代表某一类字符
| 元字符 | 含义 | 等价表达式 |
|---|---|---|
. | 匹配除换行符外的任意单个字符 | [^\n\r] |
\d | 数字字符 | [0-9] |
\D | 非数字字符 | [^0-9] |
\w | 单词字符(字母、数字、下划线) | [A-Za-z0-9_] |
\W | 非单词字符 | [^A-Za-z0-9_] |
\s | 空白符(空格、制表、换行等) | [ \t\n\r\f\v] |
\S | 非空白符 | [^ \t\n\r\f\v] |
\t | 制表符(Tab) | — |
\n | 换行符 | — |
\r | 回车符 | — |
- c.t 匹配 cat、cbt、c t,不匹配 ct 或 caat
- \d{3}-\d{4} 匹配美国邮编格式如 123-4567
1.3.字符集合与范围:自定义字符类
使用方括号 [] 自定义字符类,匹配的是一个字符(除非后面跟着量词如 +、*、{n})
| 写法 | 说明 |
|---|---|
[abc] | 匹配a、b、c中任意一个 |
[^abc] | 匹配除a、b、c之外的任意一个字符(脱字符在开头表取反) |
[a-z] | 匹配一个小写字母 a 到 z |
[0-9A-F] | 匹配一个十六进制数字字符 |
[a-zA-Z] | 匹配一个任意大小写字母 |
在正则表达式的方括号 [] 内部,除了开头的 ^(取反)、中间的 -(基于 ASCII/Unicode 码点的范围,必须放在两个字符之间)、结束的 ] 以及转义符 \外,其他所有元字符(如 . * + ? | 等)都只代表它们自己这个普通字符
| 字符 | 特殊含义的条件 | 作为普通字符的条件 |
|---|---|---|
^ | 紧跟在[后面(取反) | 不在第一个位置(例如[a^]) |
- | 在两个字符之间(表示范围,如a-z) | 在开头[-abc]或结尾[abc-] |
] | 结束字符类 | 必须转义\](几乎无法作为普通字符放在中间而不转义) |
- 在自定义字符类内部使用预定义字符类
| 写法 | 含义 |
|---|---|
[\d] | 等价于[0-9] |
[\w] | 等价于 [A-Za-z0-9_] |
[\s] | 匹配任何一个空白字符(空格、制表符等) |
[\d\s] | 匹配一个数字或空白字符 |
在字符类内部,\d、\w、\s 仍然有效,但它们的取反版本(\D、\W、\S)在字符类内没有特殊含义,会变成匹配字面量 D、W、S
大多数正则引擎中,[] 会报错
1.4.量词:重复次数控制
量词只作用于它紧邻的一个原子:
- 字符:a+ 匹配一个或多个 a
- 字符类:[ab]+ 匹配一个或多个(任意组合的 a 或 b)
- 分组:(abc)+ 匹配一个或多个 abc(如 abcabcabc)
- 注意:ab+ 匹配的是 a 后面跟一个或多个 b,而不是 ab 重复。要重复 ab 必须用 (ab)+
| 量词 | 含义 | 匹配次数 |
|---|---|---|
? | 零次或一次(可选) | {0,1} |
* | 零次或多次(任意次) | {0,} |
+ | 一次或多次(至少一次) | {1,} |
{n} | 恰好 n 次 | {n} |
{n,} | 至少 n 次 | {n,} |
{n,m} | 至少 n 次,最多 m 次 | {n,m} |
- .* 不匹配换行
- 贪婪(默认 * +):在保证整个表达式还能匹配成功的前提下,尽可能多地重复
- 懒惰(加 ?):在保证整个表达式还能匹配成功的前提下,尽可能少地重复
| 正则 | 字符串 | 匹配结果(贪婪) | 匹配结果(懒惰,加?) |
|---|---|---|---|
a.*b | aababb | aababb(整串) | aab,然后ab |
<.*> | <div>text</div> | <div>text</div>(整串) | <div>,然后</div> |
".*" | "one" "two" | "one" "two"(整串) | "one",然后"two" |
1.5.位置锚定:边界断言
不消耗字符,只匹配位置
(1)基础锚点:^ 和 $
| 锚点 | 含义 | 注意 |
|---|---|---|
^ | 匹配字符串开头(或多行模式下的行首) | 在字符类[^]中表示“取反”,不同语境 |
$ | 匹配字符串结尾(或多行模式下的行尾) | 注意$前如果有\n也可以匹配(引擎相关) |
文本:"Hello world"(单行)
- ^Hello 匹配 Hello(在开头)
- world$ 匹配 world(在结尾)
- ^world 不匹配
(多行)
Hello world- /^world/m 匹配第二行的 world(常见的定界符就是正斜杠 /,成对出现)
- 默认无 m 时 ^ 只匹配整个字符串的最开头
(2)单词边界:\b 和 \B
\w 组成单词字符:[A-Za-z0-9_](默认 ASCII 模式,Unicode 模式更广)
| 锚点 | 匹配位置 | 本质 |
|---|---|---|
\b | 单词边界 | \w和\W(或开头/结尾)之间的位置 |
\B | 非单词边界 | 不是\b的位置(\w内部或\W内部) |
- \b = 单独(作为独立单词)
- \B = 不单独(作为单词的一部分)
| 正则 | 含义 | 通俗说法 |
|---|---|---|
\bcat\b | 独立的cat | "就它自己,前后不是单词字符" |
\Bcat\B | 夹在单词内部的cat | "前后都连着其他单词字符" |
\bcat | 开头的cat | "前面是边界(开头或非单词字符),后面无所谓" |
cat\b | 结尾的cat | "后面是边界,前面无所谓" |
1.6.分组与捕获
(1)普通捕获组 (...)
分组:将多个字符/元字符组合成一个单元,让量词(*、+、{n} 等)作用于整个组
捕获:记住这个组匹配到的具体文本,编号从 1 开始
(\d{4})-(\d{2})-(\d{2})- 匹配日期 2026-04-22,捕获组 1 为 2026,组 2 为 04,组 3 为 22
(2)非捕获组 (?:...)
只分组,不捕获→ 不占用编号,性能稍好,正则更清晰
(?:http|ftp)://([^/\r\n]+)- 匹配 http://example.com,捕获的只是域名部分
- (?:http|ftp) 作为一个整体,匹配 http 或 ftp
- 捕获组 1 = 域名
- 如果不加 ?::(http|ftp)://([^/\r\n]+) → 协议会占组1,域名占组2,多了一层
(3)反向引用\n
匹配之前捕获组匹配到的相同内容(不是相同的模式,而是相同的字符串)
文本:"<div>内容</div>"
正则:<(\w+)>.*?</\1>
- \1 引用的组1的内容 div
- 能匹配 <div>...</div>,但不会匹配 <div>...</span>
文本:"the the cat"
正则:\b(\w+)\s+\1\b
- 匹配 the the
文本:Hello 'world' and \"python\""
正则:(['"]).*?\1
- 先匹配 ' 或 ",\1 保证闭合的是同一种引号
- 第一次匹配结果:'world'
- 第二次匹配结果:"python"
(4)反向引用的编号
- 编号按捕获组出现的顺序(左括号的位置),非捕获组 (?:...) 不计入
- \0 一般表示整个匹配(部分语言支持)
- 超过 9:\10 表示第 10 个组(注意:\10 可能被解析为八进制转义,建议用 \g{10} 或 ${10})
1.7.逻辑选择与修饰符
(1)或运算符 |
作用:在多个分支中选择一个匹配
| 的优先级低于连接、量词、锚点等几乎所有操作符
# 文本 "foot" (foot|foo) → 匹配 "foot"(先试 foot 成功) (foo|foot) → 匹配 "foo"(先试 foo 成功,不会再试 foot)- 在大多数引擎中,| 是短路的:从左到右尝试,第一个匹配成功的分支就停止
- 这可能导致意外,如果长的分支放在后面,可能永远不会被匹配到
(2)修饰符(标志)
加在正则字面量末尾或作为函数参数,影响匹配行为
| 修饰符 | 作用 |
|---|---|
i | 忽略大小写 |
g | 全局匹配(找到所有匹配,而不止第一个)(默认匹配第一个就停止) |
m | 多行模式:^和$匹配行首行尾,而非仅字符串首尾 |
s | 点号匹配所有字符模式:.也匹配换行符 |
u | 启用 Unicode 完整支持 |
y | 粘滞模式(sticky),从lastIndex位置严格匹配 |
- m - 多行模式
作用:改变 ^ 和 $ 的行为
| 模式 | ^匹配 | $匹配 |
|---|---|---|
无m | 仅字符串开头 | 仅字符串结尾(或结尾的\n前) |
有m | 字符串开头+ 每个\n后面 | 字符串结尾+ 每个\n前面 |
文本: "line1\nline2\nline3" /^line/g → 匹配第一个 "line"(无 m) /^line/gm → 匹配三个 "line"(每行开头) /line$/gm → 匹配每行结尾的 "line"^ 和 $ 仍然不消耗 \n,只是位置锚定变了
1.8.运算符优先级
| 优先级 | 运算符/元素 | 说明 | 正面例子 | 反面例子(容易误解) |
|---|---|---|---|---|
| 1 | 转义符\ | 改变紧随其后的字符的字面意义 | \.匹配字面量句号 | \d中的\ |
| 2 | 字符类/分组[...][^...](...)(?:...) | 方括号和圆括号构造的单元 | [a-z]是一个整体 | ab[cd]是a+b+[cd] |
| 3 | 量词*+?{n,m} | 紧贴在它前面的一个原子(字符、字符类或分组)上 | a+匹配一个或多个a;[ab]+匹配一个或多个由a或b组成的序列 | ab+匹配a+b+,而不是(ab)+ |
| 4 | 连接(拼接) | 两个相邻的原子,中间没有操作符 | ab匹配a后紧跟b | 空格在大部分模式里就是一个普通字符 |
| 5 | 锚点^$\b\B | 匹配位置,不消耗字符 | ^a匹配行首的a | a^是无效的(位置锚点放中间没意义) |
| 5 | 或| | 在多个分支之间选择 | cat|dog匹配cat或dog |
2.C++ 的正则表达式库 <regex> 简介
2.1.std::basic_regex 及其别名
- 该类表示一个正则表达式对象,是后续所有匹配操作的基础
- 常用别名有 std::regex (用于 std::string) 和 std::wregex (用于 std::wstring)
| 接口 | 说明 |
|---|---|
regex() | 默认构造函数,创建一个空的正则表达式 |
regex(const charT* s, flag_type f = ECMAScript) | 从C风格字符串构造 |
regex(const stringT& s, flag_type f = ECMAScript) | 从std::string构造 |
regex& operator=(const regex& other) | 赋值操作符 |
regex& assign(const charT* s, flag_type f = ECMAScript) | 将一个正则表达式赋值给对象 |
unsigned mark_count() const | 返回表达式中捕获组的数量 |
flag_type flags() const | 返回构造时使用的语法标志 |
2.2.std::match_results 及其别名
- 该类存储正则匹配的结果,通过 smatch 对象可以访问所有匹配信息,包括子匹配(捕获组)
- 常用别名:std::smatch (用于 std::string) 和 std::cmatch (用于 const char*)
- 存储了完整的匹配信息:整个匹配的字符串(索引 0),所有捕获组的内容(索引 1, 2, 3...)
| 接口 | 说明 |
|---|---|
bool empty() const | 检查是否有匹配结果 |
size_t size() const | 返回子匹配(捕获组)的数量,包括整个匹配 |
size_t max_size() const | 返回可存储的最大子匹配数量 |
const_reference operator[](size_t n) const | 返回第 n 个子匹配(const_reference为sub_match) |
const_reference prefix() const | 返回目标序列中匹配项之前的子序列 |
const_reference suffix() const | 返回目标序列中匹配项之后的子序列 |
string_type format(const charT* fmt, ...) const | 根据格式字符串和匹配结果生成新字符串 |
2.3.std::sub_match 及其别名
- sub_match 对象由 match_results 的 operator[] 返回,表示一个具体的捕获组
| 接口 | 说明 |
|---|---|
bool matched | 布尔成员,指示此子匹配是否成功 |
difference_type length() const | 返回子匹配的长度 |
string_type str() const | 将子匹配转换为字符串 |
operator string_type() const | 隐式转换为字符串 |
int compare(const sub_match& s) const | 与另一个sub_match对象比较 |
2.4.std::regex_match
- 用于判断整个目标字符串是否与正则表达式完全匹配
| 接口 | 说明 |
|---|---|
bool regex_match(BidirIt first, BidirIt last, match_results& m, const regex& e, match_flag_type flags = match_default) | 在迭代器范围[first, last)内进行匹配,结果存入m |
bool regex_match(BidirIt first, BidirIt last, const regex& e, match_flag_type flags = match_default) | 在迭代器范围[first, last)内进行匹配,忽略结果 |
bool regex_match(const charT* str, match_results& m, const regex& e, match_flag_type flags = match_default) | 在C风格字符串上匹配,结果存入m |
bool regex_match(const charT* str, const regex& e, match_flag_type flags = match_default) | 在C风格字符串上匹配,忽略结果 |
bool regex_match(const stringT& s, match_results& m, const regex& e, match_flag_type flags = match_default) | 在std::string上匹配,结果存入m |
bool regex_match(const stringT& s, const regex& e, match_flag_type flags = match_default) | 在std::string上匹配,忽略结果 |
bool regex_match(const charT* str, match_results& m, const regex& e, match_flag_type flags = match_default) | (对于数组) 在C风格字符串上匹配,结果存入m |
2.5. std::regex_search
- 在目标字符串中查找第一个与正则表达式匹配的子串
| 接口 | 说明 |
|---|---|
bool regex_search(BidirIt first, BidirIt last, match_results& m, const regex& e, match_flag_type flags = match_default) | 在迭代器范围[first, last)内搜索,结果存入m |
bool regex_search(BidirIt first, BidirIt last, const regex& e, match_flag_type flags = match_default) | 在迭代器范围[first, last)内搜索,忽略结果 |
bool regex_search(const charT* str, match_results& m, const regex& e, match_flag_type flags = match_default) | 在C风格字符串上搜索,结果存入m |
bool regex_search(const charT* str, const regex& e, match_flag_type flags = match_default) | 在C风格字符串上搜索,忽略结果 |
bool regex_search(const stringT& s, match_results& m, const regex& e, match_flag_type flags = match_default) | 在std::string上搜索,结果存入m |
bool regex_search(const stringT& s, const regex& e, match_flag_type flags = match_default) | 在std::string上搜索,忽略结果 |
2.6.std::regex_replace
- 将目标字符串中所有匹配的部分替换为指定格式的字符串
| 接口 | 说明 |
|---|---|
OutIt regex_replace(OutIt out, BidirIt first, BidirIt last, const regex& e, const stringT& fmt, match_flag_type flags = match_default) | 将迭代器范围[first, last)内的替换结果写入输出迭代器out |
stringT regex_replace(const stringT& s, const regex& e, const stringT& fmt, match_flag_type flags = match_default) | 返回一个替换后的新字符串 |
stringT regex_replace(const charT* s, const regex& e, const stringT& fmt, match_flag_type flags = match_default) | 返回一个替换后的新字符串 |
2.7.std::regex_iterator
- 用于遍历一个字符串中的所有匹配项。常用别名有 std::sregex_iterator (用于 std::string)
| 接口 | 说明 |
|---|---|
regex_iterator() | 默认构造函数,构造一个尾后迭代器 |
regex_iterator(BidirIt first, BidirIt last, const regex& e, match_flag_type flags = match_default) | 在范围[first, last)内进行迭代 |
bool operator==(const regex_iterator& rhs) const | 比较两个迭代器是否相等 |
const value_type& operator*() const | 返回当前匹配的match_results对象 |
regex_iterator& operator++() | 移动到下一个匹配 |
2.8.std::regex_token_iterator
- 用于遍历一个字符串中所有匹配的特定子表达式(如特定捕获组),常用于字符串分割
| 接口 | 说明 |
|---|---|
regex_token_iterator() | 默认构造函数,构造一个尾后迭代器 |
regex_token_iterator(BidirIt first, BidirIt last, const regex& e, int submatch = 0, match_flag_type flags = match_default) | 迭代第submatch个捕获组 |
bool operator==(const regex_token_iterator& rhs) const | 比较两个迭代器是否相等 |
const value_type& operator*() const | 返回当前子匹配的sub_match对象 |
regex_token_iterator& operator++() | 移动到下一个匹配 |
2.9.标志
(1)语法标志 (用于构造std::regex)
- 定义在 std::regex_constants 命名空间中,决定正则表达式的语法风格。默认使用 ECMAScript
| 标志 | 说明 |
|---|---|
ECMAScript | 默认。使用改进的 ECMAScript 正则表达式语法。 |
basic | 使用 POSIX 基本正则表达式语法。 |
extended | 使用 POSIX 扩展正则表达式语法。 |
awk | 使用 POSIXawk工具的正则表达式语法。 |
grep | 使用 POSIXgrep工具的正则表达式语法,基于basic但允许\n分隔替代。 |
egrep | 使用 POSIXgrep -E工具的正则表达式语法,基于extended但允许\n分隔替代。 |
icase | 匹配时忽略大小写。 |
nosubs | 不存储任何子匹配(捕获组),可提升性能。 |
optimize | 指示引擎进行优化,可能加快匹配速度,但会增加构造时间。 |
collate | 让形如[a-b]的字符范围对当前区域设置(locale)敏感。 |
multiline | (C++17) 在多行模式下,^和$匹配行的开头和结尾,而非整个字符串的开头和结尾。 |
(2)匹配标志 (用于 regex_match, regex_search, regex_replace)
- 定义在 std::regex_constants 命名空间中,用于调整匹配过程的行为
| 标志 | 说明 |
|---|---|
match_default | 默认行为,为空掩码。 |
match_not_bol | 将第一个字符视为不在行首,即^不会匹配第一个字符。 |
match_not_eol | 将最后一个字符视为不在行尾,即$不会匹配最后一个字符。 |
match_not_bow | 将第一个字符视为不在单词边界,即\b不会匹配第一个字符。 |
match_not_eow | 将最后一个字符视为不在单词边界,即\b不会匹配最后一个字符。 |
match_any | 如果存在多个匹配,任何匹配都是可接受的(不保证最长或最早)。 |
match_not_null | 不匹配空序列。 |
match_continuous | 仅匹配从first开始的子序列。 |
match_prev_avail | 指示--first是有效位置,会使match_not_bol和match_not_bow被忽略。 |
format_default | 在regex_replace中,使用 ECMAScript 的替换语法。 |
format_sed | 在regex_replace中,使用 POSIXsed工具的替换语法。 |
format_no_copy | 在regex_replace中,不将未匹配的字符复制到输出。 |
format_first_only | 在regex_replace中,只替换第一个匹配项。 |
2.10.使用细节简介
(1)正则对象的构造开销极大
- std::regex 的构造函数会执行正则解析 -> 语法树生成 -> 状态机编译,这是一个耗时的过程
- 错误做法(在循环内重复编译),正确做法(单次编译,复用对象)
for (const auto& line : lines) { std::regex re(R"(\d+)"); // 每次循环都重新编译,极慢 std::regex_search(line, re); } static const std::regex re(R"(\d+)"); // 编译一次,重复使用 for (const auto& line : lines) { std::regex_search(line, re); }(2)optimize 标志的误导性
- std::regex::optimize 标志指示库花更多时间构造正则对象以换取更快的匹配速度
- 适用场景:正则对象构造一次,但进行数百万次搜索(如服务器日志实时过滤)
- 反作用场景:正则对象频繁构造销毁,或者只使用一两次。此时开启 optimize 会显著增加构造时间,得不偿失
(3)利用 nosubs 进行微优化
如果只关心是否匹配(regex_match 或 regex_search 返回 bool),而不需要提取捕获组内容,务必使用 std::regex::nosubs 标志,这会通知引擎不要分配内存来存储子匹配结果
// 仅判断有效性,无需捕获邮箱前缀后缀 std::regex re(R"(\w+@\w+\.com)", std::regex::nosubs);(4)匹配结果的生命周期
std::smatch m; if (std::regex_search(str, m, re)) { // m 存储的是迭代器/指针,指向 str 内部 // 如果 str 被销毁或修改,m 失效 }- 需要保存结果时,用 m.str() 复制出来
(5)迭代器遍历所有匹配
auto begin = std::sregex_iterator(str.begin(), str.end(), re); auto end = std::sregex_iterator(); for (auto it = begin; it != end; ++it) { std::cout << it->str() << std::endl; }2.11.使用示例
#include <iostream> #include <string> #include <regex> int main() { //http请求格式:GET /bitejiuyeke/login?user=xiaoming&123123 HTTP/1.1\r\n std::string str = "GET /bitejiuyeke/login?user=xiaoming&123123 HTTP/1.1\n"; std::smatch matches; std::regex e("(GET|DELETE|PUT) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\\n|\\r\\n)?"); //GET|HEAD|POST|PUT|DELETE 表示匹配并提取其中任意一个字符串 //[^?]* [^?]匹配非问号字符,后边的*表示次或多次 //\\?(.*) \\?表示原始的?字符(*)表示提取?之后的任意字符次或多次,直到遇到空格 //HTTP/1\.[01] 表示匹配以HTTP/1.开始,后边有个O或1的字符串 //(?:\\n|\\r\\n)? (:...)表示匹配某个格式字符串,但是不提取,最后的?表示的是匹配前边的表达式0次或1次 if(std::regex_match(str, matches, e)) for(auto& s : matches) std::cout << s << std::endl; else std::cout << "匹配失败" << std::endl; return 0; }| 部分 | 正则片段 | 匹配内容 |
|---|---|---|
| 方法 | (GET|DELETE|PUT) | 捕获 HTTP 方法 |
| 空格 | 分隔符 | |
| 路径+参数 | ([^?]*) | 捕获?之前的所有内容 |
| 查询字符串(可选) | (?:\\?(.*))? | 非捕获组,内部捕获?后的内容 |
| 空格 | 分隔符 | |
| HTTP 版本 | (HTTP/1\\.[01]) | 捕获 HTTP/1.0 或 HTTP/1.1 |
| 换行符(可选) | (?:\\n|\\r\\n)? | 非捕获,匹配\n或\r\n |
