C#正则学习

正则的力量无法小觑,短短的几个字符,往往胜过几十行的代码,大大可以简化我们冗余的代码。

以前在js里用正则比较多,今天来熟悉下C#中正则的使用方法,权当笔记了!

如果把正则当做一门语言的话,那么正则的学习也和其他语言一样,从历史渊源到基本语法,从高级特性到性能优化,正则一样不少。

历史:

 正则表达式的“祖先”可以一直上溯至对人类神经系统如何工作的早期研究。Warren McCulloch 和 Walter Pitts 这两位神经生理学家研究出一种数学方式来描述这些神经网络。   1956 年, 一位叫 Stephen Kleene 的数学家在 McCulloch 和 Pitts 早期工作的基础上,发表了一篇标题为“神经网事件的表示法”的论文,引入了正则表达式的概念。正则表达式就是用来描述他称为“正则集的代数”的表达式,因此采用“正则表达式”这个术语。   随后,发现可以将这一工作应用于使用 Ken Thompson 的计算搜索算法的一些早期研究,Ken Thompson 是 Unix 的主要发明人。正则表达式的第一个实用应用程序就是 Unix 中的 qed 编辑器。   如他们所说,剩下的就是众所周知的历史了。从那时起直至现在正则表达式都是基于文本的编辑器和搜索工具中的一个重要部分。

基本语法字符:

\d (代表0-9的数字)

\D (代表除数字以外的其他字符)

\w (代表所有的单词字符-数字、字母、下划线)

\W (代表所有除单词字符外的字符)

\s (代表空白字符)

\S (代表除了空白字符以外的字符)

. (除了换行符外的任意字符)

[,,,] (匹配方括号内列出的所有字符)

[^,,,] (匹配方括号内列出的字符外的所有字符)

\b (匹配单词边界)

\B (匹配非单词边界)

^ (匹配字符开头位置)

$ (匹配字符结尾位置)

{n} (匹配n个符合条件的字符)

{n,m} (匹配n到m个符合条件的字符)

{n,} (匹配大于等于n个符合条件的字符)

? (匹配1次或0次符合条件的字符)

+ (匹配一次或多次符合条件的字符)

* (匹配0次或多次符合条件的字符)

(a|b) (匹配符合a条件或者b条件的字符)

下面练习一些基本的例子来熟悉上面的基本语法

1.匹配3个数字,例如134

\d{3}

2.匹配一个单词以字母开头字母结尾中间是一个或多个数字,例如a123b

^[a-zA-Z]\d+[a-zA-Z]$

3.匹配固定电话 例如 021-81234563 或者 0512-81755456

^\d{3,4}-\d{8}

4.匹配正整数

[1-9][0-9]*

5.匹配两位小数

(([0-9][1-9]*)|([1-9][0-9]*))+\.\d{2}

6.匹配邮政编码

^\d{6}$

7.匹配手机号码

^[1][3-9]\d{9}$

8.匹配身份证号码

^\d{18}$)|^\d{15}$

9.匹配汉字

^[\u4e00-\u9fa5]{1,}$

10.匹配URL

^http(s)?([\w-]+\.)+(\w-)+(/[\w-./?%&=]*)?$

上述是基本的使用语法,让我们来看看C#中是如何使用它们的

System.Text.RegularExpressions.Regex 这个事C#正则的使用类

他提供了如下方法来使用正则

1.IsMatch 是否匹配-示例代码:

1 //验证手机号码
2     public bool IsMobile(string mobile) {
3         return System.Text.RegularExpressions.Regex.IsMatch(mobile, @"^[1][3-9]\d{9}$");
4     }

2.Split 根据条件切割字符串

示例代码

//根据数字拆分字符串
    public String[] SplitStr(String str) {
        return System.Text.RegularExpressions.Regex.Split(str, @"[0-9]");
    }

    protected void btn_split_Click(object sender, EventArgs e) {
        string[] Result = SplitStr(this.tb_pwd.Text);
        int Len = Result.Length;
        for (int i = 0; i < Len; i++) {
            if (Result[i] != "") {
                Response.Write("<script>alert('拆分为!" + Result[i] + "')</script>");
            }
        }
    }

3.Replace

替换字符串

1     //替换字符串中的所有数字为指定字符
2     public String ReplaceWord(string str1, string str2) {
3         return System.Text.RegularExpressions.Regex.Replace(str1, @"\d", str2);
4     }

4.Matches

获取匹配集合

 1 //验证重复出现的词(正则需要优化)
 2     public String[] RepeatWords(string str) {
 3         System.Text.RegularExpressions.MatchCollection matches =
 4             System.Text.RegularExpressions.Regex.Matches(str, @"\b(?<word>\w+)\s+(\k<word>)\b", System.Text.RegularExpressions.RegexOptions.Compiled | System.Text.RegularExpressions.RegexOptions.IgnoreCase);
 5         int AIndex = matches.Count;
 6         if (AIndex != 0) {
 7             String[] RepeatWord = new string[AIndex];
 8             int i=0;
 9             foreach (System.Text.RegularExpressions.Match match in matches) {
10                 string word = match.Groups["word"].Value;
11                 RepeatWord[i] = word;
12                 i++;
13             }
14             return RepeatWord;
15         }
16         else {
17             return null;
18         }
19     }

正则的高级特性

1.分组和非捕获性分组

组是把符合括弧中组条件的字符保存起来,通过索引的方法供下面的匹配的调用

例如 需要匹配 abc123abc

我们可以这样^(abc)123\1$,这里的()即是一个需要捕获的组,他的条件是abc,这个时候在下一个位置,我们只要通过\1就可以重复利用上一次捕获过的值来匹配,如果有两个分组,那我们获取第二个分组就用\2

那在C#中如何利用呢?

string  x = "abc123abc";
Regex  r = new Regex(@"^(abc)123\1$");
if (r.IsMatch(x))
{
Console.WriteLine("group1 value:" + r.Match(x).Groups[1].Value);//输出:abc
}

这里为何是Groups[1]呢 因为在匹配的时候第一个匹配的是符合所有条件的字符串,然后存储符合条件的组

我们也可以为组命名:

string  x = "abc123abc";
Regex  r = new Regex(@"^(?<test>abc)123\1$");
if (r.IsMatch(x))
{
Console.WriteLine("group1 value:" + r.Match(x).Groups["test"].Value);//输出:abc
}

这样是不是就更加形象了呢

有的时候我们想匹配组但是不想保存这个组匹配的内容,这个时候我们可以使用?:

1 string  x = "abc123abc";
2 Regex  r = new Regex(@"^(?:abc)123\1$");
3 if (r.IsMatch(x))
4 {
5 Console.WriteLine("group1 value:" + r.Match(x).Groups[1].Value);//输出:null
6 }

2.贪婪模式和非贪婪模式

一般情况下,正则都是贪婪模式,尤其是在+或者*修饰的条件下,正则都会去尽可能的匹配更多的内容,但是如果添加了?号,这个时候立马就会变成非贪婪模式

 1 string x = "Live for nothing,die for something";
 2 Regex r1 = new Regex(@".*thing");
 3 if (r1.IsMatch(x))
 4 {
 5 Console.WriteLine("match:" + r1.Match(x).Value);//输出:Live for nothing,die for something
 6 }
 7 Regex r2 = new Regex(@".*?thing");
 8 if (r2.IsMatch(x))
 9 {
10 Console.WriteLine("match:" + r2.Match(x).Value);//输出:Live for nothing
11 }

3.回溯与非回溯

在默认情况下正则匹配贪婪模式下,当匹配的字符陷入死胡同的时候,会进行回溯直到下一个字符能够接着匹配

比如 (.*)abc 来匹配123abc123abc 首先.*会进行贪婪匹配直到匹配到字符结尾的位置,接着匹配a,发现没有字符可以匹配上,引擎就向后回溯,直到a匹配到最后abc中的a,然后紧接着匹配b,然后匹配c 所以结果是 123abc123abc

好,接着来说明下非回溯模式的执行过程,同样首先.*像一只饿狼般的匹配到字符的结尾位置,这个时候开始匹配a 发现已经无法匹配,此模式下,不进行回溯,于是匹配失败,在有的业务下我们需要这样的非回溯匹配 ,语法例:(?>.*)abc

4.正向预搜索反向预搜索

不好解释,举例说明

正向预搜索

string x = "1024 used 2048 free";
Regex r1 = new Regex(@"\d{4}(?= used)");
if (r1.Matches(x).Count==1)
{
Console.WriteLine("r1 match:" + r1.Match(x).Value);//输出:1024
}
Regex r2 = new Regex(@"\d{4}(?! used)");
if (r2.Matches(x).Count==1)
{
Console.WriteLine("r2 match:" + r2.Match(x).Value); //输出:2048
}

r1表示匹配后面紧跟着used的四个数字 于是乎匹配了1024 r2 匹配后面不是紧跟着used的四个数字 于是乎 匹配2048

反向预搜索

string x = "used:1024 free:2048";
Regex r1 = new Regex(@"(?<=used:)\d{4}");
if (r1.Matches(x).Count==1)
{
Console.WriteLine("r1 match:" + r1.Match(x).Value);//输出:1024
}
Regex r2 = new Regex(@"(?<!used:)\d{4}");
if (r2.Matches(x).Count==1)
{
Console.WriteLine("r2 match:" + r2.Match(x).Value);//输出:2048
}

r1匹配前面紧着着used:的四个数字 于是乎匹配1024 r2匹配前面不是紧跟着used:的四个数字 于是乎撇配2048

看着例子理解就很好理解了,另外正向和反向的组是不保存的