乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

std::regex 正则表达式

定义与作用

<regex> 是 C++11 引入的正则表达式标准库,提供 Perl/ECMAScript 风格的模式匹配、搜索和替换能力,核心由三大类组成:

#include <regex>

std::regex pattern(R"(\d{3}-\d{4}-\d{4})");        // 正则模式
std::smatch match;                                   // 匹配结果
bool found = std::regex_search("电话: 138-0001-3800", match, pattern);
核心类作用
std::regex编译并存储正则表达式模式
std::smatch / std::cmatch存储匹配结果(含子表达式捕获组)
std::regex_match完全匹配:整个字符串必须匹配模式
std::regex_search部分匹配:搜索字符串中第一个匹配
std::regex_replace替换匹配内容

核心原理

三大操作对比

匹配结果结构

语法选项

选项说明
std::regex::ECMAScript默认语法,JavaScript 风格
std::regex::icase忽略大小写
std::regex::nosubs不存储子表达式匹配(性能优化)
std::regex::optimize提示编译器优化匹配速度
std::regex::extendedPOSIX 扩展正则
std::regex::grepPOSIX grep 风格

多个选项通过 | 组合:std::regex::ECMAScript | std::regex::icase。

完整示例

示例一:飞翔科技用户信息验证

场景说明:高英做用户运营,需要批量验证注册信息——邮箱格式、手机号、中文姓名。

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

struct UserInfo {
    std::string name;
    std::string email;
    std::string phone;
};

int main() {
    std::cout << "===== 飞翔科技用户注册验证 =====" << std::endl;

    // 编译正则表达式(只编译一次,复用)
    std::regex emailPattern(R"(^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$)");
    std::regex phonePattern(R"(^1[3-9]\d{9}$)");
    std::regex chineseNamePattern(R"(^[\u4e00-\u9fff]{2,4}$)");

    std::vector<UserInfo> users = {
        {"大翔",  "daxiang@feixiang.net",   "13800001001"},
        {"孔蓝",  "konglan@feixiang.net",    "13912345678"},
        {"黄俪",  "huangli.feixiang.net",    "13888888888"},  // 邮箱缺少 @
        {"赵鸣",  "zhaoming@feixiang",       "13700009999"},  // 邮箱缺少顶级域
        {"小崔",  "xiaocui@feixiang.net",    "12345678901"},  // 手机号首位不是1
        {"孙鹤",  "sunhe@feixiang.net",      "13800138000"},
    };

    int passed = 0;
    for (const auto& user : users) {
        std::cout << "\n--- " << user.name << " ---" << std::endl;

        bool nameOk = std::regex_match(user.name, chineseNamePattern);
        bool emailOk = std::regex_match(user.email, emailPattern);
        bool phoneOk = std::regex_match(user.phone, phonePattern);

        std::cout << "  姓名: " << (nameOk  ? "✓" : "✗") << std::endl;
        std::cout << "  邮箱: " << (emailOk ? "✓" : "✗") << " " << user.email << std::endl;
        std::cout << "  手机: " << (phoneOk ? "✓" : "✗") << " " << user.phone << std::endl;

        if (nameOk && emailOk && phoneOk) ++passed;
    }

    std::cout << "\n总计: " << passed << "/" << users.size() << " 通过验证" << std::endl;

    return 0;
}

预期输出:

===== 飞翔科技用户注册验证 =====

--- 大翔 ---
  姓名: ✓
  邮箱: ✓
  手机: ✓

--- 孔蓝 ---
  姓名: ✓
  邮箱: ✓
  手机: ✓

--- 黄俪 ---
  姓名: ✓
  邮箱: ✗ huangli.feixiang.net
  手机: ✓

--- 赵鸣 ---
  姓名: ✓
  邮箱: ✗ zhaoming@feixiang
  手机: ✓

--- 小崔 ---
  姓名: ✓
  邮箱: ✓
  手机: ✗ 12345678901

--- 孙鹤 ---
  姓名: ✓
  邮箱: ✓
  手机: ✓

总计: 4/6 通过验证

逐段分析:

  • regex_match 要求整个字符串完全匹配模式——适合输入验证场景(如邮箱、手机号)
  • 正则对象在循环外编译一次然后复用——避免重复编译正则以提升性能
  • R"(...)" 原始字符串字面量避免转义正则中的反斜杠——\d 不会变成 \\d
  • ^ 和 $ 分别锚定字符串开头和结尾,防止部分匹配(如 "abc123@test.com!!!" 通过验证)
  • [\u4e00-\u9fff] 匹配中文字符范围

示例二:日志分析与信息提取

场景说明:李眉分析飞翔科技服务器日志,提取 IP 地址、时间戳和错误码。

#include <iostream>
#include <regex>
#include <string>
#include <vector>
#include <iomanip>

struct LogEntry {
    std::string time;
    std::string ip;
    std::string level;
    int errorCode;
    std::string message;
};

int main() {
    std::cout << "===== 飞翔科技日志分析 =====" << std::endl;

    // 模拟 Nginx 风格日志
    std::vector<std::string> logLines = {
        R"(2024-06-14 10:23:45 192.168.1.100 INFO 用户大翔登录成功)",
        R"(2024-06-14 10:24:12 10.0.0.55 ERROR 502 数据库连接超时)",
        R"(2024-06-14 10:25:01 172.16.0.88 WARN 磁盘使用率达到80%)",
        R"(2024-06-14 10:26:33 192.168.1.200 ERROR 404 页面未找到 /api/v2/old)",
        R"(2024-06-14 10:27:00 10.0.0.55 INFO 服务恢复正常)",
    };

    // 日志正则模式(含命名捕获组的效果用注释标出)
    std::regex logPattern(
        R"(^(\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2})\s)"   // 组1: 时间
        R"((\d{1,3}(?:\.\d{1,3}){3})\s)"                  // 组2: IP
        R"((INFO|WARN|ERROR)\s)"                           // 组3: 级别
        R"((?:(\d{3})\s)?)"                                // 组4: 错误码(可选)
        R"((.*)$)"                                          // 组5: 消息
    );

    std::vector<LogEntry> entries;
    int errorCount = 0, warnCount = 0, infoCount = 0;

    for (const auto& line : logLines) {
        std::smatch match;
        if (std::regex_match(line, match, logPattern)) {
            LogEntry entry;
            entry.time      = match[1].str();
            entry.ip        = match[2].str();
            entry.level     = match[3].str();
            entry.errorCode = match[4].matched ? std::stoi(match[4].str()) : 0;
            entry.message   = match[5].str();
            entries.push_back(entry);

            if (entry.level == "ERROR") ++errorCount;
            else if (entry.level == "WARN") ++warnCount;
            else ++infoCount;
        }
    }

    // 输出解析结果
    std::cout << std::left
              << std::setw(22) << "时间"
              << std::setw(18) << "IP"
              << std::setw(8)  << "级别"
              << std::setw(8)  << "错误码"
              << "消息" << std::endl;
    std::cout << std::string(72, '-') << std::endl;

    for (const auto& e : entries) {
        std::cout << std::left
                  << std::setw(22) << e.time
                  << std::setw(18) << e.ip
                  << std::setw(8)  << e.level;
        if (e.errorCode > 0)
            std::cout << std::setw(8) << e.errorCode;
        else
            std::cout << std::setw(8) << "-";
        std::cout << e.message << std::endl;
    }

    std::cout << "\n统计: INFO=" << infoCount
              << " WARN=" << warnCount
              << " ERROR=" << errorCount << std::endl;

    // ---- regex_search 演示 ----
    std::cout << "\n[regex_search: 提取所有 IP 地址]" << std::endl;
    std::regex ipPattern(R"(\b(\d{1,3}\.){3}\d{1,3}\b)");
    for (const auto& line : logLines) {
        std::smatch match;
        if (std::regex_search(line, match, ipPattern)) {
            std::cout << "  从 '" << line.substr(0, 30) << "...' 中找到: "
                      << match[0] << std::endl;
        }
    }

    // ---- regex_replace 演示 ----
    std::cout << "\n[regex_replace: IP 脱敏]" << std::endl;
    std::regex ipReplacePattern(R"((\d{1,3})\.\d{1,3}\.\d{1,3}\.\d{1,3})");
    for (const auto& line : logLines) {
        std::string masked = std::regex_replace(line, ipReplacePattern, "$1.***.***.***");
        std::cout << "  " << masked.substr(0, 60) << std::endl;
    }

    return 0;
}

预期输出:

===== 飞翔科技日志分析 =====

时间                   IP               级别    错误码   消息
------------------------------------------------------------------------
2024-06-14 10:23:45   192.168.1.100    INFO    -       用户大翔登录成功
2024-06-14 10:24:12   10.0.0.55        ERROR   502     数据库连接超时
2024-06-14 10:25:01   172.16.0.88      WARN    -       磁盘使用率达到80%
2024-06-14 10:26:33   192.168.1.200    ERROR   404     页面未找到 /api/v2/old
2024-06-14 10:27:00   10.0.0.55        INFO    -       服务恢复正常

统计: INFO=2 WARN=1 ERROR=2

[regex_search: 提取所有 IP 地址]
  从 '2024-06-14 10:23:45 192.168.1.1...' 中找到: 192.168.1.100
  从 '2024-06-14 10:24:12 10.0.0.55 ...' 中找到: 10.0.0.55
  ...

[regex_replace: IP 脱敏]
  2024-06-14 10:23:45 192.***.***.*** 用户大翔登录成功
  2024-06-14 10:24:12 10.***.***.*** 502 数据库连接超时
  ...

逐段分析:

  • regex_match 验证整个字符串结构并用捕获组提取各字段——组 1=时间、组 2=IP、组 3=级别、组 4 (可选)=错误码、组 5=消息
  • match[i].matched 检查第 i 个捕获组是否实际参与了匹配——错误码是可选的
  • (?:...) 是非捕获组——用于分组但不保存子匹配,比捕获组更高效
  • regex_search 找到字符串中第一个匹配的子串——适合"有没有"的探测性搜索
  • regex_replace 的替换字符串中 $1 引用第一个捕获组的内容——实现 IP 部分脱敏

示例三:regex 性能最佳实践

#include <iostream>
#include <regex>
#include <chrono>
#include <vector>
#include <string>

int main() {
    std::cout << "===== regex 性能对比 =====" << std::endl;
    const int N = 10'000;

    std::vector<std::string> emails(N, "daxiang@feixiang.net");

    // ---- 错误做法:每次循环都编译 regex ----
    {
        auto start = std::chrono::steady_clock::now();
        int count = 0;
        for (const auto& email : emails) {
            std::regex re(R"(^[\w.+-]+@[\w.-]+\.[a-zA-Z]{2,}$)");
            if (std::regex_match(email, re)) ++count;
        }
        auto end = std::chrono::steady_clock::now();
        auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
        std::cout << "每次编译 regex:   " << ms << " ms (匹配 " << count << " 个)"
                  << std::endl;
    }

    // ---- 正确做法:编译一次复用 ----
    {
        auto start = std::chrono::steady_clock::now();
        std::regex re(R"(^[\w.+-]+@[\w.-]+\.[a-zA-Z]{2,}$)");
        int count = 0;
        for (const auto& email : emails) {
            if (std::regex_match(email, re)) ++count;
        }
        auto end = std::chrono::steady_clock::now();
        auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
        std::cout << "复用 regex 对象:   " << ms << " ms (匹配 " << count << " 个)"
                  << std::endl;
    }

    // ---- 优化:使用 nosubs 不存储捕获组 ----
    {
        auto start = std::chrono::steady_clock::now();
        std::regex re(R"(^[\w.+-]+@[\w.-]+\.[a-zA-Z]{2,}$)",
                      std::regex::ECMAScript | std::regex::nosubs);
        int count = 0;
        for (const auto& email : emails) {
            if (std::regex_match(email, re)) ++count;
        }
        auto end = std::chrono::steady_clock::now();
        auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
        std::cout << "复用 + nosubs:      " << ms << " ms (匹配 " << count << " 个)"
                  << std::endl;
    }

    std::cout << "\n结论: regex 编译开销大,应尽量复用对象" << std::endl;

    return 0;
}

预期输出:

===== regex 性能对比 =====
每次编译 regex:   2847 ms (匹配 10000 个)
复用 regex 对象:   32 ms (匹配 10000 个)
复用 + nosubs:     22 ms (匹配 10000 个)

结论: regex 编译开销大,应尽量复用对象

逐段分析:

  • regex 编译是重操作——在循环内重复创建 std::regex 比复用慢约 100 倍
  • std::regex::nosubs 不存储捕获组的结果——如果只需要 true/false,关闭子表达式保存可额外提速
  • 编译一次然后复用是所有 regex 场景的基础优化

易错场景与面试考点

易错场景

场景错误表现正确做法
循环内重复编译 regex性能灾难(比复用慢 100 倍)在循环外编译,循环内复用
regex_match vs regex_search 混淆期望部分匹配却用 match验证完整字符串用 match,搜索用 search
正则中反斜杠未转义\d 被当作转义序列使用原始字符串字面量 R"(\d+)"
未处理匹配失败时访问 match[0]访问空 smatch 未定义行为先检查 regex_match/search 返回值
忽略 std::regex_error 异常非法正则表达式静默失败用 try-catch 包裹 regex 构造

常见面试问题

  1. regex_match 和 regex_search 的区别?——regex_match 要求整个字符串完全匹配模式(隐式锚定 ^...$),regex_search 搜索字符串中第一个匹配的子串。验证输入格式用 match,搜索关键词用 search。

  2. std::regex 的性能注意事项?——regex 编译开销大,应尽量复用对象。不需要捕获组时使用 nosubs 选项。GCC 4.9 之前的 <regex> 实现不完整,建议 GCC 5+ 或 Clang。

  3. 为什么推荐使用 R"(...)" 写正则?——正则表达式中反斜杠极多(\d、\w、\s 等),不用原始字符串需要写成 \\d、\\w,极易出错。

  4. 捕获组和非捕获组的区别?——捕获组 (...) 将匹配内容保存到 smatch[i],非捕获组 (?:...) 仅分组不保存。不需要提取子匹配时用非捕获组以提升性能。

小结

  • 三大核心操作:regex_match(完全匹配)、regex_search(搜索)、regex_replace(替换)
  • 默认 ECMAScript 语法,可通过 | 组合选项(如 icase | nosubs)
  • regex 编译开销大——循环外编译一次,循环内复用
  • 捕获组通过 smatch[i] 提取子匹配,非捕获组 (?:...) 用于纯分组
  • 使用 R"(...)" 原始字符串避免正则转义地狱
上一页
字符串流