在C 中使用正規表示式

2021-06-17 17:56:55 字數 3324 閱讀 2655

摘要:描述正規表示式在專案中的實際應用,介紹如何利用它來解析字串。

諶總和老譚這兩個人,有不少相似的地方。比如說,在軟體設計和實現的時候,都希望系統的邏輯能清晰地呈現出來,也就是說,使軟體具備清晰的結構。

但這一次,老譚走向了自己的反面。

討論的是公式的管理問題。專案中的節點量、指標值等資料的計算,都會用到公式。公式中包含四則運算,也可以用括弧提高運算的優先順序,參加運算的物件都是系統中的其他數值,如罐量、儀表量之類。

諶總建議將公式的結構儲存在資料庫中,乙個公式用到若干記錄和字段,這樣便於了解相互之間的引用關係。老譚則建議乾脆乙個公式乙個字串,用名字標識要引用的物件。由於老譚是具體幹活的,諶總也樂於這樣。

於是,定義了公式的格式,如下面的例子所示:

($b'1車間一號表'+10-$g'一罐區二號罐'+$j'n-213號節點')*5

其中 $ 是引導符,b、g、j是物件型別,分別表示儀表、罐、節點(用漢語拼音總是覺得有點土,是麼?),後面跟上用引號引起來的物件名稱。

計算公式值的思路是,將公式中的物件找出來,用物件的值替換後,得到乙個純數字的計算公式,再找乙個工具來計算這個公式的值。

現在的問題是怎麼解析公式,找出物件,再用物件的值替換公式的內容。

這活兒交給了玉龍,他最初採用的是傳統的切割字串的做法:

private double evaluateformula(string formula)

var name = item.replace("$", string.empty);

string s = name.split('\'');

switch (s[0])}}

return evaluateexpression(formula);

}

實現的思路是,用運算子(加號和減號)切分字串,在切分結果中去掉引導符($)得到所引用的物件的型別和名稱,據此獲得物件的值。最後,通過呼叫第三方的函式計算替換後的表示式的值。

這種方案的缺陷是明顯的。首先,只考慮了部分運算子(加號和減號),其次,沒有考慮括弧;再次,沒有考慮物件名稱中有運算子的問題;……設想一下處理了這些問題,實現會變得比這個複雜得多。

老譚建議用正規表示式解析公式。

利用正規表示式,可以從公式中提取所關注的物件,稱參與運算的物件為公式項。獲取公式項的值後,用它替換公式中的內容。為此,首先要確定公式項的模式(pattern)。

在前面所給出的公式例子(($b'1車間一號表'+10-$g'一罐區二號罐'+$j'n-213號節點')*5))中,有三個運算物件,即三個公式項,它們的模式為:

\$[b|g|j]'.+'

其中,\$ 是原來的引導符,由於$是正規表示式中的保留字,需要轉義;[b|g|j]表示b、g、j三者之一,.+表示乙個或多個任意字元。

\$([b|g|j])'(.+)'

使用正規表示式時要注意乙個很隱蔽的問題。如果用上面的模式去匹配公式例子,我們本希望能匹配成功三次,匹配上的字串分別是三個公式項,即:

$b'1車間一號表'

$g'一罐區二號罐'

$j'n-213號節點'

但實際上只匹配成功一次,匹配上的字串是:

$b'1車間一號表'+10-$g'一罐區二號罐'+$j'n-213號節點'

其中,\$([b|g|j]) 匹配到「

$b」,這沒有問題。但 (.+) 匹配到 「

1車間一號表'+10-$g'一罐區二號罐'+$j'n-213號節點

」。出現這個問題的原因,是正規表示式用了貪婪匹配演算法,它總是設法匹配到最多的內容。阻止貪婪匹配的方法是在分組中加上問號運算子:

\$([b|g|j])'(.+?)'

利用正規表示式求解公式的**如下:

private const string formulaitempattern = @"\$([b|g|j])'(.+?)'";

private double evaluateformula(string formula)

}return evaluateexpression(formula);

}

首先根據模式建立正規表示式,即regex物件,利用該物件的matches方法獲取公式中符合該模式的所有匹配(順便鄙視一下.net的命名問題,這時候你把match當作名詞還是動詞用呀?)。對於每個匹配,獲取我們所需要的資訊,將公式項的值計算出來,用值計算替換項。

與字串拆分的方法比起來,使用正規表示式有很多優勢。我們只需要關注感興趣的內容(公式項),把公式中的其他內容排除在**之外,如不必理睬運算子中是否還包含乘除,是否還包含括弧,等等。這樣不僅解決了上段**中存在的問題,還使得這個版本的**更簡潔。

注意從每次匹配中獲取分組中內容的方法。groups[0]表示整個模式匹配到的內容,即公式項本身,如 $b'1車間一號表'。groups[i],其中 i 大於0,則表示模式中第 i 個括弧中的內容。為了防止被序號搞暈,還可以對分組命名。

private const string formulaitempattern = @"\$(?[b|g|j])'(?.+?)'";

private double evaluateformula(string formula)

這樣修改後,我們過上了幸福的生活,直到有一天……

我們有乙個神秘的組織,叫產品組。這天該組織突然發布通知,說 $ 符號已作他用,不能當作本專案的引導符,而應當用 #。而且,型別和物件名稱之間應當用半形句號分割開來。原來的公式

($b'1車間一號表'+10-$g'一罐區二號罐'+$j'n-213號節點')*5

應當變成這個樣子的:

(#b.'1車間一號表'+10-#g.'一罐區二號罐'+#j.'n-213號節點')*5

不管這樣的要求有沒有道理,開發方面遵照執行。用了正規表示式,面對這樣的修改要求,變得簡單了,只需要修改公式項模式即可:

private const string formulaitempattern = @"#(?[b|g|j])\.'(?.+?)'";
這樣修改後,我們又過上了幸福的生活。

實際專案中,關於公式定義的故事還有續集。公式項模式的定義,以及**處理,也遠比這裡複雜。但關於正規表示式的使用,核心內容都提到了。

再喊一遍口號吧:使用正規表示式解析字串,可以使**編得更為簡潔,可以靈活應對情況的變化,可以使程式設計師的生活不那麼苦逼。

有一天,玉龍問老譚:「博士,上次您說的我忘了,怎麼用正規表示式表示乙個或多個字元呀?」

老譚沒有回答他的問題,反問要用正規表示式做什麼。玉龍說要拆分這樣的字串:abc/xyz,得到用斜槓分開的前後兩部分。

那用string.split不是更簡單麼?玉龍則回答:「是您說的一定要用正規表示式呀?」

老譚真不記得這樣說過。

C 中使用正規表示式

正規表示式是一種用於模式匹配和替換的強有力工具,它通過構建乙個表示式對輸入的字串進行模式匹配,然後返回處理後的結果,如果你對它還不了解,請繼續往下看。以最廣泛的web身份驗證為例 我們從頁面中接收到輸入的使用者名稱,存入變數temp username,此時變數中可能含有惡意的資訊,我們想要使用者名稱...

在UltraEdit中使用正規表示式

在ultraedit中使用正規表示式 刪除空行 替換 t p 為 空串 刪除行尾空格 替換 t 為 空串 刪除行首空格 替換 t 為 空串 每行設定為固定的4個空格開頭 替換 t t p 為 1 每段設定為固定的4個空格開頭 替換 t 為 如果一行是以空格開始的,則視之為一段的開始行 將一段合併為一...

在DELPHI中使用正規表示式

在網上發現,有多種方法可在delphi中使用正規表示式。竊以為直接使用微軟的regexp物件會比較簡單,無需額外工作。使用微軟regexp方法 2.註冊vbscript.dll regsvr32 命令,若安裝過vb或ie5以上會預設安裝該dll 3.在delphi中引入 microsoft vbsc...