C++正则表达式

📅 发布时间:2026/8/16 21:19:18
C++正则表达式
正则表达式Regular Expression简称regex是一种用于描述字符串模式的工具可实现字符串的匹配、搜索、替换、提取等高效操作。C自C11起通过标准库regex头文件正式引入正则表达式支持无需依赖第三方库即可完成复杂的文本处理任务。C标准库的正则表达式体系围绕清晰的结构化模型构建核心是将正则模式封装为std::regex对象通过标准算法如regex_match、regex_search应用于目标字符串匹配结果则通过std::match_results等对象存储支持对完整匹配结果和捕获子表达式的结构化访问。1.正则表达式语法1.1 基础匹配普通字符与转义字符普通字符直接匹配自身如abc匹配字符串abc转义字符需加双反斜杠\\因为C字符串中反斜杠本身是转义字符常用转义字符如下\\d匹配任意数字0-9等价于[0-9] \\D匹配任意非数字等价于[^0-9] \\w匹配任意字母、数字、下划线a-z、A-Z、0-9、_ \\W匹配任意非字母、数字、下划线 \\s匹配任意空白字符空格、制表符、换行符等 \\S匹配任意非空白字符 \\.匹配任意单个字符除换行符\n外 \\|匹配竖线本身竖线在正则中表示“或”需转义。1.2 量词控制匹配次数量词用于指定前面的字符/子表达式匹配的次数常用量词如下优先级从高到低? 匹配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 捕获组分组匹配与提取用()将正则表达式的一部分括起来称为捕获组可实现“分组匹配”后续可通过match[1]、match[2]等获取分组内容常用于提取复杂字符串中的特定部分。示例正则表达式std::regex re((\\d{4})-(\\d{2})-(\\d{2}));可匹配日期格式如2026-04-17其中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::regexstd::regex是C正则表达式的核心类用于封装编译后的正则表达式模式构造时需指定正则字符串和可选的语法/匹配标志。构造方式直接传入正则字符串可搭配语法标志如ECMAScript、icase(不区分大小写)等语法与匹配选项常量说明1Grammar 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 的跨行匹配语义。2Grammar variation语法变体 / 匹配选项修饰匹配行为如忽略大小写、多行模式等可以用 | 与语法选项组合。-icase忽略大小写匹配可以同时匹配 hello/Hello/HELLO 等不同大小写形式-nosubs禁用捕获组匹配结果中不存储子表达式内容适用于只需要判断是否匹配而无需提取分组的场景可以提升性能-optimize优化匹配速度编译正则时会做额外处理耗时更长适合正则会被重复调用的场景如循环中以空间换时间- collate使字符范围 [a-b] 受 locale 影响如非 ASCII 字符的排序规则用于处理本地化字符如中文、法语等- multilineC17 引入多行模式让 ^ 匹配每行开头、$ 匹配每行结尾而非仅匹配整个字符串的首尾用于处理多行文本如日志文件时的逐行匹配需求。#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种根据目标字符串类型选择1std::smatch目标字符串为std::string最常用匹配结果与string对应可通过str()获取字符串形式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);#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; }2std::cmatch目标字符串为const char*适用于C风格字符串使用方式与smatch一致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);3std::wsmatch目标字符串为std::wstring用于宽字符字符串处理中文等多字节字符场景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);核心用法匹配成功后通过下标访问匹配结果match[0]表示整个匹配的字符串match[1]、match[2]...表示第1、2...个捕获组的内容捕获组由正则中的()定义。2.3 核心匹配函数C提供3个核心正则匹配函数分别对应不同的匹配场景需根据需求选择避免混用导致逻辑错误1std::regex_match功能完整匹配——要求目标字符串从头到尾完全符合正则模式一个字符都不能多、不能少常用于字符串格式验证如邮箱、手机号、日期等。// 函数原型 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);#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 userexample.com; std::string invalid_email my email is userexample.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; } }2std::regex_search功能部分匹配搜索匹配——在目标字符串中搜索第一个符合正则模式的子串无需整个字符串匹配常用于提取子串、判断字符串中是否包含某类内容如日志中提取IP地址。// 函数原型 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);#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使用。3std::regex_replace功能替换匹配——在目标字符串中将所有符合正则模式的子串替换为指定字符串支持替换为捕获组的内容常用于文本清洗如去除空格、替换敏感字符。// 函数原型 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 中指定替换格式字符串#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 pHello bWorld/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对象。// 构造函数指定搜索范围和正则表达式 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; // 处理匹配结果 }#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。#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; };#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; }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 无效的排序元素 同上某些实现中重复定义