using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
//注意在文件头引用命名空间 System.Text.RegularExpressions。
namespace Pattern202405
{
internal class Program
{
static void Main(string[] args)
{
string text = "The Colony is a beautiful town...Kitty lives in Room415...";
//正则表达式
Regex expression = new Regex(@"Room\d\d\d");
//找出所有匹配的字符串,放入集合中
MatchCollection matches = expression.Matches(text);
//输出匹配的字符串
foreach (Match match in matches)
{
Console.WriteLine("She lives in {0}", match);
Console.ReadKey();
}
}
}
}
可选字符集
除了通配符外,我们还可以把某个位置上允许出现的字符写在方括号[]内,组成可选字符集,比如
abc\]表示该位置可以出现字母 a、b、c
\[A-D\]表示该位置可以出现字母 A、B、C、D
\[A-DM-P\]表示该位置可以出现字母 A 到 D 或 M 到 P
\[A-Da-d\]表示该位置可以出现字母 A 到 D 或 a 到 d
\[12\] 表示该位置可以出现数字 1 或数字 2
\[1-5\]表示该位置可以出现数字 1 到 5
\[0-57-9\]表示该位置可以出现除 6 外的所有数字
\[\\s\\S\]表示该位置可以出现任何字符,包括任何可见字符和不可见字符(如空格、制表符、换行等)
如果想从字符串中找出名字以 V 或 R 开头,出生于 80\~90 年代的男孩,可以用下面
的正则表达式:
\[VR\] \[a-z\] \[a-z\] \[a-z\] \[a-z\] - 1 9 \[89\] \[0-9
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Pattern202405_2
{
internal class Program
{
static void Main(string[] args)
{
string boys = "Vitor-1979 Verne-1982 Regan-1998 Robin-2008";
//string text = "Vitor-1970 Verne-1982 Regan-1998 Robin2008";
//正则表达式
Regex expression = new Regex(@"[VR][a-z][a-z][a-z][a-z]-19[89][0-9]");
//获取并输出匹配的字符串
foreach (Match match in expression.Matches(boys))
{
Console.WriteLine(match);
}
Console.ReadKey();
}
}
}
启动程序:
反向字符集
在中括号表达式中用符号"^"表示"非"。比如
\^x\] 匹配除 x 以外的所有字符
\[\^abc\] 匹配除 a、b、c 以外的所有字符
\[\^0-9\] 匹配除 0 到 9 以外的所有字符
\[\^#\] 匹配除#以外的所有字符
\[\^\\n\] 匹配除换行符\\n 以外的所有字符
string sentence = "bog dog fog hog log";
//正则表达式
Regex expression = new Regex(@"\[\^bd\]og");
//输出匹配的字符串
foreach (Match match in expression.Matches(sentence ))
{
Console.WriteLine(match);
}

正则表达式"\[\^bd\]og"匹配的字符串包含三个字符,第一个字符 是除 b 和 d 以外的字符,后两个字符为 og
###### 或匹配
在正则表达式中用符号"\|"表示"或"。比如
"x\|y" 匹配 "x"或"y"
"good\|ok" 匹配 "good"或"ok"
"(tr\|b)ee" 匹配 "tree"或"bee"
"th(i\|a)nk" 匹配 "think"或"thank"
"Book One\|Two" 匹配 "Book One"或"Two"
"Book (One\|Two)" 匹配 "Book One"或"Book Two"
###### 数量限定符
假设我们要从一串人名中找出以 V 开头的人名,因为人名的长度不是固定的,所以用 一一对应的方法就无法实现,这时需要使用数量限定符
数量限定符"\*"将前面的字符重复 0 次或多次。
string words = "lg log loog looog loooog looooog";
//正则表达式
Regex expression = new Regex("lo\*g");
//获取并输出匹配的字符串
foreach (Match match in expression.Matches(words))
{
Console.WriteLine(match);
}
正则表达式"lo\*g"表示字符串的第一个字符为 l,最后一个字符为 g,中间有零个或
多个字母 o。运行结果如图 所示

数量限定符"+"将前面的字符重复 1 次或多次
//正则表达式 Regex expression = new Regex("lo+g");
数量限定符"?"将前面的字符重复 0 次或 1 次。
//正则表达式 Regex expression = new Regex("lo?g");
数量限定符{n,}表示把它前面的字符至少重复 n 遍
//正则表达式 Regex expression = new Regex("lo{3}g");
正则表达式"lo{3}g"表示字符串的第一个字符为 l,最 后一个字符为 g,中间有 3 个字母 o。
数量限定符{n,m}把前面的字符重复 n 至 m 遍
//正则表达式 Regex expression = new Regex("lo{2,4}g");
正则表达式"lo{2,4}g"表示字符串的第一个字符为 l,最后一个字符为 g,中间有 2 到 4 个字母 o。
正则表达式"l(eo)+g"表示字符串的第一个字符为 l,最后一个字符为 g,中间有一个 或多个 eo。
最奇妙的是这些数量限定符还可以与通配符组合。比如通配符"\\w"匹配任意单词字 符,限定符"\*"表示把前面的字符重复 0 次或多次,所以正则表达式"\\w\*"匹配由任意 单词字符组成的任意长度的字符串。
string girls = @"Van is 16; Vicky is 18; Vivien is 19; Vinvcent is 22";
Regex expression = new Regex(@"V\\w\* is \\d\\d");
foreach (Match match in expression.Matches(girls))
{
Console.WriteLine(match);
}

###### 贪婪和懒惰 的限定符
"贪婪的"(Greedy),它们会匹配尽可能多的文本。如果在限定符后 加上?号,它就会变成"懒惰的"(Lazy),会匹配尽可能少的文本。
限定符"\<.\*\>"和"\<.\*?\>"都表示以"\<"开头,以"\>"结尾,中间是若干个字符的字符串,
贪婪限定符尽量找到一个最长(保留中间可能的匹配)的结果,而懒惰限定符尽量找最短的结 果(忽略中间可能的匹配)。 在匹配以特定字符为首尾的字符串时,懒惰的限定符往往能轻松的达到目的。
```cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace 贪婪和懒惰
{
internal class Program
{
static void Main(string[] args)
{
string words = "ab
Hello World
c";
//贪婪的限定符
Regex expression1 = new Regex("<.*>");
MatchCollection matchs1 = expression1.Matches(words);
Console.WriteLine("There is {0} match with greedy quantifier:", matchs1.Count);
foreach (Match match in matchs1)
{
Console.WriteLine(match);
}
//懒惰的限定符
Regex expression2 = new Regex("<.*?>");
MatchCollection matchs2 = expression2.Matches(words);
Console.WriteLine("There are {0} matchs with lazy quantifier:", matchs2.Count);
foreach (Match match in matchs2)
{
Console.WriteLine(match);
}
Console.ReadKey();
}
}
}
```

懒惰限定符,有两个匹配模式值得注意。一个是"{n}?",它实际上与"{n}"是 等价的,因为不管是贪婪还是懒惰,都已经限定死了,只能重复 n 次。
另一个匹配模式是"??",我们知道"?"表示把前面的字符重复 0 到 1 次,而"??"又是什么意思呢?
string sentence = "Where bee is, there is honey.";
//用?限定
Regex expression1 = new Regex("be?");
foreach (Match match in expression1.Matches(sentence))
{
Console.WriteLine("be? = {0}", match);
}
//用??限定
Regex expression2 = new Regex("be??");
foreach (Match match in expression2.Matches(sentence))
{
Console.WriteLine("be?? = {0}", match);
}
运行结果如图

在正则表达式"be?"中,"?"并不是把整个单词 be 重复 0 到 1 次,而 是把"?"前面字母"e"重复 0 到 1 次。因为"be?"是贪婪的,能重复 1 次就不重复 0 次,所以结果中 e 出现了 1 次;
而"be??"是懒惰的,重复次数要尽量少,所 以它选择把 e 重复 0 次
所有限定符都可以和"?"组合

###### 定位符
通过定位符可以在指定位置寻找匹配的子串。 若正则表达式中使用了定位符"\^",则在整个字符串的头部寻找匹配的子串。
string words = "year1998 year2008 year2018";
//正则表达式
Regex expression = new Regex(@"\^year\\d\\d\\d\\d");
//获取并输出匹配的字符串
foreach (Match match in expression.Matches(words))
{
Console.WriteLine(match);
}
"\^"匹配字符串的开头位置,所以"\^year\\d\\d\\d\\d"表示要从字符串的开头位置开始寻 找。运行结果如图

若正则表达式中使用了定位符"$",则在整个字符串的尾部寻找匹配的子串
//正则表达式 Regex expression = new Regex(@"year\\d\\d\\d\\d$");
"$"匹配字符串的结尾位置,所以"year\\d\\d\\d\\d$"表示匹配的子串紧邻字符串的结尾 位置。运行结果如图 所示

若正则表达式中使用了定位符"\\b",则在字符串中每个"单词"的边界①寻找匹配 的子串。
string words = "formfordfork";
Console.Write("None anchors:");
Regex expression = new Regex(@"for\\w");
foreach (Match match in expression.Matches(words))
{
Console.Write(match + "\\t");
}
Console.Write("\\nAnchor start:");
expression = new Regex(@"\\bfor\\w");
foreach (Match match in expression.Matches(words))
{
Console.Write(match + "\\t");
}
Console.Write("\\n Anchor end:");
expression = new Regex(@"for\\w\\b");
foreach (Match match in expression.Matches(words))
{
Console.Write(match + "\\t");
}
"\\b"匹配单词的边界位置。运行结果如图 所示。由结果可以看出,当没有定位 符时,匹配单词中所有符合要求的子串;当定位符\\b 在前面时,在单词的头部寻找匹配的子串;当定位符\\b 在后面时,在单词的结尾寻找匹配的子串

常用正则表达式"\\b\\w+\\b"找出一句话里的所有单词。
用于正则表达式的定位符如表
 符号"\^"和"$"在正则表达式中也有特殊用处,所以当要 匹配它们本身时也需使用它们的转义字符"\\\^"和"\\$"。
###### 分组和后向引用
如果没有括号,正则表达式"tr\|bee"匹配"tr"或"bee",加了括号后,"(tr\|b)ee"匹 配"tree"或"bee",这种带括号的形式称为括号表达式。
括号表达式并不简单的起着确 定范围的作用,它同时会创建子表达式,每个子表达式形成一个分组,并把捕获到的与子 表达式匹配的子串保存在分组中,以供将来使用
举一个例子,电子邮件地址通常由用户名、二级域名、一级域名三部分构成。比 如 waterwood @163.com,其中 waterwood 为用户名,163 为二级域名,com 为一级域名。 我们可以通过下面的正则表达式匹配这类电子邮件地址: "(\\w+)@(\\w+)\\.(\\w+)"
这里有三个括号,形成三个子表达式,就会出现三个分组。默认情况下,每个分组会 自动拥有一个组号,规则是:从左向右,按分组左括号的出现顺序进行编号,第一个分组 的组号为 1,第二个为 2,以此类推。在正则表达式里引用分组的语法为"\\number",比如 "\\1"代表与分组 1 匹配的子串,"\\2"代表与分组 2 匹配的字串,等等
如果出现嵌套的分组,它们的编号也按左括号的出现顺序排列。

存储起来的分组有什么作用呢?下面我们通过一个例子来说明。很多句子里会有 重复的单词,下面我们就通过正则表达式找出这些重复的单词。
string text = "you are very very good";
foreach (Match match in Regex.Matches(text, @"\\b(\\w+)\\b \\1\\b"))
{
Console.WriteLine(match);
}
运行结果如图 所示

我们前面已经知道"\\b(\\w+)\\b"匹配一个单词,因为这里面有一个括号,所以会捕获 一个以 1 为组号的分组,后面的"\\1"就代表这个分组,表示这个位置上出现和分组 1 一 样的内容。所以两者连起来就表示两个重复的单词。这种在后面的表达式中引用前面的分组的方式就叫做后向引用
除了匹配重复的单词,后向引用还有一个较为常见的应用,那就是匹配有效的 HTML 标签。我们知道,HTML 标签大多是成对出现的
string words = "\not valid\ \