C#基础回顾:正则表达式

时间:2023-12-14 10:15:08

写在前面:本文根据笔者的学习体会结合相关书籍资料对正则表达式的语法和使用(C#)进行基本的介绍。适用于初学者。

摘要:正则表达式(Regular Expressions),相信做软件开发的朋友或多或少都对其有所了解,但是你是否可以用其来解决一些问题呢?本文将带着读者从基本的正则语法入手,先向大家展示语法的全貌,然后通过实例演示来对部分语法进行详细介绍。并在结尾给出一些综合性的实例,以便大家参考。

索引
            1.正则表达式语法概述            2.正则匹配模式
            3.Dot Net正则核心对象[部分]            4.部分语法演示
            5.综合实例介绍

1.正则表达式语法概述(下表摘自网络)
      下表基本介绍了在进行正则匹配中会用到的一些元字符以及相应的描述。这个可以当作字典来用,并不要求一下子全部记住。元字符:具有特定含义的字符,而不是解释为字符本身的含义,如转义字符'\'等。元字符是区分大小写的。

元字符 描述
\ 将下一个字符标记为一个特殊字符、或一个原义字符、或一个 向后引用、或一个八进制转义符。例如,'n' 匹配字符 "n"。'\n' 匹配一个换行符。序列 '\\' 匹配 "\" 而 "\(" 则匹配 "("。
^ 匹配输入字符串的开始位置。如果设置了正则对象的 Multiline 模式,^ 也匹配 '\n' 或 '\r' 之后的位置。
$ 匹配输入字符串的结束位置。如果设置了正则对象的 Multiline 模式,$ 也匹配 '\n' 或 '\r' 之前的位置。
* 匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等价于{0,}。
+ 匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。
? 匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 中的"do" 。? 等价于 {0,1}。
{n} n 是一个非负整数。匹配确定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。
{n,} n 是一个非负整数。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。
{n,m} m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'。请注意在逗号和两个数之间不能有空格。
? 当该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 "oooo",'o+?' 将匹配单个 "o",而 'o+' 将匹配所有 'o'。
. 匹配除 "\n" 之外的任何单个字符。要匹配包括 '\n' 在内的任何字符,请使用象 '[.\n]' 的模式。当设置了正则对象的Singleline模式,也匹配"\n"
(表达式) 匹配 表达式 并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到,在VBScript 中使用 SubMatches 集合,在JScript 中则使用 $0$9 属性。要匹配圆括号字符,请使用 '\(' 或 '\)'。
(?:表达式) 匹配 表达式 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 "或" 字符 (|) 来组合一个模式的各个部分是很有用。例如, 'industr(?:y|ies) 就是一个比 'industry|industries' 更简略的表达式。
(?=表达式) 正向预查,在任何匹配 表达式 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,'Windows (?=95|98|NT|2000)' 能匹配 "Windows 2000" 中的 "Windows" ,但不能匹配 "Windows 3.1" 中的 "Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?!表达式) 负向预查,在任何不匹配 表达式 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如'Windows (?!95|98|NT|2000)' 能匹配 "Windows 3.1" 中的 "Windows",但不能匹配 "Windows 2000" 中的 "Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始
x|y 匹配 x 或 y。例如,'z|food' 能匹配 "z" 或 "food"。'(z|f)ood' 则匹配 "zood" 或 "food"。
[xyz] 字符集合。匹配所包含的任意一个字符。例如, '[abc]' 可以匹配 "plain" 中的 'a'。
[^xyz] 负值字符集合。匹配未包含的任意字符。例如, '[^abc]' 可以匹配 "plain" 中的'p'。
[a-z] 字符范围。匹配指定范围内的任意字符。例如,'[a-z]' 可以匹配 'a' 到 'z' 范围内的任意小写字母字符。
[^a-z] 负值字符范围。匹配任何不在指定范围内的任意字符。例如,'[^a-z]' 可以匹配任何不在 'a' 到 'z' 范围内的任意字符。
\b 匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。
\B 匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。
\cx 匹配由 指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 'c' 字符。
\d 匹配一个数字字符。等价于 [0-9]。
\D 匹配一个非数字字符。等价于 [^0-9]。
\f 匹配一个换页符。等价于 \x0c 和 \cL。
\n 匹配一个换行符。等价于 \x0a 和 \cJ。
\r 匹配一个回车符。等价于 \x0d 和 \cM。
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
\S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。
\t 匹配一个制表符。等价于 \x09 和 \cI。
\v 匹配一个垂直制表符。等价于 \x0b 和 \cK。
\w 匹配包括下划线的任何单词字符。等价于'[A-Za-z0-9_]'。
\W 匹配任何非单词字符。等价于 '[^A-Za-z0-9_]'。
\xn 匹配 n,其中 n 为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,'\x41' 匹配 "A"。'\x041' 则等价于 '\x04' & "1"。正则表达式中可以使用 ASCII 编码。.
\num 匹配 num,其中 num 是一个正整数。对所获取的匹配的引用。例如,'(.)\1' 匹配两个连续的相同字符。
\n 标识一个八进制转义值或一个向后引用。如果 \n 之前至少 n 个获取的子表达式,则 n 为向后引用。否则,如果 n 为八进制数字 (0-7),则 n 为一个八进制转义值。
\nm 标识一个八进制转义值或一个向后引用。如果 \nm 之前至少有 nm 个获得子表达式,则 nm 为向后引用。如果 \nm 之前至少有n 个获取,则 n 为一个后跟文字 的向后引用。如果前面的条件都不满足,若 n 和 m 均为八进制数字 (0-7),则 \nm 将匹配八进制转义值 nm
\nml 如果 n 为八进制数字 (0-3),且 m 和 l 均为八进制数字 (0-7),则匹配八进制转义值 nml。
\un 匹配 n,其中 n 是一个用四个十六进制数字表示的 Unicode 字符。例如, \u00A9 匹配版权符号 (©)。

2.正则匹配模式
      在正则匹配中,一般有三种匹配模式:单行、多行和忽略大小写。除了忽略大小写这个之外,其余2个模式很容易误导使用者(包括我自己在内),初学者会下意识的认为此2者不能共存,其实不然。这2个模式的作用效果并不冲突,使用这2个模式只会改变某几个元字符(或者称关键字)的意义,从而使得匹配的时候产生不同的效果。
            2.1 单行模式(Singleline)
            如果观察上表,会发现已经在描述[.]元字符的时候对单行模式的作用进行了解释:使得点[.]可以用来解释换行符。这在多行应用中会有很大的便捷性。试想,原来如果你需要匹配<script></script>标签内容你会如何操作:

<script>
function js()
{
   alert('恶意代码')
}
</script>

因为,[.]不解释换行符,这将使得你需要手动输入换行符来进行匹配。当启用了单行模式后,你就可以简单的一句话搞定:<script>.*?</script>。

2.2 多行模式(Multiline)
            同样上表中已经有关于多行模式的解释即:使得"^"和"$"元字符匹配每一行的开头和结尾。相比未采用该模式的时候,这两个字符匹配的则是整个字符串的开头和结尾。

2.3 忽略大小写(IgnoreCase)
            这个相信不需要多讲,如果未采用该模式,为了匹配全部所需的结果,可能你要在表达式中罗列所有大小写情况,现在你只需要启用该模式就可以省了很多麻烦。

本节内容可以参考:正则表达式的3种匹配模式

3.Dot Net正则核心对象[部分]
      命名空间:using System.Text.RegularExpressions;

3.1 Regex类
            该类是Dot Net正则表达式的核心。其中包括了若干静态方法,这使得我们可以不构造Regex对象就可以使用其功能。Regex 类是不可变(只读)的,并且具有固有的线程安全性。可以在任何线程上创建 Regex 对象,并在线程间共享。一般可以利用该类的构造函数来定义所需要的表达式及匹配模式。演示(摘自MSDN):

Regex使用演示

3.2 Match类
            
该类用于表示单个正则表达式的匹配。可以通过多种方式来得到该对象:1)利用Regex.Match()方法返回一个Match对象;2)利用Match对象本身的NextMatch()方法来返回一个新的Match对象。
            Match对象的主要属性及方法:

属性名称 说明
Captures  按从里到外、从左到右的顺序获取由捕获组匹配的所有捕获的集合(如果正则表达式用RegexOptions.RightToLeft 选项修改了,则顺序为按从里到外、从右到左)。该集合可以有零个或更多的项。(从 Group 继承。)
Empty 获取空组。所有失败的匹配都返回此空匹配。
Groups 获取由正则表达式匹配的组的集合。
Index  原始字符串中发现捕获的子字符串的第一个字符的位置。(从 Capture 继承。)
Length  捕获的子字符串的长度。(从 Capture 继承。)
Success  获取一个值,该值指示匹配是否成功。(从 Group 继承。)
Value  从输入字符串中获取捕获的子字符串。(从 Capture 继承。)
方法名称 说明
NextMatch 从上一个匹配结束的位置(即在上一个匹配字符之后的字符)开始返回一个包含下一个匹配结果的新 Match
Result 返回已传递的替换模式的扩展。例如,如果替换模式为 $1$2,则 Result 返回Groups[1].Value 和 Groups[2].Value(在 Visual Basic 中为 Groups[1].Value 和 Groups[2].Value)的串联。

3.3 Group类
            从Match类的主要属性中可以看出有一部分属性是继承自Group类,如果你看了Group类,则会发现Match和Group很类似。

属性名称 说明
Captures 按从里到外、从左到右的顺序获取由捕获组匹配的所有捕获的集合(如果正则表达式用RegexOptions.RightToLeft 选项修改了,则顺序为按从里到外、从右到左)。该集合可以有零个或更多的项。
Index  原始字符串中发现捕获的子字符串的第一个字符的位置。(从 Capture 继承。)
Length  捕获的子字符串的长度。(从 Capture 继承。)
Success 获取一个值,该值指示匹配是否成功。
Value  从输入字符串中获取捕获的子字符串。(从 Capture 继承。)
            这里只列出了常用的核心对象,其它对象请大家参考:MSDN

4.部分语法演示
            4.1 匹配纯文本
            这个是最简单的正则匹配,但是实际场景中很少会单独进行纯文本的匹配,一般都会与其它情况相结合。
            源文本:This is a test .
            表达式:test
            匹配结果:This is a test .
            C# Code

Regex r = new Regex("test");//构造表达式
Match m = r.Match("This is a test .");//匹配源文本
if(m.Success)
{//匹配成功
    Console.WriteLine(m.Value);//获取捕获的字符串
}

4.2 匹配任意字符
            匹配纯文本,并没有显示出正则表达式的魅力,接着来看看如何匹配任意字符。
            要点:1)点[.]字符,可以用来匹配任何单个字符,除换行符。只有当选择单行模式的时候,才可以匹配换行符;2)*号用来表示重复多次匹配[会在后面介绍]
            源文本:afewf@#$%^&"'./,:~!123 sffsf
            表达式:.*
            匹配结果:afewf@#$%^&"'./,:~!123 sffsf
            C# Code

Regex r = new Regex(".*");
Match m = r.Match("afewf@#$%^&"'./,:~!123 sffsf");
if(m.Success)
{
    Console.WriteLine(m.Value);
}

4.3 匹配特殊字符
            如果需要匹配特定的某个字符,该怎么办?如果你还记得4.1的演示,就应该知道可以直接用该字符去匹配。可是,如果要匹配的那个字符是元字符,那这个办法就无效了,因为它被解释为其它含义,而非字符本身。这个时候,需要用到转义字符‘\’。
            要点:使用转义字符对元字符进行转义。
            源文本:C:\windows 
            表达式:C:\\windows
            匹配结果:C:\windows
            C# Code:类似于4.1的代码,不在缀述。

4.4 匹配字符集合
            有的时候,我们要匹配的字符有多种可能的情形,比如大小写不同的字符或者干脆就是完全不同的字符。
            要点:使用“[”和“]”元字符来囊括所有可能出现的字符情形。
            源文本:1.txt 2.txt 3.txt a1.txt a2.txt 4b.txt 4B.txt
            表达式:[123bB]\.txt
            匹配结果:1.txt 2.txt 3.txt a1.txt a2.txt 4b.txt 4B.txt
            分析:“[”和“]”本身不匹配任何字符,只负责定义一个字符集合。这两个符号之间的所有组成部分都是字符。
            技巧:在使用字符集合的时候,可能会经常使用[0123456789]、[abcdefgh]等等连接的集合,这个时候我们可以利用一个“-”连字符来简化。如[0-9]、[a-h]等。需要注意的是:1)避免让这个区间的尾字符小于它的首字符,如[z-a];2)“-”连字符只有出现在“[”和“]”之间才被视为元字符,在其它情况下,它只是一个普通的字符。
            C# Code:类似于4.1的代码,不在缀述。

4.5 匹配数字
            [0-9]可以用来匹配任何一个数字,还可以有更简化的写法"\d"。
            源文本:1.txt 2.txt 3.txt 
            表达式:\d\.txt
            匹配结果:1.txt 2.txt 3.txt 
            C# Code:类似于4.1的代码,不在缀述。

            4.6 匹配字母和数字
            字母、数字及下划线经常用作文件名的规范,可以用"\w"来匹配这三种情形,类似于[a-zA-Z0-9_]。
            源文本:1.txt 2.txt 3.txt a.txt 
            表达式:\w\.txt
            匹配结果:1.txt 2.txt 3.txt a.txt
            C# Code:类似于4.1的代码,不在缀述。

4.7 匹配一个或多个字符
            4.6 所示的文件名都只有一个字符,但是更多的情况是会出现多个字符如"abc.txt"、 "a2a2.txt"。这种情况就需要我们匹配多个字符。
            要点:想要匹配一个字符的多次出现。可以使用“+”元字符。“+”元字符用来匹配字符的一次或多次重复。比如可以用a+\.txt来匹配a.txt、aa.txt、aaaaaaa.txt。
            源文本:a234_234.txt
            表达式:\w+\.txt
            匹配结果:a234_234.txt
            分析:上述匹配时,\w作为整一个元字符被进行一次或多次重复。
            C# Code:类似于4.1的代码,不在缀述。

4.8 匹配零个或多个字符
            与4.7不同的是可以匹配零个字符。而4.7必须要匹配到一个字符。如果将4.7的表达式改成\w*\.txt,仍可以匹配成功。
            要点:匹配零个或多个,可以使用“*”元字符。可以利用a*\.txt来匹配.txt、a.txt、aa.txt。
            源文本:a234_234.txt、.txt
            表达式:\w*\.txt
            匹配结果:a234_234.txt、.txt
            C# Code:类似于4.1的代码,不在缀述。

4.9 匹配零个或1个字符
            要点:“?”元字符,可以用来匹配零个或1个字符。
            源文本:a234_234.txt、.txt
            表达式:\w?\.txt
            匹配结果:a234_234.txt、.txt
            C# Code:类似于4.1的代码,不在缀述。

4.10 匹配的重复次数
            +、*都可以进行多次重复匹配,但是无法限制匹配次数,如果只需要匹配有限次数,该怎么办呢?
            要点:“{”和“}”之间的数字可以用来限制匹配的最小、最大次数。1){2}表示匹配2次;2){2,}表示至少匹配2次,最多不限;3){2,4}表示至少匹配2次,最多匹配4次。
            源文本:a234_234.txt
            表达式:\w{2,4}\.txt
            匹配结果:a234_234.txt
            C# Code:类似于4.1的代码,不在缀述。

4.11 贪婪型匹配与懒惰型匹配
            上述4.7、4.8、4.10中的{n, }都属于贪婪型元字符。之所以称之为贪婪型,是由于*、+、{n, }在匹配的时候都是按多匹配、多多益善而不是适可而止。如,使用<title>.*</title>匹配"<title>this is title</title> aaaa </title> ssss",则匹配的结果并不是所希望的<title>this is title</title>而是""<title>this is title</title> aaaa </title>"。那么如何才能让匹配适可而止呢?
            要点:在贪婪型匹配元字符后加上“?”元字符,就可以成为懒惰型匹配,进行适可而止的匹配。
            源文本:<title>this is title</title> aaaa </title> ssss
            表达式:<title>.*?</title>
            匹配结果:<title>this is title</title> aaaa </title> ssss
            C# Code:类似于4.1的代码,不在缀述。

4.12 子表达式
            顾名思义,子表达式自然是整个表达式的一部分。就好像我们小学数学的算式一样:1+2*(3+4)+3,其中(3+4)就是一个子表达式。可以把子表达式看成是一个小的整体。子表达式,会在许多场合使用到,且允许嵌套。
            源文本:abcabcabc.txt
            表达式:(abc){3}\.txt
            匹配结果:abcabcabc.txt
            C# Code:类似于4.1的代码,不在缀述。

4.13 回溯引用匹配
            回溯即要前后呼应,比如<H1>test</H1>,开始标签是<H1>,结束标签也要是</H1>。
            要点:利用子表达式作为参数,根据子表达式出现的顺序进行相应的查找。
            源文本:<H1>this is Test</H1>
            表达式:<H([1-6])>.*?</H(\1)>
            匹配结果:<H1>this is Test</H1>
            分析:([1-6])作为一个子表达式,相当于一个参数。(\1)中的"1"表示第一个子表达式,但是1只是一个普通的字符,因此需要\1对1进行转义,使之表示第一个表达式所匹配到的结果。
            C# Code:类似于4.1的代码,不在缀述。

4.14 向前查找
            有的时候,我们需要匹配的是某一个字符之前的一段字符串,比如我们需要匹配stg609@163.com中@前面的一段字符串,该如何呢?
            要点:向前查找,实际上就是匹配一个必须匹配但并不返回该结果的匹配方法,使用(?=)来实现。
            源文本:stg609@163.com
            表达式:\w+?(?=@)
            匹配结果:stg609@163.com
            C# Code:类似于4.1的代码,不在缀述。

4.15 向后查找
            如果你明白了向前查找,那向后查找就很容易了。区别的只是元字符的不同。
            要点:使用(?<=)来实现。
            源文本:stg609@163.com
            表达式:(?<=@)[\w\.]+
            匹配结果:stg609@163.com
            C# Code:类似于4.1的代码,不在缀述。

4.16 单词边界
            看过上面几个例子,你是不是觉得有什么不妥?是否发现我们匹配出来的结果有的时候只是某个单词的一部分,我想这应该不是你希望得到的结果。那么如何来匹配一个完整的单词呢?下面我们对4.10进行改进
            要点:使用\b来匹配一个单词的开始与结束。
            源文本:a234_234.txt、234.txt
            表达式:\b\w{2,4}\.txt\b
            匹配结果:a234_234.txt、234.txt
            分析:因为增加了单词边界的限制,所以a234_234.txt就不能得到匹配。
            C# Code:类似于4.1的代码,不在缀述。

4.17 分组匹配
            一个URL,如果你想同时获取这个URL的协议部分和域名部分,该怎么做呢?
            要点:通过(?name)来定义一个分组。
            源文本:this is a url : http://stg609.cnblogs.com/
            表达式:(?<Protocol>\w+?)://(?<Address>[\w\.]+)
            匹配结果:this is a url : http://stg609.cnblogs.com/
                        分组1名称:Protocol  分组1捕获:http
                        分组2名称:Address  分组2捕获:stg609.cnblogs.com
            分析:(?<Protocol>\w+?)用来捕获http,其中<Protocol>是分组的名称,\w+?则是普通的表达式。
            C# Code

分组匹配

5.综合性实例
      以下实例按照每一个示例所写的“规则”,进行正则匹配。其中的“规则”不一定严密,只为举例所用,实际应用中,请大家根据具体的“规则”编写正则表达式。

5.1 匹配邮箱地址
            邮箱命名规则:1)邮箱用户名可由英文字母、数字、连接符即[减号-]、下划线、点[.]组成,但开头只能用英文字母或数字。
                                2)必须包含一个"@"
                                3)在"@"后面的字符串至少包含一个点[.]号
            匹配表达式:[\w\d]+[\w._-\d]*@[\w._-\d]+\.[\w._-\d]+
            重点分析:[\w\d]+用来匹配开头1个或多个字母、数字;[\w._-\d]*用来匹配0个或多个字母、数字、下划线、连接符、点;@用来匹配'@'

5.2 匹配IP地址
            IPv4地址规则:1)每一个数字范围为0-255
                                2) 共有4个数字,每相邻的2个数字之间通过点[.]分隔
            匹配表达式:((1\d{2}|25[0-5]|2[0-4]\d|\d{1,2})\.){3}(1\d{2}|25[0-5]|2[0-4]\d|\d{1,2})
            重点分析:(1\d{2}|25[0-5]|2[0-4]\d|\d{1,2})利用分支语法提供4种可选的数字即1XX、250-255、20X-24X、XX;
            要点:分支条件根据从左到右的顺序进行匹配,如果已经找到适合的匹配,则不再进行其它分支的匹配。因此,要把\d{1,2}作为最后一个分支条件,否则可能会丢失对某些3位数的匹配。

5.3 匹配HTML注释
            HTML注释规则:注释必须放在<!--和-->标签之间
            匹配表达式:<!--.*?-->

5.4 匹配HTML标签对
            HTML标签对规则:1)标签必须由'<'和'>'包括
                                     2)结束标签必须由'</'和'>'包括
            匹配表达式:<td\s*?.*?>.*?</td>
            匹配模式:单行模式、忽略大小写
            要点:此表达式中,通过?来限制重复度,防止过度匹配。

5.5 匹配HTML标签对2
            标题标签(<H1>-<H6>)规则:开始标签必须与结束标签相同
            匹配表达式:<h([1-6])>.*?</h(\1)>
            匹配模式:单行模式、忽略大小写
            重点分析:([1-6])用来表示一个子表达式(分组);(\1)用来回溯引用整个表达式前半部分中定义的子表达式,\1表示是第一个子表达式,\2表示第二个表达式,依次类推。
            要点:使用回溯引用来难保前后一致。

5.6 匹配<Title></Title>标签对之间的内容
            匹配表达式:(?<=<Title>).*?(?=</Title>)
            匹配模式:单行模式、忽略大小写
            重点分析:(?<=<Title>)用来向后匹配<Title>开头的字符串,但是并不消费<Title>本身;(?=</Title>)用来向前匹配</Title>结尾的字条串,但是并不消费</Title>本身;最终返回的匹配结果包含且仅包含该标签对之间的文字;
            要点:使用向前?=、向后?<=查找来进行匹配。

参考资料:
      1) Ben Forta,《正则表达式必知必会》,[M],2007.
      2) 正则表达式的3种匹配模式
      3) 正则表达式30分钟入门教程
      4) MSDN