當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > CPP開(kāi)發(fā)者
[導(dǎo)讀]↓推薦關(guān)注↓正則表達(dá)式可以說(shuō)是軟件開(kāi)發(fā)中最常用的功能之一。本文將以C語(yǔ)言為例,介紹其中的正則表達(dá)式相關(guān)知識(shí)。前言當(dāng)你想要判斷許多字符串是否符合某個(gè)特定格式;當(dāng)你想在一大段文本中查找出所有的日期和時(shí)間;當(dāng)你想要修改大量日志中所有的時(shí)間格式,在這些情況下,正則表達(dá)式都能幫上忙。簡(jiǎn)單來(lái)...

推薦關(guān)注↓

正則表達(dá)式可以說(shuō)是軟件開(kāi)發(fā)中最常用的功能之一。本文將以C 語(yǔ)言為例,介紹其中的正則表達(dá)式相關(guān)知識(shí)。

前言

當(dāng)你想要判斷許多字符串是否符合某個(gè)特定格式;當(dāng)你想在一大段文本中查找出所有的日期和時(shí)間;當(dāng)你想要修改大量日志中所有的時(shí)間格式,在這些情況下,正則表達(dá)式都能幫上忙。

簡(jiǎn)單來(lái)說(shuō),正則表達(dá)式描述了一系列規(guī)則,通過(guò)這些規(guī)則,可以在字符串中找到相關(guān)的內(nèi)容,規(guī)則使得搜索的能力更加強(qiáng)大。匹配的過(guò)程由正則表達(dá)式引擎完成。開(kāi)發(fā)者通常不需要關(guān)心正則表達(dá)式引擎的實(shí)現(xiàn)細(xì)節(jié),直接使用其提供的能力即可。

正則表達(dá)式非常的常用,但真正精通它的人卻不多。本文試圖給大家講解一些對(duì)于C 語(yǔ)言使用正則表達(dá)式的基礎(chǔ)知識(shí)。

代碼示例

本文中所貼出的代碼示例可以到我的Github上獲?。?span>paulQuei/cpp-regex[1]

或者,你也可以直接通過(guò)下面這條命令獲取所有源碼:

git clone https://github.com/paulQuei/cpp-regex.git

C 中正則表達(dá)式的API基本上都位于頭文件中。

為了簡(jiǎn)化書寫,本文中給出的代碼都已經(jīng)默認(rèn)做了以下操作:

#include?
#include?

using?namespace?std;

入門示例

為了使大家有一個(gè)直觀的感受,文章的開(kāi)頭先通過(guò)一些入門示例給大家一個(gè)直觀的感受。在這個(gè)基礎(chǔ)之上,再詳細(xì)講解其中的細(xì)節(jié)。

使用正則表達(dá)式的大致流程如下:首先你有一段需要處理的文本。這可能是一個(gè)字符串對(duì)象,也可能是一個(gè)文本文件,或者是一大堆日志。接下來(lái)你會(huì)有特定的目標(biāo),例如:找出文本中所有的時(shí)間和日期。這個(gè)時(shí)候你就需要根據(jù)可能的格式寫出具體的正則表達(dá)式,例如,日期的格式是:2020-01-01,那么你的正則表達(dá)式可能是這樣:\d{4}-\d{2}-\d{2}。(你現(xiàn)在不必糾結(jié)與這個(gè)正則表達(dá)式是什么意思,因?yàn)檫@是本文接下來(lái)要講解的內(nèi)容。)

有了正則表達(dá)式之后,你需要將你的文本和正則表達(dá)式交給正則表達(dá)式引擎 – 由C 語(yǔ)言(或者其他語(yǔ)言)提供。引擎會(huì)在文本中搜索到匹配的結(jié)果。這個(gè)結(jié)果的格式可能是包含了多個(gè)組,例如:你可能需要分離出年份和月份。有了引擎返回的結(jié)果之后,你就可以進(jìn)一步處理了。

img
使用正則表達(dá)式的流程大體都是一致的,下面是最常見(jiàn)的三種使用方式。

匹配

匹配是判斷給定的字符串是否符合某個(gè)正則表達(dá)式。例如:你想判斷當(dāng)前文本是否全部由數(shù)字構(gòu)成。

下面是一段代碼示例:

string?s1?=?"ab123cdef";?//?①
string?s2?=?"123456789";?//?②

regex?ex("\\d ");?//?③

cout?<"?is?all?digit:?"?<cout?<"?is?all?digit:?"?<
在這段代碼中:

  1. 這是一個(gè)包含了數(shù)字和字母的字符串
  2. 這是一個(gè)只包含了數(shù)字的字符串
  3. 這是我們的正則表達(dá)式,它表示:有多個(gè)數(shù)字
  4. 通過(guò)regex_match判斷第一個(gè)字符串是否匹配,這里將返回false
  5. 通過(guò)regex_match判斷第二個(gè)字符串是否匹配,這里將返回true
這段代碼輸出如下:

ab123cdef?is?all?digit:?0
123456789?is?all?digit:?1
請(qǐng)注意,正則表達(dá)式有它自身的語(yǔ)法。這與C 的語(yǔ)法是兩回事。C 編譯器只會(huì)檢查C 代碼的語(yǔ)法。因此,即便你的代碼通過(guò)了C 編譯器的語(yǔ)法檢查,但在運(yùn)行的時(shí)候,由于正則表達(dá)式的語(yǔ)義,還可能出現(xiàn)正則表達(dá)式的錯(cuò)誤。正則表達(dá)式的錯(cuò)看起來(lái)類似這樣:terminating with uncaught exception of type std::__1::regex_error: The expression contained an invalid escaped character, or a trailing escape.

搜索

還有一些時(shí)候,我們要判斷的并非是文本的全體是否匹配。而是在一大段文本中搜索匹配的目標(biāo)。

下面是一段代碼示例,這段示例演示了在一個(gè)字符串中查找數(shù)字:

string?s?=?"ab123cdef";?//?①
regex?ex("\\d ");????//?②

smatch?match;?//?③
regex_search(s,?match,?ex);?//?④

cout?<"?contains?digit:?"?<
  1. 這是一個(gè)包含了數(shù)字和字母的字符串
  2. 和前面一樣的正則表達(dá)式
  3. 通過(guò)std::smatch來(lái)保存匹配的結(jié)果。除了std::smatch,還有std::cmatch也很常用。前者是以std::string的形式返回結(jié)果,后者是以const char*的形式返回結(jié)果。
  4. 通過(guò)regex_search函數(shù)搜索結(jié)果
  5. 打印出匹配的結(jié)果
這段代碼輸出如下:

ab123cdef?contains?digit:?123

替換

最后,使用正則表達(dá)式的還有一個(gè)常見(jiàn)功能是文本替換。很多的編輯器都有這樣的功能。

例如,下圖是我的Sublime編譯器,在搜索替換文本的時(shí)候,可以使用正則表達(dá)式,這時(shí)搜索的能力就更加強(qiáng)大了?!癋ind:”部分可以通過(guò)正則表達(dá)式來(lái)描述待替換的字符串,“Replace:”部分填寫替換的字符串。

img
下面是在C 中使用正則表達(dá)式完成字符串替換的代碼示例:

string?s?=?"ab123cdef";?//?①
regex?ex("\\d ");????//?②

string?r?=?regex_replace(s,?ex,?"xxx");?//?③

cout?<
  1. 仍然是前面這個(gè)字符串
  2. 仍然是同樣的正則表達(dá)式
  3. 通過(guò)regex_replace完成替換
  4. 通過(guò)cout輸出結(jié)果
最終輸出的字符串如下:

abxxxcdef
通過(guò)上面的三個(gè)示例我們看到,regex_match,regex_searchregex_replace三個(gè)函數(shù)是正則表達(dá)式的核心,它們會(huì)運(yùn)行正則表達(dá)式引擎完成匹配,查找和替換任務(wù)。

正則表達(dá)式文法

文法

C 中內(nèi)置了多種正則表達(dá)式文法,在創(chuàng)建正則表達(dá)式的時(shí)候可以通過(guò)參數(shù)來(lái)選擇。

它們?nèi)缦卤硭荆?/p>
文法說(shuō)明
ECMAScriptECMAScript正則表達(dá)式語(yǔ)法[2],默認(rèn)選項(xiàng)
basic基礎(chǔ)POSIX正則表達(dá)式語(yǔ)法[3]
extended擴(kuò)展POSIX正則表達(dá)式語(yǔ)法[4]
awkawk工具的正則表達(dá)式語(yǔ)法[5]
grepgrep工具的正則表達(dá)式語(yǔ)法[6]
egrepgrep工具的正則表達(dá)式語(yǔ)法[7]
不同的文法在表達(dá)上有一些不同,如果你原先已經(jīng)很熟悉awk或者egrep文法的正則表達(dá)式,你可以直接使用它們。對(duì)于其他人來(lái)說(shuō),我們直接使用默認(rèn)的ECMAScript文法即可(Javascript的正則表達(dá)式也是使用ECMAScript文法)。

grep的全稱是Global Regular Expression Print。這個(gè)名字是在提示我們,它本身與正則表達(dá)式的歷史有著特定的聯(lián)系。

C 中的 ECMAScript 正則表達(dá)式文法是 ECMA-262 文法[8],你可以點(diǎn)擊鏈接查看詳細(xì)內(nèi)容。

下文中,我們僅會(huì)講解該標(biāo)準(zhǔn)下的正則表達(dá)式。

Raw string literal

在代碼中寫字符串有時(shí)候是比較麻煩的,因?yàn)楹芏嘧址枰ㄟ^(guò)反斜杠轉(zhuǎn)義。當(dāng)有多個(gè)反斜杠連在一起時(shí),就很容易寫錯(cuò)或者理解錯(cuò)了。

當(dāng)通過(guò)字符串來(lái)寫正則表達(dá)式時(shí),這個(gè)問(wèn)題就更嚴(yán)重了。因?yàn)檎齽t表達(dá)式本身也有一些字符需要轉(zhuǎn)義。例如,對(duì)于這樣一個(gè)字符串 "('(?:[^\\\\']|\\\\.)*'|\"(?:[^\\\\\"]|\\\\.)*\")|" 大部分人恐怕很難一眼看出其含義了。

在正則表達(dá)式很復(fù)雜的時(shí)候,推薦大家使用Raw string literal來(lái)表達(dá)。這種表達(dá)式是告訴編譯器:這里的內(nèi)容是純字符串,因此不再需要增加反斜杠來(lái)轉(zhuǎn)義特殊字符。

Raw string literal的格式如下:

R"delimiter(raw_characters)delimiter"
這其中:

  • delimiter是可選的分隔符,通常不用寫
  • raw_characters是具體的字符串
也就是說(shuō),R"(content)"中的content是你需要的字符串本身。

下面是一個(gè)代碼示例:

string?s?=?R"("\w\\w\\\w)";cout?<它將輸出:

"\w\\w\\\w
可以看到,這里的雙引號(hào)和反斜杠不會(huì)被解釋成轉(zhuǎn)義字符,而是當(dāng)成字符串內(nèi)容本身,因此會(huì)原樣輸出。這樣就減少了轉(zhuǎn)義字符的復(fù)雜度,于是更容易理解了。

特殊字符

正則表達(dá)式本身定義了一些特殊的字符,這些字符有著特殊的含義。它們?nèi)缦卤硭尽?/p>
字符說(shuō)明
.匹配任意字符
[字符類的開(kāi)始
]字符類的結(jié)束
{量詞重復(fù)數(shù)開(kāi)始
}量詞重復(fù)數(shù)結(jié)束
(分組開(kāi)始
)分組結(jié)束
\轉(zhuǎn)義字符
\轉(zhuǎn)義字符自身
*量詞,0個(gè)或者多個(gè)
量詞,1個(gè)或者多個(gè)
?量詞,0個(gè)或者1個(gè)
|
^行開(kāi)始;否定
$行結(jié)束
\n換行
\tTab符
\xhhhh表示兩位十六進(jìn)展表示的Unicode字符
\xhhhhhhhh表示四位十六進(jìn)制表示的Unicode字符串
這些字符并不少,剛開(kāi)始接觸可能記不住,但隨著下文的講解,相信你會(huì)逐漸熟悉它們。

字符類

字符類,顧名思義:是對(duì)字符的分類。

例如:1234567890這些都屬于數(shù)字字符類。除此之外,還有其他的分類,它們?nèi)缦卤硭荆?/p>
字符類簡(jiǎn)寫說(shuō)明
[[:alnum:]]
字母和數(shù)字
[_[:alnum:]]\w字母,數(shù)字以及下劃線
[^_[:alnum:]]\W非字母,數(shù)字以及下劃線
[[:digit:]]\d數(shù)字
[^[:digit:]]\D非數(shù)字
[[:space:]]\s空白字符
[^[:space:]]\S非空白字符
[[:lower:]]
小寫字母
[[:upper:]]
大寫字母
[[:alpha:]]
任意字母
[[:blank:]]
非換行符的空白字符
[[:cntrl:]]
控制字符
[[:graph:]]
圖形字符
[[:print:]]
可打印字符
[[:punct:]]
標(biāo)點(diǎn)字符
[[:xdigit:]]
十六進(jìn)制的數(shù)字字符
這里我們可以看到:

  • 字符類通過(guò)[]作為標(biāo)識(shí),因此這兩個(gè)字符是正則表達(dá)式的中的特殊字符。如果是想使用這兩個(gè)字符本身,需要對(duì)它們進(jìn)行轉(zhuǎn)義。
  • []內(nèi)部,通過(guò)[:xxx:]來(lái)描述字符類的名稱。
  • []中可以通過(guò)^表示否定,即:字符類的反面。
  • 字母,數(shù)字和空白字符由于這些字符類非常常用,因此它們有簡(jiǎn)寫的方法。簡(jiǎn)寫使得正則表達(dá)式更加簡(jiǎn)潔,但表達(dá)的含義是一樣的。
接下來(lái)我們看一個(gè)代碼示例:

#include?
#include?

using?namespace?std;

static?void?search_string(const?string
本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
關(guān)閉
關(guān)閉