参考:http://www.cnblogs.com/youring2/archive/2009/11/07/1597786.html
在原文的基础上增加一些自己的理解实例。
目前为止,许多编程语言和工具都包含对正则表达式的支持,C#也不例外,C#基础类库中包含有一个命名空间(System.Text.RegularExpressions)和一系列可以充分发挥规则表达式威力的类(Regex、Match、Group等)。那么,什么是正则表达式,怎么定义正则表达式呢?
一、正则表达式基础
1.什么是正则表达式
在编写字符串的处理程序时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。
通常,我们在使用WINDOWS查找文件时,会使用通配符(和?)。如果你想查找某个目录下的所有Word文档时,你就可以使用.doc进行查找,在这里,*就被解释为任意字符串。和通配符类似,正则表达式也是用来进行文本匹配的工具,只不过比起通配符,它能更精确地描述你的需求——当然,代价就是更复杂。
2.一个简单的例子——验证电话号码
学习正则表达式的最好方法是从例子开始,下面我们从验证电话号码开始,一步一步的了解正则表达式。
在我们国家,电话号码(如:0379-65624150)通常包含3到4为以0开头的区号和一个7或8为的号码,中间通常以连字符’-’隔开。在这个例子中,首先我们要介绍一个元字符\d,它用来匹配一个0到9的数字。这个正则表达式可以写成:^0\d{2,3}-\d{7,8}$
我们来对他进行分析,0匹配数字“0”,\d匹配一个数字,{2,3}表示重复2到3次,-只匹配”-”自身,接下来的\d同样匹配一个数字,而 {7,8}则表示重复7到8次。当然,电话号码还可以写成 (0379)65624150,这里就交给读者完成。
元字符
在上面的例子中,我们接触到了一个元字符\d,正如你所想的,正则表达式还有很多像\d一样的元字符,下表列出了一些常用的元字符:
元字符 | 说明 |
---|---|
. | 匹配除换行符以外的任意字符 |
\b | 匹配单词的开始或结束 |
\d | 匹配数字 |
\s | 匹配任意的空白符 |
\w | 匹配字母或数字或下划线或汉字 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
表1、常用的元字符
3.转义字符
如果你想查找元字符本身的话,比如你查找.,或者*,就出现了问题:你没办法指定它们,因为它们会被解释成别的意思。这时你就得使用\来取消这些字符的特殊意义。因此,你应该使用.和*。当然,要查找\本身,你也得用\.
例如:unibetter.com匹配unibetter.com,C:\Windows匹配C:\Windows。
4.限定符
限定符又叫重复描述字符,表示一个字符要出现的次数。比如我们在匹配电话号码时使用的{3,4}就表示出现3到4次。常用的限定符有:
限定符 | 说明 |
---|---|
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复n到m次 |
表2、常用的限定符
二、.NET中正则表达式的支持
System.Text.RegularExpressions 命名空间包含一些类,这些类提供对 .NET Framework 正则表达式引擎的访问。该命名空间提供正则表达式功能,可以从运行在 Microsoft .NET Framework 内的任何平台或语言中使用该功能。
实例:在C#中使用正则表达式
在了解了C#中支持正则表达式的类后,我们一起来将上面提到的验证电话号码的正则表达式写入C#代码中,实现电话号码的验证。
第一步,建立一个名为SimpleCheckPhoneNumber的Windows项目。
第二步,引入System.Text.RegularExpressions命名空间。
第三步,写出正则表达式。这里的正则表达式就是上面的验证号码的字符串。由于上面的字符串只能验证用连字符连接区号和号码的方式的电话号码,所以我们做了一些修改:0\d{2,3}-\d{7,8}|0\d2,3\d{7,8}。在这个表达式中,| 号面的一部分是我们上面提到过的,后面一部分是用来验证(0379)65624150这种电话号码写法的。由于 ( 和 ) 也是元字符,所以要用转义字符。| 表示分支匹配,要么匹配前面的一部分,要么匹配后面的一部分。
第四步,正则表达式构造一个Regex类。
第五步,使用Regex类的IsMatch方法验证匹配。Regex类的IsMatch()方法返回一个bool值,如果有匹配项,返回true,否则返回false。
static void Main(string[] args)
{
string st = "0379-65624150";
string input = @"0\d{2,3}-\d{7,8}";
MatchCollection myMatches = Regex.Matches(st, input,
RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
foreach (Match nextMatch in myMatches)
{
Console.WriteLine(nextMatch.ToString());
}
if (Regex.IsMatch(st, input))
{
Console.WriteLine("yes");
}
}
三、正则表达式进阶
1.分组
在匹配电话号码的时候,我们已经用到过重复单个字符。下面我们来了解如何使用分组来匹配一个IP地址。
众所周知,IP地址,是由32位数字二进制转为四个十进制的字符串组成。
怎么转化?下面讲解:
二进制:11111111111111111111111111111111
分为四部分:11111111.11111111.11111111.11111111
转化:2^7+2^6+2^5+2^4+2^3+2^2+2^1+2^0=255
转为十进制范围:0~255.0~255.0~255.0~255
这就是IP地址的范围。
根据这个生成IP的规则和范围,我们可以用正则表达式来匹配出IP地址,但怎么匹配呢?各人有各人的方法,这里我讲解一下我的思路。
根据IP地址的字符串规律,我把匹配IP地址的表达式分为两部分来考虑。
第一部分:匹配3个0~255.(注意后面的一个点)
第二部分:匹配最后的数字0~255
也就是说,先匹配出 0~255.(注意后面的一个点) 这个字符串,然后重复匹配3次,然后再匹配最后的数字部分0~255。这就是我匹配IP地址的思路。
在第一部分中可以分为5种情况:
2[0-4][0-9]
25[0-5]
[1-9][0-9]
[0-9]
1[0-9][0-9]
所以,最后匹配的完整的正则表达式为:
@"^((2[0-4]\d\.)|(25[0-5]\.)|(1\d\d\.)|([1-9]\d\.)|(\d\.)){3}((2[0-5][0-5])|(25[0-5])|(1\d\d)|([1-9]\d)|(\d))$";
再举个例子,电子邮件地址通常都是由用户名、二级域名、一级域名组成。如yss@126.com,yss是用户名,126是二级域名,com是一级域名。我们可以通过下面的正则表达式匹配这类电子邮件地址:
@"(\w+)@(\w+).(\w+)";
这里有三个括号,形成三个子表达式,就会出现三个分组。默认情况下,每个分组会自动拥有一个组号,规则是:从左向右,索引从1开始。在正则表达式里引用分组的语法为”\数字”,比如”\1”代表与分组1匹配的子串,索引不能超过分组个数。
后向引用
在我们了解分组以后,我们就可以使用后向引用了。所谓后向引用,就是使用前面捕获的结果,对后面的字符进行匹配。多用于匹配重复字符。比如匹配 Go go 这样的重复字符。我们就可以使用 (go) \1来进行匹配。
例如用后向引用匹配HTML标记:
string st = "<h1>not valid</h2> <h2>valid</h2> <h2>not valid</h3>";
string input = @"<(.*?)>.*?</\1>";
foreach (Match match in Regex.Matches(st,input))
{
Console.WriteLine(match);
}
结果为:
<h2>valid</h2>
对于复杂的匹配,尤其是子表达式嵌套子表达式的情况,会出现非常复杂的分组,它们的组号不容易分清楚,后向引用时常常会引用错误的组号。如果能为分组命名就是了,这样在后向引用中通过名称引用分组,就会清晰很多。在.NET中,分组的命名方式为:(?subexpression),后向引用的语法为:\k。这样上例中的正则表达式就可以写为:
@"<(?<openingtag>.*?)>.*</\k<openingtag>>";
替换文本
正则表达式除了查找文本外,另一个重要功能就是替换文本。通过Regex类的Replace()方法就能以特定字符串替换原字符串中与正则表达式匹配的子串。还是回到我们电话号码的例子,我们需要将(0379)65624150(正则表达式“((\d{4}))(\d{8})”)替换为0379-65624150这样的格式。
string st = "(0379)65624150\n";
string input = @"\((\d{4})\)(\d{8})";
string result = Regex.Replace(st, input, "$1-$2");
Console.WriteLine(result);
运行结果为:
0379-65624150
在大部分语言的正则表达式中,查找时,使用后向引用的语法为”\数字”;而在替换时,其语法为” 数字"。Regex.Replace()方法的第三个参数是替换内容,其中 1对应与分组1(即区号),$2对应分组2(即电话号码)。
在.NET中是用正则表达式进行替换时,分组的命名方式为:(?subexpression),后向引用语法是:${name}。所以上例也可以写为:
string st = "(0379)65624150\n";
//string input = @"\((\d{4})\)(\d{8})";
string input = @"\((?<areacode>\d{4})\)(?<number>\d{8})";
string result = Regex.Replace(st, input, "${areacode}-${number}");
Console.WriteLine(result);
零宽断言
在前面的元字符介绍中,我们已经知道了有这样一类字符,可以匹配一句话的开始、结束(^ $)或者匹配一个单词的开始、结束(\b)。这些元字符只匹配一个位置,指定这个位置满足一定的条件,而不是匹配某些字符,因此,它们被成为 零宽断言。所谓零宽,指的是它们不与任何字符相匹配,而匹配一个位置;所谓断言,指的是一个判断。正则表达式中只有当断言为真时才会继续进行匹配。
在有些时候,我们精确的匹配一个位置,而不仅仅是句子或者单词,这就需要我们自己写出断言来进行匹配。下面是断言的语法:
断言语法 | 说明 |
---|---|
(?=pattern) | 前向肯定断言,匹配pattern前面的位置 |
(?!pattern) | 前向否定断言,匹配后面不是pattern的位置 |
(?<=pattern) | 后向肯定断言,匹配pattern后面的位置 |
? | 后向否定断言,匹配前面不是pattern的位置 |
表3、断言的语法及说明
要特别注意的是,零宽断言是不占用位置的,也就是说,匹配结果里是不会返回它的。直接看例子:
static void Main(string[] args)
{
//(exp) 匹配exp,并捕获文本到自动命名的组里
Regex reg = new Regex(@"A(\w+)A");
Console.WriteLine(reg.Match("dsA123A")); //输出 A123A
Console.WriteLine(reg.Match("dsA123A").Groups[1]); //输出123
//(?<name>exp) 匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name'exp)
Regex reg2 = new Regex(@"A(?<num>\w+)A");
Console.WriteLine(reg2.Match("dsA123A").Groups["num"]); //输出123
Regex reg3 = new Regex(@"A(?:\w+A)");
Console.WriteLine(reg3.Match("dsA123A"));
Console.WriteLine("==============================");
//(?=exp) 匹配exp前面的位置 零宽正预测先行断言
Regex reg4 = new Regex(@"sing(?=ing)"); //表达式的意思是,我认为在sing的后面会有ing,如果sing后面紧跟着ing,那么这个sing才匹配成功,注意断言词不会被匹配
Console.WriteLine(reg4.Match("ksingkksingingkkk")); //输出 sing
Console.WriteLine(reg4.Match("singddddsingingd").Index); //输出 8 输出8就意味住前面那个sing被没有被匹配
//(?<=exp) 匹配exp后面的位置 零宽度正回顾后发断言
Regex reg5 = new Regex(@"(?<=wo)man");
Console.WriteLine(reg5.Match("Hi man Hi woman")); //输出 man
Console.WriteLine(reg5.Match("Hi man Hi woman").Index); //输出 12 掰着手指头算算到底匹配的是哪一个
//(?!exp) 匹配后面跟的不是exp的位置 零宽度负预测先行断言
Regex reg6 = new Regex(@"sing(?!ing)");
Console.WriteLine(reg6.Match("singing-singabc")); //输出 sing
Console.WriteLine(reg6.Match("singing-singabc").Index); //输出 8 还得掰着手指头算算匹配的是哪一个
//(?<!exp) 匹配前面不是exp的位置 零宽度负回顾后发断言
Regex reg7 = new Regex(@"(?<!wo)man");
Console.WriteLine(reg7.Match("Hi woman Hi man")); //输出 man
Console.WriteLine(reg7.Match("Hi woman Hi man").Index); //输出 12 算算匹配的是哪一个
//(?#comment) 不对正则表达式的处理产生任何影响,用于提供注释让人阅读
Regex reg8 = new Regex("ABC(?#这只是一段注释而已)DEF");
Console.WriteLine(reg8.Match("ABCDEFG")); //输出 ABCDEF
}
懒惰匹配
代码/语法 | 说明 |
---|---|
*? | 重复任意次,但尽可能少重复 |
+? | 重复1次或更多次,但尽可能少重复 |
?? | 重复0次或1次,但尽可能少重复 |
{n,m}? | 重复n到m次,但尽可能少重复 |
{n,}? | 重复n次以上,但尽可能少重复 |
表4、懒惰语法及说明
如果你细心的留意到,会发现,其实懒惰匹配符只是在原有限定符后面加了个?以表示尽可能少地匹配的意思。
static void Main(string[] args)
{
//懒惰匹配
Regex reg1 = new Regex(@"A(\w)*B");
Console.WriteLine(reg1.Match("A12B34B56B")); //输出 A12B34B56B //留意到默认是尽可能多地匹配
Regex reg2 = new Regex(@"A(\w)*?B"); //\w重复任意次,但尽可能少
Console.WriteLine(reg2.Match("A12B34B56B")); //输出 A12B
Regex reg3 = new Regex(@"A(\w)+?"); //\w重复1次或更多次,但尽可能少
Console.WriteLine(reg3.Match("AB12B34B56B")); //输出AB 注意此处测试字符串
Regex reg4 = new Regex(@"A(\w)??B"); //\w重复0次或1次,但尽可能少
Console.WriteLine(reg4.Match("A12B34B56B")); //输出 空白,匹配失败,因为至少也要重复\w两次
Console.WriteLine(reg4.Match("A1B2B34B56B")); //输出 A1B
Regex reg5 = new Regex(@"A(\w){4,10}?B"); //\w至少重复4次,最多重复10次
Console.WriteLine(reg5.Match("A1B2B3B4B5B")); //输出 A1B2B3B 到了第4个的时候,恰好第4个字符是3只有匹配3后面的那个B了
Regex reg6 = new Regex(@"A(\w){4,}?"); //\w至少重复4次,最多无上限
Console.WriteLine(reg5.Match("A1B2B3B4B5B")); //输出 A1B2B3B 到了第4个的时候,恰好第4个字符是3只有匹配3后面的那个B了
Console.ReadKey();
}
附上一些使用正则表达式:
1.纯数字检测:public string A = “^[0-9]+$”;
2.11位手机号码(1开头):public string B = “^1\d{10}$”’;
3.数字或英文:public string C = “^[A-Za-z0-9]+$”;
4.纯汉字:public string D = “^[\u4e00-\u9fa5]+$”;
5.身份证:
//身份证正则表达式(15位)
public string E1 =”/^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}
/”;//身份证正则表达式(18位)publicstringE2=”/[1−9]\d5[1−9]\d3((0\d)|(1[0−2]))(([0|1|2]\d)|3[0−1])\d4
/”;
6.验证 E-mail 格式: string F= “\w{1,}@\w{1,}\.\w{1,}”;
7.判断日期时间,
1.2009-1-2/12:01
2.12-1/13:05
3.15:52
同时支持3种格式的判断: string G=”(\d{4}-)([01]\d-[0-3]\d\/)[0-2]\d:[0-5]\d”;
8.验证密码 由不小于6位不大于15位的字母数字下划线特殊符号组成
string H= “^.{6,15}___FCKpd___0quot;;//限定开头,须从第一位开始匹配,限定结尾,总位数不得超过15位,否则即使大于15位仍然可以验证通过
System.Text.RegularExpressions.Regex regPwd = new System.Text.RegularExpressions.Regex(H, options);
string pwd = txtPwd.Text;
if (regPwd.IsMatch(pwd))
{ YES}else{no};
9.校验用户姓名:只能输入1-30个以字母开头的字串
function isTrueName(s)
{
var patrn=/^[a-zA-Z]{1,30}$/;
if (!patrn.exec(s)) return false
return true
}
10.校验密码:只能输入6-20个字母、数字、下划线
function isPasswd(s)
{
var patrn=/^(\w){6,20}$/;
if (!patrn.exec(s)) return false
return true
}
11.校验普通电话、传真号码:可以“+”开头,除数字外,可含有“-”
function isTel(s)
{
//var patrn=/^[+]{0,1}(\d){1,3}[ ]?([-]?(\d){1,12})+
/;varpatrn=/[+]0,1(\d)1,3[]?([−]?((\d)|[])1,12)+
/;
if (!patrn.exec(s)) return false
return true
}
12.校验手机号码:必须以数字开头,除数字外,可含有“-”
function isMobil(s)
{
var patrn=/^[+]{0,1}(\d){1,3}[ ]?([-]?((\d)|[ ]){1,12})+$/;
if (!patrn.exec(s)) return false
return true
}
13.校验邮政编码
function isPostalCode(s)
{
//var patrn=/^[a-zA-Z0-9]{3,12}
/;varpatrn=/[a−zA−Z0−9]3,12
/;
if (!patrn.exec(s)) return false
return true
}
14.校验搜索关键字
function isSearch(s)
{
var patrn=/^[^~!@#$%^&*()+=|\\\][\]\{\}:;\'\,.<>/?]{1}[^
~!@
/;
if (!patrn.exec(s)) return false
return true
}
function isIP(s) //by zergling
{
var patrn=/^[0-9.]{1,20}$/;
if (!patrn.exec(s)) return false
return true
15.校验邮箱
function isEmail(s)
{
var patrn=/^[a-zA-Z0-9_-]{1,}@[a-zA-Z0-9_-]{1,}.[a-zA-Z0-9_-.]{1,}$/;
if (!patrn.exec(s)) return false
return true
}
16.校验日期
function isdate(s)
{
var patrn=/^((\d{2}(([02468][048])|([13579][26]))[-\/\s]?((((0?[13578])|(1[02]))[-\/\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[-\/\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[-\/\s]?((0?[1-9])|([1-2][0-9])))))|(\d{2}(([02468][1235679])|([13579][01345789]))[-\/\s]?((((0?[13578])|(1[02]))[-\/\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[-\/\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[-\/\s]?((0?[1-9])|(1[0-9])|(2[0-8]))))))(\s(((0?[0-9])|([1-2][0-3])):([0-5]?[0-9])((\s)|(:([0-5]?[0-9])))))?$/;
if (!patrn.exec(s)) return false
return true
}
17.校验货币格式
function isCurrency(s)
{
var patrn=/^\d+(.\d+)?$/;
if (!patrn.exec(s)) return false
return true
}
18.邮政编码判断: string I=”^[1-9]\d{5}$”;