跳到主要内容

C++ 11正则表达式

引言

在C++11之前,C++标准库中并没有内置的正则表达式支持,程序员往往需要依赖第三方库如Boost.Regex等。随着C++11标准的发布,正则表达式终于被纳入标准库,使文本处理变得更加高效和简洁。

正则表达式(Regular Expression,简称regex)是一种用于描述字符串模式的强大工具,可以用于搜索、匹配和替换文本。C++11中的正则表达式功能位于<regex>头文件中,提供了与其他主流编程语言类似的正则表达式支持。

正则表达式基础

包含头文件

要在C++11中使用正则表达式功能,首先需要包含相应的头文件:

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

C++ 11正则表达式的核心类

C++11正则表达式库主要包含以下几个核心类:

  • std::regex:表示一个正则表达式
  • std::smatch:存储匹配结果的容器
  • std::regex_match:检查整个字符串是否匹配正则表达式
  • std::regex_search:搜索字符串中的匹配内容
  • std::regex_replace:替换匹配的内容

基本用法示例

匹配整个字符串

regex_match函数用于检查整个字符串是否符合指定的模式:

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

int main() {
std::string email = "user@example.com";
std::regex pattern("([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+)\\.([a-zA-Z]{2,})");

if (std::regex_match(email, pattern)) {
std::cout << "有效的邮箱地址!" << std::endl;
} else {
std::cout << "无效的邮箱地址!" << std::endl;
}

return 0;
}

输出:

有效的邮箱地址!

搜索字符串中的匹配

regex_search函数用于在字符串中搜索匹配的部分:

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

int main() {
std::string text = "我的电话号码是123-456-7890,办公室电话是098-765-4321";
std::regex pattern("(\\d{3})-(\\d{3})-(\\d{4})");
std::smatch matches;

std::string::const_iterator searchStart(text.cbegin());
while (std::regex_search(searchStart, text.cend(), matches, pattern)) {
std::cout << "找到电话号码: " << matches[0] << std::endl;
std::cout << " 区号: " << matches[1] << std::endl;
std::cout << " 前缀: " << matches[2] << std::endl;
std::cout << " 后缀: " << matches[3] << std::endl;
searchStart = matches.suffix().first;
}

return 0;
}

输出:

找到电话号码: 123-456-7890
区号: 123
前缀: 456
后缀: 7890
找到电话号码: 098-765-4321
区号: 098
前缀: 765
后缀: 4321

替换匹配的内容

regex_replace函数可以用新内容替换匹配的部分:

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

int main() {
std::string text = "用户名: admin, 邮箱: admin@example.com";
std::regex pattern("([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+)\\.([a-zA-Z]{2,})");

// 用"[邮箱已隐藏]"替换邮箱地址
std::string result = std::regex_replace(text, pattern, "[邮箱已隐藏]");
std::cout << "原文: " << text << std::endl;
std::cout << "替换后: " << result << std::endl;

return 0;
}

输出:

原文: 用户名: admin, 邮箱: admin@example.com
替换后: 用户名: admin, 邮箱: [邮箱已隐藏]

正则表达式语法

C++11的正则表达式库支持多种语法风格,可以通过正则表达式标志来指定:

cpp
std::regex pattern("pattern", std::regex_constants::ECMAScript); // 默认
// 其它选项:basic, extended, awk, grep, egrep

以下是常用的正则表达式元字符:

元字符描述
.匹配任意单个字符(除了换行符)
^匹配字符串的开头
$匹配字符串的结尾
*匹配前面的子表达式零次或多次
+匹配前面的子表达式一次或多次
?匹配前面的子表达式零次或一次
{n}匹配前面的子表达式恰好n次
{n,}匹配前面的子表达式至少n次
{n,m}匹配前面的子表达式至少n次,最多m次
[abc]匹配方括号内的任意一个字符
[^abc]匹配除了方括号内字符的任意一个字符
\d匹配一个数字字符,等价于[0-9]
\D匹配一个非数字字符,等价于[^0-9]
\w匹配一个字母、数字或下划线,等价于[a-zA-Z0-9_]
\W匹配一个非字母、数字或下划线的字符
\s匹配一个空白字符
\S匹配一个非空白字符
提示

在C++字符串字面量中,反斜杠\是一个转义字符,需要使用两个反斜杠\\来表示正则表达式中的一个反斜杠。例如,要匹配数字,需要使用\\d而不是\d

高级用法

捕获组

圆括号()用于创建捕获组,可以在后续引用:

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

int main() {
std::string html = "<div>标题</div><p>段落内容</p>";
std::regex pattern("<([a-z]+)>(.*?)</\\1>");

std::smatch matches;
std::string::const_iterator searchStart(html.cbegin());

while (std::regex_search(searchStart, html.cend(), matches, pattern)) {
std::cout << "完整匹配: " << matches[0] << std::endl;
std::cout << "标签名: " << matches[1] << std::endl;
std::cout << "内容: " << matches[2] << std::endl;
std::cout << "------------------------" << std::endl;
searchStart = matches.suffix().first;
}

return 0;
}

输出:

完整匹配: <div>标题</div>
标签名: div
内容: 标题
------------------------
完整匹配: <p>段落内容</p>
标签名: p
内容: 段落内容
------------------------

非捕获组

有时我们需要一个分组但不想捕获它的内容,可以使用非捕获组(?:...)

cpp
std::regex pattern("(?:https?|ftp)://([^/\\s]+)");

这个模式会匹配URL的协议和主机名,但只捕获主机名部分。

先行断言与后行断言

C++11正则表达式支持先行断言和后行断言:

  • 正向先行断言:(?=...)
  • 负向先行断言:(?!...)
  • 正向后行断言:(?<=...)(某些实现可能不支持)
  • 负向后行断言:(?<!...)(某些实现可能不支持)
cpp
#include <iostream>
#include <string>
#include <regex>

int main() {
std::string passwords[] = {
"password123", "Pass123word", "123Password", "Pass123"
};

// 密码必须包含至少一个数字,使用先行断言
std::regex pattern("^(?=.*\\d).{6,}$");

for (const auto& password : passwords) {
if (std::regex_match(password, pattern)) {
std::cout << password << " - 有效密码" << std::endl;
} else {
std::cout << password << " - 无效密码" << std::endl;
}
}

return 0;
}
警告

需要注意的是,C++11的正则表达式在某些编译器(特别是早期版本)上可能对先行断言和后行断言的支持有限。

实际应用场景

验证用户输入

正则表达式可以用于验证用户输入的格式,例如邮箱地址、电话号码等:

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

bool validateEmail(const std::string& email) {
std::regex pattern("([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+)\\.([a-zA-Z]{2,})");
return std::regex_match(email, pattern);
}

bool validatePhone(const std::string& phone) {
std::regex pattern("\\d{3}-\\d{3}-\\d{4}");
return std::regex_match(phone, pattern);
}

int main() {
std::string email, phone;

std::cout << "请输入邮箱地址: ";
std::cin >> email;

if (validateEmail(email)) {
std::cout << "邮箱格式正确!" << std::endl;
} else {
std::cout << "邮箱格式不正确!" << std::endl;
}

std::cout << "请输入电话号码 (格式: 123-456-7890): ";
std::cin >> phone;

if (validatePhone(phone)) {
std::cout << "电话号码格式正确!" << std::endl;
} else {
std::cout << "电话号码格式不正确!" << std::endl;
}

return 0;
}

解析和提取信息

正则表达式可以从文本中提取特定信息:

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

std::vector<std::string> extractURLs(const std::string& text) {
std::regex urlRegex("https?://([\\w.-]+)(/[\\w./\\-~]*)?");
std::vector<std::string> urls;

auto words_begin = std::sregex_iterator(text.begin(), text.end(), urlRegex);
auto words_end = std::sregex_iterator();

for (std::sregex_iterator i = words_begin; i != words_end; ++i) {
urls.push_back((*i).str());
}

return urls;
}

int main() {
std::string text = "请访问我们的网站 https://www.example.com/products 或者 http://blog.example.org 获取更多信息";

std::vector<std::string> urls = extractURLs(text);

std::cout << "提取到的URL:" << std::endl;
for (const auto& url : urls) {
std::cout << " " << url << std::endl;
}

return 0;
}

文本替换和格式化

正则表达式可以用于替换和格式化文本:

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

std::string formatDate(const std::string& text) {
// 将YYYY-MM-DD格式转换为MM/DD/YYYY格式
std::regex dateRegex("(\\d{4})-(\\d{2})-(\\d{2})");
return std::regex_replace(text, dateRegex, "$2/$3/$1");
}

int main() {
std::string text = "会议将在2023-05-15举行,而截止日期是2023-06-30。";

std::string formatted = formatDate(text);

std::cout << "原文: " << text << std::endl;
std::cout << "格式化后: " << formatted << std::endl;

return 0;
}

输出:

原文: 会议将在2023-05-15举行,而截止日期是2023-06-30。
格式化后: 会议将在05/15/2023举行,而截止日期是06/30/2023。

效率和性能考虑

C++11正则表达式库虽然功能强大,但性能可能不如一些专门的正则表达式库,特别是在处理大量文本时。以下是一些使用时的性能考虑:

  1. 预编译正则表达式:如果需要重复使用某个正则表达式,应该预编译它而不是每次都创建新的对象。
cpp
// 优化前
for (const auto& line : lines) {
std::regex pattern("\\d+"); // 每次循环都编译一次
// 处理 line...
}

// 优化后
std::regex pattern("\\d+"); // 只编译一次
for (const auto& line : lines) {
// 处理 line...
}
  1. 选择适当的迭代器:使用std::sregex_iterator可以一次性找到所有匹配,比反复调用regex_search更高效。
cpp
#include <iostream>
#include <string>
#include <regex>

int main() {
std::string text = "12 apples, 34 bananas, 56 oranges";
std::regex numberRegex("\\d+");

// 使用正则表达式迭代器
auto words_begin = std::sregex_iterator(text.begin(), text.end(), numberRegex);
auto words_end = std::sregex_iterator();

std::cout << "找到 " << std::distance(words_begin, words_end) << " 个数字:" << std::endl;

for (std::sregex_iterator i = words_begin; i != words_end; ++i) {
std::smatch match = *i;
std::cout << match.str() << std::endl;
}

return 0;
}
  1. 避免过于复杂的正则表达式:特别是包含大量回溯的正则表达式可能会导致性能问题。

总结

C++11的正则表达式库为C++开发者提供了一个强大的文本处理工具。通过<regex>头文件,您可以执行字符串匹配、搜索和替换操作,极大地简化了文本处理任务。

本文介绍了:

  • C++11正则表达式库的基本使用方法
  • 正则表达式的核心语法和元字符
  • 捕获组、非捕获组和断言等高级功能
  • 实际应用场景和示例
  • 性能优化技巧

虽然C++11的正则表达式库在功能上与其他语言的实现相似,但初学者需要注意它有时可能存在性能和兼容性问题,特别是在某些旧编译器上。

练习

  1. 编写一个程序,验证输入的字符串是否为有效的IP地址(IPv4)。
  2. 使用正则表达式从文本中提取所有的日期(支持多种常见格式,如YYYY-MM-DD、MM/DD/YYYY等)。
  3. 编写一个简单的HTML解析器,使用正则表达式提取所有的<a>标签及其href属性。
  4. 创建一个密码强度检查器,使用正则表达式验证密码是否包含至少一个大写字母、一个小写字母、一个数字和一个特殊字符。
  5. 编写一个程序,用正则表达式替换文本中的markdown链接格式[文本](URL)为HTML链接格式<a href="URL">文本</a>

附加资源

通过掌握C++11的正则表达式,您将能够更高效地处理各种文本处理任务,大大提高开发效率。随着练习的增加,您会发现正则表达式是处理复杂文本模式的强大工具。