stl中最難看的組件(沒(méi)有之一),無(wú)疑就是string這貨了,一百多個(gè)成員函數(shù),當(dāng)然里面大多數(shù)是重載的,不必多想,一個(gè)class,如果擁有如此之多的函數(shù),必然一定肯定是失敗的,并且,即便是這么一大打函數(shù),string的功能還是很不完備,要不然,就不會(huì)有boost里面的string算法。這真是尷尬,string作為最基本最基本的語(yǔ)言組件,又出自官方標(biāo)準(zhǔn)庫(kù),長(zhǎng)成這樣子,真是讓無(wú)數(shù)的c++粉絲要失望,失望歸失望,畢竟師出iso,用起來(lái)還是很有保障的,論性能什么,再怎樣,也不會(huì)虧到那里去。只是,很讓人好奇的是,這成百個(gè)函數(shù)又功能不完備的string,里面都有些什么貨色,對(duì)此,c++exception系列中有過(guò)分析。但是,在此,想探討一下,除了小胡子的方法之外,用其他方法壓縮string的成員函數(shù)的數(shù)量。
我們先來(lái)看看string的append成員函數(shù),怪怪龍的東,總共有8個(gè)重載之多,好像還不止,突然想起狗語(yǔ)言的名言,少即是多,反過(guò)來(lái)說(shuō),多即是少。
basic_string<CharType, Traits, Allocator>& append(
const value_type* _Ptr
);
basic_string<CharType, Traits, Allocator>& append(
const value_type* _Ptr,
size_type _Count
);
basic_string<CharType, Traits, Allocator>& append(
const basic_string<CharType, Traits, Allocator>& _Str,
size_type _Off,
size_type _Count
);
basic_string<CharType, Traits, Allocator>& append(
const basic_string<CharType, Traits, Allocator>& _Str
);
basic_string<CharType, Traits, Allocator>& append(
size_type _Count,
value_type _Ch
);
template<class InputIterator>
basic_string<CharType, Traits, Allocator>& append(
InputIterator _First,
InputIterator _Last
);
basic_string<CharType, Traits, Allocator>& append(
const_pointer _First,
const_pointer _Last
);
basic_string<CharType, Traits, Allocator>& append(
const_iterator _First,
const_iterator _Last
);
這么多的重載,其實(shí)可分為兩類(lèi),一類(lèi)是迭代器版本的append,對(duì)于插入n個(gè)相同的字符append,可以看做是特殊迭代器。另一類(lèi)是連續(xù)字節(jié)內(nèi)存塊的append。這里,只關(guān)注后一類(lèi)。雖然有4個(gè)之多,但其實(shí)只需要一個(gè)就行了,那就是 append(const basic_string<CharType, Traits, Allocator>& _Str)。因?yàn)樽址羔樋梢噪[式轉(zhuǎn)換為string,另外的兩個(gè)重載可以臨時(shí)構(gòu)造string,然后傳遞進(jìn)append就好了。之所以存在4個(gè),老朽的猜想可能是因?yàn)樾剩劣谡{(diào)用上的方便性,并沒(méi)有帶來(lái)多少提高。string的其他類(lèi)似于用append的通過(guò)參數(shù)來(lái)string的操作,如replace,insert,+=,那么多的重載版本,應(yīng)該也是同樣的原因。
假如,臨時(shí)string對(duì)象的構(gòu)造沒(méi)有造成任何性能上的損失,那么,應(yīng)該就可以減少幾十個(gè)成員函數(shù),這無(wú)疑很值得嘗試。那么,能否存在廉價(jià)的string臨時(shí)構(gòu)造方法,因?yàn)樗雷约菏桥R時(shí)對(duì)象,只作為臨時(shí)參數(shù)傳遞的使命,不會(huì)在其上面作什么賦值,添加,修改等操作,也就是說(shuō),它是不可變的,那么,這個(gè)臨時(shí)string對(duì)象就不需要分配內(nèi)存了,只要節(jié)用ptr作為自己字符串的起始地址,然后以長(zhǎng)度作為自己的長(zhǎng)度。參數(shù)傳遞使命完成后,也不需要銷(xiāo)毀內(nèi)存了。
可是,C++中,也不僅僅是C++,所有的語(yǔ)言并沒(méi)有這樣的機(jī)制來(lái)判斷對(duì)象它在構(gòu)造的時(shí)候,就是僅僅作為參數(shù)傳遞來(lái)使用的。為了達(dá)到這種目的,很多時(shí)候還不惜使用引用計(jì)數(shù),但是,很多場(chǎng)合,臨時(shí)string對(duì)象始終要構(gòu)造緩沖存放字符串,比如這里。
除了C++,任何語(yǔ)言的字符串都是不可變的,任何對(duì)于字符串的修改,都意味著要?jiǎng)?chuàng)建另一個(gè)全新的字符串來(lái),那怕僅僅是修改了一個(gè)字符。其實(shí),不可變的字符串,在C++中運(yùn)用很廣的,很多時(shí)候,我們僅僅只需要不可變的字符串,比如說(shuō),這里的append,全部只需要immutable的string。只要知道string是immutable的,那么,c++完全可以高效的應(yīng)付,既然是immutable,就不需要考慮什么資源分配釋放的龜毛問(wèn)題了。下面,就嘗試class一個(gè)immutable的字符串,這,太容易了。就是:
struct Str
{
typedef const char* PCStr;
PCStr start;
size_t length;
Str(PCStr text, size_t len)
{
start = text;
length = len;
}
//
};
然后,在basic_string中加入operator Str的函數(shù),以完成從一個(gè)string到一個(gè)Str的隱式轉(zhuǎn)換,這個(gè)隱式轉(zhuǎn)換簡(jiǎn)直沒(méi)有任何性能上的損失。還有,string中再增加一個(gè)Sub的成員函數(shù),用于截取一段子字符串,也即是immutable的Str對(duì)象。顯然,我們的Str其實(shí)表達(dá)了一個(gè)概念,內(nèi)存中一節(jié)連續(xù)的字符內(nèi)存,也即是數(shù)組。
最后,append就變成append(Str str);了。Str加不加const,或者Str是否為引用,關(guān)系都不大。下面,看看它的運(yùn)作。
對(duì)于,append(const char* text),由于Str中有一個(gè)const char*參數(shù)的構(gòu)造函數(shù),text自動(dòng)隱式轉(zhuǎn)換為一個(gè)Str,很好;
對(duì)于,append(const char* text,size_t count),用append(Str(text, count)),就地構(gòu)造一個(gè)臨時(shí)的Str對(duì)象,嗯,語(yǔ)法調(diào)用上多了一個(gè)Str和一對(duì)括號(hào),多了5個(gè)字符,的確有點(diǎn)不便。
對(duì)于,append(const string& text),同上,string中有一個(gè)operator Str的函數(shù),隱式轉(zhuǎn)換自動(dòng)完成。
對(duì)于,append(const string& text,size_t offset,size_t count),用append(text.Sub(offse, count)),就地構(gòu)造一個(gè)臨時(shí)的Str對(duì)象,嗯,語(yǔ)法調(diào)用上多了一個(gè)Sub和一對(duì)括號(hào)和一個(gè)點(diǎn),但是少了一個(gè)逗號(hào),多了5個(gè)字符,有點(diǎn)不便。
即此以推,string中的replace,insert,assign,+=,=等函數(shù),每個(gè)平均減少3個(gè),總共差不多可以減少20個(gè)左右啦,而功能上沒(méi)有任何減少,可喜可賀。
然后,string中的各種查找比較操作的const的成員函數(shù),比如find,find_first_not_of,rfind等,都可以挪到Str旗下了。因?yàn)檫@些函數(shù),我們也希望可以用之于其他地方,只要那是一塊連續(xù)的字符內(nèi)存好比數(shù)組,那么我們就可以就地快速構(gòu)造一個(gè)臨時(shí)Str對(duì)象,進(jìn)行find,rfind這些操作了。當(dāng)然,原來(lái)string也可以有這個(gè)功能,但是想到僅僅為了做一個(gè)find或者find_first_not_of的查找,就要分配內(nèi)存釋放內(nèi)存,對(duì)于性能優(yōu)先的巴普洛夫反應(yīng)的C++猿猴來(lái)說(shuō),這絕對(duì)是望而生畏的大事。現(xiàn)在通過(guò)不可變的Str,馬上就釋放出來(lái)string的成員函數(shù)的隱含的生產(chǎn)力了。 由于Str的廉價(jià)和透明性,就可以到處亂使用,想用就用,何其快哉。
原來(lái)string沒(méi)有了這些查找的函數(shù),每次要用它們,必須轉(zhuǎn)換這樣調(diào)用,((Str)text).find,無(wú)疑很不方便,對(duì)此,我們只要在string中再增加一個(gè)Str的成員函數(shù),以返回臨時(shí)Str對(duì)象,就可以text.Str().find(),似乎有點(diǎn)不便,但也不是不能接受。
當(dāng)然,Str也有缺點(diǎn),那就是它不以0結(jié)束,導(dǎo)致很多對(duì)于要求以0結(jié)束的地方,就變成禁區(qū)了,這坑爹的C語(yǔ)言規(guī)定。
這不是很明顯嗎?字符串的一部分也是字符串,隨便取出字符串的一節(jié),本來(lái)就應(yīng)該是字符串,這么簡(jiǎn)明統(tǒng)一簡(jiǎn)潔明顯的概念,這樣可以簡(jiǎn)化多少代碼呢,結(jié)果,偏偏只有帶有0結(jié)束的那一節(jié)字符串,才是C語(yǔ)言承認(rèn)的字符串。一個(gè)很好的概念,就這樣在很多地方失去用武之地了。你因?yàn)橐?結(jié)束的字符串很好嗎,要不cstring頭文件中也不會(huì)有那么多帶有字符串長(zhǎng)度版本的字符函數(shù),如strncpy,來(lái)補(bǔ)充了。
對(duì)了,有沒(méi)有覺(jué)得string中的find_last_of,find_first_of,find_last_not_of,find_first_not_of很礙眼啊,顯然這是一種不用組合思想下設(shè)計(jì)出來(lái)的api產(chǎn)物了。其實(shí),別看stl是官方iso的嫡出親子,但是,內(nèi)中的很多api的設(shè)計(jì)都不咋樣,實(shí)在不是學(xué)習(xí)的好對(duì)象。你還別不服,想想人家C#linq的鏈?zhǔn)秸{(diào)用,那個(gè)用起來(lái),才叫痛快。