1 正則表達式的概念
正則表達式,又稱規則表達式,(Regular Expression,在代碼中常簡寫為 regex 、regexp 或 RE),是一種文本模式,包括普通字符(例如,a 到 z 之間的字母)和特殊字符(稱為元字符)。
正則表達式使用單個字符串來描述、匹配一系列符合某個句法規則的字符串,通常被用來檢索、替換那些符合某個模式(規則)的文本。許多程序設計語言都支持利用正則表達式進行字符串操作。正則表達式是對字符串操作的一種邏輯公式,就是用事先定義好的一些特定字符及這些特定字符的組合,組成一個規則字符串,這個規則字符串用來表達對字符串的一種過濾邏輯。正則表達式是一種文本模式,該模式描述在搜索文本時要匹配的一個或多個字符串。
1.1 正則表達式的作用和應用場景
正則表達式的主要作用包括文本的檢索、替換以及從一個字符串中提取出符合特定條件的子串。它描述了一種字符串匹配的模式,可以用來檢查一個字符串是否含有某種子串、將匹配的子串做替換或者從某個字符串中取出符合某個條件的子串等。
正則表達式的應用場景非常廣泛,包括但不限于以下幾個方面:
(1)Web開發: 在Web開發中,正則表達式被廣泛應用于表單驗證、URL處理、數據提取等方面。例如,可以使用正則表達式來驗證用戶輸入的郵箱地址是否符合規范格式。
(2)服務器日志分析: 服務器日志通常包含大量的信息,使用正則表達式可以方便地提取和分析日志數據中的關鍵信息。
(3)網頁爬蟲: 正則表達式可以用于解析和處理網頁上的文本數據,提取出需要的信息。
(4)文件批量處理: 在處理大量文件時,可以使用正則表達式來快速高效地查找與分析字符串。
此外,正則表達式還被應用于數據驗證、信息校驗、字符串格式化與美化、以及與其他技術的結合如人工智能和大數據處理等領域。
1.2 C++ 與正則表達式
C++ 與正則表達式的關系主要體現在 C++ 標準庫提供的正則表達式支持,這使得在 C++ 程序中使用正則表達式變得更加方便和高效。
從 C++11 開始,C++ 標準庫引入了<regex>頭文件,其中包含了處理正則表達式的類和函數。這些類和函數使得開發者可以在 C++ 程序中方便地使用正則表達式進行文本匹配、搜索、替換等操作。
C++ 標準庫中的正則表達式功能主要包括:
(1)std::regex類: 表示一個正則表達式對象,用于存儲和編譯正則表達式模式。
(2)std::smatch類: 表示正則表達式匹配的結果,用于存儲匹配到的子表達式信息。
(3)std::sregex_iterator類: 是一個迭代器,用于遍歷字符串中所有與正則表達式匹配的子串。
(4)正則表達式操作函數:
std::regex_match:檢查整個字符串是否與正則表達式匹配。
std::regex_search:在字符串中搜索與正則表達式匹配的子串。
std::regex_replace:替換字符串中與正則表達式匹配的子串。
std::regex_split:根據正則表達式將字符串分割成多個子串。
使用 C++ 標準庫中的正則表達式功能,可以執行各種復雜的文本處理任務,如驗證輸入格式、提取數據、文本替換等。
如下是一個驗證字符串是否符合電子郵件地址的格式的樣例:
#include <iostream>
#include <regex>
#include <string> int main()
{std::string email = "example@example.com";std::regex emailRegex(R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})");if (std::regex_match(email, emailRegex)) {std::cout << "valid email address" << std::endl;}else {std::cout << "invalid email address" << std::endl;}return 0;
}
上面代碼的輸出為:
valid email address
在上面代碼中,定義了一個正則表達式模式來匹配電子郵件地址,并使用 std::regex_match 函數來檢查給定的字符串是否符合這個模式。如果匹配成功,輸出"valid email address",否則輸出"invalid email address"。
2 正則表達式基礎語法
正則表達式由一系列普通字符和特殊字符(也被稱為是元字符)組成,它們賦予了正則表達式強大的模式匹配能力。
2.1 字符類
在正則表達式中,字符類(也稱為字符集合)允許匹配一個字符集合中的任意一個字符。字符類是由方括號 [] 包圍的,其中可以包含一系列字符、范圍或特殊的轉義序列。
以下是一些正則表達式字符類,以及相應的樣例:
(1)基本字符類: 匹配方括號內列出的任意一個字符。
樣例:[abc] 會匹配字符 “a”、“b” 或 “c”。
(2)字符范圍: 在方括號內使用連字符 - 來指定字符范圍。
樣例:[a-z] 會匹配任意小寫字母。
(3)排除字符類: 在方括號內使用 ^ 符號作為第一個字符來排除列出的字符。
樣例:[^0-9] 會匹配任意非數字字符。
(4)特殊字符類:
.(點號) 匹配任何單個字符(除了換行符 \n)。
\d 匹配任意數字,等價于 [0-9]。
\D 匹配任意非數字字符,等價于 [^0-9]。
\w 匹配任意字母、數字或下劃線,等價于 [a-zA-Z0-9_]。
\W 匹配任意非字母、非數字和非下劃線字符,等價于 [^a-zA-Z0-9_]。
\s 匹配任意空白字符,包括空格、制表符、換行符等。
\S 匹配任意非空白字符。
樣例:
\d 會匹配字符 “1” 或 “2”。
\w 會匹配字符 “a”、“A”、“1” 或 “_”。
(5)預定義字符類: 某些正則表達式引擎提供了預定義的字符類,如 [:alpha:](字母)、[:digit:](數字)等。這些類可能因不同的引擎而有所不同。
(6)Unicode 字符類: 使用 \u 或 \U 后跟 Unicode 碼點來匹配特定的 Unicode 字符或字符范圍。
樣例:\u0041-\u005A 會匹配任意大寫英文字母(A-Z)。
(7)否定字符類: 在字符類的開始處使用 ^ 符號可以表示否定,即匹配不在字符類中的任意字符。
樣例:[^aeiou] 會匹配任意非元音字母。
2.2 限定符與量詞
正則表達式中的限定符和量詞用于指定模式中的元素可以出現的次數。它們提供了強大的控制能力,可以精確地描述和匹配特定模式的文本。
以下是一些常見的正則表達式限定符和量詞,以及相應的樣例:
(1)*(星號): 匹配前面的元素零次或多次。
樣例:zo* 會匹配 “z” 以及 “zoo”,因為 “o” 可以出現零次(即沒有 “o”)或多次(如 “oo”)。
(2)+(加號): 匹配前面的元素一次或多次。
樣例:zo+ 會匹配 “zo” 以及 “zoo”,但不會匹配 “z”,因為 “o” 必須至少出現一次。
(3)?(問號): 匹配前面的元素零次或一次。
樣例:do(es)? 會匹配 “do” 或 “does” 中的 “do”,因為 “es” 可以出現零次(即 “do”)或一次(即 “does”)。
(4){n}: 匹配前面的元素恰好 n 次。
樣例:o{2} 會匹配 “oo”,因為 “o” 必須恰好出現兩次。
(5){n,}: 匹配前面的元素至少 n 次。
樣例:o{2,} 會匹配 “oo”、“ooo”、“oooo” 等,因為 “o” 至少出現兩次,可以出現更多次。
(6){n,m}: 匹配前面的元素至少 n 次,但不超過 m 次。
樣例:o{2,3} 會匹配 “oo” 和 “ooo”,但不會匹配 “o” 或 “oooo”,因為 “o” 的出現次數必須在 2 到 3 次之間。
這些限定符和量詞可以與其他正則表達式元素(如字符、字符組、捕獲組等)結合使用,以構建復雜的匹配模式。注意:在實際使用時,正則表達式引擎會根據模式從左到右的順序進行匹配,并盡量多地匹配字符,這稱為貪婪模式。如果希望盡量少的匹配字符,可以使用非貪婪模式(通過在限定符或量詞后面加上 ? 來實現)。
2.3 定位符與錨點
正則表達式中的定位符(也稱為錨點)用于規定匹配模式在目標對象中的出現位置。這些定位符不會消耗字符,只是指定模式必須出現在字符串的特定位置。
以下是一些常見的正則表達式定位符和錨點,以及相應的樣例:
(1)^(脫字號): 匹配輸入字符串的開始位置。
樣例:^hello 會匹配以 “hello” 開頭的字符串,如 “hello world”。
(2)$(美元符號): 匹配輸入字符串的結束位置。
樣例:world$ 會匹配以 “world” 結尾的字符串,如 “hello world”。
(3)\b(單詞邊界): 匹配一個單詞的的邊界位置,即單詞字符(如字母、數字、下劃線)和非單詞字符(如空格、標點符號等)之間的位置。
樣例:\bcat\b 會匹配獨立的單詞 “cat”,而不會匹配 “concatenate” 中的 “cat”。
(4)\B(非單詞邊界): 匹配非單詞邊界的位置,即兩個單詞字符之間或兩個非單詞字符之間的位置。
樣例:\Bcat\B 會匹配 “concatenate” 中的 “cat”,但不會匹配獨立的單詞 “cat”。
(5)\A(絕對開頭位置): 僅匹配字符串的絕對開頭位置,忽略多行模式中的換行符
樣例:在多行模式下,\Ahello 只會匹配第一行以 “hello” 開頭的字符串。
(6)\Z(絕對結尾位置): 僅匹配字符串的絕對結尾位置,忽略多行模式中的換行符
樣例:在多行模式下,world\Z 只會匹配最后一行以 “world” 結尾的字符串。
(7)\z(結尾位置): 匹配字符串的結尾位置,不考慮多行模式
樣例:\z 在任何模式下都會匹配字符串的結尾位置。
這些定位符和錨點可以幫助精確地指定模式在字符串中的位置,從而實現更準確的匹配。注意:在不同的正則表達式引擎和編程語言中,這些定位符和錨點的行為可能略有不同。
2.4 分組(捕獲組)與選擇結構
正則表達式中的分組與選擇結構是構建復雜模式的重要工具。分組允許將一部分模式組合起來,作為一個整體進行處理,而選擇結構則允許指定多個可能的匹配選項。
(1)分組(捕獲組):
分組也稱為捕獲組,是使用圓括號 () 來實現。分組不僅可以對模式進行組合,還可以用于捕獲匹配的子串,以便后續引用。
樣例:
基本分組:將模式組合成一個整體。 (abc) 這個模式會匹配字符串 “abc” 中的 “abc”。
捕獲分組:通過分組捕獲匹配的子串。 (\d{3})-(\d{3})-(\d{4}) 這個模式會匹配形如 “123-456-7890” 的字符串,并將 “123”、“456” 和 “7890” 作為捕獲的分組保存起來。
(2)選擇結構:
選擇結構使用豎線 | 來實現,允許你指定多個可能的匹配選項。正則表達式會嘗試從左到右匹配選項,一旦找到匹配的選項,就會停止搜索。
樣例:red|blue|green 這個模式會匹配 “red”、“blue” 或 “green” 中的任意一個。
(3)分組與選擇結合:
在分組內部使用選擇結構。
樣例:(apple|orange) juice 這個模式會匹配 “apple juice” 或 “orange juice”。
(4)捕獲分組與選擇結合:
使用捕獲分組捕獲選擇結構中的匹配項。
樣例:(apple|orange)(\s+)juice 這個模式會匹配 “apple juice” 或 “orange juice”,并且捕獲 “apple” 或 “orange” 以及至少一個空白字符。
2.5 反向引用
在正則表達式中,反向引用是一種特殊的機制,允許引用之前匹配的子模式(即捕獲組)的內容。這通常用于匹配重復或對稱的文本模式。
反向引用是通過使用反斜杠(\)加上捕獲組的數字索引來實現的。第一個捕獲組的索引是 1,第二個捕獲組的索引是 2,依此類推。
樣例 1:匹配重復的單詞
(\b\w+\b)\s+\1
這個正則表達式會匹配一個單詞,后面跟著一個或多個空格,然后是這個單詞的再次出現。\1 是對第一個捕獲組((\b\w+\b))的反向引用,它匹配與第一個捕獲組相同的文本。
例如,在字符串 “hello world hello again” 中,這個正則表達式會匹配 “hello hello”。
樣例 2:匹配重復的的數字序列
(\d+)\s+\1
這個正則表達式會匹配一個或多個數字(\d+),后面跟著一個或多個空格,然后是這個數字序列的再次出現。\1 是對第一個捕獲組的反向引用。
例如,在字符串 “123 123 456 4567” 中,這個正則表達式會匹配 “123 123” 和 “456 456”。
樣例 3:匹配HTML標簽
<(\w+)>\s+<\/\1>
這個正則表達式用于匹配簡單的HTML開標簽和閉標簽。它捕獲開標簽中的元素名(\w+),并使用 \1 反向引用該元素名來匹配相應的閉標簽。
例如,在字符串 “<div>This is a div</div> <p>This is a paragraph</p>” 中,這個正則表達式會匹配 “<div>…</div>” 和 “<p>…</p>”。
2.6 非捕獲組
在正則表達式中,非捕獲組是一種特殊類型的分組,它允許對一部分模式進行分組,但不捕獲該分組匹配的文本。這在只需要對模式的一部分進行分組,而不需要保存其匹配結果時非常有用。非捕獲組使用 (?: …) 的語法。
非捕獲組的主要優點是它們不會消耗額外的內存來存儲捕獲的文本,這可以提高性能并減少潛在的內存使用。這對于處理大型文本或復雜模式時特別重要。
以下是一些使用非捕獲組的樣例:
樣例 1:匹配重復的單詞(但不捕獲它們)
(?:\b\w+\b)\s+\1
這個正則表達式與捕獲組的版本類似,但它使用非捕獲組來匹配單詞。這意味著雖然 \1 仍然可以引用第一個捕獲組的內容,但第一個分組本身不會捕獲或保存匹配的單詞。
樣例 2:匹配日期格式(但不捕獲分隔符)
(?:\d{4})-(?:\d{2})-(?:\d{2})
這個正則表達式匹配形如 “YYYY-MM-DD” 的日期格式,但使用非捕獲組來避免捕獲年份、月份和日期之間的分隔符(破折號)。
樣例 3:使用非捕獲組進行條件匹配
(?i:hello)\s+world
在這個例子中,(?i:hello) 是一個非捕獲組,它匹配單詞 “hello”,并且不區分大小寫(由于 ?i 修飾符)。這個修飾符僅適用于該非捕獲組內的內容,而不影響整個正則表達式的其他部分。
非捕獲組在編寫更復雜的正則表達式時非常有用,尤其是當不需要捕獲某個特定部分,但仍然想利用分組提供的分組功能(如條件匹配或引用)時。使用非捕獲組可以減少正則表達式的復雜性和潛在的性能開銷。
2.7 樣例
(1)匹配數字
\d+
這個表達式會匹配一個或多個連續的數字字符,如 “123”、“4567” 等:其中 \d 表示匹配一個任何數字,后面的 + 號表示匹配前面的子表達式一次或多次。
(2)匹配電子郵件地址
[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}
這個表達式會匹配大多數常見的電子郵件地址格式。
對于 [a-zA-Z0-9._%±]+ ,其中的 [a-zA-Z0-9] 匹配任何單個的小寫字母、大寫字母或數字。 ._%± 匹配點號、下劃線、百分號、加號和減號中的任何一個。 + 表示前面的字符集(即 [a-zA-Z0-9._%±])可以出現一次或多次。
對于 @ ,用于匹配字面上的 “@” 符號。這是電子郵件地址的標準部分,用來分隔用戶名和域名。
對于 [a-zA-Z0-9.-]+ ,其中的 [a-zA-Z0-9] 匹配任何單個的小寫字母、大寫字母或數字。 .- 匹配點號或減號中的任何一個。 + 表示前面的字符集(即 [a-zA-Z0-9.-])可以出現一次或多次。
對于 \. ,用于匹配字面上的點號(.)。在正則表達式中,點號是一個特殊字符,所以需要使用反斜杠 \ 進行轉義,使其表示字面上的點號。
對于 [a-zA-Z]{2,} ,其中的 [a-zA-Z] 匹配任何單個的小寫字母或大寫字母。 {2,} 表示前面的字符集(即 [a-zA-Z])必須出現至少兩次。
(3)匹配 IPv4 地址
\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b
這個表達式會匹配 IPv4 地址,如 “127.0.0.1”。
對于第一個 \b ,這是一個單詞邊界,確保 IPv4 地址是一個獨立的單詞,而不是其他文本的一部分。
對于 (?: … ) ,這是一個非捕獲組,它允許對一組模式進行分組,但不捕獲匹配的文本。非捕獲組主要用于組織正則表達式,而不影響匹配的結果。
對于 (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) ,這是一個捕獲組,用于匹配一個 0 到 255 之間的數字。其中的 25[0-5] 匹配250到255之間的數字。 2[0-4][0-9] 匹配200到249之間的數字。 [01]?[0-9][0-9]? 匹配0到199之間的數字。
對于 \. ,匹配點號(.)。在正則表達式中,點號是一個特殊字符,表示任何字符(除了換行符)。為了匹配字面上的點號,需要使用反斜杠進行轉義。
對于 {3} ,這表示前面的非捕獲組(即數字段和點號的組合)必須出現三次。這確保了 IPv4 地址有三個點號分隔四個數字段。
對于 (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) ,用來匹配最后一個數字段。
對于最后一個 \b ,再次使用單詞邊界,確保 IPv4 地址是獨立的。
(4)匹配日期(YYYY-MM-DD 格式)
\b\d{4}-\d{2}-\d{2}\b
這個表達式會匹配形如 “2010-01-12” 的日期格式。
3 正則表達式高級特性
3.1 貪婪匹配與懶惰量詞
在正則表達式中,貪婪匹配(greedy matching)和懶惰匹配(lazy matching)或稱為非貪婪匹配(non-greedy matching)是量詞(quantifiers)的兩種不同行為模式。貪婪量詞會盡可能多地匹配字符,而懶惰量詞或非貪婪量詞則會盡可能少地匹配字符。
貪婪匹配
貪婪量詞會盡可能多地匹配字符,直到遇到無法繼續匹配的字符為止。常見的貪婪量詞有:
(1)*: 匹配前面的子表達式零次或多次。
(2)+: 匹配前面的子表達式一次或多次。
(3)?: 匹配前面的子表達式零次或一次。
(4){n,}: n 是一個非負整數,匹配至少 n 次。
(5){n,m}: m 和 n 均為非負整數,其中 n <= m,匹配至少 n 次,但不超過 m 次。
例如,正則表達式 a*b 在字符串 “aaab” 中會匹配整個 “aaab”,因為 * 是貪婪的,它會盡可能多地匹配 “a”。
懶惰量詞(非貪婪匹配)
懶惰量詞(非貪婪匹配)會盡可能少地匹配字符,直到遇到能夠繼續匹配的字符為止。要在貪婪量詞后面加上一個 ? 來使其成為懶惰量詞。
(1)*?: 非貪婪匹配,盡可能少地匹配前面的子表達式。
(2)+?: 非貪婪匹配,盡可能少地匹配前面的子表達式。
(3)??: 非貪婪匹配,在可能的情況下盡可能少地匹配前面的子表達式。
(4){n,}?: 非貪婪匹配,至少匹配 n 次。
(5){n,m}?: 非貪婪匹配,至少匹配 n 次,但不超過 m 次。
例如,正則表達式 a*?b 在字符串 “aaab” 中會匹配最短的 “a” 后面跟著 “b”,即 “ab”,因為 *? 是非貪婪的,它會盡可能少地匹配 “a”。
樣例
考慮字符串 <div>Hello</div><div>World</div>,需要提取兩個 <div> 標簽之間的內容。
使用貪婪匹配的正則表達式:
<div>(.*)</div>
這個表達式會匹配整個字符串,因為 .* 是貪婪的,它會匹配盡可能多的字符。
要使用非貪婪匹配來提取每個 <div> 標簽內的內容,則需要使用懶惰量詞:
<div>(.*?)</div>
在這里,.*? 會盡可能少地匹配字符,因此它會匹配每個單獨的 <div> 標簽內的內容,而不是整個字符串。
3.2 前瞻斷言與后顧斷言
正則表達式中的前瞻斷言和后顧斷言(也稱為后發斷言)允許你基于當前位置之前的或之后的字符序列來匹配文本。它們用于檢查一個子串前后上下文的情況。
前瞻斷言
前瞻斷言支持檢查當前位置之后的字符序列。在大多數正則表達式引擎中,前瞻斷言通常使用?=(正向前瞻斷言)或?!(負向前瞻斷言)來表示。
?=: 正向前瞻斷言,表示"后面跟著…“。
?!: 負向前瞻斷言,表示"后面不跟著…”。
假設有一個包含多個 HTML 鏈接的文本,并且需要提取所有的鏈接地址,但只提取那些鏈接文本為"news"的鏈接地址,其正則表達式為:
<a href="([^"]+)">(?=news)</a>
后顧斷言
后顧斷言(后發斷言)支持檢查當前位置之前的字符序列。后顧斷言通常使用?<=(正向后顧斷言)或?<!(負向后顧斷言)來表示。
?<=: 正向后顧斷言,表示"前面是…“。
?<!: 負向后顧斷言,表示"前面不是…”。
假設有一個文本,其中包含多種日期格式,但只想匹配那些前面有"Tomorrow is"的日期,其正則表達式為:
(?<=Tomorrow is) \d{4}-\d{2}-\d{2}
注意:前瞻和后顧斷言只是檢查文本,并不消耗字符,因此它們不會成為匹配結果的一部分。
3.3 嵌套與遞歸
正則表達式的嵌套指的是在一個正則表達式內部使用另一個正則表達式。這通常是通過使用捕獲組或者非捕獲組來實現的。遞歸正則表達式則是一種特殊類型的嵌套,其中正則表達式能夠引用自身來匹配嵌套或重復的結構。
以下是遞歸正則表達式的樣例,該樣例用于匹配正確嵌套的括號對(圓括號、方括號或花括號),其正則表達式為:
(?R)|[^()\[\]{}]+
這個正則表達式使用了(?R)來引用整個正則表達式本身,實現遞歸。 [^()\[\]{}]+
匹配任何不是括號字符的字符序列。這個表達式將匹配任何正確嵌套的括號對,包括混合類型的括號。
其中, (?R) 表示一個遞歸調用,它告訴引擎再次嘗試匹配整個正則表達式。
| 表示一個選擇運算符,它允許匹配 (?R) 或后面的模式。
[^()\[\]{}]+
表示匹配任何不是括號字符(圓括號、方括號或花括號)的字符序列。 + 表示匹配一個或多個這樣的字符。
注意:由于遞歸正則表達式的復雜性,它們可能會導致性能問題,尤其是在處理大型或復雜的輸入時。此外,不是所有的正則表達式引擎都支持(?R)語法。在使用遞歸正則表達式時,請確保正則表達式引擎支持這種功能,并注意性能影響。
4 C++ 中的正則表達式
C++11 引入了 <regex> 頭文件,提供了對正則表達式的原生支持。C++ 的正則表達式庫基于 ECMAScript 正則表達式語法,并且與 POSIX 兼容。C++ 中的正則表達式庫允許你編譯正則表達式,然后使用這些編譯后的模式來匹配、搜索、替換字符串中的文本。
4.1 std::regex
std::regex 用于表示編譯后的正則表達式模式。可以使用 std::regex 對象來執行各種正則表達式相關的操作,如匹配、搜索和替換。
(1)創建 std::regex 對象
要創建一個 std::regex 對象,需要提供一個正則表達式字符串和可選的標志位來指定正則表達式的語法和行為:
#include <regex> // 使用默認 ECMAScript 語法創建一個正則表達式對象
std::regex ecmascript_regex(R"(\d+)"); // 匹配一個或多個數字 // 使用基本語法創建一個正則表達式對象
std::regex basic_regex(R"(\d+)", std::regex_constants::basic); // 使用擴展語法和忽略大小寫的標志創建正則表達式對象
std::regex extended_regex(R"(hello)", std::regex_constants::extended | std::regex_constants::icase);
上面代碼中使用默認 ECMAScript 語法和使用基本語法創建 std::regex 對象的區別主要在于所支持的特性和語法。
- 默認ECMAScript語法(不顯式指定標志):
當不提供任何標志來編譯正則表達式時,C++ 默認使用 ECMAScript 語法。
ECMAScript 語法通常與 JavaScript 中的正則表達式語法相似,因此它對于前端開發者來說可能是更熟悉的。
ECMAScript 語法支持許多常見的正則表達式特性,如字符類、量詞、分組、捕獲組、前瞻斷言等。
它也支持一些特定的 ECMAScript 擴展,例如非捕獲組(?:…)。
- 基本語法(使用 std::regex_constants::basic 標志):
基本語法主要基于 POSIX 風格的正則表達式,這意味著它不支持一些 ECMAScript 特有的特性,如前瞻斷言。
基本語法通常更加簡單和直接,對于只需要基礎正則表達式的場景可能更合適。
(2)基本使用
#include <iostream>
#include <regex>int main()
{std::regex reg(R"(ab|cd)"); // 匹配"ab"或"cd" // 測試字符串 std::string testStr = "abcdef";if (std::regex_search(testStr, reg)){std::cout << "successfully found" << std::endl;}return 0;
}
上面代碼的輸出為:
successfully found
4.2 std::regex_match 與 std::regex_smatch
std::regex_match 和 std::regex_smatch 都用于正則表達式匹配,但它們有不同的用途和語義。
std::regex_match:
std::regex_match 用于檢查整個輸入字符串是否與正則表達式完全匹配。如果整個字符串都符合正則表達式的模式,那么 std::regex_match 返回 true;否則,返回 false。它不接受一個單獨的 std::match_results 對象作為參數,但可以接受一個 std::match_results 引用以捕獲匹配結果。
示例:
std::string text = "hello";
std::regex pattern(R"(hello)");
bool isMatch = std::regex_match(text, pattern);
// isMatch 將會是 true
std::regex_smatch:
std::regex_smatch 實際上是一個類型,而不是一個函數。它是 std::match_results 的一個特化版本,用于存儲字符串的匹配結果。當使用 std::regex_search、std::regex_match 或 std::regex_replace 等函數時,如果需要捕獲匹配的結果,可以使用 std::smatch 類型的變量。std::regex_smatch 用于處理 std::string 類型的輸入。
示例:
std::string text = "The price is 12 dollars";
std::regex price_pattern(R"(\d+)");
std::smatch match;
bool found = std::regex_search(text, match, price_pattern);
if (found)
{ std::cout << "Price found: " << match.str() << std::endl;
}
// 輸出將會是 "Price found: 12"
4.3 std::sregex_iterator
std::sregex_iterator 用于遍歷輸入字符串中所有與給定正則表達式匹配的子串。它通常與 std::sregex_token_iterator 一起使用,后者用于遍歷由正則表達式分割的字符串的子串。
std::sregex_iterator 提供了從輸入字符串的開始位置到結束位置的所有匹配項的迭代器接口。可以使用它來初始化一個范圍,并通過這個范圍迭代訪問所有匹配的子串。
下面是一個使用 std::sregex_iterator 的示例,演示如何遍歷一個字符串中所有匹配的數字:
#include <iostream>
#include <string>
#include <regex> int main()
{std::string text = "There are 12 children playing on the playground, and 2 children playing soccer.";std::regex price_regex(R"(\d+)"); // 匹配一個或多個數字 // 使用 sregex_iterator 遍歷所有匹配項 auto begin = std::sregex_iterator(text.begin(), text.end(), price_regex);auto end = std::sregex_iterator();for (std::sregex_iterator i = begin; i != end; i++){std::smatch match = *i; // 獲取當前的匹配項 std::cout << "Matched number: " << match.str() << std::endl;}return 0;
}
上面代碼的輸出為:
Matched number: 12
Matched number: 2
4.4 std::regex_search
std::regex_search 用于在輸入字符串中搜索與給定正則表達式匹配的子串。如果找到匹配項,則 std::regex_search 返回 true,并可以通過傳入的 std::match_results 對象來獲取匹配的詳細信息;如果沒有找到匹配項,則返回 false。
下面是一個使用 std::regex_search 的示例:
#include <iostream>
#include <string>
#include <regex> int main()
{std::string text = "There are 12 children playing on the playground, and 2 children playing soccer.";std::regex price_pattern(R"(\d+)"); // 正則表達式,匹配一個或多個數字 // 使用 std::regex_search 搜索匹配項 std::smatch match;bool found = std::regex_search(text, match, price_pattern);if (found) {std::cout << "Match found!" << std::endl;std::cout << "Matched string: " << match.str() << std::endl;std::cout << "Position: " << match.position() << std::endl;std::cout << "Length: " << match.length() << std::endl;}else {std::cout << "No match found." << std::endl;}return 0;
}
上面代碼的輸出為:
Match found!
Matched string: 12
Position: 10
Length: 2
在上面代碼中,std::regex_search 在 text 字符串中搜索與 price_pattern 正則表達式匹配的子串。一旦找到匹配項,它就將匹配的結果存儲在 match 對象中,并返回 true。然后可以使用 match 對象來獲取匹配的字符串、匹配在字符串中的位置以及匹配的長度。
std::regex_search 默認只會返回第一個匹配項。如果你想要獲取所有匹配項,可以使用上面提到的 std::sregex_iterator,它提供了一個迭代器接口來遍歷所有匹配項。
4.5 std::regex_replace
std::regex_replace 用于替換基于正則表達式的子串的函數。它接受一個輸入字符串、一個正則表達式模式、一個替換字符串以及一個可選的 std::regex_constants::match_flag_type 標志,并返回一個新的字符串,其中所有與正則表達式匹配的子串都被替換字符串所替代。
下面是一個使用 std::regex_replace 的例子:
#include <iostream>
#include <string>
#include <regex> int main()
{std::string text = "test hello world hello";std::regex reg(R"(\bhello\b)"); // 正則表達式,匹配單詞 "hello" std::string replacement = "ok"; // 替換字符串 // 使用 std::regex_replace 替換匹配的子串 std::string newText = std::regex_replace(text, reg, replacement);std::cout << newText << std::endl;return 0;
}
上面代碼的輸出為:
test ok world ok
在這個例子中,std::regex_replace 使用正則表達式 \bhello\b 來匹配單詞 “hello”,并用字符串 “ok” 替換它。\b 是一個單詞邊界,確保僅匹配整個單詞 “hello”,而不是其他包含 “hello” 的子串。
如果需要在替換字符串中使用原始匹配的子串,則可以使用特殊語法 $& 來引用整個匹配項,$n 來引用第 n 個捕獲組,其中 n 是一個正整數:
#include <iostream>
#include <string>
#include <regex> int main()
{std::string text = "1 apple, 2 oranges, 3 bananas";std::regex reg(R"(\d+)"); // 正則表達式,匹配數字 std::string replacement = "[$&] Fruits"; // 在替換字符串中使用 $& 引用匹配的數字 std::string newText = std::regex_replace(text, reg, replacement);std::cout << newText << std::endl;return 0;
}
上面代碼的輸出為:
[1] Fruits apple, [2] Fruits oranges, [3] Fruits bananas
在上面代碼中,$& 在替換字符串中被用來插入原始匹配的數字。