C++ 11正则表达式
引言
在C++11之前,C++标准库中并没有内置的正则表达式支持,程序员往往需要依赖第三方库如Boost.Regex等。随着C++11标准的发布,正则表达式终于被纳入标准库,使文本处理变得更加高效和简洁。
正则表达式(Regular Expression,简称regex)是一种用于描述字符串模式的强大工具,可以用于搜索、匹配和替换文本。C++11中的正则表达式功能位于<regex>
头文件中,提供了与其他主流编程语言类似的正则表达式支持。
正则表达式基础
包含头文件
要在C++11中使用正则表达式功能,首先需要包含相应的头文件:
#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
函数用于检查整个字符串是否符合指定的模式:
#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
函数用于在字符串中搜索匹配的部分:
#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
函数可以用新内容替换匹配的部分:
#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的正则表达式库支持多种语法风格,可以通过正则表达式标志来指定:
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
。
高级用法
捕获组
圆括号()
用于创建捕获组,可以在后续引用:
#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
内容: 段落内容
------------------------
非捕获组
有时我们需要一个分组但不想捕获它的内容,可以使用非捕获组(?:...)
:
std::regex pattern("(?:https?|ftp)://([^/\\s]+)");
这个模式会匹配URL的协议和主机名,但只捕获主机名部分。
先行断言与后行断言
C++11正则表达式支持先行断言和后行断言:
- 正向先行断言:
(?=...)
- 负向先行断言:
(?!...)
- 正向后行断言:
(?<=...)
(某些实现可能不支持) - 负向后行断言:
(?<!...)
(某些实现可能不支持)
#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的正则表达式在某些编译器(特别是早期版本)上可能对先行断言和后行断言的支持有限。
实际应用场景
验证用户输入
正则表达式可以用于验证用户输入的格式,例如邮箱地址、电话号码等:
#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;
}
解析和提取信息
正则表达式可以从文本中提取特定信息:
#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;
}
文本替换和格式化
正则表达式可以用于替换和格式化文本:
#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正则表达式库虽然功能强大,但性能可能不如一些专门的正则表达式库,特别是在处理大量文本时。以下是一些使用时的性能考虑:
- 预编译正则表达式:如果需要重复使用某个正则表达式,应该预编译它而不是每次都创建新的对象。
// 优化前
for (const auto& line : lines) {
std::regex pattern("\\d+"); // 每次循环都编译一次
// 处理 line...
}
// 优化后
std::regex pattern("\\d+"); // 只编译一次
for (const auto& line : lines) {
// 处理 line...
}
- 选择适当的迭代器:使用
std::sregex_iterator
可以一次性找到所有匹配,比反复调用regex_search
更高效。
#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;
}
- 避免过于复杂的正则表达式:特别是包含大量回溯的正则表达式可能会导致性能问题。
总结
C++11的正则表达式库为C++开发者提供了一个强大的文本处理工具。通过<regex>
头文件,您可以执行字符串匹配、搜索和替换操作,极大地简化了文本处理任务。
本文介绍了:
- C++11正则表达式库的基本使用方法
- 正则表达式的核心语法和元字符
- 捕获组、非捕获组和断言等高级功能
- 实际应用场景和示例
- 性能优化技巧
虽然C++11的正则表达式库在功能上与其他语言的实现相似,但初学者需要注意它有时可能存在性能和兼容性问题,特别是在某些旧编译器上。
练习
- 编写一个程序,验证输入的字符串是否为有效的IP地址(IPv4)。
- 使用正则表达式从文本中提取所有的日期(支持多种常见格式,如YYYY-MM-DD、MM/DD/YYYY等)。
- 编写一个简单的HTML解析器,使用正则表达式提取所有的
<a>
标签及其href属性。 - 创建一个密码强度检查器,使用正则表达式验证密码是否包含至少一个大写字母、一个小写字母、一个数字和一个特殊字符。
- 编写一个程序,用正则表达式替换文本中的markdown链接格式
[文本](URL)
为HTML链接格式<a href="URL">文本</a>
。
附加资源
- C++参考文档 - regex
- 正则表达式101 - 在线测试和调试正则表达式的工具
- 书籍:《Mastering Regular Expressions》by Jeffrey Friedl - 深入理解正则表达式的权威资源
通过掌握C++11的正则表达式,您将能够更高效地处理各种文本处理任务,大大提高开发效率。随着练习的增加,您会发现正则表达式是处理复杂文本模式的强大工具。