当前位置:   article > 正文

突破编程_C++_高级教程(正则表达式)

突破编程_C++_高级教程(正则表达式)

1 正则表达式的概念

正则表达式,又称规则表达式,(Regular Expression,在代码中常简写为 regex 、regexp 或 RE),是一种文本模式,包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为元字符)。
正则表达式使用单个字符串来描述、匹配一系列符合某个句法规则的字符串,通常被用来检索、替换那些符合某个模式(规则)的文本。许多程序设计语言都支持利用正则表达式进行字符串操作。正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符及这些特定字符的组合,组成一个规则字符串,这个规则字符串用来表达对字符串的一种过滤逻辑。正则表达式是一种文本模式,该模式描述在搜索文本时要匹配的一个或多个字符串。

1.1 正则表达式的作用和应用场景

正则表达式的主要作用包括文本的检索、替换以及从一个字符串中提取出符合特定条件的子串。它描述了一种字符串匹配的模式,可以用来检查一个字符串是否含有某种子串、将匹配的子串做替换或者从某个字符串中取出符合某个条件的子串等。

正则表达式的应用场景非常广泛,包括但不限于以下几个方面:
(1)Web开发: 在Web开发中,正则表达式被广泛应用于表单验证、URL处理、数据提取等方面。例如,可以使用正则表达式来验证用户输入的邮箱地址是否符合规范格式。
(2)服务器日志分析: 服务器日志通常包含大量的信息,使用正则表达式可以方便地提取和分析日志数据中的关键信息。
(3)网页爬虫: 正则表达式可以用于解析和处理网页上的文本数据,提取出需要的信息。
(4)文件批量处理: 在处理大量文件时,可以使用正则表达式来快速高效地查找与分析字符串。

此外,正则表达式还被应用于数据验证、信息校验、字符串格式化与美化、以及与其他技术的结合如人工智能和大数据处理等领域。

1.2 C++ 与正则表达式

C++ 与正则表达式的关系主要体现在 C++ 标准库提供的正则表达式支持,这使得在 C++ 程序中使用正则表达式变得更加方便和高效。
从 C++11 开始,C++ 标准库引入了<regex>头文件,其中包含了处理正则表达式的类和函数。这些类和函数使得开发者可以在 C++ 程序中方便地使用正则表达式进行文本匹配、搜索、替换等操作。

C++ 标准库中的正则表达式功能主要包括:
(1)std::regex类: 表示一个正则表达式对象,用于存储和编译正则表达式模式。
(2)std::smatch类: 表示正则表达式匹配的结果,用于存储匹配到的子表达式信息。
(3)std::sregex_iterator类: 是一个迭代器,用于遍历字符串中所有与正则表达式匹配的子串。
(4)正则表达式操作函数:
std::regex_match:检查整个字符串是否与正则表达式匹配。
std::regex_search:在字符串中搜索与正则表达式匹配的子串。
std::regex_replace:替换字符串中与正则表达式匹配的子串。
std::regex_split:根据正则表达式将字符串分割成多个子串。

使用 C++ 标准库中的正则表达式功能,可以执行各种复杂的文本处理任务,如验证输入格式、提取数据、文本替换等。

如下是一个验证字符串是否符合电子邮件地址的格式的样例:

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

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

	if (std::regex_match(email, emailRegex)) 
	{
		std::cout << "valid email address" << std::endl;
	}
	else 
	{
		std::cout << "invalid email address" << std::endl;
	}

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

上面代码的输出为:

valid email address
  • 1

在上面代码中,定义了一个正则表达式模式来匹配电子邮件地址,并使用 std::regex_match 函数来检查给定的字符串是否符合这个模式。如果匹配成功,输出"valid email address",否则输出"invalid email address"。

2 正则表达式基础语法

正则表达式由一系列普通字符和特殊字符(也被称为是元字符)组成,它们赋予了正则表达式强大的模式匹配能力。

2.1 字符类

在正则表达式中,字符类(也称为字符集合)允许匹配一个字符集合中的任意一个字符。字符类是由方括号 [] 包围的,其中可以包含一系列字符、范围或特殊的转义序列。
以下是一些正则表达式字符类,以及相应的样例:

(1)基本字符类: 匹配方括号内列出的任意一个字符。
样例:[abc] 会匹配字符 “a”、“b” 或 “c”。

(2)字符范围: 在方括号内使用连字符 - 来指定字符范围。
样例:[a-z] 会匹配任意小写字母。

(3)排除字符类: 在方括号内使用 ^ 符号作为第一个字符来排除列出的字符。
样例:[^0-9] 会匹配任意非数字字符。

(4)特殊字符类:
.(点号) 匹配任何单个字符(除了换行符 \n)。
\d 匹配任意数字,等价于 [0-9]。
\D 匹配任意非数字字符,等价于 [^0-9]。
\w 匹配任意字母、数字或下划线,等价于 [a-zA-Z0-9_]。
\W 匹配任意非字母、非数字和非下划线字符,等价于 [^a-zA-Z0-9_]。
\s 匹配任意空白字符,包括空格、制表符、换行符等。
\S 匹配任意非空白字符。
样例:
\d 会匹配字符 “1” 或 “2”。
\w 会匹配字符 “a”、“A”、“1” 或 “_”。

(5)预定义字符类: 某些正则表达式引擎提供了预定义的字符类,如 [:alpha:](字母)、[:digit:](数字)等。这些类可能因不同的引擎而有所不同。

(6)Unicode 字符类: 使用 \u 或 \U 后跟 Unicode 码点来匹配特定的 Unicode 字符或字符范围。
样例:\u0041-\u005A 会匹配任意大写英文字母(A-Z)。

(7)否定字符类: 在字符类的开始处使用 ^ 符号可以表示否定,即匹配不在字符类中的任意字符。
样例:[^aeiou] 会匹配任意非元音字母。

2.2 限定符与量词

正则表达式中的限定符和量词用于指定模式中的元素可以出现的次数。它们提供了强大的控制能力,可以精确地描述和匹配特定模式的文本。
以下是一些常见的正则表达式限定符和量词,以及相应的样例:

(1)*(星号): 匹配前面的元素零次或多次。
样例:zo* 会匹配 “z” 以及 “zoo”,因为 “o” 可以出现零次(即没有 “o”)或多次(如 “oo”)。

(2)+(加号): 匹配前面的元素一次或多次。
样例:zo+ 会匹配 “zo” 以及 “zoo”,但不会匹配 “z”,因为 “o” 必须至少出现一次。

(3)?(问号): 匹配前面的元素零次或一次。
样例:do(es)? 会匹配 “do” 或 “does” 中的 “do”,因为 “es” 可以出现零次(即 “do”)或一次(即 “does”)。

(4){n}: 匹配前面的元素恰好 n 次。
样例:o{2} 会匹配 “oo”,因为 “o” 必须恰好出现两次。

(5){n,}: 匹配前面的元素至少 n 次。
样例:o{2,} 会匹配 “oo”、“ooo”、“oooo” 等,因为 “o” 至少出现两次,可以出现更多次。

(6){n,m}: 匹配前面的元素至少 n 次,但不超过 m 次。
样例:o{2,3} 会匹配 “oo” 和 “ooo”,但不会匹配 “o” 或 “oooo”,因为 “o” 的出现次数必须在 2 到 3 次之间。

这些限定符和量词可以与其他正则表达式元素(如字符、字符组、捕获组等)结合使用,以构建复杂的匹配模式。注意:在实际使用时,正则表达式引擎会根据模式从左到右的顺序进行匹配,并尽量多地匹配字符,这称为贪婪模式。如果希望尽量少的匹配字符,可以使用非贪婪模式(通过在限定符或量词后面加上 ? 来实现)。

2.3 定位符与锚点

正则表达式中的定位符(也称为锚点)用于规定匹配模式在目标对象中的出现位置。这些定位符不会消耗字符,只是指定模式必须出现在字符串的特定位置。
以下是一些常见的正则表达式定位符和锚点,以及相应的样例:

(1)^(脱字号): 匹配输入字符串的开始位置。
样例:^hello 会匹配以 “hello” 开头的字符串,如 “hello world”。

(2)$(美元符号): 匹配输入字符串的结束位置。
样例:world$ 会匹配以 “world” 结尾的字符串,如 “hello world”。

(3)\b(单词边界): 匹配一个单词的的边界位置,即单词字符(如字母、数字、下划线)和非单词字符(如空格、标点符号等)之间的位置。
样例:\bcat\b 会匹配独立的单词 “cat”,而不会匹配 “concatenate” 中的 “cat”。

(4)\B(非单词边界): 匹配非单词边界的位置,即两个单词字符之间或两个非单词字符之间的位置。
样例:\Bcat\B 会匹配 “concatenate” 中的 “cat”,但不会匹配独立的单词 “cat”。

(5)\A(绝对开头位置): 仅匹配字符串的绝对开头位置,忽略多行模式中的换行符
样例:在多行模式下,\Ahello 只会匹配第一行以 “hello” 开头的字符串。

(6)\Z(绝对结尾位置): 仅匹配字符串的绝对结尾位置,忽略多行模式中的换行符
样例:在多行模式下,world\Z 只会匹配最后一行以 “world” 结尾的字符串。

(7)\z(结尾位置): 匹配字符串的结尾位置,不考虑多行模式
样例:\z 在任何模式下都会匹配字符串的结尾位置。

这些定位符和锚点可以帮助精确地指定模式在字符串中的位置,从而实现更准确的匹配。注意:在不同的正则表达式引擎和编程语言中,这些定位符和锚点的行为可能略有不同。

2.4 分组(捕获组)与选择结构

正则表达式中的分组与选择结构是构建复杂模式的重要工具。分组允许将一部分模式组合起来,作为一个整体进行处理,而选择结构则允许指定多个可能的匹配选项。

(1)分组(捕获组):
分组也称为捕获组,是使用圆括号 () 来实现。分组不仅可以对模式进行组合,还可以用于捕获匹配的子串,以便后续引用。
样例:
基本分组:将模式组合成一个整体。 (abc) 这个模式会匹配字符串 “abc” 中的 “abc”。
捕获分组:通过分组捕获匹配的子串。 (\d{3})-(\d{3})-(\d{4}) 这个模式会匹配形如 “123-456-7890” 的字符串,并将 “123”、“456” 和 “7890” 作为捕获的分组保存起来。

(2)选择结构:
选择结构使用竖线 | 来实现,允许你指定多个可能的匹配选项。正则表达式会尝试从左到右匹配选项,一旦找到匹配的选项,就会停止搜索。
样例:red|blue|green 这个模式会匹配 “red”、“blue” 或 “green” 中的任意一个。

(3)分组与选择结合:
在分组内部使用选择结构。
样例:(apple|orange) juice 这个模式会匹配 “apple juice” 或 “orange juice”。

(4)捕获分组与选择结合:
使用捕获分组捕获选择结构中的匹配项。
样例:(apple|orange)(\s+)juice 这个模式会匹配 “apple juice” 或 “orange juice”,并且捕获 “apple” 或 “orange” 以及至少一个空白字符。

2.5 反向引用

在正则表达式中,反向引用是一种特殊的机制,允许引用之前匹配的子模式(即捕获组)的内容。这通常用于匹配重复或对称的文本模式。

反向引用是通过使用反斜杠(\)加上捕获组的数字索引来实现的。第一个捕获组的索引是 1,第二个捕获组的索引是 2,依此类推。

样例 1:匹配重复的单词

(\b\w+\b)\s+\1
  • 1

这个正则表达式会匹配一个单词,后面跟着一个或多个空格,然后是这个单词的再次出现。\1 是对第一个捕获组((\b\w+\b))的反向引用,它匹配与第一个捕获组相同的文本。

例如,在字符串 “hello world hello again” 中,这个正则表达式会匹配 “hello hello”。

样例 2:匹配重复的的数字序列

(\d+)\s+\1
  • 1

这个正则表达式会匹配一个或多个数字(\d+),后面跟着一个或多个空格,然后是这个数字序列的再次出现。\1 是对第一个捕获组的反向引用。

例如,在字符串 “123 123 456 4567” 中,这个正则表达式会匹配 “123 123” 和 “456 456”。

样例 3:匹配HTML标签

<(\w+)>\s+<\/\1>
  • 1

这个正则表达式用于匹配简单的HTML开标签和闭标签。它捕获开标签中的元素名(\w+),并使用 \1 反向引用该元素名来匹配相应的闭标签。

例如,在字符串 “<div>This is a div</div> <p>This is a paragraph</p>” 中,这个正则表达式会匹配 “<div>…</div>” 和 “<p>…</p>”。

2.6 非捕获组

在正则表达式中,非捕获组是一种特殊类型的分组,它允许对一部分模式进行分组,但不捕获该分组匹配的文本。这在只需要对模式的一部分进行分组,而不需要保存其匹配结果时非常有用。非捕获组使用 (?: …) 的语法。

非捕获组的主要优点是它们不会消耗额外的内存来存储捕获的文本,这可以提高性能并减少潜在的内存使用。这对于处理大型文本或复杂模式时特别重要。

以下是一些使用非捕获组的样例:

样例 1:匹配重复的单词(但不捕获它们)

(?:\b\w+\b)\s+\1
  • 1

这个正则表达式与捕获组的版本类似,但它使用非捕获组来匹配单词。这意味着虽然 \1 仍然可以引用第一个捕获组的内容,但第一个分组本身不会捕获或保存匹配的单词。

样例 2:匹配日期格式(但不捕获分隔符)

(?:\d{4})-(?:\d{2})-(?:\d{2})
  • 1

这个正则表达式匹配形如 “YYYY-MM-DD” 的日期格式,但使用非捕获组来避免捕获年份、月份和日期之间的分隔符(破折号)。

样例 3:使用非捕获组进行条件匹配

(?i:hello)\s+world
  • 1

在这个例子中,(?i:hello) 是一个非捕获组,它匹配单词 “hello”,并且不区分大小写(由于 ?i 修饰符)。这个修饰符仅适用于该非捕获组内的内容,而不影响整个正则表达式的其他部分。

非捕获组在编写更复杂的正则表达式时非常有用,尤其是当不需要捕获某个特定部分,但仍然想利用分组提供的分组功能(如条件匹配或引用)时。使用非捕获组可以减少正则表达式的复杂性和潜在的性能开销。

2.7 样例

(1)匹配数字

\d+
  • 1

这个表达式会匹配一个或多个连续的数字字符,如 “123”、“4567” 等:其中 \d 表示匹配一个任何数字,后面的 + 号表示匹配前面的子表达式一次或多次。

(2)匹配电子邮件地址

[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}
  • 1

这个表达式会匹配大多数常见的电子邮件地址格式。

对于 [a-zA-Z0-9._%±]+ ,其中的 [a-zA-Z0-9] 匹配任何单个的小写字母、大写字母或数字。 ._%± 匹配点号、下划线、百分号、加号和减号中的任何一个。 + 表示前面的字符集(即 [a-zA-Z0-9._%±])可以出现一次或多次。

对于 @ ,用于匹配字面上的 “@” 符号。这是电子邮件地址的标准部分,用来分隔用户名和域名。

对于 [a-zA-Z0-9.-]+ ,其中的 [a-zA-Z0-9] 匹配任何单个的小写字母、大写字母或数字。 .- 匹配点号或减号中的任何一个。 + 表示前面的字符集(即 [a-zA-Z0-9.-])可以出现一次或多次。

对于 \. ,用于匹配字面上的点号(.)。在正则表达式中,点号是一个特殊字符,所以需要使用反斜杠 \ 进行转义,使其表示字面上的点号。

对于 [a-zA-Z]{2,} ,其中的 [a-zA-Z] 匹配任何单个的小写字母或大写字母。 {2,} 表示前面的字符集(即 [a-zA-Z])必须出现至少两次。

(3)匹配 IPv4 地址

\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b
  • 1

这个表达式会匹配 IPv4 地址,如 “127.0.0.1”。

对于第一个 \b ,这是一个单词边界,确保 IPv4 地址是一个独立的单词,而不是其他文本的一部分。

对于 (?: … ) ,这是一个非捕获组,它允许对一组模式进行分组,但不捕获匹配的文本。非捕获组主要用于组织正则表达式,而不影响匹配的结果。

对于 (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) ,这是一个捕获组,用于匹配一个 0 到 255 之间的数字。其中的 25[0-5] 匹配250到255之间的数字。 2[0-4][0-9] 匹配200到249之间的数字。 [01]?[0-9][0-9]? 匹配0到199之间的数字。

对于 \. ,匹配点号(.)。在正则表达式中,点号是一个特殊字符,表示任何字符(除了换行符)。为了匹配字面上的点号,需要使用反斜杠进行转义。

对于 {3} ,这表示前面的非捕获组(即数字段和点号的组合)必须出现三次。这确保了 IPv4 地址有三个点号分隔四个数字段。

对于 (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) ,用来匹配最后一个数字段。

对于最后一个 \b ,再次使用单词边界,确保 IPv4 地址是独立的。

(4)匹配日期(YYYY-MM-DD 格式)

\b\d{4}-\d{2}-\d{2}\b
  • 1

这个表达式会匹配形如 “2010-01-12” 的日期格式。

3 正则表达式高级特性

3.1 贪婪匹配与懒惰量词

在正则表达式中,贪婪匹配(greedy matching)和懒惰匹配(lazy matching)或称为非贪婪匹配(non-greedy matching)是量词(quantifiers)的两种不同行为模式。贪婪量词会尽可能多地匹配字符,而懒惰量词或非贪婪量词则会尽可能少地匹配字符。
贪婪匹配
贪婪量词会尽可能多地匹配字符,直到遇到无法继续匹配的字符为止。常见的贪婪量词有:

(1)*: 匹配前面的子表达式零次或多次。

(2)+: 匹配前面的子表达式一次或多次。

(3)?: 匹配前面的子表达式零次或一次。

(4){n,}: n 是一个非负整数,匹配至少 n 次。

(5){n,m}: m 和 n 均为非负整数,其中 n <= m,匹配至少 n 次,但不超过 m 次。

例如,正则表达式 a*b 在字符串 “aaab” 中会匹配整个 “aaab”,因为 * 是贪婪的,它会尽可能多地匹配 “a”。

懒惰量词(非贪婪匹配)
懒惰量词(非贪婪匹配)会尽可能少地匹配字符,直到遇到能够继续匹配的字符为止。要在贪婪量词后面加上一个 ? 来使其成为懒惰量词。

(1)*?: 非贪婪匹配,尽可能少地匹配前面的子表达式。

(2)+?: 非贪婪匹配,尽可能少地匹配前面的子表达式。

(3)??: 非贪婪匹配,在可能的情况下尽可能少地匹配前面的子表达式。

(4){n,}?: 非贪婪匹配,至少匹配 n 次。

(5){n,m}?: 非贪婪匹配,至少匹配 n 次,但不超过 m 次。

例如,正则表达式 a*?b 在字符串 “aaab” 中会匹配最短的 “a” 后面跟着 “b”,即 “ab”,因为 *? 是非贪婪的,它会尽可能少地匹配 “a”。

样例
考虑字符串 <div>Hello</div><div>World</div>,需要提取两个 <div> 标签之间的内容。

使用贪婪匹配的正则表达式:

<div>(.*)</div>
  • 1

这个表达式会匹配整个字符串,因为 .* 是贪婪的,它会匹配尽可能多的字符。

要使用非贪婪匹配来提取每个 <div> 标签内的内容,则需要使用懒惰量词:

<div>(.*?)</div>
  • 1

在这里,.*? 会尽可能少地匹配字符,因此它会匹配每个单独的 <div> 标签内的内容,而不是整个字符串。

3.2 前瞻断言与后顾断言

正则表达式中的前瞻断言和后顾断言(也称为后发断言)允许你基于当前位置之前的或之后的字符序列来匹配文本。它们用于检查一个子串前后上下文的情况。

前瞻断言
前瞻断言支持检查当前位置之后的字符序列。在大多数正则表达式引擎中,前瞻断言通常使用?=(正向前瞻断言)或?!(负向前瞻断言)来表示。

?=: 正向前瞻断言,表示"后面跟着…“。
?!: 负向前瞻断言,表示"后面不跟着…”。

假设有一个包含多个 HTML 链接的文本,并且需要提取所有的链接地址,但只提取那些链接文本为"news"的链接地址,其正则表达式为:

<a href="([^"]+)">(?=news)</a>
  • 1

后顾断言
后顾断言(后发断言)支持检查当前位置之前的字符序列。后顾断言通常使用?<=(正向后顾断言)或?<!(负向后顾断言)来表示。

?<=: 正向后顾断言,表示"前面是…“。
?<!: 负向后顾断言,表示"前面不是…”。

假设有一个文本,其中包含多种日期格式,但只想匹配那些前面有"Tomorrow is"的日期,其正则表达式为:

(?<=Tomorrow is) \d{4}-\d{2}-\d{2}
  • 1

注意:前瞻和后顾断言只是检查文本,并不消耗字符,因此它们不会成为匹配结果的一部分。

3.3 嵌套与递归

正则表达式的嵌套指的是在一个正则表达式内部使用另一个正则表达式。这通常是通过使用捕获组或者非捕获组来实现的。递归正则表达式则是一种特殊类型的嵌套,其中正则表达式能够引用自身来匹配嵌套或重复的结构。

以下是递归正则表达式的样例,该样例用于匹配正确嵌套的括号对(圆括号、方括号或花括号),其正则表达式为:

(?R)|[^()\[\]{}]+
  • 1

这个正则表达式使用了(?R)来引用整个正则表达式本身,实现递归。 [^()\[\]{}]+ 匹配任何不是括号字符的字符序列。这个表达式将匹配任何正确嵌套的括号对,包括混合类型的括号。

其中, (?R) 表示一个递归调用,它告诉引擎再次尝试匹配整个正则表达式。

| 表示一个选择运算符,它允许匹配 (?R) 或后面的模式。

[^()\[\]{}]+ 表示匹配任何不是括号字符(圆括号、方括号或花括号)的字符序列。 + 表示匹配一个或多个这样的字符。

注意:由于递归正则表达式的复杂性,它们可能会导致性能问题,尤其是在处理大型或复杂的输入时。此外,不是所有的正则表达式引擎都支持(?R)语法。在使用递归正则表达式时,请确保正则表达式引擎支持这种功能,并注意性能影响。

4 C++ 中的正则表达式

C++11 引入了 <regex> 头文件,提供了对正则表达式的原生支持。C++ 的正则表达式库基于 ECMAScript 正则表达式语法,并且与 POSIX 兼容。C++ 中的正则表达式库允许你编译正则表达式,然后使用这些编译后的模式来匹配、搜索、替换字符串中的文本。

4.1 std::regex

std::regex 用于表示编译后的正则表达式模式。可以使用 std::regex 对象来执行各种正则表达式相关的操作,如匹配、搜索和替换。
(1)创建 std::regex 对象
要创建一个 std::regex 对象,需要提供一个正则表达式字符串和可选的标志位来指定正则表达式的语法和行为:

#include <regex>  
  
// 使用默认 ECMAScript 语法创建一个正则表达式对象  
std::regex ecmascript_regex(R"(\d+)"); // 匹配一个或多个数字  
  
// 使用基本语法创建一个正则表达式对象  
std::regex basic_regex(R"(\d+)", std::regex_constants::basic);  
  
// 使用扩展语法和忽略大小写的标志创建正则表达式对象  
std::regex extended_regex(R"(hello)", std::regex_constants::extended | std::regex_constants::icase);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

上面代码中使用默认 ECMAScript 语法和使用基本语法创建 std::regex 对象的区别主要在于所支持的特性和语法。

  • 默认ECMAScript语法(不显式指定标志):

当不提供任何标志来编译正则表达式时,C++ 默认使用 ECMAScript 语法。
ECMAScript 语法通常与 JavaScript 中的正则表达式语法相似,因此它对于前端开发者来说可能是更熟悉的。
ECMAScript 语法支持许多常见的正则表达式特性,如字符类、量词、分组、捕获组、前瞻断言等。
它也支持一些特定的 ECMAScript 扩展,例如非捕获组(?:…)。

  • 基本语法(使用 std::regex_constants::basic 标志):

基本语法主要基于 POSIX 风格的正则表达式,这意味着它不支持一些 ECMAScript 特有的特性,如前瞻断言。
基本语法通常更加简单和直接,对于只需要基础正则表达式的场景可能更合适。

(2)基本使用

#include <iostream>
#include <regex>

int main()
{
	std::regex reg(R"(ab|cd)"); // 匹配"ab"或"cd"  

   // 测试字符串  
	std::string testStr = "abcdef";

	if (std::regex_search(testStr, reg))
	{
		std::cout << "successfully found" << std::endl;
	}

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

上面代码的输出为:

successfully found
  • 1

4.2 std::regex_match 与 std::regex_smatch

std::regex_match 和 std::regex_smatch 都用于正则表达式匹配,但它们有不同的用途和语义。

std::regex_match:
std::regex_match 用于检查整个输入字符串是否与正则表达式完全匹配。如果整个字符串都符合正则表达式的模式,那么 std::regex_match 返回 true;否则,返回 false。它不接受一个单独的 std::match_results 对象作为参数,但可以接受一个 std::match_results 引用以捕获匹配结果。

示例:

std::string text = "hello";  
std::regex pattern(R"(hello)");  
bool isMatch = std::regex_match(text, pattern);  
// isMatch 将会是 true
  • 1
  • 2
  • 3
  • 4

std::regex_smatch:
std::regex_smatch 实际上是一个类型,而不是一个函数。它是 std::match_results 的一个特化版本,用于存储字符串的匹配结果。当使用 std::regex_search、std::regex_match 或 std::regex_replace 等函数时,如果需要捕获匹配的结果,可以使用 std::smatch 类型的变量。std::regex_smatch 用于处理 std::string 类型的输入。

示例:

std::string text = "The price is 12 dollars";  
std::regex price_pattern(R"(\d+)");  
std::smatch match;  
bool found = std::regex_search(text, match, price_pattern);  
if (found) 
{  
    std::cout << "Price found: " << match.str() << std::endl;  
}  
// 输出将会是 "Price found: 12"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

4.3 std::sregex_iterator

std::sregex_iterator 用于遍历输入字符串中所有与给定正则表达式匹配的子串。它通常与 std::sregex_token_iterator 一起使用,后者用于遍历由正则表达式分割的字符串的子串。

std::sregex_iterator 提供了从输入字符串的开始位置到结束位置的所有匹配项的迭代器接口。可以使用它来初始化一个范围,并通过这个范围迭代访问所有匹配的子串。

下面是一个使用 std::sregex_iterator 的示例,演示如何遍历一个字符串中所有匹配的数字:

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

int main() 
{
	std::string text = "There are 12 children playing on the playground, and 2 children playing soccer.";
	std::regex price_regex(R"(\d+)"); // 匹配一个或多个数字  

	// 使用 sregex_iterator 遍历所有匹配项  
	auto begin = std::sregex_iterator(text.begin(), text.end(), price_regex);
	auto end = std::sregex_iterator();

	for (std::sregex_iterator i = begin; i != end; i++)
	{
		std::smatch match = *i; // 获取当前的匹配项  
		std::cout << "Matched number: " << match.str() << std::endl;
	}

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

上面代码的输出为:

Matched number: 12
Matched number: 2
  • 1
  • 2

4.4 std::regex_search

std::regex_search 用于在输入字符串中搜索与给定正则表达式匹配的子串。如果找到匹配项,则 std::regex_search 返回 true,并可以通过传入的 std::match_results 对象来获取匹配的详细信息;如果没有找到匹配项,则返回 false。

下面是一个使用 std::regex_search 的示例:

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

int main() 
{
	std::string text = "There are 12 children playing on the playground, and 2 children playing soccer.";
	std::regex price_pattern(R"(\d+)"); // 正则表达式,匹配一个或多个数字  

	// 使用 std::regex_search 搜索匹配项  
	std::smatch match;
	bool found = std::regex_search(text, match, price_pattern);

	if (found) 
	{
		std::cout << "Match found!" << std::endl;
		std::cout << "Matched string: " << match.str() << std::endl;
		std::cout << "Position: " << match.position() << std::endl;
		std::cout << "Length: " << match.length() << std::endl;
	}
	else 
	{
		std::cout << "No match found." << std::endl;
	}

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

上面代码的输出为:

Match found!
Matched string: 12
Position: 10
Length: 2
  • 1
  • 2
  • 3
  • 4

在上面代码中,std::regex_search 在 text 字符串中搜索与 price_pattern 正则表达式匹配的子串。一旦找到匹配项,它就将匹配的结果存储在 match 对象中,并返回 true。然后可以使用 match 对象来获取匹配的字符串、匹配在字符串中的位置以及匹配的长度。

std::regex_search 默认只会返回第一个匹配项。如果你想要获取所有匹配项,可以使用上面提到的 std::sregex_iterator,它提供了一个迭代器接口来遍历所有匹配项。

4.5 std::regex_replace

std::regex_replace 用于替换基于正则表达式的子串的函数。它接受一个输入字符串、一个正则表达式模式、一个替换字符串以及一个可选的 std::regex_constants::match_flag_type 标志,并返回一个新的字符串,其中所有与正则表达式匹配的子串都被替换字符串所替代。

下面是一个使用 std::regex_replace 的例子:

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

int main() 
{
	std::string text = "test hello world hello";
	std::regex reg(R"(\bhello\b)"); // 正则表达式,匹配单词 "hello"  
	std::string replacement = "ok"; // 替换字符串  

	// 使用 std::regex_replace 替换匹配的子串  
	std::string newText = std::regex_replace(text, reg, replacement);

	std::cout << newText << std::endl;

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

上面代码的输出为:

test ok world ok
  • 1

在这个例子中,std::regex_replace 使用正则表达式 \bhello\b 来匹配单词 “hello”,并用字符串 “ok” 替换它。\b 是一个单词边界,确保仅匹配整个单词 “hello”,而不是其他包含 “hello” 的子串。

如果需要在替换字符串中使用原始匹配的子串,则可以使用特殊语法 $& 来引用整个匹配项,$n 来引用第 n 个捕获组,其中 n 是一个正整数:

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

int main() 
{
	std::string text = "1 apple, 2 oranges, 3 bananas";
	std::regex reg(R"(\d+)"); // 正则表达式,匹配数字  
	std::string replacement = "[$&] Fruits"; // 在替换字符串中使用 $& 引用匹配的数字  

	std::string newText = std::regex_replace(text, reg, replacement);

	std::cout << newText << std::endl;

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

上面代码的输出为:

[1] Fruits apple, [2] Fruits oranges, [3] Fruits bananas
  • 1

在上面代码中,$& 在替换字符串中被用来插入原始匹配的数字。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/153850
推荐阅读
相关标签
  

闽ICP备14008679号