正則表達式

# 正則表達式 [toc]ios

正則表達式是一種描述字符序列的方法,是一種極其強大的計算工具。本文 重點介紹如何是使用C++正則表達式庫(RE庫),它是C++11新標準的一部分。c++

RE庫定義在頭文件regex中,它包含多個組件,以下表所示:正則表達式

正則表達式庫組件
regex 表示有一個正則表達式的類
regex_match 將一個字符序列與一個正則表達式匹配
regex_search 尋找第一個與正則表達式匹配的子序列
regex_replace 使用給定格式替換一個正則表達式
sregex_iterator 迭代器適配器,調用regex_search來遍歷一個string中全部匹配的子串
smatch 容器類,保存在string中搜索的結果
ssub_match string中匹配的子表達式的結果

使用正則表達式庫

從一個簡單的例子開始——查找違反拼寫規則「i除非在c以後,不然必須在e以前」的單詞express

#include <iostream>
#include <regex>

using namespace std;

int main()
{
	string pattern("[^c]ei");
	pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";
	regex r(pattern);	//構造一個用於查找模式的regex
	smatch results;		//定義一個對象保存搜索結果

	string test_str = "receipt freind theif receive";
	if (regex_search(test_str, results, r))
	{
		cout << results.str() << endl;
	}
	return 0;
}

正則表達式[^c]代表咱們但願匹配任意不是'c'的字符,而[^c]ei指出咱們想要匹配這種字符後接ei的字符串。此模式描述的字符串恰好是三個字符,爲了與整個單詞匹配,還須要一個正則表達式與這三個字母模式以前和以後的字母匹配。數組

默認狀況下,regex使用的正則表達式語言是ECMAScript。在ECMAScript中,模式[[:alpha:]]匹配任意字母,符號 + 和 * 分別表示咱們但願「一個或多個」 或「零個或多個」匹配。函數

函數regex_search在輸入序列中只要找到一個匹配子串就會中止查找,所以結果輸出是工具

freind編碼

指定regex對象的選項


regex r(re) regex r(re, f)spa

re表示一個正則表達式,它能夠是一個string、一個表示字符範圍的迭代對、一個指向空字符結尾的字符數組的指針、一個字符指針和一個計數器或是一個花括號包圍的字符列表。指針

f是指出對象如何處理的標誌。f經過下面列出的值來設置。若是未指定f,默認值爲ECMAScript。


r1 = re

將r1中的正則表達式替換成re。

re表示一個正則表達式,它能夠是另外一個regex對象、一個string、一個指向空字符結尾的字符數組的指針或是一個花括號包圍的字符列表。


r1.assign(re, f)

與使用賦值運算符(=)效果相同


r.mark_count()

r中子表達式的數目


r.flags()

返回r的標誌集


定義regex時指定的標誌
icase 在匹配過程當中忽略大小寫
nosubs 不保存匹配的子表達式
optimize 執行速度優於構造速度
ECMAScript 使用ECMA-262語法

一個正則表達式來識別擴展名的示例:

//1個或多個字母或數字字符後面接一個'.'再接"cpp"或"cxx"或"cc"
regex r("([[:alnum:]+)\\.(cpp|cxx|cc)$", regex::icase);
smatch results;
string filename;
while( cin >> filename)
    if( regex_search(filename, results, r))
        cout << results.str() << endl;

在正則表達式語言中,字符點(.)一般匹配任意字符。與C++同樣,能夠在字符以前放置一個反斜線\來去掉其特殊含義。因爲反斜線\也是C++中的一個特殊字符,咱們在字符串常量中必須連續使用兩個反斜線。

一個正則表達式的語法是否正確是在運行時解析的。若是咱們編寫的正則表達式存在錯誤,則在運行時標準庫會拋出一二類型爲regex_error的異常。相似標準庫異常類型,regex_error有一個描述發生什麼錯誤的what操做,和一個返回錯誤類型對應的數值編碼的code成員。

try{
    regex r("([[:alnum:]+)\\.(cpp|cxx|cc)$", regex::icase);
}catch (regex_error e)
{
    cout << e.what() << "\ncode:" << e.code() << endl;
}

1558963548400

  • 正則表達式類和輸入序列類型

咱們使用的RE庫類型必須與輸入序列類型匹配。例如

regex r("([[:alnum:]+)\\.(cpp|cxx|cc)$", regex::icase);
smatch results;
if( regex_search("myfile.cc", results, r)) //錯誤:輸入爲char*
    cout << results.str() << endl;

這段代碼編譯失敗,由於match參數的類型與輸入序列的類型不匹配。

cmatch results;
if( regex_search("myfile.cc", results, r)) //正確
    cout << results.str() << endl;

匹配與Regex迭代器類型

在第一個查找違反單詞拼寫規則例子中,它只打印輸入序列中第一個匹配的單詞。咱們可使用sregex_iterator來獲取全部匹配。regex迭代器是一種迭代器適配器,被綁定到一個輸入序列和一個regex對象上。

sregex_iterator it(b, e, r); 一個sregex_iterator,遍歷迭代器b和e表示的string。它調用sregex_search(b, e, r)將it定位到輸入中第一個匹配的位置
sregex_iterator end; sregex_iterator的尾後迭代器

當咱們將一個sregex_iterator綁定到一個string和一個regex對象時,迭代器自動定位到給定string中第一個匹配位置。

//查找不在字符c以後的字符串ei
	string pattern("[^c]ei");

	pattern = "[[:alpha:]]" + pattern + "[[:alpha:]]*";
	regex r(pattern, regex::icase);    //構造一個用於查找模式的regex
	string test_str = "receipt freind theif receive";
	for (sregex_iterator it(test_str.begin(), test_str.end(), r), end_it;
         it != end_it; ++it)
		cout << it->str() << endl;

end_it是一個空的sregex_iterator,起到尾後迭代器的做用。

Output:

freind
thief

匹配類型有兩個名爲prefix和suffix的成員,分別返回表示輸入序列中當前匹配以前和以後部分的ssub_match對象。一個ssub_match對象有兩個名爲str和length的成員,分別返回匹配的string和該string的大小

for( sregex_iterator it(file.begin(), file.end(), r), end_it;
        it != end_it; ++it)
{
    auto pos = it->prefix().length();   //前綴大小
    pos = pos > 40 ? pos - 40 : 0;		//咱們想要最多40個字符
    cout << it->prefix().str().substr(pos)	//前綴的最後一部分
            << "\n\t\t" << it->str() << " <<<\n"	//匹配的單詞
            << it->suffix().str().substr(0, 40)		//後綴的第一部分
            << endl;
}

這裏補充下substr()用法:

// string::substr
#include <iostream>
#include <string>

int main ()
{
  std::string str="We think in generalities, but we live in details.";
                                           // (quoting Alfred N. Whitehead)

  std::string str2 = str.substr (3,5);     // "think"

  std::size_t pos = str.find("live");      // position of "live" in str

  std::string str3 = str.substr (pos);     // get from "live" to the end

  std::cout << str2 << ' ' << str3 << '\n';

  return 0;
}

Output:

`think live in details. `

使用子表達式

正則表達式中的模式一般包含1個或多個**子表達式(subexpression)。**一個子表達式是模式的一部分,自己也具備意義。正則表達式語法一般用括號來表示子表達式。

regex r("([[:alnum:]+)\\.(cpp|cxx|cc)$", regex::icase);

這個模式保護兩個括號括起來的子表達式:

  • ([[:alnum:]]+),匹配1個或多個字符的序列
  • (cpp|cxx|cc),匹配文件擴展名
if( regex_search(filename, results, r))
    cout << results.str(1) << endl;

匹配對象除了提供匹配總體的相關信息外,還提供訪問模式中每一個子表達式的能力。子匹配是按位置來訪問的。第一個子匹配位置爲0,表示整個模式對應的匹配,隨後是每一個子表達式對應的匹配。

例如若是文件名爲foo.cpp,則results.str(0)將保存foo.cpp,results.str(1)將保存foo,results.str(2)將保存cpp。

驗證必須匹配特定格式的數據

  • {d}表示單個數字,而{d}{n}則表示一個n個數字的序列。(如:{d}{3}匹配三個數字的序列)

  • 在方括號中的字符集合表示匹配這些字符中的任意一個。(如:[-. ]匹配一個短橫線或一個點或一個空格。注意,點再括號中沒有特殊含義)

  • 後接'?'的組建是可選的。(如:{d}{3}[-. ]?{d}{4}匹配序列是開始是三個數字,後接一個可選的短橫線或點或空格,而後是四個數字。此模式能夠匹配555-0132或555.0132或555 0132或5550132)

  • 相似C++,ECMAScript使用反斜線\表示一個字符自己而不是其特殊含義。因爲咱們的模式包括括號,而括號是ECMAScript中的特殊字符,所以必須使用 和和 來表示括號是咱們模式的一部分而不是特殊字符。

    因爲反斜線\是C++的特殊字符,在模式中每次出現\的地方,必須用一個額外的反斜線來告知是反斜線而不是特殊字符。即咱們用\{d}{3}來表示正則表達式{d}{3}。

string phone = 
    "(\\()?\\{d}{3}(\\))?([-. ])?(\\{d}{3})([-. ])?(\\{d}{4})";
regex r(phone);
smatch m;
string s;
 
while(getline(cin, s))
{
    for( sregex_iterator it(s.begin(), s.end(), r), end_it;
            it != end_it; ++it)
    {
        if(valid(*it))
            cout << "valid:" << it->str() << endl;
        else
            cout << "not valid:" << it->str() << endl;    
    }
}

上述代碼是匹配美國電話號碼的模式示例:

  • (\()? :表示區號部分可選的左括號
  • \{d}{3} :表示區號
  • (\))? :表示區號部分可選的右括號
  • ([-. ])? :表示區號部分可選的分隔符
  • (\{d}{3}) :表示號碼的下三位數字
  • ([-. ])? :表示可選的分隔符
  • (\{d}{4}) :表示號碼的最後四位數字

使用子匹配操做

咱們將使用下表描述的子匹配操做來編寫valid函數。上述代碼的pattern有7個子表達式。每一個smatch對象會包含8個ssub_match元素。若是一個子表達式是完整匹配的一部分,則其對應的ssub_match對象的matched成員是true。

1558967336038

bool valid(const smatch &m)
{
    //若是區號前有一個左括號
    if(m[1].matched)
        //則區號後面有一個右括號,且以後緊跟剩餘號碼或一個空格
        return m[3].matched &&
                (m[4].matched == 0 || m[4].str() == " ");
    else
        //不然,區號後面不能有由括號,且後兩個組成部分間的分隔符必須匹配
        return !m[3].matched &&
                m[4].str() == m[6].str();
}

使用regex_replace

正則表達式不只用在咱們但願查找一個給定序列的時候,還用在當咱們想將找到序列替換成另外一個序列的時候。此時,能夠調用regex_replace。它接受一個輸入字符序列和一個regex對象,還接受一個描述咱們想要輸出形式的字符串。


m.format(dest, fmt, mft)

m.format(fmt, mft)

使用格式字符串 fmt 生成格式化輸出。匹配在m中,可選的match_flag_type標誌在mft中。

第一個版本寫入迭代器dest指向的目的位置並接受 fmt 參數,能夠是一個string,也能夠是表示字符數組範圍的一對指針。

第二個版本返回一個string,保存輸出,並接受 fmt 參數,能夠是一個string,也能夠是一個指向空字符結尾的字符數組指針。

mft的默認值爲format_default


regex_replace(dest, seq, r, fmt ,mft)

regex_replace( seq, r, fmt , mft)

遍歷seq,用regex_search查找和regex對象r匹配的子串。使用格式字符串fmt和可選match_flag_type標誌來生成輸出

第一個版本寫入迭代器dest指向的目的位置並接受一對迭代器seq表示範圍。

第二個版本返回一個string,保存輸出,且seq便可以是一個string,也能夠是一個指向空字符結尾的字符數組指針。

在全部狀況下, fmt便可以是一個string,也能夠是一個指向空字符結尾的字符數組指針。mft的默認值爲match_default


替換字符串由咱們想要的字符組合與匹配的子串對於的子表達式而組成。咱們用一個符號$後跟子表達式的索引號來表示一個特定的子表達式:

string phone = 
    "(\\()?\\{d}{3}(\\))?([-. ])?(\\{d}{3})([-. ])?(\\{d}{4})";
string fmt = "$2.$5.$7";
regex r(phone);
string number = "(908) 555-1800";
cout << regex_replace(number, r, fmt) << endl;

Output

908.555.1800

參考:《C++ Primer》 第五版

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息