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