正在加载,请稍候…

正则表达式提取与分割字符串:C++ 实战指南

学习使用 C++ 正则表达式提取和分割字符串,包含真实代码示例。涵盖 regex_search、sregex_token_iterator、常见陷阱和性能建议。

正则表达式(regex)是基于模式提取和分割字符串的强大工具。C++ 的 <regex> 库提供了健壮的、类似 Perl 的语法,可跨 Linux、Windows 和嵌入式环境使用。本指南聚焦于实际的提取和分割任务——从文本中提取电话号码、分割 CSV 行、解析键值对——使用 std::regexstd::regex_searchstd::sregex_token_iteratorstd::regex_replace。我们将涵盖常见陷阱、性能注意事项以及一个完整的示例。

开发者正在笔记本电脑上编写正则表达式,代码编辑器打开

为什么使用正则表达式进行提取和分割?

使用循环和条件语句手动解析字符串容易出错且难以维护。正则表达式提供了:

  • 声明式模式:描述要匹配的内容,而不是方式
  • 可移植性:相同的正则表达式可在 C++、Python、Java 等语言中工作。
  • 灵活性:处理可选部分、不同空白符和嵌套结构。

常见用例:

  • 从文档中提取所有电子邮件地址。
  • 将日志行分割为时间戳、级别和消息。
  • 验证和解析用户输入(电话号码、ID)。

用于提取的核心 C++ 正则表达式类

C++ 正则表达式位于 <regex> 中,核心有三个类:

用途
std::regex 编译后的正则表达式对象(类似 Java 的 Pattern
std::smatch 针对 std::string 的匹配结果(子匹配)
std::sregex_token_iterator 迭代所有匹配项或按非匹配项分割

使用 std::regex_search 进行提取

regex_search 查找第一个匹配项并填充 std::smatch 对象。使用捕获组 () 提取子部分。

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

int main() {
    std::string text = "Contact: (123) 456-7890 or 987-654-3210";
    std::regex phone_regex(R"(\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4})");
    std::smatch match;

    if (std::regex_search(text, match, phone_regex)) {
        std::cout << "Found: " << match.str() << std::endl;  // (123) 456-7890
    }
    return 0;
}

注意:原始字符串字面量 R"(...)" 避免了转义反斜杠。在 C++11 及更高版本中,正则表达式优先使用原始字符串。

使用 sregex_token_iterator 提取所有匹配项

要提取所有出现,使用 std::sregex_token_iterator,索引 0 表示整个匹配,正索引表示特定捕获组。

std::string text = "Emails: alice@example.com, bob@test.org, charlie@domain.co.uk";
std::regex email_regex(R"([\w.-]+@[\w.-]+\.\w+)");

std::sregex_token_iterator it(text.begin(), text.end(), email_regex, 0);
std::sregex_token_iterator end;

while (it != end) {
    std::cout << *it++ << std::endl;
}
// 输出:
// alice@example.com
// bob@test.org
// charlie@domain.co.uk

使用 sregex_token_iterator 分割字符串(负索引)

传递 -1 作为第四个参数,按正则表达式分割(类似 Python 的 re.split)。

std::string csv = "apple, banana; cherry | date";
std::regex delimiter(R"([,;|]\s*)");

std::sregex_token_iterator it(csv.begin(), csv.end(), delimiter, -1);
std::sregex_token_iterator end;

while (it != end) {
    std::cout << "[" << *it++ << "]" << std::endl;
}
// 输出:
// [apple]
// [banana]
// [cherry]
// [date]

完整示例:解析电话簿条目

假设我们有一行电话簿:

John Doe: (555) 123-4567, jdoe@example.com; Jane Smith: 555-987-6543, jane@work.net

目标:提取每个人的姓名、电话和电子邮件。

第 1 步:定义模式

我们将使用编号的捕获组(C++ 不支持命名组):

(\w+\s+\w+):\s+(\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}),\s+([\w.-]+@[\w.-]+\.\w+)
  • 组 1:姓名(两个单词)
  • 组 2:电话
  • 组 3:电子邮件

第 2 步:迭代所有匹配项

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

int main() {
    std::string text = "John Doe: (555) 123-4567, jdoe@example.com; Jane Smith: 555-987-6543, jane@work.net";
    std::regex entry_regex(R"((\w+\s+\w+):\s+(\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}),\s+([\w.-]+@[\w.-]+\.\w+))");

    std::sregex_iterator it(text.begin(), text.end(), entry_regex);
    std::sregex_iterator end;

    for (; it != end; ++it) {
        std::smatch match = *it;
        std::cout << "Name: "  << match[1] << std::endl;
        std::cout << "Phone: " << match[2] << std::endl;
        std::cout << "Email: " << match[3] << std::endl;
        std::cout << "---" << std::endl;
    }
    return 0;
}

输出: ``` Name: John Doe Phone: (555) 123-4567 Email: jdoe@example.com

Name: Jane Smith Phone: 555-987-6543 Email: jane@work.net


## 常见陷阱

- **贪婪 vs. 懒惰量词**:`.*` 尽可能多地匹配;使用 `.*?` 进行最小匹配。
- **普通字符串中的转义**:始终使用原始字符串字面量 `R"(...)"` 以避免双反斜杠。
- **重叠匹配**:`regex_search` 和 `sregex_token_iterator` 默认不查找重叠匹配。对于重叠,使用前瞻或手动定位。
- **性能**:只编译一次正则表达式(静态或重用对象)。避免在循环中重新编译。
- **Unicode**:C++ `std::regex` 配合 `std::string` 处理的是 UTF-8 字节,而不是码点。要获得正确的 Unicode 匹配,请使用 `std::wregex` 配合 `std::wstring` 或 ICU 等库。

## 性能注意事项

- **预编译正则表达式**:当重复使用时,将 `std::regex` 存储为静态或全局变量。
- **使用 `std::regex::optimize` 标志**:`std::regex(pattern, std::regex::optimize)` 可能加快匹配速度,但编译速度较慢。
- **避免在热循环中使用 `std::regex`**:如果必须,在循环外提取一次。
- **优先使用 `std::string_view`(C++17)**:使用 `std::regex_iterator` 配合 `std::string_view` 以避免拷贝。

## 常见问题解答

### 如何只提取第一个匹配项?

使用 `std::regex_search`(如上所示)。它返回 `bool` 并填充一个 `std::smatch`。

### 能否使用正则表达式分割字符串并保留分隔符?

可以。使用索引 `-1` 的 `sregex_token_iterator` 获取分隔符*之间*的部分。要包含分隔符,使用索引 `0` 迭代并与分割部分手动组合,或使用捕获组和索引 `-1` 配合交替技巧。

### 为什么我的正则表达式匹配不到任何内容?

检查:
- 转义不正确(使用原始字符串字面量)。
- 缺少锚点(`^`、`

  
    
    
    
    正则表达式提取与分割字符串:C++ 实战指南 | MyUtl
    
    
    
    
    

    
    
    
    
    
    

    
    
    
    
    

    
    
    
    
    

    

    
    
    

    
    
    
    
    
    
    
    
      
    
  
  
    
    

    
    
正在加载,请稍候…
),如果你想要全字符串匹配。 - 贪婪量词消耗过多。 - 使用我们的 [regex tester](/regex-tester) 进行调试。 ### `std::regex` 是线程安全的吗? 是的,`std::regex` 对象在构造后是线程安全的。但 `std::smatch` 和迭代器不是——每个线程需要自己的匹配对象。 ### 如何在 C++ 正则表达式中处理 Unicode? `std::regex` 配合 `std::string` 将 UTF-8 视为字节。字符类 `\w` 可能不匹配 Unicode 字母。使用 `std::wregex` 配合 `std::wstring` 和 `\w` 及 `std::regex::ECMAScript` 标志,或考虑 Boost.Regex 或 ICU 以获得完整的 Unicode 支持。 ## 亲自尝试 在我们的 [regex tester](/regex-tester) 中试验这些模式。粘贴你的文本和模式,实时查看高亮匹配结果。它支持与 C++ 相同的 ECMAScript 语法。 ![正则表达式测试工具界面,显示高亮匹配的文本](https://images.pexels.com/photos/248515/pexels-photo-248515.png?auto=compress&cs=tinysrgb&h=650&w=940)