正则表达式(regex)是基于模式提取和分割字符串的强大工具。C++ 的 <regex> 库提供了健壮的、类似 Perl 的语法,可跨 Linux、Windows 和嵌入式环境使用。本指南聚焦于实际的提取和分割任务——从文本中提取电话号码、分割 CSV 行、解析键值对——使用 std::regex、std::regex_search、std::sregex_token_iterator 和 std::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
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 语法。
