1. 程式人生 > >C++ 中的也能使用正則表示式

C++ 中的也能使用正則表示式

正則表示式Regex(regular expression)是一種強大的描述字元序列的工具。在許多語言中都存在著正則表示式,C++11中也將正則表示式納入了新標準的一部分,不僅如此,它還支援了6種不同的正則表示式的語法,分別是:ECMASCRIPT、basic、extended、awk、grep和egrep。其中ECMASCRIPT是預設的語法,具體使用哪種語法我們可以在構造正則表示式的時候指定。

注:ECMAScript是一種由Ecma國際(前身為歐洲計算機制造商協會,英文名稱是European Computer Manufacturers Association)通過ECMA-262標準化的指令碼程式設計語言。它往往被稱為JavaScript,但實際上後者是ECMA-262標準的實現和擴充套件。

下面我們就以本篇部落格的頁面(http://www.cnblogs.com/ittinybird/p/4853532.html)原始碼為例,從零開始演示如何在C++中使用正則表示式提取一個網頁原始碼中所有可用的http連結。如果有時間的話,近期我想用C++11的新特性,改寫一下以前的C++爬蟲程式,分享出來。

確保你的編譯器支援Regex

如果你的編譯器是GCC-4.9.0或者VS2013以下版本,請升級後,再使用。我之前使用的C++編譯器,是GCC 4.8.3,有regex標頭檔案,但是GCC很不厚道的沒有實現,語法完全支援,但是庫還沒跟上,所以編譯的時候是沒有問題的,但是一執行就會直接丟擲異常,非常完美的一個坑有木有!具體錯誤如下:

123 terminate called after throwing an instance of'std::regex_error'what():regex_errorAborted(core dumped)

如果你也遇到了這個問題,請不要先懷疑自己,GCC這一點是非常坑爹的!!!我在這個上面浪費了半天的時間才找了出來。所以在嚐鮮C++的正則表示式之前,請升級你的編譯器,確保你的編譯器支援它。

regex庫概覽

在標頭檔案<regex>中包含了多個我們使用正則表示式時需要用到的元件,大致有:

regex
ECMASCRIPT正則表示式語法

正則表示式式的語法基本大同小異,在這裡就浪費篇幅細摳了。ECMASCRIPT正則表示式的語法知識可以參考W3CSCHOOL。

構造正則表示式

構造正則表示式用到一個類:basic_regex。basic_regex是一個正則表示式的通用類模板,對char和wchar_t型別都有對應的特化:

12 typedefbasic_regex<char>regex;typedefbasic_regex<wchar_t>wregex;

建構函式比較多,但是非常簡單:

123456789101112131415161718 //預設建構函式,將匹配任何的字元序列basic_regex();//用一個以‘\0’結束的字串s構造一個正則表示式explicit basic_regex(constCharT*s,flag_typef=std::regex_constants::ECMAScript);//同上,但是制定了用於構造的字串s的長度為countbasic_regex(constCharT*s,std::size_t count,flag_typef=std::regex_constants::ECMAScript);//拷貝構造,不贅述basic_regex(constbasic_regex&other);//移動建構函式basic_regex(basic_regex&&other);//以basic_string型別的str構造正則表示式template<classST,classSA>explicit basic_regex(conststd::basic_string<CharT,ST,SA>&str,flag_typef=std::regex_constants::ECMAScript);//指定範圍[first,last)內的字串構造正則表示式template<classForwardIt>basic_regex(ForwardIt first,ForwardIt last,flag_typef=std::regex_constants::ECMAScript);//使用initializer_list構造basic_regex(std::initializer_list<CharT>init,flag_typef=std::regex_constants::ECMAScript);

以上除預設構造之外的建構函式,都有一個flag_type型別的引數用於指定正則表示式的語法,ECMASCRIPT、basic、extended、awk、grep和egrep均是可選的值。除此之外還有其他幾種可能的的標誌,用於改變正則表示式匹配時的規則和行為:

flag
有了建構函式之後,現在我們就可以先構造出一個提取http連結的正則表示式:

12 std::stringpattern("http(s)?://([\\w-]+\\.)+[\\w-]+(/[\\w- ./?%&=]*)?");//匹配規則很簡單,如果有疑惑,可以對照語法檢視std::regexr(pattern);

值得一提的是在C++中’\’這個字元需要轉義,因此所有ECMASCRIPT正則表示式語法中的’\’都需要寫成“\\”的形式。我測試的時候,這段regex如果沒有加轉義,在gcc中會給出警告提示,vs2013編譯後後執行直接崩潰了。

正確地處理輸入

先扯一個題外話,假設我們不是使用了網路庫自動在程式中下載的網頁,在我們手動下載了網頁並儲存到檔案後,首先我們要做的還是先把網頁的內容(html原始碼)存入一個std::string中,我們可能會使用這樣的錯誤方式:

123456 intmain(){std::stringtmp,html;while(std::cin>>tmp)html+=tmp;}

這樣一來原始碼中所有的空白字元就無意中被我們全處理了,這顯然不合適。這裡我們還是使用getline()這個函式來處理:

123456789 intmain(){std::stringtmp,html;while(getline(std::cin,tmp)){html+=tmp;html+='\n';}}

這樣一來原來的文字才能得到正確的輸入。當然個人以為這些小細節還是值得注意的,到時候出錯debug的時候,我想我們更多地懷疑的是自己的正則表示式是否是有效。

regex_search() 只查詢到第一個匹配的子序列

根據函式的字面語義,我們可能會錯誤的選擇regex_search()這個函式來進行匹配。其函式原型也有6個過載的版本,用法也是大同小異,函式返回值是bool值,成功返回true,失敗返回false。鑑於篇幅,我們只看我們下面要使用的這個:

12345 template<classSTraits,classSAlloc,classAlloc,classCharT,classTraits>boolregex_search(conststd::basic_string<CharT,STraits,SAlloc>&s,std::match_results<typename std::basic_string<CharT,STraits,SAlloc>::const_iterator,Alloc>&m,conststd::basic_regex<CharT,Traits>&e,std::regex_constants::match_flag_type flags=std::regex_constants::match_default);

第一個引數s是std::basic_string型別的,它是我們待匹配的字元序列,引數m是一個match_results的容器用於存放匹配到的結果,引數e則是用來存放我們之前構造的正則表示式物件。flags引數值得一提,它的型別是std::regex_constants::match_flag_type,語義上匹配標誌的意思。正如在構造正則表示式物件時我們可以指定選項如何處理正則表示式一樣,在匹配的過程中我們依然可以指定另外的標誌來控制匹配的規則。這些標誌的具體含義,我從cppreference.com 引用過來,用的時候查一下就可以了:

constsa

根據引數型別,於是我們構造了這樣的呼叫:

1 std::smatch results;<br>regex_search(html,results,r);

不過,標準庫規定regex_search()在查詢到第一個匹配的子串後,就會停止查詢!在本程式中,results引數只帶回了第一個滿足條件的http連結。這顯然並不能滿足我們要提取網頁中所有HTTP連結需要。

使用 regex_iterator 匹配所有子串

嚴格意義上regex_iterator是一種迭代器介面卡,它用來繫結要匹配的字元序列和regex物件。regex_iterator的預設建構函式比較特殊,就直接構造了一個尾後迭代器。另外一個建構函式原型:

123 regex_iterator(BidirIta,BidirItb,//分別是待匹配字元序列的首迭代器和尾後迭代器constregex_type&re,//regex物件std::regex_constants::match_flag_typem=std::regex_constants::match_default);//標誌,同上面的regex_search()中的

和上邊的regex_search()一樣,regex_iterator的建構函式中也有std::regex_constants::match_flag_type型別的引數,用法一樣。其實regex_iterator的內部實現就是呼叫了regex_search(),這個引數是用來傳遞給regex_search()的。用gif或許可以演示的比較形象一點,具體是這樣工作的(顏色加深部分,表示可以匹配的子序列):

首先在構造regex_iterator的時候,建構函式中首先就呼叫一次regex_search()將迭代器it指向了第一個匹配的子序列。以後的每一次迭代的過程中(++it),都會在以後剩下的子序列中繼續呼叫regex_search(),直到迭代器走到最後。it就一直“指向”了匹配的子序列。

知道了原理,我們寫起來程式碼就輕鬆多了。結合前面的部分我們,這個程式就基本寫好了:

12345678910111213141516171819202122 #include <iostream>#include <regex>#include <string>intmain(){std::stringtmp,html;while(getline(std::cin,tmp)){tmp+='\n';html+=tmp;}std::stringpattern("http(s)?://([\\w-]+\\.)+[\\w-]+(/[\\w- ./?%&=]*)?");pattern="[[:alpha:]]*"+pattern+"[[:alpha:]]*";std::regexr(pattern);for(std::sregex_iterator it(html.begin(),html.end(),r),end;//end是尾後迭代器,regex_iterator是regex_iterator的string型別的版本it!=end;++it){std::cout<<it->str()<<std::endl;}}

下載本頁的html原始碼儲存為test.html,編譯這個原始碼測試一下,大功告成:

123456789101112131415161718192021222324252627 [regex]g++regex.cpp-std=c++11-omain[regex]main<test.htmlhttp://www.cnblogs.com/ittinybird/rsshttp://www.cnblogs.com/ittinybird/rsd.xmlhttp://www.cnblogs.com/ittinybird/wlwmanifest.xmlhttp://common.cnblogs.com/script/jquery.jshttp://files.cnblogs.com/files/ittinybird/mystyle.csshttp://www.cnblogs.com/ittinybird/http://www.cnblogs.com/ittinybird/http://www.cnblogs.com/ittinybird/http://i.cnblogs.com/EditPosts.aspx?opt=1http://msg.cnblogs.com/send/%E6%88%91%E6%98%AF%E4%B8%80%E5%8F%AAC%2B%2B%E5%B0%8F%E5%B0%8F%E9%B8%9Fhttp://www.cnblogs.com/ittinybird/rsshttp://www.cnblogs.com/ittinybird/rsshttp://www.cnblogs.com/images/xml.gifhttp://i.cnblogs.com/http://www.cnblogs.com/ittinybird/p/4853532.htmlhttp://www.cnblogs.com/ittinybird/p/4853532.htmlhttp://www.w3school.com.cn/jsref/jsref_obj_regexp.asphttp://www.cnblogs.com/ittinybird/http://i.cnblogs.com/EditPosts.aspx?postid=4853532http://www.cnblogs.com/http://q.cnblogs.com/http://news.cnblogs.com/http://home.cnblogs.com/ing/http://job.cnblogs.com/http://kb.cnblogs.com/

regex 和異常處理

如果我們的正則表示式存在錯誤,則在執行的時候標準庫會丟擲一個regex_error異常,他有一個名為code的成員,用於標記錯誤的型別,具體錯誤值和語義如下表所示:

code
有關異常處理的基本內容,不是本篇要討論的內容,就不贅述了。

小結

C++11標準庫中的正則表示式部分還有部分內容本文沒有涉及,個人以為掌握了以上的內容後,基本上看一看介面就知道怎麼使用了,這裡就不浪費篇幅了。

謝謝你的閱讀,錯誤之處還請您指正,我將萬分感謝:)。