正则表达式(Regular Expression,简称regex)是一种用于描述字符串模式的工具,可实现字符串的匹配、搜索、替换、提取等高效操作。C++自C++11起,通过标准库<regex>头文件正式引入正则表达式支持,无需依赖第三方库,即可完成复杂的文本处理任务。
C++标准库的正则表达式体系围绕清晰的结构化模型构建,核心是将正则模式封装为std::regex对象 ,通过标准算法(如regex_match、regex_search)应用于目标字符串,匹配结果则通过std::match_results等对象存储,支持对完整匹配结果和捕获子表达式的结构化访问。
1.正则表达式语法
1.1 基础匹配(普通字符与转义字符)
普通字符:直接匹配自身(如"abc"匹配字符串"abc");
转义字符:需加双反斜杠(\\),因为C++字符串中反斜杠本身是转义字符,常用转义字符如下:
cpp
\\d:匹配任意数字(0-9),等价于[0-9];
\\D:匹配任意非数字,等价于[^0-9];
\\w:匹配任意字母、数字、下划线(a-z、A-Z、0-9、_);
\\W:匹配任意非字母、数字、下划线;
\\s:匹配任意空白字符(空格、制表符、换行符等);
\\S:匹配任意非空白字符;
\\.:匹配任意单个字符(除换行符\n外);
\\|:匹配竖线本身(竖线在正则中表示"或",需转义)。
1.2 量词(控制匹配次数)
量词用于指定前面的字符/子表达式匹配的次数,常用量词如下(优先级从高到低):
cpp
? 匹配0次或1次(可选) ab?c:匹配"ac"、"abc"
+ 匹配1次或多次(至少1次) \\d+:匹配"1"、"123"、"4567"
* 匹配0次或多次(任意次数) abc*:匹配"ab"、"abc"、"abcc"
{n} 匹配恰好n次 \\d{3}:匹配"123"、"456"(仅3位数字)
{n,} 匹配至少n次 \\d{2,}:匹配"12"、"123"、"1234"
{n,m} 匹配n到m次(包含n和m) \\d{2,4}:匹配"12"、"123"、"1234"
注意:量词默认是"贪婪匹配" (尽可能多匹配),在量词后加?可改为"非贪婪匹配"(尽可能少匹配),例如:"a.*?"匹配最短的以a开头的子串。
1.3 捕获组(分组匹配与提取)
用()将正则表达式的一部分括起来,称为捕获组,可实现"分组匹配",后续可通过match1、match2等获取分组内容,常用于提取复杂字符串中的特定部分。
示例:正则表达式std::regex re("(\\d{4})-(\\d{2})-(\\d{2})");,可匹配日期格式(如2026-04-17),其中:
cpp
match[0]:整个日期字符串(2026-04-17);
match[1]:年份(2026);
match[2]:月份(04);
match[3]:日期(17)。
补充:非捕获组(?:...)------仅用于分组,不捕获结果,可减少不必要的内存开销,例如R"(?:abc)+",匹配多次abc,但不单独捕获abc。
1.4 边界匹配(精准定位)
用于精准定位匹配的位置,避免部分匹配导致的错误,常用边界符号:
-
^:匹配字符串的开头(如^abc匹配以abc开头的字符串);
-
**:匹配字符串的结尾**(如abc匹配以abc结尾的字符串);
-
\\b:单词边界(匹配单词的开头或结尾,如\\bhello\\b匹配独立的hello单词,不匹配helloworld)。
1.5 逻辑匹配(或、非、与)
-
或(|) :匹配多个模式中的一个,例如 abc|def 匹配abc或def;
-
非(\^...) :匹配不在括号内的任意字符,例如 \^0-9 匹配非数字字符;
-
与(默认):正则表达式中,多个字符/子表达式连续书写,即为"与"关系,例如abc匹配a且b且c连续出现。
2.核心组件
2.1 正则表达式对象:std::regex
std::regex是C++正则表达式的核心类,用于封装编译后的正则表达式模式,构造时需指定正则字符串和可选的语法/匹配标志。
构造方式:直接传入正则字符串,可搭配语法标志(如ECMAScript、icase(不区分大小写)等)
语法与匹配选项常量说明:
(1)Grammar option(语法选项):指定正则表达式的语法规则,一次只能选一种,默认是 ECMAScript。
-
ECMAScript:默认选项,使用修改过的 ECMAScript 语法,与 JavaScript/Java 正则兼容,在绝大多数 C++ 开发场景中最通用;
-
basic:是 POSIX BRE(基本正则),语法老旧,不支持 `+` `?` `{m,n}`,用于兼容传统 `grep` 等老旧 POSIX 工具;
-
extended:是 POSIX ERE(扩展正则),支持 `+` `?` `{m,n}`,更接近现代语法,兼容 `grep -E` / `egrep`;
-
awk:采用 POSIX awk 工具的正则语法,兼容 awk 脚本的写法;
-grep:类似 basic,但额外把换行符 `\n` 作为"或"操作符;
- egrep:类似 extended,但把 `\n` 和制表符 `\t` 都作为分隔符,以兼容 `grep -E` / `egrep` 的跨行匹配语义。
(2)Grammar variation(语法变体 / 匹配选项):修饰匹配行为(如忽略大小写、多行模式等),可以用 | 与语法选项组合。
-
icase:忽略大小写匹配,可以同时匹配 hello/Hello/HELLO 等不同大小写形式;
-
nosubs :禁用捕获组 ,匹配结果中不存储子表达式内容,适用于只需要判断是否匹配而无需提取分组的场景,可以提升性能;
-
optimize:优化匹配速度,编译正则时会做额外处理(耗时更长),适合正则会被重复调用的场景(如循环中),以空间换时间;
-
collate:使字符范围 a-b 受 locale 影响(如非 ASCII 字符的排序规则),用于处理本地化字符(如中文、法语等);
-
multiline(C++17 引入):多行模式,让 `^` 匹配每行开头、`$` 匹配每行结尾,而非仅匹配整个字符串的首尾,用于处理多行文本(如日志文件)时的逐行匹配需求。
cpp
#include <iostream>
#include <regex>
#include <string>
using namespace std;
int main() {
// 邮箱正则表达式
// R 的作用是引入原始字符串字面量,让字符串中的反斜杠\、双引号等特殊字符不再被转义
regex email_regex(R"(^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$)");
//regex email_regex("(^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$)");
string email;
cout << "请输入邮箱地址:";
cin >> email;
try {
smatch match_res;
if (regex_match(email, match_res, email_regex)) {
cout << "邮箱格式合法!匹配结果:" << match_res.str() << endl;
} else {
cout << "邮箱格式非法!" << endl;
}
} catch (const regex_error& e) {
cout << "正则表达式错误:" << e.what() << endl;
}
return 0;
}
2.2 匹配结果存储:std::match_results
用于存储正则匹配的结果,本质是一个包含子匹配(sub_match)的容器,常用的实例化类型有3种,根据目标字符串类型选择:
(1)std::smatch:目标字符串为std::string
最常用,匹配结果与string对应,可通过str()获取字符串形式
cpp
std::regex_match(const std::string& s, std::smatch& m, const std::regex& e);
std::regex_search(const std::string& s, std::smatch& m, const std::regex& e);
cpp
#include <regex>
#include <iostream>
#include <string>
int main() {
std::string text = "出生日期: 1990-05-20";
std::regex re(R"((\d{4})-(\d{2})-(\d{2}))");
std::smatch match;
if (std::regex_search(text, match, re)) {
std::cout << "完整匹配: " << match.str() << std::endl; // 1990-05-20
std::cout << "年份: " << match[1].str() << std::endl; // 1990
std::cout << "月份: " << match[2].str() << std::endl; // 05
std::cout << "日期: " << match[3].str() << std::endl; // 20
}
return 0;
}
(2)std::cmatch:目标字符串为const char*
适用于C风格字符串,使用方式与smatch一致
cpp
std::regex_match(const char* s, std::cmatch& m, const std::regex& e);
std::regex_search(const char* s, std::cmatch& m, const std::regex& e);
(3)std::wsmatch:目标字符串为std::wstring
用于宽字符字符串,处理中文等多字节字符场景
cpp
std::regex_match(const std::wstring& s, std::wsmatch& m, const std::wregex& e);
std::regex_search(const std::wstring& s, std::wsmatch& m, const std::wregex& e);
核心用法:匹配成功后,通过下标访问匹配结果,match0表示整个匹配的字符串,match1、match2...表示第1、2...个捕获组的内容(捕获组由正则中的()定义)。
2.3 核心匹配函数
C++提供3个核心正则匹配函数,分别对应不同的匹配场景,需根据需求选择,避免混用导致逻辑错误:
(1)std::regex_match
功能:完整匹配------要求目标字符串从头到尾完全符合正则模式,一个字符都不能多、不能少,常用于字符串格式验证(如邮箱、手机号、日期等)。
cpp
// 函数原型
bool regex_match(const std::string& s, const std::regex& e);
bool regex_match(const std::string& s, std::smatch& m, const std::regex& e);
cpp
#include <regex>
#include <iostream>
int main() {
std::regex email_regex(R"([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})");
std::string valid_email = "user@example.com";
std::string invalid_email = "my email is user@example.com";
if (std::regex_match(valid_email, email_regex)) {
std::cout << "邮箱格式正确" << std::endl; // 执行
}
if (!std::regex_match(invalid_email, email_regex)) {
std::cout << "邮箱格式不正确" << std::endl; // 执行
}
// 提取捕获组
std::regex date_regex(R"((\d{4})-(\d{2})-(\d{2}))");
std::string date = "2024-12-25";
std::smatch match;
if (std::regex_match(date, match, date_regex)) {
std::cout << "年: " << match[1] << ", 月: " << match[2] << ", 日: " << match[3] << std::endl;
}
}
(2)std::regex_search
功能:部分匹配 (搜索匹配)------在目标字符串中搜索第一个符合正则模式的子串,无需整个字符串匹配,常用于提取子串、判断字符串中是否包含某类内容(如日志中提取IP地址)。
cpp
// 函数原型
bool regex_search(const std::string& s, const std::regex& e);
bool regex_search(const std::string& s, std::smatch& m, const std::regex& e);
bool regex_search(const char* first, const char* last, std::cmatch& m, const std::regex& e);
cpp
#include <regex>
#include <iostream>
#include <string>
int main() {
std::string log = "2024-12-25 10:30:45 ERROR: Connection failed from 192.168.1.100";
std::regex ip_regex(R"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})");
std::smatch match;
if (std::regex_search(log, match, ip_regex)) {
std::cout << "找到IP: " << match[0] << std::endl; // 192.168.1.100
}
// 搜索所有匹配(配合迭代器)
std::string text = "苹果15元,香蕉8元,橙子12元";
std::regex price_regex(R"(\d+元)");
auto begin = std::sregex_iterator(text.begin(), text.end(), price_regex);
auto end = std::sregex_iterator();
for (auto it = begin; it != end; ++it) {
std::cout << "价格: " << it->str() << std::endl;
}
// 输出: 15元, 8元, 12元
}
注意:默认从字符串开头开始搜索,可通过传入迭代器指定搜索范围(如跳过前导空格);若需搜索所有匹配子串,需结合std::sregex_iterator使用。
(3)std::regex_replace
功能:替换匹配 ------在目标字符串中,将所有符合正则模式的子串替换为指定字符串,支持替换为捕获组的内容,常用于文本清洗(如去除空格、替换敏感字符)。
cpp
// 函数原型
std::string regex_replace
(const std::string& s, const std::regex& e, const std::string& fmt);
std::string regex_replace(const std::string& s, const std::regex& e,
const std::string& fmt, std::regex_constants::match_flag_type flags);
fmt 在 std::regex_replace 中指定替换格式字符串
cpp
#include <regex>
#include <iostream>
#include <string>
int main() {
// 基础替换:去除所有空格
std::string text1 = "Hello World C++";
std::regex space_regex(R"(\s+)");
std::string result1 = std::regex_replace(text1, space_regex, " ");
std::cout << result1 << std::endl; // Hello World C++
// 使用捕获组:格式化电话号码
std::string phone = "13812345678";
std::regex phone_regex(R"((\d{3})(\d{4})(\d{4}))");
// $1、$2、$3 是反向引用,用于引用正则表达式中捕获组匹配到的内容。
std::string formatted = std::regex_replace(phone, phone_regex, "$1-$2-$3");
std::cout << formatted << std::endl; // 138-1234-5678
// 敏感信息脱敏
std::string id_card = "身份证号:11010119900307663X";
std::regex id_regex(R"((\d{6})\d{8}(\d{3}[0-9X]))");
std::string masked = std::regex_replace(id_card, id_regex, "$1********$2");
std::cout << masked << std::endl; // 身份证号:110101********63X
// 清洗HTML标签
std::string html = "<p>Hello <b>World</b></p>";
std::regex tag_regex(R"(<[^>]*>)");
std::string clean = std::regex_replace(html, tag_regex, "");
std::cout << clean << std::endl; // Hello World
}
2.4 迭代器(批量匹配)
当需要提取目标字符串中所有符合正则模式的子串时,需使用正则迭代器,常用std::sregex_iterator(对应std::string),可遍历所有匹配结果。
核心逻辑:通过迭代器初始化时传入目标字符串和正则对象,循环遍历迭代器,直到迭代器指向end(),每次迭代可获取一个匹配结果(smatch对象)。
cpp
// 构造函数:指定搜索范围和正则表达式
std::sregex_iterator it(字符串起始迭代器, 字符串结束迭代器, 正则表达式对象);
std::sregex_iterator end; // 默认构造的尾后迭代器
// 遍历所有匹配
for (auto it = std::sregex_iterator(str.begin(), str.end(), regex);
it != std::sregex_iterator();
++it) {
std::smatch match = *it;
// 处理匹配结果
}
cpp
#include <regex>
#include <iostream>
#include <string>
int main() {
std::string text = "订单号:ORD-001,金额:299元;订单号:ORD-002,金额:450元;订单号:ORD-003,金额:128元";
std::regex order_regex(R"(ORD-(\d{3}))");
// 创建正则迭代器
auto begin = std::sregex_iterator(text.begin(), text.end(), order_regex);
auto end = std::sregex_iterator(); // 尾后迭代器
// 遍历所有匹配结果
for (auto it = begin; it != end; ++it) {
std::smatch match = *it;
std::cout << "完整匹配: " << match.str() << std::endl;
std::cout << "捕获组(订单号后3位): " << match[1].str() << std::endl;
std::cout << "---" << std::endl;
}
return 0;
}
3. std::regex_error 错误处理详解
std::regex_error 是 C++ 标准库中专门用于报告正则表达式相关异常的类,继承自std::runtime_error。
cpp
#include <regex>
#include <iostream>
class regex_error : public std::runtime_error {
public:
// 构造函数(内部使用,用户无法直接构造)
explicit regex_error(regex_constants::error_type ecode);
// 返回错误码枚举值
regex_constants::error_type code() const noexcept;
// 返回错误描述字符串(继承自 std::runtime_error)
const char* what() const noexcept override;
};
cpp
#include <regex>
#include <iostream>
#include <string>
void test_regex(const std::string& pattern) {
std::cout << "测试模式: \"" << pattern << "\"" << std::endl;
try {
std::regex re(pattern);
std::cout << "✓ 编译成功" << std::endl;
} catch (const std::regex_error& e) {
std::cout << "✗ 编译失败: " << e.what() << std::endl;
std::cout << " 错误码: " << e.code() << std::endl;
}
std::cout << std::endl;
}
int main() {
test_regex("(unclosed"); // 括号不匹配
test_regex("*"); // 量词前无内容
test_regex("a{1,2"); // 花括号语法错误
test_regex("a{5,2}"); // 花括号范围无效
test_regex("\\c"); // 无效转义序列
test_regex("[[:invalid:]]"); // 无效字符类名
test_regex("[z-a]"); // 字符范围无效
test_regex("(valid)\\d+"); // 正确正则
return 0;
}
cpp
error_brack 括号不匹配 "(" 缺少 ")"、"[]" 不闭合
error_badrepeat 重复量词前无表达式 "*"、"+?"、"{2}" 前面没有可重复的内容
error_brace 花括号 {} 错误 "{1,2" 缺少 }、"{1,a}" 非数字
error_brace_range 花括号范围无效 "{5,2}" 下限大于上限
error_escape 无效的转义序列 "\\c"(无效转义)、"\\u123"(不完整)
error_content 无效的字符内容 字符类内无效使用,如 "[a-"
error_collate 无效的排序元素 "[[.xx.]]" 无效的排序名
error_ctype 无效的字符类名 "[[:invalid:]]"
error_range 字符范围无效 "[z-a]" 起始大于结束
error_parsing 解析失败(通用错误) 语法解析器遇到无法识别的内容
error_space 内存不足 编译后的正则表达式太大,超出内存限制
error_stack 栈溢出 正则表达式过于复杂,递归深度超限
error_complexity 复杂度超限 匹配操作过于耗时(如灾难性回溯)
error_collate 无效的排序元素 同上(某些实现中重复定义)