正则表达式
正则表达式提供了灵活高效的处理文本字符串的方法。正则表达式的全面模式匹配表示法使程序员可以快速分析大量文本并找到特定的字符模式,能够提取、编辑、替换或删除文本子字符串,也可以将提取的字符串添加到集合中。对于处理字符串的许多应用程序而言,正则表达式是不可缺少的工具。
1 正则表达式简介
正则表达式最初是在UNIX 环境中开发的,与UNIX 环境下编程的Perl 脚本语言一起发挥了强大的功能。Microsoft .NET Framework 正则表达式兼容Perl 正则表达式,并在此基础上增加了新的功能,例如反向匹配和即时编译等。在本章的介绍中,我们所说的正则表达式均指Microsoft .NET Framework 正则表达式。
所谓正则表达式,就是用某种模式去匹配指定字符串的一种表示方式。正则表达式由普通字符和元字符组成。普通字符指我们平常使用的字符,例如字母、数字、汉字等;元字符指可以匹配某些字符形式的具有特殊含义的字符,其作用类似于DOS 命令使用的通配符。
1.1 基本书写符号
与算术表达式需要用加减乘除以及小括号表示不同的含义相似,正则表达式的基本书写符号同样表示了特殊的含义。在第2 章,我们曾经学习过用反斜杠指定的转义符,它可以表示一些特殊的字符,包括具有特殊含义的字符本身。除此之外,还必须再增加一些包含元字符或者对元字符进行分组的其他特殊的表示形式,以组成各种复杂的正则表达式。表1列出了正则表达式的常用基本书写符号。
表 1 正则表达式的基本书写符号
符号 | 含义 | 示例 | 解释 | 匹配输入 |
\ | 转义符 | \# | 符号“#” | # |
[ ] | 可接收的字符列表 | [abcd] | a、b、c、d中的任意1 个字符 | a、b、c、d |
[^ ] | 不接收的字符列表 | [^abc] | 除a、b、c之外的任意1 个字符,包括数字和特殊符号 | d、e、1、# |
| | 匹配“|”之前或之后的表达式 | ab|cd | ab或者cd | ab、cd |
( ) | 将子表达式分组 | (abc) | 将字符串abc作为一组 | abc |
- | 连字符 | a-z | 任意单个小写字母 | 小写字母 |
注意,在定义字符列表时,如果可接收的字符在字符编码中是连续的,则可以使用连字符“–”指定字符范围。如[0-9]表示字符列表是从0 到9 的10 个字符;[a-d]表示字符列表是从a到d 的4 个小写英文字母。
1.2 限定符
限定符用于限制某个字符串满足正则表达式要求的特征,如起止字符、特定字符或字符集重复次数等。常用的限定符如表2 所示。
表 7-2 正则表达式中的部分限定符
符号 | 含义 | 示例 | 解释 | 匹配输入 | 不匹配输入 |
* |
指定字符重复0次或n次 |
(abc)* | 仅包含任意个abc的字符串 | abc、abcabcabc | a、abca |
+ | 指定字符重复1次或n次 | m+(abc)* |
以至少1 个m开头,后接任意个abc的字符串 |
m、mabc、mabcabc | ma、abc |
? |
指定字符重复0次或1次 |
m+abc? |
以至少1 个m开头,后接ab或abc的字符串 |
mab、mabc、mmmab、 mmabc |
ab、abc、mabcc |
^ | 指定起始字符 | ^[0-9]+[a-z]* |
以至少1 个数字开头,后接任意个小写字母的字符串 |
123、6aa、555edf | abc、aaa、a33 |
$ | 指定结束字符 | ^[0-9]\-[a-z]+$ |
以1 个数字开头,后接连字符“–”,并以至少1个小写 字母结尾的字符串 |
2-a、3-ddd、5-efg | 33a、8-、7-Ab |
{n} | 只能输入n个字符 | [abcd]{3} |
由abcd中字母组成的任意长度为3 的字符串 |
abc、dbc、adc | a、aa、dcbd |
{n,} | 至少输入n个字符 | [abcd]{3,} |
由abcd中字母组成的任意长度不小于3的字符串 |
aab、dbc、aaabdc | a、cd、bb |
{n,m} |
输入至少n个,至多m个字符的 字符串 |
[abcd]{3,5} |
由abcd中字母组成的任意长度不小于3,不大于 5的字符串 |
abc、abcd、aaaaa、 bcdab |
ab、ababab、a |
1.3 匹配字符集
匹配字符集是预定义的用于正则表达式中的符号集。如果字符串与字符集中的任何一个字符相匹配,它就会找到这个匹配项。使用匹配字符集有效的简化了表达式的书写。常用的匹配字符集如表3 所示。
表 3 正则表达式中的部分匹配字符集
符号 | 含义 | 示例 | 解释 | 匹配输入 | 不匹配输入 |
. |
匹配除换行(\n)之外的任何单个字 符 |
a..b |
以a开头,b结尾,中间包括2个 任意字符的长度为4 的字符串 |
aaab、aefb、 |
ab、aaaa、a347b |
\d | 匹配单个数字字符,相当于[0-9] | \d{3}(\d)? | 包含3 个或4 个数字的字符串 | 123、9876 | 12、01023 |
\D | 匹配单个非数字字符,相当于[^0-9] | \D(\d)* |
以单个非数字字符开头,后接任 |
a、A342 | aa、AA78、1234 |
\w |
匹配单个数字、大小写字母字符, |
\d{3}\w{4} |
以3 个数字字符开头的长度为7 |
234abcd 、 |
58a、Ra46 |
\W |
匹配单个非数字、大小写字母字符, |
\W+\d{2} |
以至少1 个非数字字母字符开 |
#29、#?@10 | 23、#?@100 |
匹配字符集只能用于检查字符串中是否包含指定的字符,如果将其用于验证输入内容是否合法,还需要在此基础上增加一些限定符。
1.4 分组构造
当正则表达式比较复杂时,可以将其分组,以便捕获子表达式组,表4 列出了本文用到的部分分组构造形式。
表 4 本文用到的分组构造形式
分组构造 | 说明 |
( ) |
非命名捕获。用于捕获匹配的子字符串(或非捕获组)。编号为零的第一个捕获是由整个正则表达式模式匹配的 |
(?<name>) |
命名捕获。用于将匹配的子字符串捕获到一个组名称或编号名称中。用于name的字符串不能包含任何标点符号, |
不论是非命名捕获还是命名捕获,都是根据左括号从左到右按顺序编号,而且编号为0的第一个捕获结果总是指全部匹配的结果。如果在正则表达式中既有命名捕获又有非命名捕获,则先处理所有非命名捕获,并对其进行编号,处理完成后,再重新从左到右开始处理命名捕获,但是组的编号仍继续增加。例如,对于模式:
((?<One>abc)/d+)?(?<Two>xyz)(.*)
产生的捕获组分别为:
第0 组:((?<One>abc)/d+)?(?<Two>xyz)(.*)
第1 组(非命名捕获):((?<One>abc)/d+)
第2 组(非命名捕获):(.*)
第3 组(命名捕获):(?<One>abc)
第4 组(命名捕获):(?<Two>xyz)
除了上面介绍的基本内容外,正则表达式还提供了回溯和替换模式等其他更高级的处理功能,有兴趣的读者可以进一步参考相关资料。
1.5 正则表达式书写举例
下面列出一些常用的正则表达式,读者可以通过这些例子举一反三,写出符合程序中需要的各种正则表达式。
1) 至少1 个字符:.{0,}
2) 3 个“.”句点符号:\.{3}
3) 括号括起来的2-3 个数字构成的字符串:\([0-9]{2,3}\)
其中的反斜杠“\”表示转义。
4) 必须包含“ab”的字符串:.{0,}ab.{0,}
5) 网址URL:[a-zA-Z]+://(w+(-w+)*)(.(w+(-w+)*))*(?S*)?
6) 以字母开头,允许包含字母、数字及下划线,长度为5-16:[a-zA-Z][a-zA-Z0-9_]{5,16}
7) 电子邮件(方法1):
([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)
8) 电子邮件(方法2):\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
9) 国内电话号码:(d{3}-|d{4}-)?(d{8}|d{7})
10) 至少3 个汉字:[\u4e00-\u9fa5]{3,}
注意,在Windows 应用程序中,如果利用正则表达式验证输入结果是否有效,一般在正则表达式的前面加上“^”,表示与输入字符串的起始字符相匹配,在后面加上“$”,表示与输入字符串的结束字符相匹配,例如“^[\u4e00-\u9fa5]{3,}$”表示字符串必须全部是汉字,而且至少有三个。
如果利用正则表达式搜索包含的子串,而不是检查匹配的字符串是否刚好和指定的字符串相等,则一般不加这两个符号。
2 Regex类
在System.Text.RegularExpressions 命名空间下,提供了正则表达式相关的类。包括Regex类、Match 类、MatchCollection 类、Capture 类、CaptureCollection 类、Group 类和GroupCollection类等。
Regex 类提供了使用正则表达式的方法。这个类的使用比较特殊,既可以直接调用该类提供的静态方法,也可以创建该类的一个实例后再调用实例方法。如果直接使用静态方法,则在方法的参数中指定正则表达式;如果创建了实例,则在构造函数的参数中指定正则表达式。常用方法有:
1) IsMatch 方法:表示正则表达式在输入字符串中是否找到匹配项。该方法有四种重载的形式:
public bool IsMatch(string str);
表示在构造函数中指定的正则表达式在str 中是否找到匹配项。
public bool IsMatch(string str, int start);
表示在构造函数中指定的正则表达式在str 的指定起始位置开始是否找到匹配项。参数start 表示开始搜索的字符位置。
public static bool IsMatch(string str, string pattern);
表示使用pattern 参数中指定的正则表达式是否在str 中找到匹配项。
public static bool IsMatch(string str, string pattern, RegexOptions options);
表示使用pattern 参数中指定的正则表达式和options 枚举提供的匹配选项在str 中是否找到匹配项。其中options 是RegexOption 枚举值的按位“或”组合。
例如:
Regex r = new Regex(@"[0-9a-z]{3,5}"); string[] tests = { "abc", "123456", "(aa22bb33)", "ab" }; foreach (string test in tests) { if (r.IsMatch(test)) { Console.WriteLine("{0}中有匹配的项", test); } else { Console.WriteLine("{0}中没有匹配的项", test); } }
2) Match 方法:在输入字符串中搜索正则表达式的匹配项,并将精确结果作为单个Match对象返回。例如:
Regex r = new Regex(@"(\w+)\s+(car)", RegexOptions.IgnoreCase); //不区分大小写 Match m = r.Match("One car red car blue car"); int matchCount = 0; while (m.Success) { Console.WriteLine("Match{0},{1}", ++matchCount, m.Index); m = m.NextMatch(); }
运行结果为:
Match1,0
Match2,8
Match3,16
3) Matchs 方法:在输入字符串中搜索正则表达式的所有匹配项并返回所有成功的匹配,就像多次调用Match 一样。例如:
Regex r = new Regex(@"\w+"); string text = "The abc 123."; MatchCollection matches = r.Matches(text); Console.WriteLine("{0} matches found.", matches.Count);
【例】编写一个Windows 应用程序,输入某个正则表达式和一个字符串,然后验证该字符串中是否包含与正则表达式匹配的内容。
(1) 创建一个名为RegexExample1 的Windows 应用程序, 修改Form1.cs 为FormRegexValidation.cs,设计界面如图7-1 所示。
(2) 添加对应的事件:
//单击【开始验证】按钮触发的事件 private void buttonValidation_Click(object sender, EventArgs e) { System.Text.RegularExpressions.Regex r = new System.Text.RegularExpressions.Regex(textBoxValidateString.Text); if (r.IsMatch(textBoxValidatedString.Text) == true) { textBoxIsMatch.Text = "有匹配项"; } else { textBoxIsMatch.Text = "没有匹配项"; } } // textBoxValidatedString 成为活动控件时触发的事件 private void textBoxValidatedString_Enter(object sender, EventArgs e) { textBoxIsMatch.Clear(); } // textBoxValidateString 成为活动控件时触发的事件 private void textBoxValidateString_Enter(object sender, EventArgs e) { textBoxIsMatch.Clear(); }
(3) 按<F5>键编译并运行,输入7.1.1 正则表达式书写举例中的正则表达式,或者其他自定义的正则表达式,然后输入被验证的字符串,单击【开始验证】按钮,观察自己的理解是否正确。
注意,这个例子仅检查输入字符串中是否包含与正则表达式匹配的子串,该子串可能刚好和字符串相等,也可能不相等。如果希望验证的字符串刚好和正则表达式要求匹配的字符串相等,必须在正则表达式的前面加上符号“^”,在正则表达式的末尾加上符号“$”。
假如正则表达式为“\d{1,3}”,意思是1-3 个数字,则只要字符串中包含1-3 个数字的都满足匹配条件,像“256”、“2345678”等。可是如果正则表达式为“^\d{1,3}$”,则“2”、“25”、“256”均匹配,但是“2345678”不匹配。
3 Match类
Match 类表示单个正则表达式匹配操作的结果,得到的结果是只读的。该类没有公共构造函数,而是用Regex 对象的Match 方法返回的结果创建该类的对象。例如:
Regex r = new Regex("abc"); Match m = r.Match("123abc456"); if (m.Success) { Console.WriteLine("找到匹配位置:" + m.Index); Console.WriteLine("找到匹配结果:" + m.Value); }
运行结果:
找到匹配位置:3
找到匹配结果:abc
3.1 MatchCollection类
MatchCollection 类表示成功的非重叠匹配的序列,得到的集合是只读的。该类同样没有公共构造函数,而是用Regex.Matches 方法返回的结果创建该类的对象。例如:
Regex r = new Regex("abc"); MatchCollection mc = r.Matches("123abc4abcd"); int count = mc.Count; String[] results = new String[count]; int[] matchPosition = new int[count]; for (int i = 0; i < count; i++) { results[i] = mc[i].Value; matchPosition[i] = mc[i].Index; Console.WriteLine("第{0}个匹配结果:{1},位置:{2}",i+1, results[i], matchPosition[i]); }
运行结果:
第 1 个匹配结果:abc,位置:3
第2 个匹配结果:abc,位置:7
这段代码使用Regex 类的Matches 方法,通过在输入字符串中找到的所有匹配填充MatchCollection 集合,并将此集合复制到一个字符串数组和一个整数数组中,其中字符串数组用以保存每个匹配项,整数数组用以指示每个匹配项的位置。
既然已经得到了匹配的集合,再将其保存到数组中有什么用呢?这是因为得到的集合是只读的,如果我们希望对所有匹配的值进行替换操作,而且又可以取消替换,这种情况下就需要这样做了。
【例】编写一个Windows 应用程序,利用正则表达式验证用户注册信息是否符合要求。
(1) 创建一个名为RegexExample2 的Windows 应用程序,修改Form1.cs 为FormRegister.cs,设计界面如图所示。
(2) 切换到代码模式,添加命名空间引用:
using System.Text.RegularExpressions;
(3) 添加对应的方法和事件:
//更改textBoxName.Text 属性值触发的事件 private void textBoxName_TextChanged(object sender, EventArgs e) { labelName.Visible = !Regex.IsMatch(textBoxName.Text, "^[\u4e00-\u9fa5]{2,}$"); } //更改textBoxAge.Text 属性值触发的事件 private void textBoxAge_TextChanged(object sender, EventArgs e) { labelAge.Visible = !Regex.IsMatch(textBoxAge.Text, @"^\d{1,3}$"); } //更改textBoxPassword.Text 属性值触发的事件 private void textBoxPassword_TextChanged(object sender, EventArgs e) { labelPassword.Visible = !Regex.IsMatch(textBoxPassword.Text, @"^\w{5,}$"); } //单击【确定】按钮触发的事件 private void buttonOK_Click(object sender, EventArgs e) { if (labelName.Visible || labelAge.Visible || labelPassword.Visible) { MessageBox.Show("请按要求输入", "", MessageBoxButtons.OK, MessageBoxIcon.Hand); } else { MessageBox.Show("注册验证成功", "", MessageBoxButtons.OK, MessageBoxIcon.Warning); } }
(4) 按<F5>键编译并运行。
5 Group类
Group 类表示单个捕获组的结果。当与正则表达式匹配的子字符串有多组时,可以使用该类得到某一组的结果。例如:
using System; using System.Text.RegularExpressions; class Program { static void Main(string[] args) { string text = "One car red car blue car"; string pat = @"(\w+)\s+(car)"; Regex r = new Regex(pat, RegexOptions.IgnoreCase); Match m = r.Match(text); int matchCount = 0; while (m.Success) { Console.WriteLine("Match" + (++matchCount)); for (int i = 1; i <= 2; i++) { Group g = m.Groups[i]; Console.WriteLine(string.Format("Group{0}='{1}'", i, g)); CaptureCollection cc = g.Captures; for (int j = 0; j < cc.Count; j++) { Capture c = cc[j]; Console.WriteLine(string.Format( "Capture{0}='{1}', Position={2}", j, c, c.Index)); } } m = m.NextMatch(); } Console.ReadLine(); } }
输出结果:
Match1
Group1 = 'One'
Capture0='One', Position=0
Group2='car'
Capture0='car', Position=4
Match2
Group1 = 'red'
Capture0='red', Position=8
Group2='car'
Capture0='car', Position=12
Match3
Group1 = 'blue'
Capture0='blue', Position=16
Group2='car'
Capture0='car', Position=21
Group类如果不清楚可以看下这篇文章:https://www.cnblogs.com/lip-blog/p/9112285.html