C#基礎回顧:正規表示式

iDotNetSpace發表於2009-06-03

     寫在前面:本文根據筆者的學習體會結合相關書籍資料對正規表示式的語法和使用(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 匹配由 x 指明的控制字元。例如, \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 為一個後跟文字 m 的向後引用。如果前面的條件都不滿足,若 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)
            如果觀察上表,會發現已經在描述[.]元字元的時候對單行模式的作用進行了解釋:使得點[.]可以用來解釋換行符。這在多行應用中會有很大的便捷性。試想,原來如果你需要匹配

標籤內容你會如何操作:

<BR>function js()<BR>{<BR>&nbsp;&nbsp; alert('惡意程式碼')<BR>}<BR>

            因為,[.]不解釋換行符,這將使得你需要手動輸入換行符來進行匹配。當啟用了單行模式後,你就可以簡單的一句話搞定:
.*?

            2.2 多行模式(Multiline)
            同樣上表中已經有關於多行模式的解釋即:使得"^"和"$"元字元匹配每一行的開頭和結尾。相比未採用該模式的時候,這兩個字元匹配的則是整個字串的開頭和結尾。

            2.3 忽略大小寫(IgnoreCase)
            這個相信不需要多講,如果未採用該模式,為了匹配全部所需的結果,可能你要在表示式中羅列所有大小寫情況,現在你只需要啟用該模式就可以省了很多麻煩。

            本節內容可以參考:正規表示式的3種匹配模式

      3.Dot Net正則核心物件[部分]
      名稱空間:using System.Text.RegularExpressions;

            3.1 Regex類
            該類是Dot Net正規表示式的核心。其中包括了若干靜態方法,這使得我們可以不構造Regex物件就可以使用其功能。Regex 類是不可變(只讀)的,並且具有固有的執行緒安全性。可以在任何執行緒上建立 Regex 物件,並線上程間共享。一般可以利用該類的建構函式來定義所需要的表示式及匹配模式。演示(摘自MSDN):

Regex使用演示
using System;
using System.Text.RegularExpressions;

public class Test
{
    public static void Main ()
    {
        // Define a regular expression for repeated words.
        Regex rx = new Regex(@"\b(?\w+)\s+(\k)\b",RegexOptions.Compiled | RegexOptions.IgnoreCase);

        // Define a test string.       
        string text = "The the quick brown fox  fox jumped over the lazy dog dog.";
       
        // Find matches.
        MatchCollection matches = rx.Matches(text);

        // Report the number of matches found.
        Console.WriteLine("{0} matches found.", matches.Count);

        // Report on each match.
        foreach (Match match in matches)
        {
            string word = match.Groups["word"].Value;
            int index = match.Index;
            Console.WriteLine("{0} repeated at position {1}", word, index);  
        }      
    } 
}


            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, }在匹配的時候都是按多匹配、多多益善而不是適可而止。如,使用

.*匹配"this is title aaaa ssss",則匹配的結果並不是所希望的this is title而是""this is title aaaa "。那麼如何才能讓匹配適可而止呢?
            要點:在貪婪型匹配元字元後加上“?”元字元,就可以成為懶惰型匹配,進行適可而止的匹配。
            源文字:this is title aaaa ssss
            表示式:.*?
            匹配結果:this is title aaaa 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 回溯引用匹配
            回溯即要前後呼應,比如

test

,開始標籤是

,結束標籤也要是


            要點:利用子表示式作為引數,根據子表示式出現的順序進行相應的查詢。
            源文字:

this is Test


            表示式:.*?
            匹配結果:

this is Test


            分析:([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的程式碼,不在綴述。


      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註釋規則:註釋必須放在<!--和--&gt標籤之間
            匹配表示式:<!--.*?--&gt

            5.4 匹配HTML標籤對
            HTML標籤對規則:1)標籤必須由''包括
                                     2)結束標籤必須由''和'>'包括
            匹配表示式:

.*?
            匹配模式:單行模式、忽略大小寫
            要點:此表示式中,通過?來限制重複度,防止過度匹配。

            5.5 匹配HTML標籤對2
            標題標籤(

-

)規則:開始標籤必須與結束標籤相同
            匹配表示式:.*?
            匹配模式:單行模式、忽略大小寫
            重點分析:([1-6])用來表示一個子表示式(分組);(\1)用來回溯引用整個表示式前半部分中定義的子表示式,\1表示是第一個子表示式,\2表示第二個表示式,依次類推。
            要點:使用回溯引用來難保前後一致。

            5.6 匹配

標籤對之間的內容
            匹配表示式:(?<=).*?(?=)
            匹配模式:單行模式、忽略大小寫
            重點分析:(?<=)用來向後匹配<title>開頭的字串,但是並不消費<title>本身;(?=)用來向前匹配結尾的字條串,但是並不消費本身;最終返回的匹配結果包含且僅包含該標籤對之間的文字;
            要點:使用向前?=、向後?<=查詢來進行匹配。


      參考資料:
      1) Ben Forta,《正規表示式必知必會》,[M],2007.
      2) 正規表示式的3種匹配模式
      3) 正規表示式30分鐘入門教程
      4) MSDN

作者:stg609
出處:http://stg609.cnblogs.com/

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-605010/,如需轉載,請註明出處,否則將追究法律責任。

相關文章