分組的引入:
對於要重復單個字符,非常簡單,直接在字符后賣弄加上限定符即可,例如 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)) 中,存在四個這樣的組:
0 |
(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);