考慮一下我們經常遇到的問題,比如gemfield想從青島之光讀書(www.civilnet.cn/book)中找一個關鍵的電話號碼,通常第一步就是將書中所有的電話號碼尋找出來放在手邊。那麼怎麼擬定查詢條件呢?電話的格式有如下幾種:
01088888888
010 88888888
010-88888888
88888888
0532-88888888
0534-8888888
88888888beijing
qingdao88888888
…………
省略符號的意思是格式的種類有很多種,但也有更多種明顯就不是電話類型。這種情況下如何擬定我們的查詢條件呢。如果面面俱到的話,代碼中得多少次if或者switch分支呢。
Gemfield此刻是多麼的希望有一個語句能夠簡單的描述上述所有可能的格式。這或許就是Regex的來曆。Regex的英文原意是:Regular Expression。Regular Expression的“Regular”一般被譯為“正則”、“正規”、“常規”。此處的“Regular”即是“規則”、“規律”的意思,Regular Expression即“描述某種規則的運算式”之意。
來看看gemfield如何一步步實現這個想法。
我們可以用
ddd-dddddddd或者ddddddddwwwwwww
來分別描述類似於010-88888888和88888888beijing這樣的例子。
這裡,d表示所有可能出現的數字:0、1、2、3、4…… 9;w表示所有可能出現的字母:a、b、c、d……z。
但有時我們在這個位置上明確的限定就是’w’這個字母,而非代表所有字母的w,那怎麼辦呢?上面的舉例肯定會帶來混淆。我們藉助反斜線/來實現:
/d/d/d-/d/d/d/d/d/d/d/d或者/d/d/d/d/d/d/d/d/w/w/w/w/w/w/w
這樣就能區分w到底是代表所有字母,還是僅僅代表w本身這個字母。不過看起來就有點醜陋了,這麼長,輸錯或者看錯的個數怎麼辦?gemfield可以使用下面的寫法來使其變得簡潔:
/d3-/d8或者/d8/w7
但問題又來了,我們這裡出現的3是表示前面的數字出現3次,而非3本身,後面的8、7等數字同理。怎麼辦呢?gemfield可以這樣寫:
/d{3}-/d{8}或者/d{8}/w{7}
這裡{}的意義和反斜線/差不多。都是明確告訴你,w不是w,3不是3。:-D
但很明顯,有的電話號是不加區號的,比如010-88888888寫作88888888。這樣,就不是/d{3}-/d{8}了,而是/d{8}。或許你可以寫作:
/d{8}或者/d{3}-/d{8}
但“或者”不可能成為程式語言中的關鍵字的,沒有編譯器會認識他。怎麼辦?我們想到了程式中有個運算子意思和“或者”相似,它就是| 。gemfield可以寫作:
/d{8} | /d{3}-/d{8}
但怎麼看怎麼都像少個東西,看起來混在一起分辨不清。我們想到了圓括弧:
(/d{8})|(/d{3}-/d{8})
用圓括弧括起來就表示一個整體了,這裡的意思就是說,有2組模式,其中的哪一種都可以。傳統上,?這個符號(問號)可以用來表示某項是可選的。這樣的話,上面的運算式可以寫作:
(/d{3}-)?(/d{8})
這樣就表示前面的那一組(/d{3}-)是可選的,也即既可以有,也可以不要,這剛好表達了gemfield的本意。由於還有010 88888888這種形式,我們還要考慮空格的可選,對於空格,我們可以用/s來表示。運算式修正如下:
(/d{3})?(-)?/s?(/d{8})
但是電話號碼010 88888888中間的空格在輸入的時候也許多輸了一個,比如
10 88888888,那怎麼辦呢?我們用*來表示0個、1個、2個或者多個。修正如下:
(/d{3})?(-)?/s*(/d{8})
但是,似乎好像電話不都像北京一樣,有8個數字(除了區號),大多數中國城市還是7個呢。所以這個數量也必須是可選的。我們可以擴充一下:
(/d{3})?(-)?/s*(/d{7,8})
這樣就表示電話號碼是7個或者8個,也就是大於等於7,小於等於8。
好了,雖然上面的演化對於嚴謹的文法來說沒有什麼意義,但有了這個思路,就可以認識我們的QregExp了,歡迎來到Qt的Regex——QregExp。
****************************************************
Regex由語句、數量、界定符三者組成。
語句是最簡單的,由[]括起來一個完整的子語句。如[ABCD] 匹配字母A或B或C或D,而[A-Z]表示26個大寫的英文字母。
A{1,26}匹配1個、2個、3個……26個字母A;
[0-9]{1,2}匹配0~99,但同時也匹配ab34、a34b等;
^[0-9]{1,2},匹配34bc、26abcd,只要數字前面別有其他東西;
[0-9]{1,2}$ 匹配ab65、aaaaa56,只要數字後面別有其他東西;
^[0-9]{1,2}$ 只能是2位元字了。
但是^一旦出現在方括弧中就不一樣了。它表示“不包含”。
例如:[^abc]匹配所有的東西,除了a或b或c。
+表示至少出現一次,如([abc]+)表示a或者b或者c至少出現一次。
至於*、?等界定符的意思,和gemfield文中初始部分的推理是一個意思。
******************************************************
而QregExp這個類是怎樣使用這些regexp呢,gemfield總結大致有2種情況,這兩種情況剛好是事物的兩面。第一種是“檢索”類的。看個例子:
******************************************************
str = "CIVILNET Corporation/tcivilnet.cn/tGELE";
QString company, web, country;
rx.setPattern("^([^/t]+)/t([^/t]+)/t([^/t]+)$");
if (rx.indexIn(str) != -1) {
company = rx.cap(1);
web = rx.cap(2);
country = rx.cap(3);
}
******************************************************
正如上面這個程式碼片段所揭示的,indexIn和cap這兩個函數是比較常用的,至於具體的含義,可以閱讀Qt線上文檔:http://www.civilnet.cn/book/embedded/GUI/Qt_assistant/index.php
第二種是“禁止”類的,比如一個QlineEdit裡禁止輸入一些東西,比如郵箱名禁止輸入&等。這個是用QRegExpValidator 來實現的,該類接收一個QregExp型的Regex作為執行個體化時的參數:
**********************************************************
QRegExp rx("-?//d{1,3}");
QValidator *validator = new QRegExpValidator(rx, this);
QLineEdit *edit = new QLineEdit(this);
edit->setValidator(validator);
************************************************************