Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

正则表达式 (regex)

C++11 引入的正则表达式库,支持匹配、搜索、替换和迭代遍历。

用之前先有个心理准备:<regex> 的性能和编译期开销都不太理想。它的接口设计依赖模板实例化,编译慢;运行时也不如专门的库快。如果正则只是简单模式(比如找子串、判断前缀),用 std::string 的成员函数更快也更清晰。

1. 引入

#include <regex>

2. 核心概念

正则库由三个部分组成:

组件作用
std::regex编译后的正则表达式对象
std::match_results(smatch/cmatch)保存匹配结果
算法函数regex_match、regex_search、regex_replace

三个匹配函数

函数语义典型用途
regex_match整个字符串必须完全匹配校验格式(邮箱、日期)
regex_search字符串中任意位置匹配即可查找、提取
regex_replace替换所有匹配文本处理

这个区别很关键。regex_match 要求从头到尾完全吻合,regex_search 只要找到一处就行:

std::regex re(R"(\d+)");
std::string s = "abc123def";

std::regex_match(s, re);    // false:整个串不全是数字
std::regex_search(s, re);   // true:中间有数字

字符类型

类型字符类型字符串类型
std::regexcharstd::string
std::wregexwchar_tstd::wstring

对应的匹配结果类型:

类型对应
std::smatchstd::string 的匹配结果
std::cmatchconst char* 的匹配结果
std::wsmatchstd::wstring 的匹配结果

3. 用法

1. 校验格式

#include <iostream>
#include <regex>
#include <string>

bool isValidDate(const std::string& s)
{
    // 匹配 YYYY-MM-DD
    static const std::regex re(R"(\d{4}-\d{2}-\d{2})");
    return std::regex_match(s, re);
}

isValidDate("2026-09-19");   // true
isValidDate("2026-9-19");    // false:月份必须是两位
isValidDate("x2026-09-19");  // false:regex_match 要求完全匹配

用原始字符串 R"(...)" 可以避免反斜杠转义,正则里写 \d 而不用写 \\d。

2. 提取内容

用捕获组 (...) 提取子串:

std::string log = "2026-09-19 14:30:25 [INFO] 启动完成";
std::regex re(R"((\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2}))");

std::smatch m;
if (std::regex_search(log, m, re))
{
    std::cout << "完整匹配: " << m[0] << '\n';   // 2026-09-19 14:30:25
    std::cout << "年: " << m[1] << '\n';         // 2026
    std::cout << "月: " << m[2] << '\n';         // 09
    std::cout << "日: " << m[3] << '\n';         // 19
    std::cout << "时: " << m[4] << '\n';         // 14
}

m[0] 是完整匹配,m[1] 起是各个捕获组。m.size() 返回组数加一。

3. 遍历所有匹配

用 sregex_iterator 遍历:

std::string text = "apple 10, banana 20, cherry 30";
std::regex re(R"((\w+)\s+(\d+))");

for (auto it = std::sregex_iterator(text.begin(), text.end(), re);
     it != std::sregex_iterator(); ++it)
{
    const std::smatch& m = *it;
    std::cout << m[1] << " = " << m[2] << '\n';
}
// 输出:
// apple = 10
// banana = 20
// cherry = 30

4. 替换

std::string text = "2026-09-19";

// 把 YYYY-MM-DD 换成 DD/MM/YYYY
std::regex re(R"((\d{4})-(\d{2})-(\d{2}))");
std::string result = std::regex_replace(text, re, "$3/$2/$1");
// 结果:19/09/2026

替换字符串里用 $1、$2 引用捕获组,$& 表示整个匹配。

5. 正则语法速查

语法含义
.任意字符(默认不含换行)
\d / \w / \s数字 / 单词字符 / 空白
\D / \W / \S上述的补集
[abc]字符集合
[^abc]排除集合
[a-z]范围
* / + / ?0 次或多次 / 1 次或多次 / 0 或 1 次
{n} / {n,m}恰好 n 次 / n 到 m 次
^ / $行首 / 行尾
\b单词边界
(...)捕获组
(?:...)非捕获组
|或
\1反向引用第 1 组

6. 语法选项

// 忽略大小写
std::regex re("hello", std::regex::icase);

// 多行模式:^ 和 $ 匹配每行
std::regex re2("^abc", std::regex::multiline);

// 扩展语法(默认)
std::regex re3("a+", std::regex::ECMAScript);

可用的语法标志:ECMAScript(默认)、basic、extended、awk、grep、egrep。

4. 注意事项

regex_match 和 regex_search 语义不同。前者要求完全匹配,后者只要部分匹配。校验格式用前者,提取内容用后者。

正则对象应该复用。构造 std::regex 需要编译正则表达式,开销很大:

// 错误:每次调用都重新编译
bool check(const std::string& s) {
    std::regex re(R"(\d+)");
    return std::regex_match(s, re);
}

// 正确:静态复用
bool check(const std::string& s) {
    static const std::regex re(R"(\d+)");
    return std::regex_match(s, re);
}

std::regex 的性能一般。它在很多实现里是基于回溯的,遇到复杂模式或恶意输入可能指数级退化(ReDoS)。性能敏感场景考虑 RE2、PCRE2 等库。

编译期开销大。<regex> 会引入大量模板实例化,一个简单的 #include <regex> 可能让编译时间增加不少。如果只是简单匹配,用字符串函数更划算。

std::regex 不是线程安全的(构造和修改时)。但构造完成后多个线程可以并发使用同一个 const 对象。

注意 \d 在原始字符串中的写法。用 R"(\d+)" 而不是 "\\d+",前者更易读。

异常处理。正则语法错误会在构造时抛 std::regex_error:

try {
    std::regex re("([");   // 括号不匹配
} catch (const std::regex_error& e) {
    std::cerr << "正则错误: " << e.what() << '\n';
}

简单场景用字符串函数。判断前缀用 starts_with,查找子串用 find,这些都比正则快得多也清晰得多。

5. 相关章节