C++ 正则表达式指南:模式匹配的强大工具
正则表达式(Regular Expression,简称 RegEx 或 RegExp)是一种强大的文本处理工具,它使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。在文本搜索、替换、验证、解析等领域,正则表达式扮演着核心角色。从简单的查找特定单词,到复杂的解析日志文件或验证数据格式,正则表达式都能以简洁高效的方式完成任务。
C++ 语言自 C++11 标准起,通过 <regex>
头文件提供了内建的正则表达式支持。这个库提供了一套类和函数,允许开发者在 C++ 代码中直接使用正则表达式进行字符串操作,而无需依赖第三方库。本指南将带您深入了解 C++ 标准库中的正则表达式功能,从基础概念到高级用法,帮助您掌握这一强大的工具。
1. 正则表达式基础:模式的构建
在深入 C++ 的 <regex>
库之前,理解正则表达式本身的语法是至关重要的。一个正则表达式模式由普通字符和特殊字符(元字符)组成。普通字符匹配它们自身,而元字符具有特殊的含义,用于描述匹配规则。
以下是一些最常用的正则表达式元字符和概念:
- 字面字符 (Literal Characters): 大多数字符,如
a
,1
,=
, 匹配它们自身。 - 点号 (
.
): 匹配除换行符外的任意单个字符。 - 脱字符 (
^
): 匹配行的开头。在字符集内部 ([^...]
) 表示取反。 - 美元符号 (
$
):匹配行的结尾。 - 星号 (
*
): 匹配前一个元素零次或多次。 - 加号 (
+
): 匹配前一个元素一次或多次。 - 问号 (
?
): 匹配前一个元素零次或一次。 - 花括号 (
{n}
): 匹配前一个元素恰好n
次。 - 花括号 (
{n,}
): 匹配前一个元素至少n
次。 - 花括号 (
{n,m}
): 匹配前一个元素至少n
次,但不超过m
次。 - 圆括号 (
()
): 用于分组,也可以创建捕获组,捕获匹配的子字符串。 - 方括号 (
[]
):定义一个字符集。匹配方括号内的任意一个字符。[abc]
匹配 ‘a’, ‘b’, 或 ‘c’。[a-z]
匹配任意小写字母。[0-9]
匹配任意数字。[a-zA-Z0-9]
匹配任意字母或数字。
- 脱字符在方括号内 (
[^...]
): 匹配不在方括号内的任意字符。[^0-9]
匹配任意非数字字符。
- 管道符 (
|
): 逻辑 OR 操作,匹配管道符左边或右边的模式。cat|dog
匹配 “cat” 或 “dog”。
- 反斜杠 (
\
): 转义字符。用于匹配元字符本身或引入特殊序列。\.
匹配点号本身。\*
匹配星号本身。
- 预定义字符类 (使用反斜杠):
\d
: 匹配任意数字 (等价于[0-9]
)。\D
: 匹配任意非数字字符 (等价于[^0-9]
)。\w
: 匹配任意单词字符 (字母、数字、下划线,等价于[a-zA-Z0-9_]
)。\W
: 匹配任意非单词字符 (等价于[^a-zA-Z0-9_]
)。\s
: 匹配任意空白字符 (空格、制表符、换行符等)。\S
: 匹配任意非空白字符。\b
: 匹配单词边界。\B
: 匹配非单词边界。
量词的贪婪性 (Greedy Quantifiers): 默认情况下,量词 (*
, +
, ?
, {n,}
, {n,m}
) 是“贪婪的”,它们会尽可能多地匹配字符。例如,模式 <.*>
应用到字符串 <b>bold</b>
会匹配整个字符串 <b>bold</b>
,而不是只匹配 <b>
或 </b>
。
量词的非贪婪性 (Non-Greedy/Lazy Quantifiers): 通过在量词后加上 ?
可以使其变为非贪婪的,它们会尽可能少地匹配字符。例如,模式 <.*?>
应用到字符串 <b>bold</b>
会首先匹配 <b>
,然后匹配 </b>
。
理解这些基本构建块是编写有效正则表达式的第一步。现在,我们将看看如何在 C++ 中使用这些模式。
2. C++ <regex>
库概述
C++ 的 <regex>
库主要提供了以下几个核心组件:
std::regex
: 表示一个编译后的正则表达式模式。std::smatch
,std::cmatch
,std::wsmatch
,std::wcmatch
: 用于存储匹配结果的类型。s
前缀表示操作std::string
,c
前缀表示操作 C 风格字符串 (char*
),w
前缀表示操作宽字符串 (std::wstring
,wchar_t*
)。match
表示存储的是整个匹配结果和捕获组。std::regex_match()
: 尝试将整个输入序列与正则表达式匹配。std::regex_search()
: 尝试在输入序列中查找与正则表达式匹配的子序列。std::regex_replace()
: 使用正则表达式进行查找和替换。std::regex_iterator
,std::regex_token_iterator
: 用于迭代查找所有匹配项或基于匹配项/非匹配项进行分词。
这些组件共同构成了 C++ 正则表达式处理框架。
3. 使用 std::regex
:创建正则表达式对象
在使用正则表达式进行匹配、搜索或替换之前,需要将模式字符串编译成一个 std::regex
对象。这个编译过程会解析模式字符串,并构建一个内部表示,以便后续的高效匹配。
“`cpp
include
include
include
int main() {
// 定义一个正则表达式模式字符串
// 注意:在 C++ 字符串字面量中,反斜杠 ‘\’ 需要双写来表示一个反斜杠
// 所以正则表达式中的 \d 需要写成 \d
std::string pattern_str = “\d+”; // 匹配一个或多个数字
try {
// 创建 std::regex 对象
std::regex reg(pattern_str);
std::cout << "Regex object created successfully for pattern: " << pattern_str << std::endl;
// 也可以使用原始字符串字面量 R"(...)" 来避免双写反斜杠
std::regex reg_raw(R"(\d+)");
std::cout << "Regex object created successfully using raw string literal for pattern: " << R"(\d+)" << std::endl;
} catch (const std::regex_error& e) {
// 处理正则表达式语法错误
std::cerr << "Regex error caught: " << e.what() << std::endl;
std::cerr << "Error code: " << e.code() << std::endl;
}
return 0;
}
“`
注意:
- 反斜杠问题: 在 C++ 的普通字符串字面量中,反斜杠
\
是转义字符。如果您的正则表达式模式中包含反斜杠(例如\d
,\s
,\b
),您需要在字符串字面量中将其双写,写成\\d
,\\s
,\\b
。 - 原始字符串字面量 (Raw String Literals): C++11 引入了原始字符串字面量
R"(...)"
,在()
内部的任何字符(包括反斜杠)都被视为普通字符,不会进行转义。这对于编写包含大量反斜杠的正则表达式非常方便,可以避免双写。建议在编写正则表达式模式时优先使用原始字符串字面量。 - 错误处理: 正则表达式模式字符串可能包含语法错误。创建
std::regex
对象时可能会抛出std::regex_error
异常。最佳实践是使用try-catch
块来捕获并处理这些错误,以防止程序崩溃。
4. 匹配整个字符串:std::regex_match()
std::regex_match()
函数尝试将整个输入序列(字符串)与正则表达式模式进行匹配。只有当整个字符串都符合模式时,regex_match()
才返回 true
。如果只想查找字符串中是否存在匹配的子串,应该使用 std::regex_search()
。
std::regex_match()
的基本用法如下:
“`cpp
include
include
include
int main() {
std::string text1 = “12345”;
std::string text2 = “12345abc”;
std::string text3 = “abc12345”;
std::string text4 = “abc”;
// 匹配一个或多个数字
std::regex reg(R"(\d+)");
// 检查整个字符串是否匹配
if (std::regex_match(text1, reg)) {
std::cout << "\"" << text1 << "\" matches the pattern." << std::endl; // 输出
} else {
std::cout << "\"" << text1 << "\" does not match the pattern." << std::endl;
}
if (std::regex_match(text2, reg)) {
std::cout << "\"" << text2 << "\" matches the pattern." << std::endl;
} else {
std::cout << "\"" << text2 << "\" does not match the pattern." << std::endl; // 输出
}
if (std::regex_match(text3, reg)) {
std::cout << "\"" << text3 << "\" matches the pattern." << std::endl;
} else {
std::cout << "\"" << text3 << "\" does not match the pattern." << std::endl; // 输出
}
if (std::regex_match(text4, reg)) {
std::cout << "\"" << text4 << "\" matches the pattern." << std::endl;
} else {
std::cout << "\"" << text4 << "\" does not match the pattern." << std::endl; // 输出
}
// 使用更精确的模式:匹配严格的5位数字
std::regex reg5(R"(\d{5})");
if (std::regex_match(text1, reg5)) {
std::cout << "\"" << text1 << "\" matches the pattern (exactly 5 digits)." << std::endl; // 输出
} else {
std::cout << "\"" << text1 << "\" does not match the pattern (exactly 5 digits)." << std::endl;
}
return 0;
}
“`
std::regex_match()
的另一个常用重载版本是接收一个匹配结果对象作为参数。如果匹配成功,匹配结果会被存储在这个对象中,包括整个匹配的字符串和所有捕获组的内容。
“`cpp
include
include
include
int main() {
std::string date_str = “2023-10-26″;
// 匹配日期格式 YYYY-MM-DD,并捕获年、月、日
std::regex date_reg(R”((\d{4})-(\d{2})-(\d{2}))”);
std::smatch date_match; // 使用 smatch 来存储 string 的匹配结果
if (std::regex_match(date_str, date_match, date_reg)) {
std::cout << "Date string \"" << date_str << "\" fully matches the pattern." << std::endl;
std::cout << "Number of capture groups: " << date_match.size() << std::endl; // 包括整个匹配项 (index 0)
// 访问匹配结果
std::cout << "Full match: " << date_match[0].str() << std::endl; // date_match[0] 或 date_match.str(0)
std::cout << "Year: " << date_match[1].str() << std::endl; // date_match[1] 或 date_match.str(1)
std::cout << "Month: " << date_match[2].str() << std::endl; // date_match[2] 或 date_match.str(2)
std::cout << "Day: " << date_match[3].str() << std::endl; // date_match[3] 或 date_match.str(3)
// 也可以获取匹配子串的位置和长度
std::cout << "Month starts at position: " << date_match.position(2) << std::endl;
std::cout << "Month length: " << date_match.length(2) << std::endl;
} else {
std::cout << "Date string \"" << date_str << "\" does not fully match the pattern." << std::endl;
}
std::string invalid_date_str = "2023/10/26";
if (std::regex_match(invalid_date_str, date_match, date_reg)) {
std::cout << "Date string \"" << invalid_date_str << "\" fully matches the pattern." << std::endl;
} else {
std::cout << "Date string \"" << invalid_date_str << "\" does not fully match the pattern." << std::endl; // 输出
}
return 0;
}
“`
在这个例子中,std::smatch
对象 date_match
存储了匹配结果。date_match[0]
包含整个匹配到的字符串(”2023-10-26″),而 date_match[1]
, date_match[2]
, date_match[3]
分别对应模式中第一个、第二个、第三个捕获组的内容(”2023″, “10”, “26”)。
5. 查找子字符串:std::regex_search()
与 std::regex_match()
不同,std::regex_search()
在输入序列中查找 是否存在 匹配正则表达式的子序列。它会返回 第一个 找到的匹配项。如果找到匹配项,返回 true
,否则返回 false
。
std::regex_search()
的基本用法通常配合匹配结果对象来获取找到的子串及其捕获组信息:
“`cpp
include
include
include
int main() {
std::string text = “The quick brown fox jumps over the lazy dog. The year is 2023.”;
// 查找任意数字序列
std::regex number_reg(R”(\d+)”);
std::smatch number_match;
if (std::regex_search(text, number_match, number_reg)) {
std::cout << "Found a number in the text." << std::endl;
std::cout << "Matched substring: " << number_match[0].str() << std::endl; // 输出 "2023"
std::cout << "Position in string: " << number_match.position(0) << std::endl;
} else {
std::cout << "No number found in the text." << std::endl;
}
// 查找带有捕获组的模式
std::string log_line = "ERROR: File not found: document.txt at line 153.";
// 查找错误信息和行号
std::regex error_reg(R"(ERROR: (.*) at line (\d+))"); // 捕获错误描述和行号
std::smatch error_match;
if (std::regex_search(log_line, error_match, error_reg)) {
std::cout << "Found an error entry." << std::endl;
std::cout << "Full match: " << error_match[0].str() << std::endl;
std::cout << "Error description: " << error_match[1].str() << std::endl; // 捕获组 1
std::cout << "Line number: " << error_match[2].str() << std::endl; // 捕获组 2
} else {
std::cout << "No error entry found in the log line." << std::endl;
}
return 0;
}
“`
std::regex_search()
是进行模式查找和信息提取的常用函数。它只找到 第一个 匹配项。要找到所有匹配项,需要结合循环或使用迭代器(将在后面介绍)。
6. 替换匹配的子字符串:std::regex_replace()
std::regex_replace()
函数用于查找输入序列中所有与正则表达式匹配的子序列,并将它们替换为指定的格式字符串。
“`cpp
include
include
include
int main() {
std::string text = “Hello 123 world 456!”;
// 查找所有数字序列
std::regex num_reg(R”(\d+)”);
// 将所有数字替换为 "[NUMBER]"
std::string replaced_text = std::regex_replace(text, num_reg, "[NUMBER]");
std::cout << "Original: " << text << std::endl;
std::cout << "Replaced: " << replaced_text << std::endl; // 输出: Hello [NUMBER] world [NUMBER]!
// 使用捕获组进行替换
std::string email = "[email protected]";
// 查找电子邮件地址,并将其格式化为 [user]@[domain]
std::regex email_reg(R"(([^@]+)@([^@]+))"); // 捕获用户名和域名
// 替换字符串可以使用 `$n` 来引用第 n 个捕获组
std::string formatted_email = std::regex_replace(email, email_reg, "User: $1, Domain: $2");
std::cout << "Original email: " << email << std::endl;
std::cout << "Formatted: " << formatted_email << std::endl; // 输出: Formatted: User: user, Domain: example.com
// 另一种替换格式:将域名和用户名调换位置
std::string swapped_email = std::regex_replace(email, email_reg, "$2 ($1)");
std::cout << "Swapped: " << swapped_email << std::endl; // 输出: Swapped: example.com (user)
return 0;
}
“`
std::regex_replace()
的第三个参数是替换格式字符串。在这个字符串中:
- 普通字符直接插入到结果中。
$
后跟一个数字n
($n
) 表示引用正则表达式中第n
个捕获组匹配到的内容。$&
或$0
表示引用整个匹配到的子字符串。$'
表示引用匹配子串之后的所有字符。$
` 表示引用匹配子串之前的所有字符。$$
表示字面字符$
。
7. 遍历所有匹配项:std::regex_iterator
和 std::regex_token_iterator
std::regex_search()
只查找第一个匹配项。如果需要找到字符串中所有非重叠的匹配项,可以使用迭代器。std::regex_iterator
用于遍历所有匹配正则表达式的子串,而 std::regex_token_iterator
用于基于匹配或非匹配子串对字符串进行分词。
7.1 std::regex_iterator
std::regex_iterator
构造函数需要三个参数:输入序列的起始迭代器、输入序列的结束迭代器以及 std::regex
对象。它会从输入序列的开头开始查找匹配项,每次递增迭代器都会找到下一个非重叠的匹配项。当没有更多匹配项时,迭代器会变成末尾迭代器,可以用于循环终止条件。
每次迭代器解引用 (*it
) 都会得到一个匹配结果对象(如 std::smatch
),其中包含当前匹配项的信息。
“`cpp
include
include
include
include // 通常用来存储匹配结果或处理
int main() {
std::string text = “Numbers: 123, 456, 7890.”;
std::regex num_reg(R”(\d+)”);
// 创建一个 regex_iterator,从 text 的开头到结尾查找 num_reg 的匹配项
// 最后一个参数 default_iterator 表示迭代结束的标志
std::sregex_iterator it(text.begin(), text.end(), num_reg);
std::sregex_iterator end; // 默认构造函数创建一个末尾迭代器
std::cout << "Finding all numbers in the text:" << std::endl;
while (it != end) {
// 解引用迭代器得到当前的 smatch 对象
std::smatch match = *it;
std::cout << "Found: " << match.str() // match[0].str() 整个匹配项
<< " at position: " << match.position()
<< std::endl;
++it; // 移动到下一个匹配项
}
// 查找所有带有捕获组的匹配项
std::string products = "ID: P101 Name: Laptop Price: 1200, ID: P102 Name: Mouse Price: 25";
// 匹配 ID: XXX Name: YYY Price: ZZZ,并捕获 ID, Name, Price
std::regex product_reg(R"(ID: (\w+) Name: ([^ ]+) Price: (\d+))");
std::sregex_iterator prod_it(products.begin(), products.end(), product_reg);
std::sregex_iterator prod_end;
std::cout << "\nFinding all product entries:" << std::endl;
while (prod_it != prod_end) {
std::smatch match = *prod_it;
std::cout << " Full Match: " << match.str() << std::endl;
std::cout << " ID: " << match[1].str() << std::endl;
std::cout << " Name: " << match[2].str() << std::endl;
std::cout << " Price: " << match[3].str() << std::endl;
++prod_it;
}
return 0;
}
“`
std::regex_iterator
是处理需要查找所有匹配项的场景(如解析结构化文本、提取数据)的强大工具。
7.2 std::regex_token_iterator
std::regex_token_iterator
用于将字符串根据正则表达式匹配或非匹配的部分进行分词。它比 regex_iterator
更灵活,可以直接提取匹配的部分、非匹配的部分,或特定捕获组的内容。
std::regex_token_iterator
构造函数与 regex_iterator
类似,但多一个参数,指定要提取的匹配结果的索引。
* 索引 -1
表示提取匹配之间的非匹配部分(用于按分隔符拆分字符串)。
* 索引 0
表示提取整个匹配部分。
* 索引 n
(>0) 表示提取第 n
个捕获组的内容。
* 可以传递一个整数向量,指定要提取的多个捕获组索引。
“`cpp
include
include
include
include
int main() {
std::string csv_line = “Apple,Banana,Orange,Grape”;
// 使用逗号作为分隔符进行分词 (提取非匹配部分)
std::regex comma_reg(“,”);
// 提取非匹配部分 (索引 -1)
std::sregex_token_iterator token_it(csv_line.begin(), csv_line.end(), comma_reg, -1);
std::sregex_token_iterator end;
std::cout << "Tokenizing CSV line by comma:" << std::endl;
while (token_it != end) {
std::cout << "Token: " << *token_it << std::endl;
++token_it;
}
std::string text_with_tags = "<b>bold</b><i>italic</i>";
// 查找所有 HTML 标签名称 (提取捕获组内容)
std::regex tag_reg(R"(<(\w+)>)"); // 捕获标签名称
// 提取第一个捕获组的内容 (索引 1)
std::sregex_token_iterator tag_token_it(text_with_tags.begin(), text_with_tags.end(), tag_reg, 1);
std::sregex_token_iterator tag_end;
std::cout << "\nExtracting tag names:" << std::endl;
while (tag_token_it != tag_end) {
std::cout << "Tag name: " << *tag_token_it << std::endl; // 输出 bold, then italic
++tag_token_it;
}
// 提取匹配的整个标签 (索引 0)
std::sregex_token_iterator full_tag_token_it(text_with_tags.begin(), text_with_tags.end(), tag_reg, 0);
std::sregex_token_iterator full_tag_end;
std::cout << "\nExtracting full tags:" << std::endl;
while (full_tag_token_it != full_tag_end) {
std::cout << "Full tag: " << *full_tag_token_it << std::endl; // 输出 <b>, then <i>
++full_tag_token_it;
}
return 0;
}
“`
std::regex_token_iterator
提供了一种灵活的方式来处理字符串的分割或基于模式提取特定部分。
8. 匹配结果对象详解 (std::smatch
, std::cmatch
等)
当 std::regex_match()
或 std::regex_search()
成功匹配,并将匹配结果存储到 std::smatch
(或 cmatch
等) 对象时,这个对象就包含了丰富的匹配信息。std::smatch
是一个模板类 std::match_results
的特化,用于处理 std::string
。
std::match_results
对象表现得像一个容器(例如 std::vector
),其中每个元素都是一个 std::sub_match
对象,代表匹配到的一个子串。
match[0]
:代表整个正则表达式匹配到的完整子串。match[i]
(i > 0):代表正则表达式中第i
个捕获组()
匹配到的子串。
std::sub_match
对象本身也提供了有用的成员:
.str()
: 返回匹配到的子串作为一个std::string
。.first
,.second
: 返回指向子串起始和结束位置(之后)的迭代器。.length()
: 返回匹配到的子串的长度。.matched
: 一个布尔值,指示该子串是否实际匹配成功(对于可选的捕获组有用)。
std::match_results
对象本身也提供了一些方法:
.size()
: 返回捕获组的数量 + 1 (因为索引 0 是整个匹配项)。.empty()
: 检查匹配结果是否为空。.prefix()
: 返回匹配项之前的所有字符作为一个std::sub_match
。.suffix()
: 返回匹配项之后的所有字符作为一个std::sub_match
。.position(n)
: 返回第n
个匹配子串(或捕获组)在原始输入字符串中的起始位置索引。.length(n)
: 返回第n
个匹配子串(或捕获组)的长度。.str(n)
: 返回第n
个匹配子串(或捕获组)作为一个std::string
。
示例:
“`cpp
include
include
include
int main() {
std::string text = “Name: John Doe, Age: 30.”;
// 捕获姓名和年龄
std::regex reg(R”(Name: ([^,]+), Age: (\d+))”);
std::smatch match;
if (std::regex_search(text, match, reg)) {
std::cout << "Match found!" << std::endl;
std::cout << "Total matches/groups: " << match.size() << std::endl; // Should be 3 (whole + 2 groups)
// Accessing results
std::cout << "Whole match [0]: '" << match[0].str() << "'" << std::endl;
std::cout << "Name [1]: '" << match[1].str() << "'" << std::endl;
std::cout << "Age [2]: '" << match[2].str() << "'" << std::endl;
// Alternative access using str() and position()
std::cout << "Name (str): '" << match.str(1) << "'" << std::endl;
std::cout << "Age starts at position: " << match.position(2) << ", length: " << match.length(2) << std::endl;
// Accessing prefix and suffix
std::cout << "Prefix: '" << match.prefix().str() << "'" << std::endl; // Output: '' (empty, as match is at the start)
std::cout << "Suffix: '" << match.suffix().str() << "'" << std::endl; // Output: '.'
} else {
std::cout << "No match found." << std::endl;
}
return 0;
}
“`
9. 正则表达式标志 (Flags)
在创建 std::regex
对象或调用匹配/搜索函数时,可以指定标志来修改匹配的行为。这些标志定义在 std::regex_constants
命名空间中。常用的标志包括:
std::regex_constants::icase
: 忽略大小写进行匹配。std::regex_constants::grep
: 使用 POSIXgrep
语法。std::regex_constants::egrep
: 使用 POSIXegrep
语法。std::regex_constants::awk
: 使用 POSIXawk
语法。std::regex_constants::basic
: 使用 POSIX Basic Regular Expression (BRE) 语法 (默认)。std::regex_constants::extended
: 使用 POSIX Extended Regular Expression (ERE) 语法。std::regex_constants::ECMAScript
: 使用 ECMAScript (JavaScript) 语法 (C++11/14/17 的默认值)。std::regex_constants::optimize
: 指示引擎优化匹配速度,可能会增加构造std::regex
对象的时间。
示例:
“`cpp
include
include
include
int main() {
std::string text = “Hello World”;
// 默认匹配,大小写敏感
std::regex reg1(R”(world)”);
if (std::regex_search(text, reg1)) {
std::cout << “\”world\” found (case-sensitive).” << std::endl;
} else {
std::cout << “\”world\” not found (case-sensitive).” << std::endl; // 输出
}
// 忽略大小写匹配
std::regex reg2(R"(world)", std::regex_constants::icase);
if (std::regex_search(text, reg2)) {
std::cout << "\"world\" found (case-insensitive)." << std::endl; // 输出
} else {
std::cout << "\"world\" not found (case-insensitive)." << std::endl;
}
return 0;
}
“`
标志可以通过位或 (|
) 运算符组合使用,例如 std::regex_constants::icase | std::regex_constants::optimize
。
10. 常见使用场景示例
将前面学到的知识应用于一些实际场景:
10.1 验证电子邮件地址格式 (简版)
使用 std::regex_match
验证整个字符串是否符合电子邮件格式:
“`cpp
include
include
include
bool is_valid_email(const std::string& email) {
// 这是一个简化的邮箱正则,真实的更复杂
// 用户名部分:允许字母、数字、点、下划线、百分号、加号、减号
// 域名部分:允许字母、数字、点、连字符
std::regex email_reg(R”(^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$)”);
return std::regex_match(email, email_reg);
}
int main() {
std::cout << “[email protected] is valid: ” << is_valid_email(“[email protected]”) << std::endl; // 1 (true)
std::cout << “[email protected] is valid: ” << is_valid_email(“[email protected]”) << std::endl; // 1 (true)
std::cout << “invalid-email is valid: ” << is_valid_email(“invalid-email”) << std::endl; // 0 (false)
std::cout << “[email protected] is valid: ” << is_valid_email(“[email protected]”) << std::endl; // 0 (false)
return 0;
}
“`
10.2 从日志行中提取信息
使用 std::regex_search
查找并提取特定格式的日志信息:
“`cpp
include
include
include
int main() {
std::string log_entry = “[2023-10-26 10:30:15] [INFO] User ‘Alice’ logged in from 192.168.1.100″;
// 捕获时间戳、级别、消息内容
std::regex log_reg(R”([(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})] [(\w+)] (.*))”);
std::smatch log_match;
if (std::regex_search(log_entry, log_match, log_reg)) {
std::cout << "Log entry parsed successfully:" << std::endl;
std::cout << " Timestamp: " << log_match[1].str() << std::endl;
std::cout << " Level: " << log_match[2].str() << std::endl;
std::cout << " Message: " << log_match[3].str() << std::endl;
} else {
std::cout << "Log entry format not recognized." << std::endl;
}
return 0;
}
“`
10.3 从文本中提取所有数字
使用 std::regex_iterator
遍历查找所有数字序列:
“`cpp
include
include
include
include
int main() {
std::string text = “The prices are 10.50, 25, and 100.75.”;
// 匹配整数或小数
std::regex num_reg(R”(\d+(.\d+)?|.\d+)”);
std::sregex_iterator it(text.begin(), text.end(), num_reg);
std::sregex_iterator end;
std::cout << "Found the following numbers:" << std::endl;
while (it != end) {
std::cout << (*it).str() << std::endl;
++it;
}
return 0;
}
“`
10.4 按空格分割字符串
使用 std::regex_token_iterator
提取非匹配部分:
“`cpp
include
include
include
include
int main() {
std::string sentence = “This is a sample sentence.”;
// 使用一个或多个空格作为分隔符
std::regex space_reg(R”(\s+)”);
// 提取非匹配部分 (-1)
std::sregex_token_iterator it(sentence.begin(), sentence.end(), space_reg, -1);
std::sregex_token_iterator end;
std::cout << "Words in the sentence:" << std::endl;
while (it != end) {
std::cout << *it << std::endl;
++it;
}
return 0;
}
“`
11. 性能注意事项和潜在陷阱
- 编译成本: 创建
std::regex
对象涉及到模式的编译,这可能是一个相对耗时的操作。如果在一个循环中反复使用同一个正则表达式,应该在循环外部创建std::regex
对象,然后重用它,而不是在每次迭代中重新创建。 - 匹配性能: 正则表达式的匹配过程可能非常复杂,特别是对于某些复杂的模式(如包含大量嵌套、重复或回溯的模式)和长输入字符串。某些模式可能会导致指数级的匹配时间(称为“回溯失控”或“ReDoS”)。编写高效的正则表达式模式需要经验。考虑使用
std::regex_constants::optimize
标志,它可能有助于提高匹配速度,但会增加编译时间。 - 双重转义: 这是 C++ 正则表达式新手最常见的错误之一。记住 C++ 字符串字面量本身的转义规则。使用原始字符串字面量
R"(...)"
是避免双重转义的最佳实践。 - 区分
match
和search
: 清楚地知道std::regex_match
匹配整个字符串,而std::regex_search
查找子串。使用错误的函数会导致不正确的结果。 - 捕获组的索引: 捕获组的索引从 1 开始。索引 0 始终代表整个匹配项。非捕获组
(?:...)
不会创建独立的捕获结果,因此不占用索引。
12. 总结
C++ 的 <regex>
库为处理字符串中的模式匹配提供了强大而灵活的功能。通过 std::regex
对象、std::regex_match
、std::regex_search
、std::regex_replace
以及迭代器,您可以实现复杂的文本处理任务。
掌握正则表达式本身的语法是高效使用 <regex>
库的基础。理解 std::match_results
对象的结构以及如何访问捕获组是提取匹配信息的关键。
虽然 <regex>
库非常有用,但在处理极其复杂的模式或对性能要求极高的场景时,仍然需要注意模式的效率和潜在的回溯问题。对于非常规的或需要高级功能的正则表达式,您可能需要考虑使用更专业的第三方正则表达式库。
总的来说,C++ <regex>
库是您在 C++ 中进行字符串模式匹配和处理的有力武器,值得投入时间学习和掌握。通过不断的实践和实验,您将能够利用正则表达式解决各种文本处理挑战。