C# 正則表達式的使用

參考: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))$";

再舉個例子,電子郵件地址通常都是由用戶名、二級域名、一級域名組成。如[email protected],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=/[19]\d5[19]\d3((0\d)|(1[02]))(([0|1|2]\d)|3[01])\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=/[azAZ09]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}$”;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章