正则表达式介绍
正则表达式(Regular Expression,简称Regex)是一种强大的文本处理工具,它允许你以灵活和高效的方式搜索、替换、检验文本。在C++中,正则表达式的支持是通过标准库中的<regex>头文件提供的,该库自C++11起成为标准的一部分。
正则表达式使用一系列符号和字符来描述或匹配文本模式。这些模式可以简单到单个字符,也可以复杂到描述复杂的字符串模式。
C++标准库提供了多种与正则表达式相关的类和函数,位于<regex>
头文件中,包括:
std::regex
:编译后的正则表达式对象。std::regex_match
:检查给定的字符串是否完全匹配正则表达式。std::regex_search
:在给定的字符串中搜索正则表达式的匹配。std::regex_replace
:使用正则表达式匹配模式来替换字符串。
常用符号和术语
- 字面量:匹配特定字符的自身。
.
(点):匹配任何单个字符,除了换行符。[]
:字符集,匹配方括号内的任意字符。^
:行的开始,[^]
中的^表示非。$
:行的结束。*
:前面的元素零次或多次出现。+
:前面的元素一次或多次出现。?
:前面的元素零次或一次出现。{n}
:前面的元素恰好出现n次。{n,}
:前面的元素至少出现n次。{n,m}
:前面的元素至少出现n次,但不超过m次。|
:或,匹配左边或右边的表达式。()
:分组,将几个项当作一个单元进行处理,通常与|
结合使用。
正则表达式的使用其实很简单,只需要为字符串创造一个匹配规则对象regex,并设置合适的规则。
举例1
cpp
#include <iostream>
#include <regex>
int main() {
std::string text = "Hello World 123";
std::regex pattern("World");
// 检查模式是否匹配
if (std::regex_search(text, pattern)) {
std::cout << "Found a match!" << std::endl;
} else {
std::cout << "No match found." << std::endl;
}
return 0;
}
这个示例将输出"Found a match!",因为字符串"Hello World 123"中确实包含"World"。
举例2
验证电子邮箱地址
cpp
#include <iostream>
#include <regex>
int main() {
std::string email = "example@example.com";
std::regex pattern(R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})");
if (std::regex_match(email, pattern)) {
std::cout << "The email address is valid." << std::endl;
} else {
std::cout << "The email address is not valid." << std::endl;
}
return 0;
}
在C++中,R"(...)"是一种原始字符串字面量(Raw String Literal)的表示方法,它允许你包含任何字符序列作为字符串的一部分,包括反斜杠 \、双引号 " 和换行符等,而不需要进行转义。
这个规则对于正则表达式的特殊字符(如*, +, ?, |, (, ), [, ], {, }, ^, $, 和.等)不适用,因为这些特殊字符在正则表达式中有特定的含义,
-
[a-zA-Z0-9._%+-]+
:这部分是电子邮件地址的用户名部分,表示电子邮箱可以包含:- 小写字母 (
a-z
) - 大写字母 (
A-Z
) - 数字 (
0-9
) - 下划线 (
_
) - 点 (
.
) - 百分号 (
%
) - 加号 (
+
) - 减号 (
-
)
+
表示前面的字符集[a-zA-Z0-9._%+-]
可以出现一次或多次。 - 小写字母 (
-
@
:这是电子邮件地址中必须出现的"@"字符,用于分隔用户名和域名部分。 -
[a-zA-Z0-9.-]+
:这是域名部分,表示电子邮箱可以包含:- 小写字母 (
a-z
) - 大写字母 (
A-Z
) - 数字 (
0-9
) - 点 (
.
) - 减号 (
-
)
与用户名部分类似,
+
表示这个字符集可以出现一次或多次。 - 小写字母 (
-
\.
:表示点字符(.
)。在正则表达式中,点(.
)通常表示任何单一字符,因此需要用反斜线(\
)进行转义,使其表示字面上的点字符。这个点在电子邮件地址中用于分隔域名和顶级域名(TLD)。 -
[a-zA-Z]{2,}
:这是顶级域名(TLD)部分,可以包含小写字母 (a-z
) 和大写字母 (A-Z
)。{2,}
表示这部分至少有两个字符,没有上限。这反映了顶级域名通常至少有两个字母,如.com
、.org
、.net
等。
举例3
替换文本
使用正则表达式替换文本中的特定单词或字符。这个例子将文本中的所有"is"替换为"is not"。
cpp
#include <iostream>
#include <regex>
#include <string>
int main() {
std::string text = "This is a test. This test is simple.";
std::string replaceWith = "is not";
std::regex pattern(R"(\bis\b)");
std::string result = std::regex_replace(text, pattern, replaceWith);
std::cout << result << std::endl;
return 0;
}
-
\b
:这是一个正则表达式的边界匹配符,它用于匹配一个词的边界。词的边界是指不被字母、数字或下划线(\w
)字符包围的位置。这包括字符串的开头和结尾,以及空格或标点符号和字母数字之间的位置。使用\b
可以确保你匹配的是独立的单词,而不是包含在其他单词中的子字符串。 -
is
:这部分表示要直接匹配的字符串"is"。 -
\b
:再次使用,确保"is"后面也是一个单词边界。
因此,整个表达式R"(\bis\b)"
用于匹配作为独立单词存在的"is",而不会匹配像"this"或"island"这样包含"is"作为子字符串的单词。
举例4
验证字符串是否符合特定的电话号码格式。这里考虑的格式是匹配符合特定格式的中国大陆电话号码。例如+86-123-4567-8910
。
cpp
#include <iostream>
#include <regex>
int main() {
std::string phone = "+1-123-456-7890";
std::regex pattern(R"(\+86-\d{3}-\d{4}-\d{4})");
if (std::regex_match(phone, pattern)) {
std::cout << "The phone number is valid." << std::endl;
} else {
std::cout << "The phone number is not valid." << std::endl;
}
return 0;
}
-
\+
:这里的加号(+
)是一个特殊字符,在正则表达式中用于表示前面的元素可以出现一次或多次。但当你想要匹配字面上的加号字符时,需要使用反斜杠对其进行转义,所以\+
表示匹配字面上的"+"字符。 -
86
:这部分直接匹配数字"86",代表中国大陆的国家代码。 -
-
:匹配字面上的连字符或破折号。 -
\d{3}
:\d
是一个特殊的字符类,匹配任意数字(0-9
)。大括号{3}
指定了前面的\d
(数字)必须恰好出现3次。因此,\d{3}
匹配一个由3个数字组成的序列。 -
-
:再次匹配字面上的连字符。 -
\d{4}
:这里\d{4}
意味着必须有一个由4个数字组成的序列。和前面一样,\d
表示数字,{4}
指定了数字必须恰好出现4次。 -
-
:匹配字面上的连字符。 -
\d{4}
:和前面的\d{4}
一样,这也是一个由4个数字组成的序列。
举例5
解析http响应
HTTP响应由状态行、响应头部、空行和响应体组成。我们的目标是从HTTP响应文本中提取状态码、响应头部以及响应体。
示例HTTP响应文本
css
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 138
<html>
<head>
<title>An Example Page</title>
</head>
<body>
Hello World, this is a very simple HTML document.
</body>
</html>
我们将编写一个C++程序来解析上述HTTP响应,提取出状态码、Content-Type
、Content-Length
头部以及响应体。
cpp
#include <iostream>
#include <regex>
#include <string>
int main() {
// 示例HTTP响应文本
std::string httpResponse = R"(HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 138
<html>
<head>
<title>An Example Page</title>
</head>
<body>
Hello World, this is a very simple HTML document.
</body>
</html>)";
// 正则表达式匹配状态码、Content-Type、Content-Length和响应体
std::regex statusLinePattern(R"(HTTP\/\d\.\d\s(\d{3}))");
//(\d{3})括号表示要取出的内容
// \s表示代表任何空白字符(空格、制表符、换行符....)
std::regex contentTypePattern(R"(Content-Type:\s(.+))");
std::regex contentLengthPattern(R"(Content-Length:\s(\d+))");
std::regex bodyPattern(R"(\r\n\r\n([\s\S]*))");
// [\s\S]* 匹配包括换行符在内的任意字符
std::smatch matches;
// 匹配状态码
if (std::regex_search(httpResponse, matches, statusLinePattern)) {
std::cout << "Status Code: " << matches[1] << std::endl;
}
// 匹配Content-Type
if (std::regex_search(httpResponse, matches, contentTypePattern)) {
std::cout << "Content-Type: " << matches[1] << std::endl;
}
// 匹配Content-Length
if (std::regex_search(httpResponse, matches, contentLengthPattern)) {
std::cout << "Content-Length: " << matches[1] << std::endl;
}
// 匹配响应体
if (std::regex_search(httpResponse, matches, bodyPattern)) {
std::cout << "Body:\n" << matches[1] << std::endl;
}
return 0;
}
std::smatch
在C++中,std::smatch
是std::match_results
模板类的一个特化版本,专门用于处理字符串(std::string
)的搜索和匹配操作。当你使用正则表达式进行搜索或匹配时,std::smatch
对象用于存储匹配结果。每个匹配结果包括完整的匹配以及任何括号内的子匹配(即捕获组)。
matches[0]
包含了正则表达式完整匹配的文本。matches[1]
、matches[2]
、... 包含了正则表达式中第一个、第二个等括号内的子表达式(捕获组)匹配的文本。这些子匹配允许你访问正则表达式中由圆括号()
定义的各个部分的匹配结果。