boost regex 超詳細教程

頭文件: "boost/regex.hpp"

正則表達式被封裝爲一個類型 basic_regex的對象。我們將在下一節更深入地討論正則表達式如何被編譯和分析,這裏我們首先粗略地看看 basic_regex ,以及這個庫中三個最重要的算法。

namespace boost {
template <class charT,
class traits=regex_traits<charT> >
class basic_regex {
public:
explicit basic_regex(
const charT* p,
flag_type f=regex_constants::normal);

bool empty() const;

unsigned mark_count() const;

flag_type flags() const;
};

typedef basic_regex<char> regex;
typedef basic_regex<wchar_t> wregex;
}

成員函數

explicit basic_regex (
const charT* p,
flag_type f=regex_constants::normal);

這個構造函數接受一個包含正則表達式的字符序列,還有一個參數用於指定使用正則表達式時的選項,例如是否忽略大小寫。如果p中的正則表達式無效,則拋出一個 bad_expressionregex_error 的異常。注意這兩個異常其實是同一個東西;在寫這本書之時,尚未改變當前使用的名字 bad_expression ,但下一個版本的Boost.Regex將會使用 regex_error.

bool empty() const;

這個成員函數是一個謂詞,當basic_regex實例沒有包含一個有效的正則表達式時返回 true ,即它被賦予一個空的字符序列時。

unsigned mark_count() const;

mark_count 返回regex中帶標記子表達式的數量。帶標記子表達式是指正則表達式中用圓括號括起來的部分。匹配這個子表達式的文本可以通過調用某個正則表達式算法而獲得。

flag_type flags() const;

返回一個位掩碼,其中包含這個basic_regex所設置的選項標誌。例如標誌 icase, 表示正則表達式忽略大小寫,標誌 JavaScript, 表示regex使用JavaScript的語法。

typedef basic_regex<char> regex;
typedef basic_regex<wchar_t> wregex;

不要使用類型 basic_regex來定義變量,你應該使用這兩個typedef中的一個。這兩個類型,regexwregex, 是兩種字符類型的縮寫,就如 stringwstringbasic_string<char>basic_string<wchar_t>的縮寫一樣。這種相似性是不一樣的,某種程度上,regex 是一個特定類型的字符串的容器。

普通函數

template <class charT,class Allocator,class traits >
bool regex_match(
const charT* str,
match_results<const charT*,Allocator>& m,
const basic_regex<charT,traits >& e,
match_flag_type flags = match_default);

regex_match 判斷一個正則表達式(參數 e)是否匹配整個字符序列 str. 它主要用於驗證文本。注意,這個正則表達式必須匹配被分析串的全部,否則函數返回 false. 如果整個序列被成功匹配,regex_match 返回 True.

template <class charT,class Allocator, class traits> 
bool regex_search(
const charT* str,
match_results<const charT*,Allocator>& m,
const basic_regex<charT,traits >& e,
match_flag_type flags = match_default);

regex_search 類似於 regex_match, 但它不要求整個字符序列完全匹配。你可以用 regex_search 來查找輸入中的一個子序列,該子序列匹配正則表達式 e.

template <class traits,class charT>
basic_string<charT> regex_replace(
const basic_string<charT>& s,
const basic_regex<charT,traits >& e,
const basic_string<charT>& fmt,
match_flag_type flags = match_default);

regex_replace 在整個字符序列中查找正則表達式e的所有匹配。這個算法每次成功匹配後,就根據參數fmt對匹配字符串進行格式化。缺省情況下,不匹配的文本不會被修改,即文本會被輸出但沒有改變。

這三個算法都有幾個不同的重載形式:一個接受 const charT* (charT 爲字符類型), 另一個接受 const basic_string<charT>&, 還有一個重載接受兩個雙向迭代器作爲輸入參數。

用法

要使用Boost.Regex, 你需要包含頭文件"boost/regex.hpp". Regex是本書中兩個需要獨立編譯的庫之一(另一個是Boost.Signals)。你會很高興獲知如果你已經構建了Boost— —那隻需在命令提示符下打一行命令——就可以自動鏈接了(對於Windows下的編譯器),所以你不需要爲指出那些庫文件要用而費心。

你要做的第一件事就是聲明一個類型 basic_regex 的變量。這是該庫的核心類之一,也是存放正則表達式的地方。創建這樣一個變量很簡單;只要將一個含有你要用的正則表達式的字符串傳遞給構造函數就行了。

boost::regex reg("(A.*)");

這個正則表達式具有三個有趣的特性。第一個是,用圓括號把一個子表達式括起來,這樣可以稍後在同一個正則表達式中引用它,或者取出匹配它的文本。我們稍後會詳細討論它,所以如果你還不知道它有什麼用也不必擔心。第二個是,通配符(wildcard)字符,點。這個通配符在正則表達式中有非常特殊的意義;這可以匹配任意字符。最後一個是,這個表達式用到了一個重複符,*, 稱爲Kleene star, 表示它前面的表達式可以被匹配零次或多次。這個正則表達式已可以用於某個算法了,如下:

bool b=boost::regex_match(
"This expression could match from A and beyond.",
reg);

如你所見,你把正則表達式和要分析的字符串傳遞給算法 regex_match. 如果的確存在與正則表達式的匹配,則該函數調用返回結果 true ;否則,返回 false. 在這個例子中,結果是 false, 因爲 regex_match 僅當整個輸入數據被正則表達式成功匹配時才返回 true 。你知道爲什麼是這樣嗎?再看一下那個正則表達式。第一個字符是大寫的 A, 很明顯能夠匹配這個表達式的第一個字符在哪。所以,輸入的一部分"A and beyond."可以匹配這個表達式,但這不是整個輸入。讓我們試一下另一個輸入字符串。

bool b=boost::regex_match(
"As this string starts with A, does it match? ",
reg);

這一次,regex_match 返回 true. 當正則表達式引擎匹配了 A, 它接着看後續有什麼。在我們的regex變量中,A 後跟一個通配符和一個Kleene star, 這意味着任意字符可以被匹配任意次。因而,分析過程開始扔掉輸入字符串的剩餘部分,即匹配了輸入的所有部分。

接下來,我們看看如何使用regexes 和 regex_match 來進行數據驗證。

驗證輸入

正則表達式常用於對輸入數據的格式進行驗證。應用軟件通常要求輸入符合某種結構。考慮一個應用軟件,它要求輸入一定要符合如下格式,"3個數字, 一個單詞, 任意字符, 2個數字或字符串"N/A," 一個空格, 然後重複第一個單詞." 手工編寫代碼來驗證這個輸入既沉悶又容易出錯,而且這些格式還很可能會改變;在你弄明白之前,可能就需要支持其它的格式,你精心編寫的分析器可能就需要修 改並重新調試。讓我們寫出一個可以驗證這個輸入的正則表達式。首先,我們需要一個匹配3個數字的表達式。對於數字,我們應該使用一個特別的縮寫,/d。要表示它被重複3次,需要一個稱爲bounds operator的特定重複,它用花括號括起來。把這兩個合起來,就是我們的正則表達式的開始部分了。

boost::regex reg("//d{3}");

注意,我們需要在轉義字符(/)之前加一個轉義字符,即在我們的字符串中,縮寫 /d 變成了 //d 。這是因爲編譯器會把第一個/當成轉義字符扔掉;我們需要對/進行轉義,這樣/纔可以出現在我們的正則表達式中。

接下來,我們需要定義一個單詞的方法,即定義一個字符序列,該序列結束於一個非字母字符。有不只一種方法可以實現它,我們將使用字符類別(也稱爲字符集)和範圍這兩個正則表達式的特性來做。字符類別即一個用方括號括起來的表達式。例如,一個匹配字符a, b, 和 c中任一個的字符類別表示爲:[abc]. 如果用範圍來表示同樣的東西,我們要寫:[a-c]. 要寫一個包含所有字母的字符類型,我們可能會有點發瘋,如果要把它寫成: [abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ], 但不用這樣;我們可以用範圍來表示:[a-zA-Z]. 要注意的是,象這樣使用範圍要依賴於當前所用的locale,如果正則表達式的 basic_regex::collate 標誌被打開。使用以上工具以及重複符 +, 它表示前面的表達式可以重複,但至少重複一次,我們現在可以表示一個單詞了。

boost::regex reg("[a-zA-Z]+");

以上正則表達式可以工作,但由於經常要表示一個單詞,所以有一個更簡單的方法:/w. 這個符號匹配所有單詞,不僅是ASCII的單詞,因此它不僅更短,而且也更適用於國際化的環境。接下來的字符是一個任意字符,我們已經知道要用點來表示。

boost::regex reg(".");

再接下來是 2個數字或字符串 "N/A." 爲了匹配它,我們需要用到一個稱爲選擇的特性。選擇即是匹配兩個或更多子表達式中的任意一個,每種選擇之間用 | 分隔開。就象這樣:

boost::regex reg("(//d{2}|N/A)");

注意,這個表達式被圓括號括了起來,以確保整個表達式被看作爲兩個選擇。在正則表達式中增加一個空格是很簡單的;用縮寫/s. 把以上每一樣東西合併起來,就得到了以下表達式:

boost::regex reg("//d{3}[a-zA-Z]+.(//d{2}|N/A)//s");

現在事情變得有點複雜了。我們需要某種方法,來驗證接下來的輸入數據中的單詞是否匹配第一個單詞(即那個我們用表達式[a-zA-Z]+所捕獲的單詞)。關鍵是要使用後向引用(back reference),即對前面的子表達式的引用。爲了可以引用表達式 [a-zA-Z]+, 我們必須先把它用圓括號括起來。這使得表達式([a-zA-Z]+)成爲我們的正則表達式中的第一個子表達式,我們就可以用索引1來建立一個後向引用了。

這樣,我們就得到了整個正則表達式,用於表示"3個數字, 一個單詞, 任意字符, 2個數字或字符串"N/A," 一個空格, 然後重複第一個單詞.":

boost::regex reg("//d{3}([a-zA-Z]+).(//d{2}|N/A)//s//1");

乾的好!下面是一個簡單的程序,把這個表達式用於算法 regex_match, 驗證兩個輸入字符串。

#include <iostream>
#include <cassert>
#include <string>
#include "boost/regex.hpp"

int main() {
// 3 digits, a word, any character, 2 digits or "N/A",
// a space, then the first word again
boost::regex reg("//d{3}([a-zA-Z]+).(//d{2}|N/A)//s//1");

std::string correct="123Hello N/A Hello";
std::string incorrect="123Hello 12 hello";

assert(boost::regex_match(correct,reg)==true);
assert(boost::regex_match(incorrect,reg)==false);
}

第一個字符串,123Hello N/A Hello, 是正確的;123 是3個數字,Hello 是一個後跟任意字符(一個空格)的單詞, 然後是N/A和另一個空格,最後重複單詞Hello 。第二個字符串是不正確的,因爲單詞 Hello 沒有被嚴格重複。缺省情況下,正則表達式是大小寫敏感的,因而反向引用不能匹配。

寫出正則表達式的一個關鍵是成功地分解問題。看一下你剛纔建立的最終的那個表達式,對於未經過訓練的人來說它的確很難懂。但是,如果把這個表達式分解成小的部分,它就不太複雜了。

查找

現在我們來看一下另一個Boost.Regex算法, regex_search. 與 regex_match 不同的是,regex_search 不要求整個輸入數據完全匹配,則僅要求部分數據可以匹配。作爲說明,考慮一個程序員的問題,他可能在他的程序中有一至兩次忘記了調用 delete 。雖然他知道這個簡單的測試可能沒什麼意義,他還是決定計算一下newdelete出現的次數,看看數字是否符合。這個正則表達式很簡單;我們有兩個選擇,new 和 delete.

boost::regex reg("(new)|(delete)");

有兩個原因我們要把子表達式用括號括起來:一個是爲了表明我們的選擇是兩個組。另一個原因是我們想在調用regex_search時引用這些子表達式,這樣我們就可以判斷是哪一個選擇被匹配了。我們使用regex_search的一個重載,它接受一個match_results類型的參數。當 regex_search 執行匹配時,它通過一個match_results類型的對象報告匹配的子表達式。類模板 match_results 使用一個輸入序列所用的迭代器類型來參數化。

template <class Iterator,
class Allocator=std::allocator<sub_match<Iterator> >
class match_results;

typedef match_results<const char*> cmatch;
typedef match_results<const wchar_t> wcmatch;
typedef match_results<std::string::const_iterator> smatch;
typedef match_results<std::wstring::const_iterator> wsmatch;

我們將使用 std::string, 所以要留意 typedef smatch, 它是 match_results<std::string::const_iterator>的縮寫。如果 regex_search 返回 true, 傳遞給該函數的 match_results 引用將包含匹配的子表達式結果。在 match_results裏,用已索引的sub_match來表示正則表達式中的每個子表達式。我們來看一下我們如何幫助這位困惑的程序員來計算對newdelete的調用。

boost::regex reg("(new)|(delete)");
boost::smatch m;
std::string s=
"Calls to new must be followed by delete. /
Calling simply new results in a leak!";

if (boost::regex_search(s,m,reg)) {
// Did new match?
if (m[1].matched)
std::cout << "The expression (new) matched!/n";
if (m[2].matched)
std::cout << "The expression (delete) matched!/n";
}

以上程序在輸入字符串中查找 newdelete, 並報告哪一個先被找到。通過傳遞一個類型 smatch 的對象給 regex_search, 我們可以得知算法如何執行成功的細節。我們的表達式中有兩個子表達式,因此我們可以通過match_results的索引1得到子表達式 new . 這樣我們得到一個 sub_match實例,它有一個Boolean成員,matched, 告訴我們這個子表達式是否參與了匹配。因此,對於上例的輸入,運行結果將輸出"The expression (new) matched!/n". 現在,你還有一些工作要做。你需要繼續把正則表達式應用於輸入的剩餘部分,爲此,你要使用另外一個 regex_search的重載,它接受兩個迭代器,指示出要查找的字符序列。因爲 std::string 是一個容器,它提供了迭代器。現在,在每一次匹配時,你必須把指示範圍起始點的迭代器更新爲上一次匹配的結束點。最後,增加兩個變量來記錄 newdelete的次數。以下是完整的程序:

#include <iostream>
#include <string>
#include "boost/regex.hpp"

int main() {
// "new" and "delete" 出現的次數是否一樣?
boost::regex reg("(new)|(delete)");
boost::smatch m;
std::string s=
"Calls to new must be followed by delete. /
Calling simply new results in a leak!";
int new_counter=0;
int delete_counter=0;
std::string::const_iterator it=s.begin();
std::string::const_iterator end=s.end();

while (boost::regex_search(it,end,m,reg)) {
// 是 new 還是 delete?
m[1].matched ? ++new_counter : ++delete_counter;
it=m[0].second;
}

if (new_counter!=delete_counter)
std::cout << "Leak detected!/n";
else
std::cout << "Seems ok.../n";
}

注意,這個程序總是把迭代器 it 設置爲 m[0].secondmatch_results[0] 返回對匹配整個正則表達式的子匹配的引用,因此我們可以確認這個匹配的結束點就是下次運行regex_search的起始點。運行這個程序將輸出"Leak detected!", 因爲這裏有兩次 new, 而只有一次 delete. 當然,一個變量也可能在兩個地方刪除,還有可能調用 new[]delete[], 等等。

現在,你應該已經對子表達式如何分組使用有了較好的瞭解。現在是時候進入到最後一個Boost.Regex算法,該算法用於執行替換工作。

替換

Regex算法家族中的第三個算法是 regex_replace. 顧名思義,它是用於執行文本替換的。它在整個輸入數據中進行搜索,查找正則表達式的所有匹配。對於表達式的每一個匹配,該算法調用 match_results::format 並輸入結果到一個傳入函數的輸出迭代器。

在本章的介紹部分,我給出了一個例子,將英式拼法的 colour 替換爲美式拼法 color. 不使用正則表達式來進行這個拼寫更改會非常乏味,也很容易出錯。問題是可能存在不同的大小寫,而且會有很多單詞被影響,如colourize. 要正確地解決這個問題,我們需要把正則表達式分爲三個子表達式。

boost::regex reg("(Colo)(u)(r)",
boost::regex::icase|boost::regex::perl);

我們將要去掉的字母u獨立開,爲了在所有匹配中可以很容易地刪掉它。另外,注意到這個正則表達式是大小寫無關的,我們要把格式標誌 boost::regex::icase 傳給 regex 的構造函數。你還要傳遞你想要設置的其它標誌。設置標誌時一個常見的錯誤就是忽略了regex缺省打開的那些標誌,如果你沒有設置這些標誌,它們不會打開,你必須設置所有你要打開的標誌。

調用 regex_replace時,我們要以參數方式提供一個格式化字符串。該格式化字符串決定如何進行替換。在這個格式化字符串中,你可以引用匹配的子表達式,這正是我們想要的。你想保留第一個和第三個匹配的子表達式,而去掉第二個(u)。表達式 $N表示匹配的子表達式, N 爲子表達式索引。因此我們的格式化串應該是 "$1$3", 表示替換文本爲第一個和第三個子表達式。通過引用匹配的子表達式,我們可以保留匹配文本中的所有大小寫,而如果我們用字符串來作替換文本則不能做到這一點。以下是解決這個問題的完整程序。

#include <iostream>
#include <string>
#include "boost/regex.hpp"

int main() {
boost::regex reg("(Colo)(u)(r)",
boost::regex::icase|boost::regex::perl);

std::string s="Colour, colours, color, colourize";

s=boost::regex_replace(s,reg,"$1$3");
std::cout << s;
}

程序的輸出是 "Color, colors, color, colorize". regex_replace 對於這樣的文本替換非常有用。

用戶常見的誤解

我所見到的與Boost.Regex相關的最常見的問題與regex_match的語義有關。人們很容易忘記必須使regex_match的所有輸入匹配給定的正則表達式。因此,用戶常以爲以下代碼會返回 true.

boost::regex reg("//d*");
bool b=boost::regex_match("17 is prime",reg);

無疑這個調用永遠不會得到成功的匹配。只有所有輸入被 regex_match 匹配纔可以返回 true!幾乎所有的用戶都會問爲什麼 regex_search 不是這樣而 regex_match 是。

boost::regex reg("//d*");
bool b=boost::regex_search("17 is prime",reg);

這次肯定返回 true. 值得注意的是,你可以用一些特定的緩衝操作符來讓 regex_searchregex_match 那樣運行。/A 匹配緩衝的起始點,而 /Z 匹配緩衝的結束點,因此如果你把 /A 放在正則表達式的開始,把 /Z 放在最後,你就可以讓 regex_searchregex_match 那樣使用,即必須匹配所有輸入。以下正則表達式要求所有輸入被匹配掉,而不管你使用的是 regex_match 或是 regex_search.

boost::regex reg("//A//d*//Z");

請記住,這並不表示可以無需使用 regex_match;相反,它可以清晰地表明我們剛纔說到的語義,即必須匹配所有輸入。

關於重複和貪婪

另一個容易混淆的地方是關於重複的貪婪。有些重複,如 +*,是貪婪的。即是說,它們會消耗掉儘可能多的輸入。以下正則表達式並不罕見,它用於在一個貪婪的重複後捕獲兩個數字。

boost::regex reg("(.*)(//d{2})");

這個正則表達式是對的,但它可能不能匹配你想要的子表達式!表達式 .* 會吞掉所有東西而後續的子表達式將不能匹配。以下是示範這個行爲的一個例子:

int main() {
boost::regex reg("(.*)(//d{2})");
boost::cmatch m;
const char* text = "Note that I'm 31 years old, not 32.";
if(boost::regex_search(text,m, reg)) {
if (m[1].matched)
std::cout << "(.*) matched: " << m[1].str() << '/n';
if (m[2].matched)
std::cout << "Found the age: " << m[2] << '/n';
}
}

在這個程序中,我們使用了match_results的另一個特化版本,即類型 cmatch. 它就是 match_results<const char*>typedef, 之所以我們必須用它而不是用之前用過的 smatch,是因爲我們現在是用一個字符序列調用 regex_search 而不是用類型 std::string 來調用。你期望這個程序的運行結果是什麼?通常,一個剛開始使用正則表達式的用戶會首先想到 m[1].matchedm[2].matched 都爲 true, 且第二個子表達式的結果會是 "31". 接着在認識到貪婪的重複所帶來的效果後,即重複會儘可能消耗輸入,用戶會想到只有第一個子表達式是 true,即 .* 成功地吞掉了所有的輸入。最後,新用戶得到了下結論,兩個子表達式都被匹配,但第二個表達式匹配的是最後一個可能的序列。即第一個子表達式匹配的是 "Note that I'm 31 years old, not" 而第二個匹配 "32".

那麼,如果你想使用重複並匹配另一個子表達式的第一次出現,該怎麼辦?要使用非貪婪的重複。在重複符後加一個 ? ,重複就變爲非貪婪的了。這意味着該表達式會嘗試發現最短的匹配可能而不再阻止表達式的剩餘部分進行匹配。因此,要讓前面的正則表達式正確工作,我們需要把它改爲這樣。

boost::regex reg("(.*?)(//d{2})");

如果我們用這個正則表達式來修改程序,那麼 m[1].matchedm[2].matched 都會爲 true. 表達式 .*? 只消耗最少可能的輸入,即它將在第一個字符 3處停止,因爲這就是表達式要成功匹配所需要的。因此,第一個子表達式會匹配 "Note that I'm" 而第二個匹配 "31".

看一下 regex_iterator

我們已經看過如何用幾次 regex_search 調用來處理所有輸入,但另一方面,更爲優雅的方法是使用 regex_iterator. 這個迭代器類型用一個序列來列舉正則表達式的所有匹配。解引用一個 regex_iterator 會產生對一個 match_results 實例的引用。構造一個 regex_iterator 時,你要把指示輸入序列的迭代器傳給它,並提供相應的正則表達式。我們來看一個例子,輸入數據是一組由逗號分隔的整數。相應的正則表達式很簡單。

boost::regex reg("(//d+),?");

在正則表達式的最後加一個 ? (匹配零次或一次) 確保最後一個數字可以被成功分析,即使輸入序列不是以逗號結束。另外,我們還使用了另一個重複符 +. 這個重複符表示匹配一次或多次。現在,不需要多次調用 regex_search, 我們創建一個 regex_iterator, 並調用算法 for_each, 傳給它一個函數對象,該函數對象以迭代器的解引用進行調用。下面是一個接受任意形式的match_results的函數對象,它有一個泛型的調用操作符。它所執行的就是把當前匹配的值加到一個總和中(在我們的正則表達式中,第一個子表達式是我們要用的)。

class regex_callback {
int sum_;
public:
regex_callback() : sum_(0) {}

template <typename T> void operator()(const T& what) {
sum_+=atoi(what[1].str().c_str());
}

int sum() const {
return sum_;
}
};

現在把這個函數對象的一個實例傳遞給 std::for_each, 結果是對每一個迭代器it的解引用調用該函數對象,即對每一次匹配的子表達式進行調用。

int main() {
boost::regex reg("(//d+),?");
std::string s="1,1,2,3,5,8,13,21";

boost::sregex_iterator it(s.begin(),s.end(),reg);
boost::sregex_iterator end;

regex_callback c;
int sum=for_each(it,end,c).sum();
}

如你所見,傳遞給for_each的end迭代器是 regex_iterator 一個缺省構造實例。itend 的類型均爲 boost::sregex_iterator, 即爲 regex_iterator<std::string::const_iterator>typedef. 這種使用 regex_iterator 的方法要比我們前面試過的多次匹配的方法更清晰,在多次匹配的方法中我們不得不在一個循環中讓起始迭代器不斷地前進並調用 regex_search

regex_token_iterator 分割字符串

另一個迭代器類型,或者說得更準確些,迭代器適配器,就是 boost::regex_token_iterator. 它與 regex_iterator 很類似,但卻是用於列舉不匹配某個正則表達式的每一個字符序列,這對於分割字符串很有用。它也可以用於選擇對哪一個子表達式感興趣,當解引用 regex_token_iterator時,只有預訂的那個子表達式被返回。考慮這樣一個應用程序,它接受一些用斜線號分隔的數據項作爲輸入。兩個斜線號之間的數據組成應用程序要處理的項。使用 regex_token_iterator來分割這個字符串很容易。該正則表達式很簡單。

boost::regex reg("/");

這個 regex 匹配各項間的分割符。要用它來分割輸入,只需簡單地把指定的索引 1 傳遞給 regex_token_iterator 的構造函數。以下是完整的程序:

int main() {
boost::regex reg("/");
std::string s="Split/Values/Separated/By/Slashes,";
std::vector<std::string> vec;
boost::sregex_token_iterator it(s.begin(),s.end(),reg,-1);
boost::sregex_token_iterator end;
while (it!=end)
vec.push_back(*it++);

assert(vec.size()==std::count(s.begin(),s.end(),'/')+1);
assert(vec[0]=="Split");
}

就象 regex_iterator 一樣,regex_token_iterator 是一個模板類,它使用所包裝的序列的迭代器類型來進行特化。這裏,我們用的是 sregex_token_iterator, 它是 regex_token_iterator<std::string::const_iterator>typedef 。每一次解引用這個迭代器it,它返回當前的 sub_match, 當這個迭代器前進時,它嘗試再次匹配該正則表達式。這兩個迭代器類型,regex_iteratorregex_token_iterator, 都非常有用;你應該明白,當你考慮反覆調用regex_search時,就該用它們了。

更多的正則表達式

你已經看到了不少正則表達式的語法,但還有更多的要了解。這一節簡單地示範一些你每天都會使用的正則表達式的其它功能。作爲開始,我們先看一下一組完整的重複符;我們之前已經看到了 *, +, 以及使用 {} 進行限定重複。還有一個重複符,即是 ?. 你可能已經留意到它也可以用於聲明非貪婪的重複,但對於它本身而言,它是表示一個表達式必須出現零次或一次。還有一點值得提及的是,限定重複符可以很靈活;下面是三種不同的用法:

boost::regex reg1("//d{5}");
boost::regex reg2("//d{2,4}");
boost::regex reg3("//d{2,}");

第一個正則表達式匹配5個數字。第二個匹配 2個, 3個, 或者 4個數字。第三個匹配2個或更多個數字,沒有上限。

另一種重要的正則表達式特性是使用元字符 ^ 表示非字符類別。用它來表示一個匹配任意不在給定字符類別中的字符;即你所列字符類別的補集。例如,看如下正則表達式。

boost::regex reg("[^13579]");

它包含一個非字符類別,匹配任意不是奇數數字的字符。看一下以下這個小程序,試着給出程序的輸出。

int main() {
boost::regex reg4("[^13579]");
std::string s="0123456789";
boost::sregex_iterator it(s.begin(),s.end(),reg4);
boost::sregex_iterator end;

while (it!=end)
std::cout << *it++;
}

你給出答案了嗎?輸出是 "02468",即所有偶數數字。注意,這個字符類別不僅匹配偶數數字,如果輸入字符串是 "AlfaBetaGamma",那麼也會全部匹配。

我們看到的這個元字符, ^, 還有另一個意思。它可以用來表示一行的開始。而元字符 $ 則表示一行的結束。

錯的正則表達式

一個錯的正則表達式就是一個不遵守規則的正則表達式。例如,你可能忘了一個右括號,這樣正則表達式引擎將無法成功編譯這個正則表達式。這時,將拋出一個 bad_expression 類型的異常。正如我前面提到的,這個異常的名字將會在下一版本的Boost.Regex中被修改,還有在即將加入Library Technical Report的版本中也是。異常類型 bad_expression 將被更名爲 regex_error.

如果你的應用程序中的正則表達式全都是硬編碼的,你可能不用處理錯誤表達式,但如果你是接受了用戶的輸入來作爲正 則表達式,你就必須準備進行錯誤處理。這裏有一個程序,提示用戶輸入一個正則表達式,接着輸入一個用來對正則表達式進行匹配的字符串。由用戶進行輸入時, 總是有可能會導致無效的輸入。

int main() {  
std::cout << "Enter a regular expression:/n";
std::string s;
std::getline(std::cin, s);
try {
boost::regex reg(s);
std::cout << "Enter a string to be matched:/n";

std::getline(std::cin,s);

if (boost::regex_match(s,reg))
std::cout << "That's right!/n";
else
std::cout << "No, sorry, that doesn't match./n";
}
catch(const boost::bad_expression& e) {
std::cout <<
"That's not a valid regular expression! (Error: " <<
e.what() << ") Exiting.../n";
}
}

爲了保護應用程序和用戶,一個 try/catch 塊用於處理構造時拋出 boost::regex 的情形,這時會打印一個提示信息,而程序會溫和地退出。用這個程序來測試,我們開始時輸入一些合理的數據。

Enter a regular expression:
/d{5}
Enter a string to be matched:
12345
That's right!

現在,給一些錯誤的數據,試着輸入一個錯誤的正則表達式。

Enter a regular expression:
(/w*))
That's not a valid regular expression! (Error: Unmatched ( or /() Exiting...

regex reg構造時,就會拋出一個異常,因爲這個正則表達式不能被編譯。因此,進入到 catch 的處理例程中,程序將打印一個錯誤信息並退出。你只需知道有三個可能會發生異常的地方。一個是在構造一個正則表達式時,就象你剛剛看到的那樣;另一個是使用成員函數 assign 把正則表達式賦給一個 regex 時。最後一個是,regex迭代器和算法也可能拋出異常,如果內存不夠或者匹配的複雜度過快增長的話。

 

Regex 總結

無可爭議,正則表達式是非常有用和重要的,而本庫給C++帶來了強大的正則表達式功能。傳統上,用戶除了使用 POSIX C API來實現正則表達式功能以外,別無選擇。對於文本處理的驗證工作,正則表達式比手工編寫分析代碼要靈活和可靠得多。對於查找和替換,使用正則表達式可 以優美地解決很多相關問題,而不用它們則根本無法解決。

Boost.Regex是一個強大的庫,因此不可能在這一章中完全覆蓋它所有的內容。同樣,正則表達式的完美表現 和廣泛的應用範圍意味着本章也不僅僅是簡單地介紹一下它們。這個主題可以寫成一本單獨的書。要知道更多,可以學習Boost.Regex的在線文檔,並且 找一本關於正則表達式的書(考慮一下參考書目中的建議)。不論Boost.Regex有多強大,正則表達式有多廣多深,初學者還是可以有效地使用本庫中的 正則表達式。對於那些由於C++不支持正則表達式而選擇了其它語言的程序員,歡迎你們回家。

Boost.Regex並不是C++程序員唯一可以使用的正則表達式庫,但它的確是最好的一個。它易於使用,並且在匹配你的正則表達式時快如閃電。你應該儘可能去用它。

Boost.Regex 的作者是 Dr. John Maddock.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章