当前位置: 首页 > news >正文

正则表达式详解(C++20 )

正则表达式详解(C++20 )

1. 什么是正则表达式

正则表达式(Regular Expression,简称 regex)是一种用于描述字符串匹配模式的强大工具。它本质上是一种微型的领域特定语言,通过特定的语法规则来定义一组字符串的集合。正则表达式广泛应用于:

  • 输入验证(邮箱、电话、URL、密码强度等)
  • 文本搜索与提取(日志分析、数据抓取)
  • 查找替换(敏感词过滤、格式化整理)
  • 编译器词法分析、语法高亮等

在 C++20 中,标准库<regex>提供了完整的正则表达式支持,包括匹配、搜索、替换和迭代等功能。


2. 正则表达式基本语法一览

这里以默认的 ECMAScript 语法(JavaScript 风格)为例,这也是 C++std::regex的默认语法。大多数通用 regex 知识在此适用。

2.1 普通字符与元字符

  • 普通字符(字母、数字、空格等)匹配自身。
  • 元字符有特殊含义:. ^ $ * + ? { } [ ] \ | ( )

若要匹配元字符本身,需用反斜杠\转义。在 C++ 代码中,反斜杠本身需要转义,因此推荐使用原始字符串字面量R"(...)",避免转义地狱。

2.2 字符类

模式说明
[abc]匹配 a、b 或 c 中的任意一个字符
[^abc]匹配除 a、b、c 外的任意一个字符(否定)
[a-z]匹配 a 到 z 的任意小写字母
.匹配除换行符外的任意单个字符
\d匹配一个数字,等价于[0-9]
\D匹配一个非数字,等价于[^0-9]
\w匹配一个单词字符(字母、数字、下划线),等价于[A-Za-z0-9_]
\W匹配一个非单词字符
\s匹配一个空白字符(空格、制表符、换行等)
\S匹配一个非空白字符

2.3 量词(重复次数)

模式说明
*前一表达式出现 0 次或多次
+前一表达式出现 1 次或多次
?前一表达式出现 0 次或 1 次
{n}前一表达式恰好出现 n 次
{n,}前一表达式出现至少 n 次
{n,m}前一表达式出现 n 到 m 次

默认是贪婪匹配,量词后面加?变为非贪婪匹配(如*?,+?,??)。

2.4 定位符(锚点)

模式说明
^匹配字符串开头
$匹配字符串结尾
\b匹配单词边界
\B匹配非单词边界

2.5 分组与捕获

  • (pattern):捕获组,匹配并捕获内容,可通过编号访问。
  • (?:pattern):非捕获组,只匹配不捕获,不产生反向引用。
  • \1,\2…:反向引用,匹配与第 n 个捕获组相同的内容。
  • (?'name'pattern)(?<name>pattern):命名捕获组(C++ 中需std::regex::ECMAScript并注意支持情况,std::regex本身不直接支持命名捕获,可用编号替代)。

2.6 零宽断言

模式说明
(?=p)正向先行断言,要求后面是 p,但不消耗字符
(?!p)负向先行断言,要求后面不是 p
(?<=p)正向后发断言,要求前面是 p(C++std::regex不完全支持可变宽度后发断言)
(?<!p)负向后发断言,要求前面不是 p

std::regex对后发断言支持有限,使用时需测试。


3. C++20 正则表达式库核心组件

3.1 头文件与主要类

#include <regex>
  • std::regex:存储编译后的正则表达式(基于模板std::basic_regex<char>)。
  • std::wregex:用于宽字符的正则表达式。
  • std::cmatch/std::smatch:匹配结果集,分别对应 C 风格字符串和std::string
  • std::sub_match:子匹配结果,代表一个捕获组。

3.2 常用匹配函数

函数作用
std::regex_match检查整个字符串是否与正则表达式完全匹配
std::regex_search在字符串中搜索是否存在与正则表达式匹配的子串。
std::regex_replace将匹配的子串替换为指定的格式字符串。

所有函数都可接受std::regex_constants::match_flag_type标志控制行为。

3.3 编译标志

构造std::regex时可指定语法选项和优化标志,常见如下:

std::regex pattern("...", std::regex_constants::ECMAScript | std::regex_constants::optimize);
  • ECMAScript:默认语法,类似 JavaScript。
  • grepextendedawkegrep:其他语法变体。
  • icase:忽略大小写。
  • optimize:提示正则引擎尽量优化,适合多次匹配场景。
  • multiline:使^$匹配行的开头和结尾,而非整个字符串。

3.4 迭代器

  • std::regex_iterator:迭代字符串中所有匹配项。
  • std::regex_token_iterator:可迭代匹配项或特定捕获组,常用于字符串分割。

4. 安全优雅的 C++20 实践准则

4.1 用原始字符串字面量书写正则

C++ 正则中反斜杠非常多,传统写法要写"\\d{3}",极易出错且难以维护。应始终使用R"()"

auto phone_pattern = std::regex(R"(\d{3}-\d{4})"); // 清晰直观

4.2 避免重复编译正则对象

正则编译(构造std::regex)开销较大。最佳实践是将正则对象声明为static const,保证只编译一次。

static const std::regex email_regex(R"(^[\w.+-]+@[\w-]+\.[\w.-]+$)");

4.3 异常处理

正则语法错误、不支持的特性、以及内存分配等问题会抛出std::regex_error。健壮的代码应当捕获该异常:

try { static const std::regex re(R"(\d+)"); } catch (const std::regex_error& e) { std::cerr << "Regex error: " << e.what() << " (code: " << e.code() << ")\n"; // 进行合适的错误处理 }

4.4 善用std::format(C++20)输出结果

使用std::format可以让结果打印更为优雅,避免繁琐的流操作。

#include <format> #include <iostream> // ... std::cout << std::format("Match found at position {}: {}\n", match.position(), match.str());

4.5 将常用操作封装为可复用的函数

例如封装一个验证函数,返回bool;或封装一个提取函数,返回std::optionalstd::vector。这既安全又优雅。


5. 完整代码示例

5.1 邮箱格式验证

#include <iostream> #include <regex> #include <string> #include <format> bool is_valid_email(std::string_view email) { // 通用的邮箱正则(简化版) static const std::regex pattern( R"(^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$)", std::regex_constants::ECMAScript | std::regex_constants::optimize ); try { return std::regex_match(email.begin(), email.end(), pattern); } catch (const std::regex_error&) { // 理论上静态正则不会在匹配时抛出异常,但保留安全性 return false; } } int main() { std::string test = "user@example.com"; std::cout << std::format("'{}' is valid: {}\n", test, is_valid_email(test)); test = "not-an-email"; std::cout << std::format("'{}' is valid: {}\n", test, is_valid_email(test)); }

5.2 提取日志中的日期

假设日志行格式为[2026-06-03 14:30:00] ERROR: message,我们要提取日期部分。

#include <iostream> #include <regex> #include <string> #include <optional> #include <format> std::optional<std::string> extract_date(const std::string& log_line) { // 捕获组:括号内为日期,格式 YYYY-MM-DD static const std::regex date_regex( R"(\[(\d{4}-\d{2}-\d{2})\s)", std::regex_constants::optimize ); std::smatch match; if (std::regex_search(log_line, match, date_regex) && match.size() > 1) { return match[1].str(); // 第一个捕获组 } return std::nullopt; } int main() { std::string log = "[2026-06-03 14:30:00] ERROR: Disk full"; if (auto date = extract_date(log)) { std::cout << std::format("Extracted date: {}\n", *date); } else { std::cout << "No date found.\n"; } }

5.3 敏感词替换

*替换所有出现的敏感词,且忽略大小写。

#include <iostream> #include <regex> #include <string> #include <format> std::string censor_text(std::string text, const std::string& forbidden_word) { // 动态构造正则(此处演示,一般也尽量 static) try { std::regex word_regex(forbidden_word, std::regex_constants::ECMAScript | std::regex_constants::icase | std::regex_constants::optimize); return std::regex_replace(text, word_regex, "***"); } catch (const std::regex_error& e) { std::cerr << std::format("Regex error: {}\n", e.what()); return text; // 失败时返回原字符串 } } int main() { std::string message = "You are an idiot, IDIOT!"; std::string clean = censor_text(message, "idiot"); std::cout << std::format("Censored: {}\n", clean); }

5.4 遍历所有匹配(提取所有数字)

#include <iostream> #include <regex> #include <string> #include <vector> #include <format> std::vector<int> extract_all_numbers(const std::string& input) { static const std::regex number_regex(R"(\d+)", std::regex_constants::optimize); std::vector<int> numbers; // regex_iterator 遍历所有匹配 auto begin = std::sregex_iterator(input.begin(), input.end(), number_regex); auto end = std::sregex_iterator(); for (auto it = begin; it != end; ++it) { numbers.push_back(std::stoi(it->str())); } return numbers; } int main() { std::string data = "Price: 42, Discount: 15, Items: 3."; auto nums = extract_all_numbers(data); for (size_t i = 0; i < nums.size(); ++i) { std::cout << std::format("Number {}: {}\n", i + 1, nums[i]); } }

6. 注意事项与局限性

6.1 性能特性

  • std::regex在绝大多数标准库实现中性能一般,它使用回溯算法,某些模式可能导致指数级时间(灾难性回溯)。
  • 对于高性能需求场景,可考虑使用boost::regex或 RE2 等外部库,它们提供更优的算法和更稳定的表现。
  • 仍然建议:编译一次,多次使用;使用optimize标志。

6.2 Unicode 支持

C++20 标准正则库对 Unicode 的支持有限,\w等并不匹配所有 Unicode 字母。若需完整 Unicode 属性匹配,请借助第三方库(如 ICU 或 Boost.Regex)。

6.3 后发断言限制

std::regex要求后发断言(?<=...)中的模式必须是固定宽度(即不能包含*+{n,m}等不定长度量词)。这是 ECMAScript 标准的行为。

6.4 线程安全

  • std::regex对象本身是线程安全的(可以多个线程同时使用同一个const对象进行匹配)。
  • std::smatch等结果对象则不是线程安全的,每次匹配应使用局部变量。

7. 总结

正则表达式是处理文本的瑞士军刀,C++20 通过<regex>库完整支持了正则的匹配、搜索、替换和遍历。写出安全优雅的 C++ 正则代码应遵循以下要点:

  1. 使用原始字符串字面量R"(...)"定义模式,告别转义困扰。
  2. 静态常量存储编译后的正则,加上optimize标志提升效率。
  3. 始终准备捕获std::regex_error,确保程序健壮。
  4. 利用std::regex_matchstd::regex_searchstd::regex_replace处理常用场景。
  5. 结合现代 C++20 特性(如std::formatstd::optional、Range-based 循环)使代码更清晰。
  6. 理解标准库的局限性,必要时替换为更专业的正则引擎。
http://www.jsqmd.com/news/1092372/

相关文章:

  • 这些宝藏级在线工具,让你的效率原地起飞
  • HarmonyOS技术精讲-应用间跳转:一键调用系统能力(系统应用跳转)
  • 鹤壁企业采购白酒,怎么选得知道
  • Unity Mod Manager:轻松管理Unity游戏模组的终极指南
  • 专业级暗黑3战斗自动化工具深度解析:5大核心功能实战指南
  • 大麦网Python自动化抢票系统:技术架构与实战应用解析
  • MSP432硬件调试实战:适配器与插座板配置详解
  • 戴森球计划3000+工厂蓝图终极指南:从新手到专家的完整解决方案
  • TrollInstallerX突破性指南:iOS 14-16.6.1设备快速部署TrollStore的实战手册
  • HarmonyOS技术精讲-应用间跳转:跨应用传递数据与返回结果
  • Java高并发编程核心原理:程序员进阶必会!
  • Docker--Docker引擎与镜像相关命令
  • 完整学习LLM(五):Embedding是什么,为什么文本能变成向量
  • 【infra之路】10-PagedAttention 与 KV Cache 管理
  • 配置中心:为什么需要它?如何选型?
  • 开源社区新动态,Github 上值得关注的 ROCm 项目推荐
  • 有限域原根求解:Python实现与数学原理
  • 3分钟掌握AI智能分层:Layerdivider让单图变多层的终极指南
  • 3分钟掌握WorkshopDL:无需Steam轻松下载创意工坊模组
  • 终极传送技巧:掌握GTA5线上小助手的多人载具传送与坐标微调
  • MySQL 8.0——Replication
  • FireFox渗透测试环境全攻略:Hackbar与FoxyProxy核心插件实战解析
  • Spring Boot Starter 自定义封装技巧
  • 解决 Python 依赖冲突,ROCm 环境下安装深度学习库的技巧
  • 依赖引入与适用场景
  • 5分钟快速上手:diff-pdf - 免费开源的PDF差异检测神器
  • 软件客户细分化的群体划分与差异策略
  • 为什么你的ChatGPT回答总是模糊?揭秘LLM理解机制与3层结构化提问法,3分钟即用
  • AMD Ryzen处理器性能调优终极指南:用开源工具SMUDebugTool掌控你的硬件
  • 西安交大最新综述!一文带你读懂大模型智能体及其组网与安全