Effective STL 條款35

條款35:通過mismatch或lexicographical比較實現簡單的大小寫無關字符串比較

一個STL菜鳥最常問的問題是“我怎麼使用STL來進行大小寫無關的字符串比較?”這是一個令人迷惑的簡單問題。大小寫無關字符串比較要麼真的簡單要麼真的困難,依賴於你要多一般地解決這個問題。如果你忽略國際化問題而且只關注於設計成字符串strcmp那樣的類型,這個任務很簡單。如果你要有strcmp不具有的按語言處理字符串中的字符的能力(也就是,容納文本的字符串是除了英語以外的語言)或程序使用一個locale而不是默認的,這個任務很困難。

在本條款,我會作出這個問題的一個簡單版本,因爲那足以演示STL怎麼完成任務。(這個問題一個比較難的版本也能用STL解決。準確地說,它解決了你可以在附錄A中看到的locale相關的問題。)爲了讓簡單的問題變得更有挑戰性,我會處理兩次。想要使用大小寫無關比較的程序員通常需要兩種不同的調用接口,一種類似strcmp(返回一個負數、零或正數),另一種類似operator(返回true或false)。因此我會演示怎麼使用STL算法實現兩種調用接口。

但是首先,我們需要一種方法來確定兩個字符除了大小寫之外是否相等。當需要考慮國際化問題時,這是一複雜的問題。下面的字符比較顯然是一個過分簡單的解決方案,但它類似strcmp進行的字符串比較,因爲本條款我只考慮類似strcmp的字符串比較,不考慮國際化問題,這個函數就夠了:


這個函數遵循了strcmp,可以返回一個負數、零或正數,依賴於c1和c2之間的關係。與strcmp不同的是,ciCharCompare在進行比較前把兩個參數轉化爲小寫。這就產生了大小寫無關的字符比較。

正如<cctype>(也是<ctype.h>)裏的很多函數,tolower的參數和返回值類型是int,但除非這個int是EOF,它的值必須能表現爲一個unsigned char。在C和C++中,char可能或可能不是有符號的(依賴於實現),當char有符號時,唯一確認它的值可以表現爲unsigned char的方式是在調用tolower之前轉換一下。這就解釋了上面代碼中的轉換。(在char已經是無符號的實現中,這個轉換沒有作用。)這也解釋了保存tolower返回值的是int而不是char。

給定了ciCharCompare,就很容易寫出我們的第一個大小寫無關的兩個字符串比較函數,提供了一個類似strcmp的接口。ciStringCompare這個函數,返回一個負數、零或正數,依賴於要比較的字符串的關係。它基於mismatch算法,因爲mismatch確定了兩個區間中第一個對應的不相同的值的位置。

在我們可以調用mismatch前,我們必須先滿足它的前提。特別是,我們必須確定一個字符串是否比另一個短,短的字符串作爲第一個區間傳遞。因此我們可以把真正的工作放在一個叫做ciStringCompareImpl的函數,然後讓ciStringCompare簡單地確保傳進去的參數順序正確,如果參數交換了就調整返回值:


在ciStringCompareImpl中,大部分工作由mismatch來完成。它返回一對迭代器,表示了區間中第一個對應的字符不相同的位置:

幸運的是,註釋把所有東西都弄清楚了。基本上,一旦你知道了字符串中第一個不同字符的位置,就可以很容易決定哪個字符串, 如果有的話,在另一個前面。唯一可能感到奇怪的是傳給mismatch的判斷式,也就是not2(ptr_fun(ciCharCompare))。當字符匹配時這個判斷式返回true,因爲當判斷式返回false時mismatch會停止。我們不能爲此使用ciCharCompare,因爲它返回-1、1或0,而當字符匹配時它返回0,就像strcmp。如果我們把ciCharCompare作爲判斷式傳給mismatch,C++會把ciCharCompare的返回類型轉換爲bool,而當然bool中零的等價物是false,正好和我們想要的相反!同樣的,當ciCharCompare返回1或–1,那會被解釋成true,因爲,就像C,所有非零整數值都看作true。這再次和我們想要的相反。要修正這個語義倒置,我們在ciCharCompare前面放上not2和ptr_fun,而且我們都會一直很快樂地生活。

我們的第二個方法ciStringCompare是產生一個合適的STL判斷式:可以在關聯容器中用作比較函數的函數。這個實現很短也很好,因爲我們需要做的是把ciCharCompare修改爲一個有判斷式接口的字符比較函數,然後把進行字符串比較的工作交給STL中名字第二長的算法——lexicographical_compare:

不,我不會讓你再有懸念了。名字最長的算法是set_symmetric_difference。

如果你熟悉lexicographical_compare的行爲,上面的代碼在清楚不過了。如果你不,可能像隔着一塊混凝土一樣。幸運的是,要把混凝土換成玻璃並不難。

lexicographical_compare是strcmp的泛型版本。strcmp只對字符數組起作用,但lexicographical_compare對所有任何類型的值的區間都起作用。同時,strcmp總是比較兩個字符來看看它們的關係是相等、小於或大於另一個。lexicographical_compare可以傳入一個決定兩個值是否滿足一個用戶定義標準的二元判斷式。

在上面的調用中,lexicographical_compare用來尋找s1和s2第一個不同的位置,基於調用ciCharLess的結果。如果,使用那個位置的字符,ciCharLess返回true,lexicographical_compare也是;如果,在第一個字符不同的位置,從第一個字符串來的字符先於對應的來自第二個字符串的字符,第一個字符串就先於第二個。就像strcmp,lexicographical_compare認爲兩個相等值的區間是相等的,因此它對於這樣的兩個區間返回false:第一個區間不在第二個之前。也像strcmp,如果第一個區間在發現不同的對應值之前就結束了,lexicographical_compare返回true:一個先於任何區間的前綴是一個前綴。

談了很多關於mismatch和lexicographical_compare。雖然我專注於本書的輕便性,但如果我沒有提到大小寫無關字符串比較函數作爲對標準C庫的非標準擴展而廣泛存在,我可能是玩忽職守的。它們一般有stricmp或strcmpi這樣的名字,而且它們一般和我們在本條款開發的函數一樣沒有提供更多對國際化的支持。如果你想犧牲一些移植性,你知道你的字符串沒有包含嵌入的null,而且你不關心國際化,你可以找到實現一個大小寫無關字符串比較最簡單的方式完全不同STL。取而代之的是,它把兩個字符串都轉換爲const char*指針(參見條款16),然後對指針使用stricmp或strcmpi:


有的人可能稱此爲hack,但stricmp/strcmpi被優化爲只做一件事情,對長字符串運行起來一般比通用的算法mismatch和lexicographical_compare快得多。如果那對你很重要,你可能不在乎你用非標準C函數完成標準STL算法。有時候最高效地使用STL的方法是認識到其他方法更好。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章