熱(re)線電話:0755-23712116
郵(you)箱(xiang):contact@legoupos.cn
地址:深(shen)圳市寶(bao)安區沙井(jing)街道(dao)后亭茅洲山工業園工業大(da)廈全至(zhi)科技創新園科創大(da)廈2層(ceng)2A
正(zheng)則表達(da)式編程(cheng)
接下來我們會看到更多的示例。同時,也會看到C++正則表達式API的更多功能。 為了便于下文示例的講解,我們以維基百科上對于正則表達式的介紹文本為基礎。
我(wo)們將這段文字保存在(zai)名稱為(wei)content.txt的(de)文本文件中(zhong)。下(xia)面幾個示例會在(zai)這個文本上(shang)操作。
在上文中,為了從字符串中查找出所有匹配的字符,我們的做法是遍歷原始字符串的每一個子字符串來進行查找,這樣做很明顯效率很低。更好的做法當然是使用迭代器。
在一大段文(wen)本中查找所有(you)匹配的(de)目標,這是(shi)一個非常常見的(de)需(xu)求(qiu)。而迭(die)代器正好滿足這一需(xu)求(qiu),它會依次返回(hui)它從文(wen)本中找到的(de)匹配內容。
這段(duan)代(dai)碼的說(shuo)明如下:
ifstream
讀取文本文件 這段代碼輸出如下:
接(jie)下來的(de)幾個代碼(ma)示例的(de)主體結構和這里會很相似,我(wo)們總是先打開文本文件,然后讀取每一行來進行處理。
前面的示例中我們已經看到,通過std::regex并傳遞字符串就可以構造正則表達式對象。實際上,除了std::regex,還有寬字符版本的std::wregex。它們都源自std::basic_regex
在創建正則表達式對象的時候,除了描述規則本身的字符串之外,還可以傳遞一個flag_type類型的參數,該參數的值定義在std::regex_constants::syntax_option_type中。它們中與“文法”相關的已經在上文介紹過了。
這其中,第一個是我們最常用的。
思路(lu):單(dan)詞的(de)首字母(mu)有(you)些會大寫,我(wo)們可以(yi)通(tong)過[Rr]來匹配大寫或者(zhe)小寫的(de)R字母(mu),但實(shi)際上,使用icase無疑會更(geng)方便(bian)。
這段代碼與前面的結構是一樣的,我們最需要關注的可能就是下面這一行:
通過(guo)std::regex::icase我們指定了這個正(zheng)則(ze)表達式是不區分大小寫的。
另外還(huan)有一個值得(de)注意的(de)就是正則表(biao)達式末尾的(de)...s?,它意味(wei)著單(dan)詞可能是單(dan)數或者復數,因此(ci)結(jie)尾的(de)“s”可以出現0次或者1次。
這段代碼輸出如下:
std::match_results用來存(cun)儲匹(pi)配結(jie)果(guo)。與(yu)迭代(dai)器(qi)類似,匹(pi)配結(jie)果(guo)也有四種類型(xing):
當(dang)我們(men)使用(yong)(yong)正則表達式時,我們(men)的(de)目(mu)標常常不單(dan)單(dan)是判斷或者(zhe)查找完整匹(pi)配的(de)內容。而(er)是需要(yao)(yao)捕獲匹(pi)配結果中的(de)子串。例(li)如:我們(men)不僅要(yao)(yao)匹(pi)配出日(ri)(ri)期,還要(yao)(yao)捕獲日(ri)(ri)期中的(de)年(nian)份,月份等信(xin)息(xi)。這個(ge)時候就要(yao)(yao)使用(yong)(yong)分組功能。
我們在介紹正則表達式特殊字符的時候,提到過圓括號(
和)
。它們(men)的作用(yong)(yong)就(jiu)是(shi)分(fen)組(zu)。當你在(zai)正則(ze)表達式中(zhong)(zhong)配對(dui)的使用(yong)(yong)圓括號(hao)(hao)(hao)時,就(jiu)會形(xing)成(cheng)一個分(fen)組(zu),一個正則(ze)表達式中(zhong)(zhong)可(ke)以包含(han)多個分(fen)組(zu)。分(fen)組(zu)通過編號(hao)(hao)(hao)0, 1, 2, …來區分(fen)。編號(hao)(hao)(hao)0的分(fen)組(zu)是(shi)匹(pi)配的整體,其(qi)他編號(hao)(hao)(hao)根據括號(hao)(hao)(hao)的順(shun)序來確定(ding)。
這些(xie)分組(zu)最(zui)終可以在匹(pi)配完成之后,可以通過std::match_results的API來獲取。這些(xie)API如下表所示:
在C++中,分(fen)組叫做子匹配(sub_match)。std::sub_match 這個(ge)類型(xing)只有一個(ge)默認構造函數,通常你不會(hui)主動創建它(ta),而是使用std::match_results的接口(kou)來獲(huo)取它(ta)的對象。
示例:查(cha)找(zhao)出文本中所有的(de)(de)(de)年代,并分離出世(shi)紀的(de)(de)(de)部分和(he)年份的(de)(de)(de)部分。 思路:年代的(de)(de)(de)格式是四(si)位數字(zi)加上“s”作為后綴。我們可以通過分組的(de)(de)(de)形式分離出兩(liang)個部分。圖示如下:
代碼示例:
這(zhe)段代碼說(shuo)明如下:
sub_match
這段代碼輸出如(ru)下:
前面的表格中,我們看到了正則表達式的特殊字符。但需要進一步說明的是,這些特殊字符在不同的環境可能有著不同的含義。
還是以content.txt
的內(nei)容為基礎,現在假(jia)設我們(men)的目標是:找(zhao)出(chu)所有雙引號(hao)中的內(nei)容。
根(gen)據之前的知識,你可能很輕松就寫出了下面這(zhe)個(ge)正(zheng)則(ze)表達(da)式(shi):
.+
但是當你運行(xing)程序的時候卻發現它可能有點問題。它捕獲的結(jie)果(guo)是:
為什么?其實很簡單,因為雙引號本身也可以與.匹配。上面這個正則表達式的含義是:匹配一個兩端是雙引號,中間是任意文字的內容。
而將整個文本交給正則表達式的時候,它找出了最長的那個串。可見,原先的正則表達式太過“貪婪”(greedy)。是的,量詞在默認情況都是貪婪的。即:它們會盡可能多的占有內容。
小結一下(xia):
錨點是一類特(te)殊的(de)標(biao)(biao)記,它們不(bu)會匹配任何文(wen)本內(nei)容,而是尋找(zhao)特(te)定的(de)標(biao)(biao)記。你可以簡單理解(jie)為它是原(yuan)先表達式的(de)基礎上(shang)增加了(le)新的(de)匹配條件。如(ru)果條件不(bu)滿足,則無法完成匹配。
錨點主要分為三種:
下面是(shi)代(dai)碼示例:
它的輸(shu)出如下:
現在假設(she)我們有下面兩個(ge)需求:
對于(yu)第一個(ge)問題(ti),我們(men)可以分兩步:先找出所有(you)(you)的(de)單詞sometimes,然后取前四個(ge)字符。對于(yu)第二個(ge)問題(ti),我們(men)可以先找出所有(you)(you)的(de)單詞“some”,然后把(ba)后面(mian)是“birds”的(de)丟掉(diao)。
以上的(de)解法都是分兩步(bu)完成。但實(shi)際上,借助(zhu)環(huan)視(lookaround)我們可以一步(bu)就完成任(ren)務。
環視(shi)是對匹(pi)配位(wei)置的(de)附加條件(jian)(jian),只(zhi)有條件(jian)(jian)滿足(zu)時才能完成匹(pi)配。環視(shi)有:順(shun)序(xu)(向右),逆序(xu)(向左),肯定和否定一共四(si)種(zhong):
環(huan)視說起來有些拗口,但看具體的例(li)子就(jiu)容易(yi)理(li)解了(le):
這段代碼并(bing)不(bu)復雜所以就不(bu)多(duo)做說明,它的輸(shu)出結(jie)果如下:
對于包含環視的正則表達式來說,環視之外的內容是匹配的主體,環視本身只是一個附件條件。(?=sometimes)這個肯定順序環視要求從這個位置開始,接下來的字符串必須是"sometimes"才能完成匹配。(?!some birds)這個否定順序環視要是接下來的字符串一定不能是"some birds"才能完成匹配。
接下來,搜索位置(zhi)往后走一個(ge)字符:
這個過程可以一直進行,直到匹配完"some"
:
雖然正則表達式的主體"some"
完成(cheng)了匹配,但是(shi)接下來環(huan)視的條件卻無法滿足,于(yu)是(shi)匹配失敗(bai):
但是,如果要匹配內容正好是"sometimes"
,則條件是滿足的,于是就完(wan)成了匹配。