天天看點

正規表達式進階用法(分組與捕獲)

分組的引入:

    對于要重複單個字元,非常簡單,直接在字元後賣弄加上限定符即可,例如 a+ 表示比對1個或一個以上的a,a?表示比對0個或1個a。這些限定符如下所示:

X ? X ,一次或一次也沒有
X * X ,零次或多次
X + X ,一次或多次
X { n } X ,恰好 n 次
X { n ,} X ,至少 n 次
X { n , m } X ,至少 n 次,但是不超過 m 次

但是我們如果要對多個字元進行重複怎麼辦呢?此時我們就要用到分組,我們可以使用小括号"()"來指定要重複的子表達式,然後對這個子表達式進行重複,例如:(abc)? 表示0個或1個abc 這裡一 個括号的表達式就表示一個分組 。

 分組可以分為兩種形式,捕獲組和非捕獲組。

捕獲組  

捕獲組可以通過從左到右計算其開括号來編号 。例如,在表達式 (A)(B(C)) 中,存在四個這樣的組:

(A)(B(C))
1    (A)
2    (B(C))
3    (C)

組零始終代表整個表達式

之是以這樣命名捕獲組是因為在比對中,儲存了與這些組比對的輸入序列的每個子序列。捕獲的子序列稍後可以通過 Back 引用(反向引用) 在表達式中使用,也可以在比對操作完成後從比對器檢索。

 Back 引用 是說在後面的表達式中我們可以使用組的編号來引用前面的表達式所捕獲到的文本序列。注意:反向引用,引用的是前面捕獲組中的文本而不是正則,也就是說反向引用處比對的文本應和前面捕獲組中的文本相同,這一點很重要。

 【例】(["']).*\1  

其中使用了分組,\1就是對引号這個分組的引用,它比對包含在兩個引号或者兩個單引号中的所有字元串,如,"abc" 或 " ' " 或 ' " '  ,但是請注意,它并不會對" a'或者 'a"比對。原因上面已經說明,Back引用隻是引用文本而不是表達式。

非捕獲組  

      以 (?) 開頭的組是純的非捕獲 組,它不捕獲文本 ,也不針對組合計進行計數。就是說,如果小括号中以?号開頭,那麼這個分組就不會捕獲文本,當然也不會有組的編号,是以也不存在Back 引用。

我們通過捕獲組就能夠得到我們想要比對的内容了,那為什麼還要有非捕獲組呢?原因是捕獲組捕獲的内容是被存儲在記憶體中,可供以後使用,比如反向引用就是引用的記憶體中存儲的捕獲組中捕獲的内容。而非捕獲組則不會捕獲文本,也不會将它比對到的内容單獨分組來放到記憶體中。是以,使用非捕獲組較使用捕獲組更節省記憶體。在實際情況中我們要酌情選用。

1、非捕獲組(?:Pattern)

它的作用就是比對Pattern字元,好處就是不捕獲文本,不将比對到的字元存儲到記憶體中,進而節省記憶體。

【例】比對indestry或者indestries

我們可以使用indestr(y|ies)或者indestr(?:y|ies)

【例】(?:a|A)123(?:b)可以比對a123b或者A123b

非捕獲組有很多種形式,其中包括:零寬度斷言和模式修正符

2、零寬度斷言

(?= X )     X ,通過零寬度的正 lookahead
(?! X )     X ,通過零寬度的負 lookahead
(?<= X )     X ,通過零寬度的正 lookbehind
(?<! X )     X ,通過零寬度的負 lookbehind

這四個非捕獲組用于比對表達式X,但是不包含表達式的文本。

(?=X ) 零寬度正先行斷言。僅當子表達式 X 在 此位置的右側比對時才繼續比對。也就是說要使此零寬度斷言起到我們想要的效果的話,就必須把這個非捕獲組放在整個表達式的右側。例如,/w+(?=/d) 與後跟數字的單詞比對,而不與該數字比對。此構造不會回溯。
(?!X) 零寬度負先行斷言。僅當子表達式 X 不在 此位置的右側比對時才繼續比對。例如,例如,/w+(?!/d) 與後不跟數字的單詞比對,而不與該數字比對 。
(?<=X) 零寬度正後發斷言。僅當子表達式 X 在 此位置的左側比對時才繼續比對。例如,(?<=19)99 與跟在 19 後面的 99 的執行個體比對。此構造不會回溯。
(?<!X) 零寬度負後發斷言。僅當子表達式 X 不在此位置的左側比對時才繼續比對。例如,(?<!19)99 與不跟在 19 後面的 99 的執行個體比對

 上面都是理論性的介紹,這裡就使用一些例子來說明一下問題:

【例1】正規表達式 (?<!4)56(?=9)

含義:比對後面的文本56前面不能是4,後面必須是9組成。是以,可以比對如下文本 5569  ,與4569不比對。

 【例2】提取字元串 da12bka3434bdca4343bdca234bm中包含在字元a和b之間的數字,但是這個a之前的字元不能是c;b後面的字元必須是d才能提取。

顯然,這裡就隻有3434這個數字滿足要求。那麼我們怎麼提取呢?

首先,我們寫出含有捕獲組的正規表達式:[^c]a\d*bd

然後我們再将其變為非捕獲組的正規表達式:(?<=[^c]a)\d*(?=bd)

3、模式修正符

以(?)開頭的非捕獲組除了零寬度斷言之外,還有模式修正符。

正規表達式中常用的模式修正符有i、g、m、s、x、e等。它們之間可以組合搭配使用。

(?imnsx-imnsx: ) 應用或禁用子表達式中指定的選項。例如,(?i-s: ) 将打開不區分大小寫并禁用單行模式。關閉不區分大小寫的開關可以使用(?-i)。有關更多資訊,請參閱正規表達式選項。

【例1】(?i)ab

表示對(?i)後的所有字元都開啟不區分大小寫的開關。故它可以比對ab、aB、Ab、AB

【例2】(?i:a)b

它表示隻對a開啟不區分大小寫的開關。故它可以比對ab和Ab。不能比對aB和AB。

4、(?>Pattern)等同于侵占模式

比對成功不進行回溯,這個比較複雜,與侵占量詞“+”可以通用,比如:\d++ 可以寫為 (?>\d+)。

【例】将一些多位的小數截短到三位小數:\d+\.\d\d[1-9]?\d+

在這種條件下 6.625 能進行比對,這樣做沒有必要,因為它本身就是三位小數。最後一個“5”本來是給 [1-9] 比對的,但是後面還有一個 \d+ 是以,[1-9] 由于是“?”可以不比對是以隻能放棄目前的比對,将這個“5”送給 \d+ 去比對,如果改為:

\d+\.\d\d[1-9]?+\d+

的侵占形式,在“5”比對到 [1-9] 時,由于是侵占式的,是以不會進行回溯,後面的 \d+ 就比對不到任東西了,是以導緻 6.625 比對失敗。

這種情況,在替換時就有效了,比如把數字截短到小數點後三位,如果正好是三位小數的,就可以不用替換了,可以提高效率,侵占量詞基本上就是用來提高比對效率的。

把 \d+\.\d\d[1-9]?+\d+ 改為 \d+\.\d\d(?>[1-9]?)\d+ 這樣是一樣的。

【補充】js擷取分組内容的方法:(可參考JS正則執行個體)

1、 arr[n] = str.match(reg);      或者       arr[n] = reg.exec(str);

傳回的比對數組arr[n]中,arr[0]表示整個比對,arr[1],arr[2].......分别表示各個分組的比對結果

2、通過RegExp對象的靜态屬性來擷取

RegExp.$1,RegExp.$2.........RegExp.$9  分别表示比對到的第一個分組至第九個分組的内容

例:

var str = "adfasd324232sdfas";
    alert(str);
    var reg = new RegExp("([a-z]*)(\\d*)([a-z]*)");
    var arr = str.match(reg);
    alert(arr[0] + "===" + arr[1] + "===" + arr[2] + "===" + arr[3]);
    alert(RegExp.$1 + "-----" + RegExp.$2 + "----" + RegExp.$3);      

繼續閱讀