C++11:正則表達(dá)式

1、介紹

正則表達(dá)式是一種描述字符序列的方法,下面介紹C++11正則表達(dá)式庫(RE)庫的使用。

其包含的組件如下:

regex  //表示一個(gè)正則表達(dá)式的類
regex_match  //將一個(gè)字符與一個(gè)正則表達(dá)式匹配
regex_search  //尋找第一個(gè)與正則表達(dá)式匹配的子序列
regex_replace  //使用給定格式替換一個(gè)正則表達(dá)式
sregex_iterator  //迭代器適配器,調(diào)用regex_search來遍歷一個(gè)string中所有匹配的字串
smatch  //容器類,保存在string中搜索的結(jié)果
ssub_match  //string中匹配的子表達(dá)式的結(jié)果

2、正則表達(dá)式的使用

從一個(gè)例子開始,查找違反拼寫規(guī)則:i除非在c之后,否則必須在e之前的單詞。

    string pattern("[^c]ei");
    pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";
    regex r(pattern);
    smatch ret;
    string reg_test = "receipt freind theif receive";
    if (regex_search(reg_test, ret, r))
        cout << "find the str: " <<ret.str() << endl;

首先聲明我們需要使用的pattern,即正則表達(dá)式;然后定義regex 和smatch ,調(diào)用regex_search函數(shù)查找對(duì)應(yīng)的字符串中滿足pattern的字串。然后調(diào)用smatch.str()輸出。C++11中的正則表達(dá)式默認(rèn)使用的正則表達(dá)式語法是ECMAScript。

下面列出regex常用的一些選項(xiàng):

regex r(re)   //re可以是一個(gè)正則表達(dá)式,一個(gè)string,一個(gè)表示字符范圍的迭代器對(duì),一個(gè)指向空字符結(jié)尾的字符數(shù)組的指針,一個(gè)字符指針和一個(gè)計(jì)數(shù)器或者是一個(gè)花括號(hào)包圍的字符列表
regex r(re, f)  //f是指出對(duì)象如何處理的標(biāo)志
r = re  //將r中正則表達(dá)式替換為re
r.assign(re, f);  //與運(yùn)算符 = 效果相同
r.mark_count();  //r中子表達(dá)式的數(shù)目
r.flags();  //返回r的標(biāo)志集合

下面是標(biāo)志集合:

icase  忽略大小寫
nosubs  不保存匹配的子表達(dá)式
optimize  執(zhí)行速度優(yōu)先于構(gòu)造速度
ECMAScript  使用ECMA-262語法
basic  使用POSIX基本的正則表達(dá)式語法
extended  使用POSIX擴(kuò)張的正則表達(dá)式語法
awk  使用POSIX版本的awk語言的語法
grep  使用POSIX版本的grep語言的語法
grep  使用POSIX版本的egrep語言的語法

接下來再看一個(gè)例子,查找后綴為.cpp .cc. cxx的文件:

    array<string, 5> as5{"1.cpp", "sdja.txt", "qwei91.cxx", "sdauhd.c", "asdjh.cc"};
    regex r1("([[:alnum:]]+)\\.(cpp|cxx|cc)$", regex::icase); //在第一個(gè)表達(dá)式中加了()就變成了子表達(dá)式
    smatch ret1;
    for (auto e : as5)
    {
        if(regex_search(e, ret1, r1))
            cout << "find the cpp\\cxx\\cc file: " << ret1.str() << endl;
    }

在編寫正則表達(dá)式的時(shí)候如果寫錯(cuò)了,比如缺少括號(hào)這樣的錯(cuò)誤,在編譯階段是不會(huì)報(bào)錯(cuò)的,因?yàn)檎齽t表達(dá)式本身相當(dāng)于一個(gè)簡(jiǎn)單語言設(shè)計(jì)的“程序”。所以如果正則表達(dá)式寫錯(cuò)了,只會(huì)在運(yùn)行階段,拋出異常。C++11中定義的正則表達(dá)式異常有13個(gè),用到的時(shí)候再去查吧。

3、匹配與Regex迭代器類型

在使用正則表達(dá)式匹配字符串的時(shí)候,我們可以使用sregex_iterator來獲得所有的匹配。

下面是sregex_iterator的操作:

sregex_iterator it(b,e,r); //遍歷迭代器b和e表示的string,它調(diào)用sregex_search(b,e,r)將it定位到輸入中第一個(gè)匹配的位置
sregex_iterator end;  //尾后迭代器
*it //返回一個(gè)smatch對(duì)象的引用
it->  //指向smatch對(duì)象的指針
++it  //自加
it++
it1 == it2;  //如果兩個(gè)sregex_iterator都是尾后迭代器,則它們相等。兩個(gè)非尾后迭代器是從相同的輸入序列和regex對(duì)象構(gòu)造,則它們相等
it1 != it2; 

當(dāng)我們將一個(gè)sregex_iterator綁定到一個(gè)string和一個(gè)regex對(duì)象時(shí),迭代器自動(dòng)定位到給定string中第一個(gè)匹配位置。

下面是一個(gè)使用sregex_iterator的例子:

    //使用regex_iterator,輸出所有的匹配結(jié)果,并且輸出上下文
    string pattern1("[^c]ei");
    pattern1 = "[[:alpha:]]*" + pattern1 + "[[:alpha:]]*";
    regex r2(pattern1, regex::icase);
    string reg_test = "receipt freind theif receive";
    for (sregex_iterator it(reg_test.begin(), reg_test.end(), r2), end_it; it != end_it; ++it)
    {
        auto pos = it->prefix().length();
        pos = pos > 40 ? pos - 40 : 0;
        cout << it->prefix().str().substr(pos)
             << "\n\t\t>>>" << it->str() << "<<<\n"
             << it->suffix().str().substr(0, 40) << endl;
    }

上面的例子中使用sregex_iterator輸出所有匹配的結(jié)果,并且使用prefix()和suffix()輸出匹配位置的上下文。

下面是smatch的常用操作:

m.ready();  //如果已經(jīng)通過調(diào)用regex_serach或者regex_match設(shè)置了m,則返回true;否則返回false。如果ready返回false,則對(duì)m的操作是未定義的
m.size();  //如果匹配失敗,則返回0;否則返回最近一次匹配的正則表達(dá)式中子表達(dá)式的數(shù)目
m.empty();  //若m.size()為0,則返回true
m.prefix();   //一個(gè)ssub_match對(duì)象,表示當(dāng)前匹配之前的序列
m.suffix();   //一個(gè)ssub_match對(duì)象,表示當(dāng)前匹配之后的序列
m.format(...); 
m.length(n);  //第n個(gè)匹配的子表達(dá)式的大小,0表示整個(gè)匹配
m.position(n);  //第n個(gè)子表達(dá)式距離序列開始的距離
m.str(n);  //第n個(gè)子表達(dá)式匹配的string
m[n];  //第n個(gè)匹配的子表達(dá)式的ssub_match對(duì)象
m.begin(), m.end();  //表示匹配范圍的迭代器
m.cbegin(), m.cend();  //表示匹配范圍的常迭代器

4、使用匹配的子表達(dá)式

字符串使用正則表達(dá)式匹配之后有許多的子表達(dá)式,常用于進(jìn)行數(shù)據(jù)驗(yàn)證或者格式轉(zhuǎn)換。

看下面的例子:

bool phone_valid(const smatch& m)
{
    //如果區(qū)號(hào)前有一個(gè)左括號(hào)
    if (m[1].matched)
        //return m[3].matched && (m[4].matched == 0 || m[4].str() == " ");
        return m[3].matched;
    else
        //return !m[3].matched && m[4].str() == m[6].str();
        return !m[3].matched;
}
    //使用正則表達(dá)式檢查電話號(hào)碼是否合法
    string reg_phone = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ]?)(\\d{4})";
    regex r3(reg_phone);
    //smatch m;
    string reg_s = "0201234567, 020.1234567, 020-1234567, 020-123456789, (020)-1234567, (020).1234567, (020) 1234567, (020)-12345678910, (0201234567, 020)1234567, 12345678910, 020s1234567, 020 s123456, 020/1234657, +86 1234567, +86.1234567, +86-1234567";
    for (sregex_iterator it(reg_s.begin(), reg_s.end(), r3), end_it; it != end_it; ++it)
    {
        if(phone_valid(*it))
            cout << "valid: " << it->str() << endl;
        else
            cout << "not valid: " << it->str() << endl;
    }

使用regex_replace,這個(gè)對(duì)象可以用于替換字符串中的特定字符。

m.format(dest, fmt, mft)
m.format(fmt, mft);

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

上面的幾個(gè)操作是regex_replace的常用操作,光看命名就很好理解。

下面用一個(gè)例子直接展示regex_replace的使用:

    string reg_phone = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ]?)(\\d{4})";
    string reg_replace_test = "(654) 123-7856";
    regex reg_replace(reg_phone);
    string regex_replace_fmt = "$2-$5-$7";
    cout << regex_replace(reg_replace_test, reg_replace, regex_replace_fmt) << endl;

上述例子將電話的格式轉(zhuǎn)換成用 - 連接的形式。

標(biāo)準(zhǔn)庫中定義了標(biāo)志來直到如何處理正則表達(dá)式,在替換過程中也是一樣的,定義了標(biāo)志來控制匹配或格式的標(biāo)志。這些標(biāo)志可以傳遞給函數(shù)regex_search或regex_match或是類smatch的fomat成員。這些值定義在命名空間std::regex_constants中。

match_default   等價(jià)于format_default
match_not_bol  不將首字符作為行首處理
match_not_eol  不將尾字符作為行首處理
match_not_bow  不將首字符作為單詞首處理
match_not_eow  不將尾字符作為單詞尾處理
match_any   如果存在多個(gè)匹配,則返回任意一個(gè)匹配
match_not_null  不匹配任何空序列
match_continuous  匹配必須從輸入的首字符開始
match_prev_avail  輸入序列包含第一個(gè)匹配之前的內(nèi)容
format_default  用ECMAScript規(guī)則替換字符串
format_sed   用POSIX sed規(guī)則替換字符串
format_no_copy  不輸出輸入序列中未匹配的部分
format_first_only  只替換子表達(dá)式的第一次出現(xiàn)的

如修改上面的替換例子:

    string reg_phone = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ]?)(\\d{4})";
    string reg_replace_test = "(654) 123-7856";
    regex reg_replace(reg_phone);
    string regex_replace_fmt = "$2-$5-$7";
    cout << regex_replace(reg_replace_test, reg_replace, regex_replace_fmt, format_no_copy ) << endl;

這樣只會(huì)輸出regex_replace拷貝的文本,而不會(huì)輸出整個(gè)輸入序列。

正則表達(dá)式的使用建議

(1)避免創(chuàng)建不必要的正則表達(dá)式,正則表達(dá)式的編譯是一個(gè)非常慢的操作,運(yùn)行時(shí)開銷也比較大。
(2)正則表達(dá)式的的類型要與輸入的類型匹配,如輸入類型是string,使用正則表達(dá)式的類為regex, smatch等;如輸入類型為const char,使用正則表達(dá)式的類為:regex,cmatch等;如輸入類型為wstring,使用正則表達(dá)式的類為:wregex,wsmatch等;如輸入類型為const wchar_t,使用正則表達(dá)式的類為:wregex,wcmatch等;

總結(jié)

C++11新標(biāo)準(zhǔn)引入了正則表達(dá)式庫,在C++代碼里面可以直接使用標(biāo)準(zhǔn)庫的正則表達(dá)式工具,對(duì)于字符串的處理非常方便。但是因?yàn)檎齽t表達(dá)式的開銷比較大,并且在編譯階段是不會(huì)報(bào)錯(cuò)的,所以應(yīng)該避免不必要的使用以及做好異常處理。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容