C# 篇基礎知識8——正則表達式

正則表達式(Regular Expression)也叫匹配模式(Pattern),用來檢驗字符串是否知足特定規則,或從字符串中捕獲知足特定規則的子串。正則表達式的命名空間是System.Text.RegularExpressions,Regex類即正則表達式。正則表達式

1.字符匹配express

最簡單的正則表達式由「普通字符」和「通配符」組成。好比「Room\d\d\d」就這樣的正則表達式。學習

這些特殊字符在正則表達式中稱爲元字符,由於符號「.」在正則表達式裏已有特殊用處,因此要想表達「.」自己,需使用它的轉移字符「\.」,要表達符號「\」自己,一樣,需使用它的轉移字符「\\」。 .NET 提供了一批與正則表達式相關的類,它們都位於System.Text.RegularExpressions命名空間,如今咱們來學習最主要的Regex 類,並用它來匹配正則表達式。Regex 類的部分方法如表所示:spa

經過 Regex.Matches()方法,能夠從給定字符串中分解出全部與正則表達式匹配的子串,這些子串被保存在一個MatchCollection 型集合中,每一個子串都被看做Match 類的對象。.net

如今假設某份電子文件裏包含着 Kitty 的房間號碼(格式爲RoomXXX),檔案很長,人工查閱很耗時間,那麼如何經過計算機幫咱們找到房間號碼呢?code

string text = "The Colony is a beautiful town...Kitty lives in Room415...";orm

//正則表達式對象

Regex expression = new Regex(@"Room\d\d\d");blog

//找出全部匹配的字符串,放入集合中。字符串

MatchCollection matches = expression.Matches(text);

//輸出匹配的字符串

foreach (Match match in matches)

{ Console.WriteLine("She lives in {0}", match); }

@前綴和轉義字符,前面學習過控制文本格式的轉義字符,如「\n」、「\"」、「\t」、「\\」等;如今咱們又學習了正則表達式的轉義字符,如「\.」、「\w」、「\d」 、「\s」 、「\\」等。在正則表達式中它們二者是有區別的。相似Regex expression = new Regex("\d");這樣的語句是錯誤的,由於反斜槓「\」自己就是一個很特殊的字符,要想表示反斜槓自己,須要使用它的轉移字符「\\」,因此須要寫成下面的形式:Regex expression = new Regex("\\d");但這種形式會下降可讀性,因此咱們一般使用添加@前綴的方式。Regex expression = new Regex(@"\d");這時會忽略控制文本格式的轉義字符,但不忽略正則表達式的轉義字符。添加前綴@後,若是字符串裏須要雙引號自己,能夠用兩個連續的雙引號表示。Regex expression = new Regex(@"Say ""Hello"" to Heaven");。

2.可選的字符集

 

通符符限定了某個位置上匹配的某一類型字符,但有時還想縮小匹配的範圍,限定到某一類型的某一範圍,這裏能夠把某個位置上容許出現的字符寫在方括號[]內,組成可選字符集,好比:

[abc]表示該位置能夠出現字母a、b、c

[A-D]表示該位置能夠出現字母A、B、C、D

[A-DM-P]表示該位置能夠出現字母A 到D 或M 到P

[A-Da-d]表示該位置能夠出現字母A 到D 或a 到d

[12] 表示該位置能夠出現數字1 或數字2

[1-5]表示該位置能夠出現數字1 到5

[0-57-9]表示該位置能夠出現除6 外的全部數字

[\s\S]表示該位置能夠出現任何字符,包括任何可見字符和不可見字符(如空格、製表符、換行等)

注意,無論中括號看起來有多長,它都只表明一個字符。表達式中的[VR]、[a-z]、[89]、[0-9]都是一個字符,不能看做多個字符。例如:

string text = "Vitor-1970 Verne-1982 Regan-1998 Robin2008";

//正則表達式

Regex expression = new Regex(@"[VR][a-z][a-z][a-z][a-z]-19[89][0-9]");

//獲取並輸出匹配的字符串

foreach(Match match in expression.Matches(text))

{ Console.WriteLine(match); }

由於在中括號表達式中,「[」標誌着中括號表達式的開始,而「]」標誌着中括號表達式的結束,因此要想在中括號表達式外表示它自己,需使用它的轉義字符「\[」、 「\]」,一樣「-」表示連字符,要想在中括號表達式外表示它自己,需使用它們的轉義字符「\-」。對於「-」還有不用轉義字符的方式,也能使中括號包含「-」,例如把連字符放在中括號列表的開始或結尾,好比[-1-9]或[abc-];

反向字符集,在中括號表達式用符號「^」表示非,例如[^x]表示匹配除x的全部字符,[^abc] 匹配除a、b、c 之外的全部字符。固然,當須要匹配它自己時,要在中括號表達式中使用它的轉義字符「\^」。

3.或匹配| 

在正則表達式中用符號「|」表示「或」,其左右供部分與|一塊兒應放到()中。例如:

"x|y" 匹配 "x"或"y"

"good|ok" 匹配 "good"或"ok"

"(tr|b)ee" 匹配 "tree"或"bee"

"th(i|a)nk" 匹配 "think"或"thank"

"Book One|Two" 匹配 "Book One"或"Two"

"Book (One|Two)" 匹配 "Book One"或"Book Two"

由於符號「|」、「(」和「)」在正則表達式中也有特殊用處,因此當須要匹配它們自己時,也要使用它們的轉義字符「\|」、「\(」「\)」。

4.數量限定符

數量限定符有多種,它們之間有細微的區別。

數量限定符「*」將前面的字符重複0 次或屢次,而數量限定符「+」將前面的字符重複1 次或屢次,數量限定符「?」將前面的字符重複0 次或1 次,若是須要明確指定重複次數,可以使用數量限定符{n},它表示把前面的字符重複n 遍,而數量限定符{n,}表示把它前面的字符至少重複n遍, 數量限定符{n,m}把前面的字符重複n至m遍。例如:

string words = "lg log loog looog loooog looooog";

//正則表達式

Regex expression = new Regex("lo*g");

//獲取並輸出匹配的字符串

foreach (Match match in expression.Matches(words))

{ Console.WriteLine(match); }

 

Regex expression1 = new Regex("lo+g");

//獲取並輸出匹配的字符串

foreach (Match match in expression1.Matches(words))

{ Console.WriteLine(match); }

 

Regex expression2 = new Regex("lo?g");

//獲取並輸出匹配的字符串

foreach (Match match in expression2.Matches(words))

{ Console.WriteLine(match); } 

//正則表達式

Regex expression = new Regex("lo{3}g");

//獲取並輸出匹配的字符串

foreach (Match match in expression.Matches(words))

{ Console.WriteLine(match);}

 

//正則表達式

Regex expression = new Regex("lo{3,}g");

//獲取並輸出匹配的字符串

foreach (Match match in expression.Matches(words))

{ Console.WriteLine(match);} 

//正則表達式

Regex expression = new Regex("lo{2,4}g");

//獲取並輸出匹配的字符串

foreach (Match match in expression.Matches(words))

{ Console.WriteLine(match);} 

以上4個輸出結果分別爲:「lg loog looog loooog looooog」、 「log loog looog loooog looooog」、「lg log」、「looog」、「looog  loooog  looooog」、「loog looog loooog」。

用數量限定符還能夠重複多個字符,例如:

string words = "lg leog leoeog leoeoeog leoeoeoeog leoeoeoeoeog";

//正則表達式

Regex expression = new Regex("l(eo)+g");

//獲取並輸出匹配的字符串

foreach (Match match in expression.Matches(words))

{ Console.WriteLine(match); }

正則表達式「l(eo)+g」表示字符串的第一個字符爲l,最後一個字符爲g,中間有一個或多個eo。結果爲:"leog leoeog leoeoeog leoeoeoeog leoeoeoeoeog"。

最奇妙的是這些數量限定符還能夠與通配符組合。好比通配符「\w」匹配任意單詞字符,限定符「*」表示把前面的字符重複0 次或屢次,因此正則表達式「\w*」匹配由任意單字符組成的任意長度的單詞。例如:

string girls = @"Van is 16; Vicky is 18; Vivien is 19; Vinvcent is 22";

Regex expression = new Regex(@"V\w* is \d\d");

foreach (Match match in expression.Matches(girls))

{ Console.WriteLine(match); }

結果爲:Van is 16 、Vicky is 1八、Vivien is 1九、Vinvcent is 22

貪婪和懶惰 

以上限定符都是「貪婪的」(Greedy),它們會匹配儘量多的文本。若是在限定符後加上?號,它就會變成「懶惰的」(Lazy),會匹配儘量少的文本。

string words = "ab<H1>Hello World</H1>c";

//貪婪的限定符

Regex expression1 = new Regex("<.*>");

MatchCollection matchs1 = expression1.Matches(words);

Console.WriteLine("There is {0} match with greedy quantifier:",matchs1.Count);

foreach (Match match in matchs1)

{Console.WriteLine(match); }

//懶惰的限定符

Regex expression2 = new Regex("<.*?>");

MatchCollection matchs2 = expression2.Matches(words);

Console.WriteLine("There are {0} matchs with lazy quantifier:",matchs2.Count);

foreach (Match match in matchs2)

{Console.WriteLine(match);}

使用貪婪的限定符有一條長子串與之匹配,使用懶惰的限定符有兩條短子串與之匹配。因此結果分別爲:<H1>Hello World</H1>、<H1>  </H1>。。一個是「{n}?」,它實際上與「{n}」是等價的,由於不論是貪婪仍是懶惰,都已經限定死了,只能重複n次。「?」是把「?」前面字母重複0 到1 次。由於「?」是貪婪的,能重複1 次就不重複0 (實在沒有也能匹配),「??」是懶惰的,重複次數要儘可能少,因此它選擇重複0 次。

符號「*」、「+」、「?」「{」和「}」在正則表達式中也有特殊用處,因此當須要

匹配它們自己時,也要使用它們的轉義字符「\*」、「\+」、「\?」「\{」和「\}」。

5.定位符

經過定位符能夠在指定位置尋找匹配的子串。若正則表達式中使用了定位符「^」,則在整個字符串的頭部尋找匹配的子串。例如string words = "year1998 year2008 year2018"; Regex expression = new Regex(@"^year\d\d\d\d");「^」匹配字符串的開頭位置,因此「^year\d\d\d\d」表示要從字符串的開頭位置開始尋找,這裏匹配結果爲year1998。若正則表達式中使用了定位符「$」,則在整個字符串的尾部尋找匹配的子串。string words = "year1998 year2008 year2018"; Regex expression = new Regex(@"year\d\d\d\d$");匹配結果爲year2018。

若正則表達式中使用了定位符「\b」,則在字符串中每一個「單詞」的邊界尋找匹配的子串。(單詞一般以空格、段首、段尾、逗號、句號等符號做爲邊界,分隔符「-」也能夠做爲邊界。但要注意「\b」只表示單詞與邊界符號之間的「位置」,不表示邊界符號自己。)

string words = "formfordfork";

Console.Write("None anchors:");

Regex expression = new Regex(@"for\w");

foreach (Match match in expression.Matches(words))

{ Console.Write(match + "\t"); }

Console.Write("\nAnchor start:");

expression = new Regex(@"\bfor\w");

foreach (Match match in expression.Matches(words))

{ Console.Write(match + "\t"); }

Console.Write("\n Anchor end:");

expression = new Regex(@"for\w\b");

foreach (Match match in expression.Matches(words))

{ Console.Write(match + "\t");}

結果爲:form ford fork 、form、fork。

能夠看出當沒有定位符時,匹配單詞中全部符合要求的子串;當定位符\b 在前面時,在單詞的頭部尋找匹配的子串;當定位符\b 在後面時,在單詞的結尾尋找匹配的子串。經常使用正則表達式「\b\w+\b」找出一句話裏的全部單詞

string words = "are you ok?";

Regex expression = new Regex(@"\b\w+\b");

foreach (Match match in expression.Matches(words))

{Console.WriteLine(match);}

結果爲:are you ok。

顯然,「^」和「$」在正則表達式中也有特殊用處,因此當要匹配它們自己時也需使用它們的轉義字符「\^」和「\$」。

6.分組和向後引用

括號表達式並不簡單的起着肯定範圍的做用,它同時會建立子表達式,每一個子表達式造成一個分組,並把捕獲到的與子表達式匹配的子串保存在分組中,以供未來使用。電子郵件地址一般由用戶名、二級域名、一級域名三部分構成,一般用"(\w+)@(\w+)\.(\w+)"匹配,這裏有三個括號,造成三個子表達式,就會出現三個分組。默認狀況下,每一個分組會自動擁有一個組號,規則是:從左向右,按分組左括號的出現順序進行編號,第一個分組的組號爲1,第二個爲2,以此類推。在正則表達式裏引用分組的語法爲「\number」,好比「\1」表明與分組1 匹配的子串,「\2」表明與分組2 匹配的字串,等等。存儲起來的分組有什麼做用呢?下面咱們經過一個例子來講明。不少句子裏會有重複的單詞,下面咱們就經過正則表達式找出這些重複的單詞。

string text = "you are very very good";

foreach (Match match in Regex.Matches(text, @"\b(\w+)\b \1\b"))

{ Console.WriteLine(match);}

結果爲:very very。

前面已經知道「\b(\w+)\b」匹配一個單詞,由於這裏面有一個括號,因此會捕獲一個以1 爲組號的分組,後面的「\1」就表明這個分組,表示這個位置上出現和分組1 同樣的內容。因此二者連起來就表示兩個重複的單詞。這種在後面的表達式中引用前面的分組的方式就叫作後向引用。除了匹配重複的單詞,後向引用還有一個較爲常見的應用,那就是匹配有效的HTML標籤。例如:

string words = "<h0>not valid</h1> <h2>valid</h2> <h3>not valid</h4>";

foreach (Match match in Regex.Matches(words, @"<(.*?)>.*?</\1>"))

{Console.WriteLine(match);}

結果爲:<h2>valid</h2>。

7.替換文本

正則表達式除了查找文本外,另外一項重要功能就是替換文本。經過Regex 類的Replace()方法就能以特定字符串替換原字符串中與正則表達式匹配的子串。下面經過一個例子說明如何替換文本以及如何在替換中引用分組。電話號碼格式(010)8866598七、(0769)23658945 ,能夠用正則表達式「\((\d{3,4})\)(\d{7,8})」匹配,該表達式中有兩個分組,第一個分組對應區號,第二個分組對應號碼,如今來把電話本中全部的電話都改成形如010-88665987 的格式。

string phonebook = "(010)88665987 (020)23658945 (021)88965222";

string pattern = @"\((\d{3,4})\)(\d{7,8})";

string result = Regex.Replace(phonebook, pattern, "$1-$2");

Console.WriteLine(result);

結果:010-8866598七、020-2365894五、021-88965222。

在大部分語言的正則表達式中,查找時,使用後向引用的語法爲「\number」;而在替換時,其語法爲「$number」。 例子中的Regex.Replace ()方法把與正則表達式匹配的子串替換爲「$1-$2」,其中$1 對應與分組1(即區號),$2 對應分組2(即電話號碼)。

在.NET 中使用正則表達式進行替換時,分組的命名方式爲:(?<name> subexpression)(便可以對分組顯式命名),後向引用的語法是:${name}。因此上例也能夠寫爲:

string phonebook = "(010)88665987 (020)23658945 (021)88965222";

string pattern = @"\((?<areacode>\d{3})\)(?<number>\d{8})";

string result = Regex.Replace(phonebook, pattern, "${areacode}-${number}");

Console.WriteLine(result);

8. 非捕獲分組和預查

 

1)非捕獲後組?:

不少時候,咱們添加一個分組,並非爲了在後向引用中使用它,而是出於表達上的須要。好比正則表達式「(tr|b)ee」中括號,僅僅是爲了肯定範圍,使之正確匹配「tree」或「bee」。 存儲分組會花費必定的時間,若是咱們不須要存儲分組,能夠在分組內部添加元字符「?:」,把它標誌成一個非捕獲分組。"(?:tr|b)ee"。這樣就不會存儲分組了。

2)正向預查?=和負正向預查?!

string text = @"Jack boy 29 010-88127631 Microsoft, Sally girl 22 010-88127632 Microsoft,Ben boy 26 020-65423541 Google,Merry girl 23 020-65423542 Google,Alan boy 27 021-23456851 Apple,Kitty girl 18 021-23456852 Apple.";

Console.WriteLine("The Telephone numbers of Microsoft are:");

foreach (Match match in Regex.Matches(text, @"\d{3}-\d{8}(?= Microsoft)"))

{Console.WriteLine(match);}

結果爲:010-8812763一、010-88127632。

若是僅僅使用正則表達式「\d{3}-\d{8}」,它就會匹配文本里全部的電話號碼,那麼如何讓它只匹配微軟的電話號碼呢?這時就須要在它後面的一個分組中添加元字符「(?= Microsoft)」做爲限制條件,其格式以下所示。正向預查表達式用來篩選與查詢目標表達式匹配的文本,其結果不光要和查詢目標表達式匹配,它後面跟隨的文本還要和正向預查表達式匹配。須要強調的是正向預查表達式只是用來篩選查詢目標,並不屬於查詢目標自己,不包含在最終結果中。

負正向預查與正向預查相反,查詢目標後面不能跟指定的字符串,其語法爲在分組中添加元字符「?!」。 正則表達式「\d{3}-\d{8}(?! Microsoft)」匹配那些後面不跟「 Microsoft」的電話號碼。

3)反向預查?<=和負反向預查?<!

正向預查?=篩選查尋目標,限制條件在查詢目標的後面,反向預查?<=篩選查尋目標時,限制條件則在查詢目標的前面,這時就須要在它前面的一個分組中添加元字符「(?= Microsoft)」做爲限制條件。其使用方法例如:

string text = @"Jack boy 29 010-88127631 Microsoft, Sally girl 22 010-88127632 Microsoft,Ben boy 26 020-65423541 Google,Merry girl 23 020-65423542 Google,Alan boy 27 021-23456851 Apple,Kitty girl 18 021-23456852 Apple.";

Console.WriteLine("The Telephone numbers of girls are:");

foreach (Match match in Regex.Matches(text, @"(?<=girl \d\d )\d{3}-\d{8}"))

{Console.WriteLine(match);}

負反向預查與反向預查相反,查詢目標前面不能跟指定字符串,其語法是在分組中添加元字符「?<!」。正則表達式「(?<!girl \d\d )\d{3}-\d{8}」匹配這樣的電話號碼,它們前面的文本不能與「girl \d\d 」匹配。

總之,預查表達式的做用就是給查詢目標添加限制條件,能夠在前面,也能夠在後面,能夠正面限制,也能夠反面限制。

9.正則表達式的類

和正則表達式相關的類都在命名空間System.Text.RegularExpressions 中,上圖展現了部分經常使用正則表達式類。

string s = "aaa@163.com bbb@263.net ccc@363.cn";

MatchCollection matchs = Regex.Matches(s, @"(\w+)@(\w+)\.(\w+)");

//輸出MatchCollection中全部的Match

Console.WriteLine("全部Match爲:");

for (int i = 0; i < matchs.Count; i++)

{ Match match = matchs[i];

Console.WriteLine(match.Value); }

Macth 的Value 屬性就是匹配的字符串,每一個 Match 中都有一個Groups 屬性,裏面包含了該Match 的全部分組。其中第0 個分組是Match 自己

10.正則表達式的選項

經過Regex 類的Options 屬性能夠設置正則表達式的選項,下表是.Net 中經常使用的正則表達式選項。

例如:

string pattern = @"Room\d{3}";

Regex expression = new Regex(pattern, RegexOptions.IgnoreCase);

若是想同時設置多個選項,能夠把它們用按位或「|」鏈接起來。

new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline);

11.註釋

遇到複雜的、比較難理解的正則表達式時,咱們能夠經過添加註釋的方式進行解釋說明。在正則表達式中添加註釋的語法爲:(?#註釋內容)。好比:

@"\((\d{3,4})(?#該分組匹配區號)\)(\d{7,8})(?#該分組匹配電話號碼)"。 

相關文章
相關標籤/搜索