摘要: 緣起,看到一遍文章,題材取自于《C++沉思錄》,綜合利用好幾種設(shè)計(jì)模式,并且結(jié)合所謂的面向?qū)ο蟮募记桑缓笱笱笞缘茫⑶以谧詈螅€反問(wèn):“有更好的解決方案嗎?”。本座暗嘆,又一個(gè)設(shè)計(jì)模式的毒害者。以下,就提出另一種解決方案。
&...
閱讀全文
C++中,由于字符串一開(kāi)始并非內(nèi)置的類(lèi)型,于是,史前時(shí)代,江湖上出現(xiàn)了種種的字符串,各有各的優(yōu)點(diǎn),但自然,也各有各的缺陷,群雄割據(jù),天下大亂,民不聊生。大伙兒盼星星,盼月亮,盼著真正的字符串能出來(lái)一統(tǒng)江湖,好不容易,等到1998的C++標(biāo)準(zhǔn)出來(lái)了,官方的字符串string終于露面了,自然,它并不是語(yǔ)言?xún)?nèi)置支持的,而是在STL庫(kù)中。當(dāng)然,字符串這東西,就算要在C++編譯器的層面上給予支持,其實(shí)也很不容易。可是,這個(gè)官方的string,單純它復(fù)雜的模板定義和那一大陀函數(shù)成員,就足以嚇退眾多意志不堅(jiān)定之人。好了,好不容易,鼓起勇氣,再仔細(xì)瞅瞅string中的東西,發(fā)現(xiàn)它也不是很美妙,既不強(qiáng)大,部分函數(shù)的功能存在重復(fù)又或者STL的算法中已有提供,更要命的是,效率也不怎么高。總之,不能說(shuō)string的設(shè)計(jì)不如史前的各種stringS,但也強(qiáng)不到那里去。當(dāng)然,官方的總比非官方的更具權(quán)威,但每次使用string時(shí),想到它背地里除了正常字符串操作之外,還可能做了各種的低效的內(nèi)存分配釋放的操作,又或者線程安全,又或者引用計(jì)數(shù),內(nèi)心就一直惴惴不安。于是,寧愿一再小心翼翼地用著字符數(shù)組。但是,用得多了,也很郁悶,字符數(shù)組自然高效、靈活,但總是要千編一律地一再寫(xiě)著容易出錯(cuò)的代碼,我脆弱的心靈終于頭暈眼花了。于是,我決定按照自己的意愿寫(xiě)一個(gè)字符串,不敢欲與群雄爭(zhēng)鋒,只是,為了能夠在自己的代碼中,替換字符數(shù)組。我對(duì)它的要求是,字符數(shù)組能做到的事情,它也要做到,并且,效率上,絕不妥協(xié)。
俗話(huà)說(shuō),過(guò)早的優(yōu)化是萬(wàn)惡之源。但在此,在設(shè)計(jì)本家字符串時(shí),一開(kāi)始,就要考慮到效率的細(xì)節(jié)上去了。首先,它要支持堆棧變量的形式,不要它進(jìn)行內(nèi)存的分配釋放操作,就好像堆棧上的字符數(shù)組那樣。咦,好像很神奇,其實(shí),只要想到TR1中那個(gè)經(jīng)典的array,通過(guò)使用神奇的模板技術(shù),就有辦法做到了。所以,此字符串的使用好比這樣子,CStackString <MAX_PATH> sFile,暫時(shí)假定這個(gè)字符串的名字叫CStackString。
但是,使用模板之后,字符串的字符數(shù)組的長(zhǎng)度只要不一樣,它們就都屬于不同類(lèi)型變量,并且之間還都不兼容呢,雖然它們都是字符串。此外,還會(huì)編譯器還將生產(chǎn)出一堆重復(fù)的代碼。這無(wú)論如何,都不能忍受。于是,自然而然,就想到了繼承。CStackString模板類(lèi)繼承于非模板的mybasestring,mybasestring中實(shí)現(xiàn)了CStackString的各種各樣的操作,而CStackString只要仿照array那樣子,定義好自己的數(shù)據(jù)成員即可。嗯,還是看看代碼,馬上就明白到底是怎么一回事了。

class CMyBaseString


{
public:
typedef size_t size_type;
typedef char *pointer;
typedef const char *const_pointer;
typedef CMyBaseString _Myt;

public:
char operator[](size_type nPos)const

{
assert(nPos < m_nLen);
return m_str[nPos];
}


const_pointer c_str()const
{ return m_str; }

const_pointer right(size_type nLen)const

{
assert(nLen < m_nLen);
return m_str+m_nLen-nLen;
}

int compare(const_pointer str)const

{
return strcmp(m_str, str);
}

_Myt& assign(const char* str)

{
m_nLen = strlen(str);
assert(m_nLen < m_nBuffSize);
strcpy(m_str, str);
return *this;
}

_Myt& append(const_pointer str)

{
size_type nLen = strlen(str);
assert(m_nLen + nLen < m_nBuffSize);
strcpy(m_str+m_nLen, str);
m_nLen += nLen;
return *this;
}

_Myt& format(const_pointer sFormat,
)

{
va_list argList;
va_start( argList, sFormat );
m_nLen = vsprintf(m_str, sFormat, argList);
va_end( argList );
assert(m_nLen < m_nBuffSize);
return *this;
}

//
.

protected:
CMyBaseString(pointer sBuf, size_type nBuffSize)

{
m_nBuffSize = nBuffSize;
m_nLen = 0;
m_str = sBuf;
m_str[0] = 0;
}

private:
size_type m_nBuffSize;
size_type m_nLen;
pointer m_str;
};

template<size_t _size>
class CStackString : public CMyBaseString


{
public:

CStackString(const char* str) : CMyBaseString(m_sMine, _size)
{ assign(str);}

CStackString() : CMyBaseString(m_sMine, _size)
{}

private:
char m_sMine[_size];
};
int main()


{
CStackString<20> sTest("hello");
cout << sTest.c_str() << endl;
cout << sTest.right(3) << endl;
return 0;
}
于是通過(guò)基類(lèi)mybasestring,各種不同類(lèi)型的template CStackString就又聯(lián)系在一塊了。Mybasestring可看成定義了數(shù)據(jù)接口的基類(lèi),其子類(lèi)的頭部數(shù)據(jù)必須與它保持一致,嗯,很好。然后,在mybasestring中實(shí)現(xiàn)的各種功能,都可以用在mystring身上了,而CStackString中無(wú)須實(shí)現(xiàn)任何功能,它只負(fù)責(zé)在堆棧上分配內(nèi)存。所以,mybasestring不僅可以用在堆棧上,還能用于堆上,只要再繼續(xù)定義一個(gè)能在堆上分配內(nèi)存的mybasestring的子類(lèi)即可,然后都能相容于堆棧上的CStackString字符串。
……。 經(jīng)過(guò)一番努力,這個(gè)字符串類(lèi)幾乎包含了字符數(shù)組的一切基本功能,基本上可代替字符數(shù)組了。為了能夠用到STL中的各種算法,再在其上增加一些返回迭代器的函數(shù),好比begin,end,rbegin,rend,它們都很容易實(shí)現(xiàn)。還有,為了使用起來(lái)更加友好方便更具效率,貌似應(yīng)該再實(shí)現(xiàn)一些全局操作符的重載運(yùn)算;……;好了,打住。如果打算將這個(gè)字符串很好地融入到STL中,需要做出更多的努力。原本只打算代替字符數(shù)組而已。代碼在VC2005以上版本編譯時(shí),會(huì)出現(xiàn)一些警告,可以用#pregma的指令將其disable掉,或者使用其中的所謂的安全字符串操作函數(shù)。
好不容易,終于就實(shí)現(xiàn)了一個(gè)基于堆棧上的字符串,它是窄字符的。別忘了,還有寬字符的字符串呢。這也沒(méi)什么,只須將mybasestring重命名為CMyBaseStringA,CStackString改為CStackStringA。然后再分別實(shí)現(xiàn)與CMyBaseStringA與CStackStringA同樣接口的CMyBaseStringW和CStackStringW。然后,再針對(duì)UNICODE,Typedef或defined一對(duì)CMyBaseStringT和CStackStringT。在這里,并不想模仿STL中的string或MFC中的CString那樣子,template一個(gè)basic_string(simple_string),然后分別進(jìn)行模板特化,近來(lái)越看越覺(jué)得這種模板特化的方式相當(dāng)惡心,只是將代碼搞得更加復(fù)雜,卻沒(méi)帶來(lái)多大的好處。
相比于其他的字符串實(shí)現(xiàn),這個(gè)mybasestring不過(guò)是將內(nèi)存分配拱手讓人罷了。這樣一來(lái),就帶來(lái)一些新的問(wèn)題。首先,它要假設(shè)其子類(lèi)給它分配了足夠的內(nèi)存,不過(guò),在C++傳統(tǒng),經(jīng)常假設(shè)用戶(hù)分配了足夠的內(nèi)存;然后,因?yàn)槊撾x了內(nèi)存管理,有一些功能自然也就無(wú)法實(shí)現(xiàn)出來(lái)了,C的字符串函數(shù)也還不是這樣,當(dāng)緩沖溢出時(shí),該崩潰就還得崩潰。
再次向C++的無(wú)所不能頂禮膜拜。C++,你是電,你是光, 你是唯一的神話(huà), 我只愛(ài)你,You are my Super Star!
再次聲明,本字符串只為取代字符數(shù)組,至于其它的種種無(wú)理要求,均不在本座的考慮范圍之內(nèi)。
摘要: 所謂24點(diǎn),就是甩出幾個(gè)整數(shù),整數(shù)之間沒(méi)有固定的前后順序,給它們添加上加減乘除括號(hào)等,形成一條式子,最后運(yùn)算結(jié)果等于24。很自然的想法,就是祭出表達(dá)式樹(shù)。但24點(diǎn)只是一個(gè)小程序,用表達(dá)式樹(shù)來(lái)解決,實(shí)在有點(diǎn)大材小用了,太委屈表達(dá)式樹(shù)了,所以堅(jiān)決抵制。 &nb...
閱讀全文
在寫(xiě)24點(diǎn)的程序時(shí),要處理除法運(yùn)算,不可避免地將引入小數(shù)點(diǎn),但本人對(duì)于計(jì)算機(jī)中的浮點(diǎn)數(shù)實(shí)在沒(méi)有太多的好感。權(quán)衡再三,終于決定下定決心寫(xiě)一個(gè)有理數(shù)類(lèi)(以下簡(jiǎn)稱(chēng)為CRational),以解決除法運(yùn)算這個(gè)問(wèn)題。并且有理數(shù)這個(gè)類(lèi)也是一個(gè)很好的C++練習(xí)題,可以用來(lái)練練手,以下將看到,它的實(shí)現(xiàn)雖然不難,但也不是很容易。有理數(shù)這個(gè)東西,屬于用戶(hù)自定義的數(shù)據(jù)類(lèi)型,c++對(duì)此的支持,真可謂完美,既不失效率,又具備美觀。c++可以讓程序員做出來(lái)的自定義類(lèi)型,其行為可以表現(xiàn)得好像是由語(yǔ)言層面實(shí)現(xiàn)的那個(gè)樣子,不管從語(yǔ)法、安全、效率上講。這一點(diǎn),所有的語(yǔ)言都沒(méi)法和c++媲美。
不管怎么說(shuō),CRational的需求相當(dāng)明確,它一定有分子、分母、然后支持加減乘除這四種運(yùn)算,于是,一口氣馬上就能寫(xiě)下它的定義。
class CRational


{
public:
CRational(int nNumberator=0, int nDenominator=1);

int Numberator()const
{ return m_nNum;}

int Denominator()const
{ return m_nDe;}

CRational& operator+=(const CRational& _Right);
CRational& operator-=(const CRational& _Right);
CRational& operator*=(const CRational& _Right);
CRational& operator/=(const CRational& _Right);
CRational operator-()const // 一元操作符,用以取反

{
return CRational(-m_nNum, m_nDe);
}

private:
int m_nNum;
int m_nDe;
}; 嗯,我承認(rèn)代碼很匈牙利,中MFC的毒太深。毋庸置疑,分子分母只能獲取,不能設(shè)置,函數(shù)名中,沒(méi)有加Get的前綴,實(shí)在是因?yàn)榇a中它出現(xiàn)的地方太多了,所以能避免就盡量避免。沒(méi)有涉及資源分配,析構(gòu)函數(shù)可以忽略,拷貝構(gòu)造函數(shù)和賦值函數(shù)也不用寫(xiě)了,編譯器將會(huì)提供缺省的實(shí)現(xiàn),足以滿(mǎn)足我們的要求。貌似應(yīng)該還要有一個(gè)返回求取小數(shù)值的操作,但是,這個(gè)類(lèi)本身就是為了避免操作小數(shù)點(diǎn),而且,計(jì)算小數(shù)點(diǎn)值可通過(guò)分子/分母的方法計(jì)算出來(lái)。總之,CRational的終極接口就是這個(gè)樣子了。
每個(gè)有理數(shù)都有一個(gè)標(biāo)準(zhǔn)的等價(jià)類(lèi),這個(gè)標(biāo)準(zhǔn)的有理數(shù)的分子、分母都不能再約分了,而且可以暫時(shí)假設(shè)符號(hào)位出現(xiàn)于分子中,分母則為正整數(shù),比如說(shuō),3/6、-4/-8都等價(jià)于1/2,因此,在這個(gè)有理數(shù)類(lèi)中,必須有一個(gè)標(biāo)準(zhǔn)化的操作,問(wèn)題是,這個(gè)標(biāo)準(zhǔn)化函數(shù)是返回一個(gè)標(biāo)準(zhǔn)的有理數(shù)還是將有理數(shù)自身直接就標(biāo)準(zhǔn)化了。經(jīng)過(guò)多方面的權(quán)衡,特別是為了兼容現(xiàn)有的整型變量(int, char, short等),整型變量可看成分母為1的有理數(shù),我決定讓CRational一直處于標(biāo)準(zhǔn)化的狀態(tài)下,標(biāo)準(zhǔn)化的狀態(tài)由CRational自己來(lái)維持,客戶(hù)無(wú)須知道標(biāo)準(zhǔn)化的這個(gè)細(xì)節(jié),因此,將void standarlize()聲明于其private的區(qū)域下,其實(shí)現(xiàn)如下:
void CRational::standarlize()


{
if (m_nDe < 0)

{
m_nDe = -m_nDe;
m_nNum = -m_nNum;
}
int nGcd = gcd(abs(m_nNum), m_nDe);
m_nNum /= nGcd;
m_nDe /= nGcd;
}

其中,毫無(wú)疑問(wèn),gcd為最大公約數(shù),gcd沒(méi)有訪問(wèn)CRational的任何非靜態(tài)(nonstatic)變量,因此,它必將不可成為CRational的非靜態(tài)函數(shù)。將gcd聲明為靜態(tài)函數(shù),雖然可避免污染全局空間,但也使得外部代碼必須通過(guò)CRational來(lái)調(diào)用gcd函數(shù),語(yǔ)法上不方便,而且,gcd本身就是一個(gè)獨(dú)立性很強(qiáng)而且又通用的函數(shù),從意義上,它也不應(yīng)該屬于CRational里面的東西。因此,gcd只能為全局函數(shù)。關(guān)于靜態(tài)函數(shù)和全局函數(shù)的選擇,鑒于“類(lèi)的接口要盡可能的小”的原則和其他的一些問(wèn)題,只要函數(shù)不訪問(wèn)類(lèi)的靜態(tài)變量,也不通過(guò)類(lèi)的變量來(lái)訪問(wèn)到其非靜態(tài)成員,它就應(yīng)該是全局函數(shù)。全局函數(shù)是好東西,某些語(yǔ)言為了堅(jiān)持所謂的純粹的面向?qū)ο螅室獠恢С郑瑢?shí)在讓人用起來(lái)很不痛快,它的污染全局空間的問(wèn)題,完全可以通過(guò)命名空間來(lái)解決。總之,只要可能的話(huà),就應(yīng)該將函數(shù)聲明為全局函數(shù),沒(méi)什么不好。好了,請(qǐng)看gcd的實(shí)現(xiàn)。
unsigned int gcd(unsigned int x, unsigned int y)


{
unsigned int nTimes=0;
for (; 0 == (x&1) && 0 == (y&1); x>>=1, y>>=1)
++nTimes;

if (x < y)
swap(x, y);

while (y > 0)

{
for (; 0 == (x & 1 );x >>= 1 )
;

if (x < y)
swap(x, y);
x -= y;
if (x < y)
swap(x, y);
}
return x << nTimes;
} 其算法源于《編程之美》,可看成是非遞歸的版本。咦,怎么會(huì)這么長(zhǎng),與日常所見(jiàn)的到似乎不太一樣,高效算法的代碼貌似都會(huì)很長(zhǎng),好比strlen。再仔細(xì)看,里面居然沒(méi)有取余的操作。嗯,它的算法核心,用移位和減法這兩種快速的運(yùn)算來(lái)替代取模這種慢速運(yùn)算。
有了standarlize()之后,CRational的幾個(gè)函數(shù)的實(shí)現(xiàn)如下所示:
CRational::CRational(int nNumberator, int nDenominator)
: m_nNum(nNumberator), m_nDe(nDenominator)


{
assert(nDenominator != 0);
standarlize();
}

CRational& CRational::operator+=(const CRational& _Right)


{
m_nNum = m_nNum*_Right.m_nDe + _Right.m_nNum*m_nDe;
m_nDe *= _Right.m_nDe;
standarlize();
return *this;
}……
構(gòu)造函數(shù)中,似乎應(yīng)該檢查分母為0的情況,然后拋出異常。但是,這屬于契約使用的問(wèn)題,用戶(hù)違背的契約,一切后果,必須自己承擔(dān),我們的代碼無(wú)須對(duì)此負(fù)責(zé)。 此外,就是各種+、-、*、/、==、輸出等各種全局運(yùn)算符重載的操作了。得益于C++的缺省類(lèi)型轉(zhuǎn)換,我們不用再做其他事情,就可以很好地讓我們的CRational很好地與融入到原有的各種整型世界中去。當(dāng)然,為了效率起見(jiàn),似乎有必要針對(duì)各種整型提供+、-、*、/的各種重載版本(complex就是這樣做的),但在此,確實(shí)沒(méi)有必要。缺省類(lèi)型轉(zhuǎn)換有時(shí)雖然會(huì)帶來(lái)一些問(wèn)題,但是,當(dāng)確實(shí)需要它的時(shí)候,它就能發(fā)揮重大作用了。C++的各種特性就是這樣,你可以不用,它也不打擾你(你要故意或無(wú)意用錯(cuò),那也沒(méi)辦法),當(dāng)真正需要到的時(shí)候,特性的威力就顯示出來(lái)了。很多人之所以愿意沉迷于C++,就在于它不剝奪程序員的任何一點(diǎn)選擇的權(quán)利。
ostream& operator << (ostream& out, const CRational& rat)


{
cout << rat.Numberator();
if (rat.Denominator() != 1)
cout << "/" << rat.Denominator();
return out;
}

CRational operator+(const CRational& _Left, const CRational& _Right)


{
CRational _Tmp(_Left);
return _Tmp += _Right;
}……
嗯,一再寫(xiě)這些入門(mén)級(jí)的文章,本座也很自覺(jué)有失身價(jià),也算是對(duì)網(wǎng)絡(luò)世界的一點(diǎn)回報(bào)吧。只要有一個(gè)人看了,能有所啟發(fā),在下就心滿(mǎn)意足了,為免誤人子弟,也歡迎有人批評(píng)指正。
數(shù)組在所有的語(yǔ)言中,以C最為簡(jiǎn)單,就是一起始地址,外加一數(shù)組的長(zhǎng)度,而且基本上沒(méi)有任何功能可言。然而,在所有的數(shù)組使用中,卻是C的這種簡(jiǎn)單的數(shù)組形式,最一再使我心折,其靈活性和效率,簡(jiǎn)直驚天地泣鬼神,前無(wú)古人,后無(wú)來(lái)者,念天地之悠悠,高處不勝寒。首先,C數(shù)組從邏輯上講,是分形一致的(想不到其他詞來(lái)形容了),分形的意思,就是部分與整體保持一致的構(gòu)造形式,也即是數(shù)組的任何一部分也都是數(shù)組,比如一整型數(shù)組{1,2,3,4,5},連續(xù)取出其中任一連續(xù)的部分,都可以看成是一個(gè)數(shù)組,{2,3}是數(shù)組,{1,2,3}是數(shù)組,{4,5}也都是數(shù)組,差別只在于數(shù)組的起始地址和元素的個(gè)數(shù)。那怕是數(shù)組中的任何一個(gè)元素,都可以看成是一個(gè)長(zhǎng)度為1的數(shù)組。因此,C數(shù)組的這種統(tǒng)一的格式,在作為參數(shù),傳遞給函數(shù)的時(shí)候,特別是遞歸函數(shù)中,不知道有多么方便。 比如冒泡排序,BubbleSort(int* pInt, int nLen),用遞歸來(lái)實(shí)現(xiàn),只要冒完第1個(gè)元素之后,接著就可以遞歸,內(nèi)部調(diào)用自己,BubbleSort(pInt+1, nLen-1),繼續(xù)冒泡數(shù)組,只是數(shù)組是由第1個(gè)數(shù)之后的全部數(shù)組成的新的數(shù)組,元素個(gè)數(shù)比之前少1,一直如是處理,直到最后,數(shù)組的長(zhǎng)度為1,于是冒泡排序完成。這種方法的成立前提,就在于C數(shù)組格式的高度統(tǒng)一。一個(gè)操作數(shù)組的函數(shù),可以操作數(shù)組的任何一部分,甚至可以操作一個(gè)變量,因?yàn)閱为?dú)的變量可以看成是元素長(zhǎng)度為1的數(shù)組,多少次,我們用WideCharToMultiByte來(lái)轉(zhuǎn)換一個(gè)寬字符變量。C語(yǔ)言中操作數(shù)組的函數(shù),搭配上C數(shù)組的簡(jiǎn)單定義,其應(yīng)用,那是相當(dāng)相當(dāng)廣泛的,并且使用起來(lái),自然非常非常的方便,比如剛才的冒泡函數(shù)BubbleSort,只要你高興,完全可以拿來(lái)只冒數(shù)組中的隨便某一部分,這完全可以由用戶(hù)自己隨意定制,語(yǔ)言層面上直接支持了。 其他語(yǔ)言的數(shù)組,由于特點(diǎn)很多,反而失去了這種邏輯意義上的統(tǒng)一處理,因?yàn)樵谒鼈兡抢铮瑪?shù)組的任何一部分都不能看成數(shù)組,單獨(dú)的變量也沒(méi)法看成是數(shù)組,在它們世界,數(shù)組與獨(dú)立變量,無(wú)論如何,都沒(méi)法劃上等號(hào)。如果他們要用遞歸實(shí)現(xiàn)冒泡排序的代碼,形式上無(wú)論如何都比不上我們的C數(shù)組形式的函數(shù),誰(shuí)說(shuō)C的代碼就意味著代碼量多了,用得好,可以簡(jiǎn)單得讓人贊嘆不已。 我們?cè)賮?lái)看看C的字符串,也很簡(jiǎn)單,它也是一個(gè)數(shù)組,只不過(guò)最后一個(gè)元素是’\nul’,加了這么一點(diǎn)限制之后,字符串自然就失去了數(shù)組的分形強(qiáng)悍,但C的字符串依然不可小看,因?yàn)樽址校灰獛狭?#8217;\nul’,都能看成是字符串,好比,”hello”這條字符串,只要改變起始地址,就可輕而易舉地得到”ello”,”llo”,”lo”,”o”這好幾條子字符串,這個(gè)特點(diǎn),可以簡(jiǎn)化很多字符串操作,并且效率最高。此外,C字符串,只要你愿意,完成可以拿來(lái)當(dāng)成是字符數(shù)組來(lái)使用,這樣,就又恢復(fù)了數(shù)組分形功能,C函數(shù)庫(kù)中和WINDOWS API,有很多函數(shù)就是專(zhuān)門(mén)處理C字符數(shù)組的。
C數(shù)組的這種分形特性,在STL被抽象成迭代器,于是,在C++中,就失去了原有的輝煌。但是,在很多簡(jiǎn)單的設(shè)計(jì)中,依然有著不可低估的力量。
C的很多東西,就是這樣,因?yàn)楹?jiǎn)單,所以強(qiáng)大得令人心寒。函數(shù)、結(jié)構(gòu)體、數(shù)組、GOTO、枚舉這些簡(jiǎn)單的東西,巧妙的配合使用,可以玩出很多很多意想不到的神奇功能出來(lái),令人擊節(jié)贊嘆,而且不會(huì)像C++那樣,存在著所謂的什么心智負(fù)擔(dān)。此外,C中的宏,嘿嘿,俺就不提了,變化多端,鬼神莫測(cè)。對(duì)于C,我越來(lái)越敬畏,它遠(yuǎn)遠(yuǎn)不似表面上看的那么簡(jiǎn)單,其背后自然潛藏著一套精神規(guī)則。即使看到每一行C代碼,內(nèi)心都能知道它的相應(yīng)的匯編代碼,那又怎么樣,它總是有辦法讓你驚喜不已。
作為C++的堅(jiān)實(shí)粉絲,我一直很排斥JAVA,并不是JAVA這種語(yǔ)言不好,而是Java迷的那副嘴臉,事事都要與C++爭(zhēng),并且還堅(jiān)稱(chēng)JAVA比C++,甚至連執(zhí)行效率都要?jiǎng)龠^(guò)C++,什么JIT運(yùn)行時(shí)能監(jiān)視代碼,選取執(zhí)行頻率最高的代碼,根據(jù)特定的平臺(tái),進(jìn)行特別處理,生產(chǎn)出最優(yōu)化的機(jī)器代碼;又說(shuō)什么垃圾回收能夠解決C++中的內(nèi)存管理問(wèn)題,并且內(nèi)存分配遠(yuǎn)遠(yuǎn)勝過(guò)C++中的手工人肉管理內(nèi)存的毛病,其實(shí),內(nèi)存管理從來(lái)就不是C++中的嚴(yán)重問(wèn)題,只要設(shè)計(jì)得好,應(yīng)用層中的代碼甚少可以不出現(xiàn)new, delete。至少delete可以不出現(xiàn),大家知道WHY的;種種論調(diào),無(wú)理取鬧之極。而最令我受不了的,就是他們閉口開(kāi)口,都離不開(kāi)設(shè)計(jì)模式,搞得設(shè)計(jì)模式好像成為了JAVA的專(zhuān)有產(chǎn)品。須知,第一本設(shè)計(jì)模式的書(shū),也是最最經(jīng)典的那本四人書(shū),其對(duì)設(shè)計(jì)模式的實(shí)現(xiàn)語(yǔ)言,采用的就是SmallTalk 和C++,一開(kāi)始就不關(guān)JAVA的一點(diǎn)事情,并且書(shū)中C++還占了較大的比重。關(guān)于四人書(shū),最讓我欣慰的一件事情就是,四位巨頭并沒(méi)有響應(yīng)JAVA迷的強(qiáng)烈呼聲,采用JAVA來(lái)實(shí)現(xiàn)設(shè)計(jì)模式的第二版。當(dāng)然,我也承認(rèn),用JAVA來(lái)實(shí)現(xiàn)設(shè)計(jì)模式,確實(shí)來(lái)得要比C++清爽,JAVA的這種語(yǔ)言,好像就是專(zhuān)為設(shè)計(jì)模式量身訂做。只可惜,市面上任何一本JAVA的設(shè)計(jì)模式書(shū),沒(méi)有一本及得上我們C++的那一本設(shè)計(jì)模式圣經(jīng),C++中不必再需要設(shè)計(jì)模式的書(shū)了,因?yàn)樽詈玫臅?shū)就已經(jīng)擺在那里了,涵蓋了設(shè)計(jì)模式中的方方面面,濃縮精華得很。突然想起,C++的教材也不需要那么多,因?yàn)槔蠣斪右呀?jīng)寫(xiě)了一本最好的書(shū)了,其他書(shū)的內(nèi)容,都已經(jīng)涵蓋在那本C++語(yǔ)言圣經(jīng)中了。至于那些不被C++圣經(jīng)所提及的,那都是一些走火入魔的玩意,玩玩還可以,開(kāi)闊開(kāi)闊視野也不錯(cuò),但真要用在實(shí)際項(xiàng)目中,還是少用為妙。罪過(guò)罪過(guò),C++中的好書(shū)確實(shí)有好幾本,不宜一棍子打死。
有趣的是,設(shè)計(jì)模式在JAVA中被捧上了天,沒(méi)有設(shè)計(jì)模式,JAVA就沒(méi)法活下去。反而C++作為設(shè)計(jì)模式的第一實(shí)現(xiàn)語(yǔ)言,卻不怎么感冒。不能說(shuō)被輕視,但起碼沒(méi)有普遍被重視。即使C++中的高手,也沒(méi)有對(duì)設(shè)計(jì)模式如何如何的推崇備至,他們貌似更加喜歡玩語(yǔ)法糖,熱衷于在C++中模擬出其他語(yǔ)言的特性。這也難怪,C++有四種編程典范,設(shè)計(jì)模式的主要應(yīng)用就在于面向?qū)ο螅嫦驅(qū)ο蟛贿^(guò)是其中最不成熟的一種,在C++中,最成熟的要數(shù)基于對(duì)象的編程,當(dāng)然,拜C語(yǔ)言成熟,面向過(guò)程也熟得要爛了,泛型這東西也挺前衛(wèi)的,而C++中的面向?qū)ο螅恢背裘阎瑹o(wú)論怎么努力,都難以搬上臺(tái)面。注意,C++中的面向?qū)ο笈c虛函數(shù)是兩碼事,至少在我看來(lái)是這樣的。
好了,該談?wù)勎覍?duì)設(shè)計(jì)模式的看法了,一句話(huà),我對(duì)設(shè)計(jì)模式殊無(wú)好感,源于我內(nèi)心對(duì)當(dāng)前的面向?qū)ο缶幊田L(fēng)格的排斥,因?yàn)槟鞘莻蚊嫦驅(qū)ο缶幊蹋^的偽面向?qū)ο螅褪侵竿ㄟ^(guò)單根繼承和針對(duì)接口或抽象類(lèi)來(lái)寫(xiě)代碼,就以為是在進(jìn)行面向?qū)ο蟮木幊蹋菍?shí)在是太天真了。設(shè)計(jì)模式中的那些點(diǎn)子,用得好,確實(shí)能解決偽面向?qū)ο蟮囊恍﹩?wèn)題,無(wú)可厚非;用得不好,無(wú)緣無(wú)故地引入一些不必要的東西,模式的應(yīng)用意味著間接性的增厚。現(xiàn)實(shí)中,能恰當(dāng)?shù)赜煤媚J降娜耍僦稚伲J娇偸浅霈F(xiàn)在一些不必要出現(xiàn)的場(chǎng)合下。很多人都是生吞活剝了23種模式之下,內(nèi)心就沾沾自喜,到處躍躍欲試,鮮有人嘗試?yán)斫饽J奖澈蟮慕y(tǒng)一思想是什么,或者說(shuō),如果代碼本身就已經(jīng)能夠很好類(lèi)與類(lèi)之間的耦合問(wèn)題,可勝過(guò)千萬(wàn)種設(shè)計(jì)模式。
以下文字,與孟巖大俠的觀點(diǎn),存在部分重復(fù)之處,讀者認(rèn)為在下是在拾他的牙慧,我也不反對(duì),畢竟人家的文章反表在前,我無(wú)話(huà)可說(shuō),本文的重點(diǎn),旨在表達(dá)在下對(duì)設(shè)計(jì)模式的鄙視。
既然有偽面向?qū)ο螅陀姓婷嫦驅(qū)ο蟆U婷嫦驅(qū)ο蟮木幊蹋褪悄阒恢酪粋€(gè)對(duì)象的ID,其他的一切事情,它繼承自那些父類(lèi),實(shí)現(xiàn)了那些接口,統(tǒng)統(tǒng)一概都不得而知,然后你只能通過(guò)這個(gè)ID給對(duì)象發(fā)送消息,以此來(lái)驅(qū)使對(duì)象做一些事情,注意,確確實(shí)實(shí)是只是發(fā)送消息,而不是調(diào)用該對(duì)象的函數(shù),那怕是調(diào)用了該對(duì)象實(shí)現(xiàn)的接口函數(shù),也意味著該對(duì)象的依賴(lài),好吧,說(shuō)錯(cuò)了,是對(duì)該接口的依賴(lài),不管怎么樣,都是依賴(lài),而且依賴(lài)接口,還搞得客戶(hù)代碼和對(duì)象都要依賴(lài)于同一個(gè)接口了。
給對(duì)象發(fā)送消息,聽(tīng)起來(lái)似乎有點(diǎn)抽象,但是,只要聯(lián)想到WINDOWS的窗口句柄和消息處理函數(shù),就自然明白是怎么回事了。在WINDOWS下,每一個(gè)窗口都是一個(gè)對(duì)象,窗口句柄代表了對(duì)象ID。操作窗口時(shí),只能通過(guò)SendMessage或PostMessage來(lái)讓窗口作一些事情。SendMessage或PostMessage的四個(gè)參數(shù),分別是對(duì)象ID、消息編號(hào)、消息參數(shù)1和消息參數(shù)2。客戶(hù)代碼在使用窗口時(shí),不需要假設(shè)什么接口,能做的,僅須做的,僅僅就是給它發(fā)送消息,客戶(hù)代碼完全無(wú)須依賴(lài)于什么鬼接口,非常簡(jiǎn)單明了。但是,這里存在一個(gè)問(wèn)題,消息參數(shù)1和消息參數(shù)2都是void*類(lèi)型,不安全耶,要是誤發(fā)送錯(cuò)了消息,那該怎么辦。在偽面向?qū)ο笾校蛻?hù)也可能在調(diào)用接口參數(shù)時(shí),也可能會(huì)傳錯(cuò)了參數(shù),只不過(guò)是編譯器可以幫你找出來(lái)。其實(shí)類(lèi)型不安全,也沒(méi)什么,客戶(hù)既然要發(fā)送消息給對(duì)象,自然要遵守使用的協(xié)議,自然要清楚自己在做什么事情,這本來(lái)就是C語(yǔ)言的精神,一切相信程序員。
好了,該是給WINDOWS大唱贊歌了。WINDOWS系統(tǒng)的窗口是我見(jiàn)過(guò)中算是比較象樣的面向?qū)ο蟮牡浞读恕⒚嫦驅(qū)ο蟮木褙瀼氐降祝翱趯?duì)象很好地做到了僅僅是純粹解析消息,執(zhí)行消息,與外界的其他對(duì)象,不存在任何一點(diǎn)耦合。一個(gè)窗口對(duì)象中的lpfnWndProc其實(shí)可以理解成指向虛函數(shù)表的指針,但是它卻要比虛函數(shù)表的指針功能更加強(qiáng)大靈活,并且還不存在虛擬函數(shù)表的種種問(wèn)題。強(qiáng)大之處,就之于lpfnWndProc相當(dāng)于指向一個(gè)龐大的虛函數(shù)表,這個(gè)表有2的32次方多個(gè)虛函數(shù),靈活之處可以突破虛函數(shù)的種種限制。當(dāng)你要給一個(gè)窗口對(duì)象添加新的功能,或者不滿(mǎn)意它的某些原有操作的時(shí)候,你完全可以子類(lèi)化該窗口,在子類(lèi)化操作中,截取關(guān)心的消息,解析消息,執(zhí)行消息,至于其他的消息,直接交給lpfnOldWndProc或DefWindowProc就是了。從這個(gè)意義上講,所有的窗口對(duì)象繼承于DefWindowProc,而子類(lèi)化窗口即是繼承于lpfnOldWndProc,但是,這里的繼承,卻沒(méi)有偽面向?qū)ο笾械睦^承或?qū)崿F(xiàn)接口的種種弊端。而且,在你子類(lèi)化窗口的時(shí)候,不用影響到客戶(hù)的任何一點(diǎn)點(diǎn)代碼,客戶(hù)代碼依舊是發(fā)送消息,卻不知道窗口對(duì)象已經(jīng)舊貌換新顏了,OH,這實(shí)在太美妙了。所有的耦合,奇跡般的消失得干干凈凈了。消息比之于接口,那完完全全是純粹的正交關(guān)系,并且沒(méi)有接口的種種累贅,種種缺陷。并且更爽的是,即使對(duì)象沒(méi)有處理消息,也沒(méi)有關(guān)系,客戶(hù)代碼依然可以給對(duì)象發(fā)送消息。
從某種意義上講,設(shè)計(jì)模式不過(guò)是為了解耦對(duì)象與對(duì)象之間的耦合關(guān)系,當(dāng)對(duì)象之間不存在耦合的時(shí)候,設(shè)計(jì)模式還有什么存在的意義嗎?以下結(jié)合WINDOWS系統(tǒng)來(lái)理解,所謂的設(shè)計(jì)模式,不過(guò)是消息發(fā)送的一些應(yīng)用罷了。下面的舉例,例子之間沒(méi)有什么必然關(guān)系,我想到那里就寫(xiě)到什么,模式后面似乎應(yīng)該帶上英文,但我也懶得寫(xiě)了。
觀察者模式:一個(gè)廣播消息就搞定了;
模板方法:不過(guò)是按順序給一個(gè)對(duì)象發(fā)送某些指定的消息而已;
工廠方法、抽象工廠:用一個(gè)或幾個(gè)lpClassName就搞定了;
原型:不過(guò)是發(fā)送一條WM_COPYOBJECT的消息而已;
裝飾者或者狀態(tài):嘿,子類(lèi)化就完成了,并且非常徹底,一點(diǎn)都不覺(jué)得別扭;
命令:對(duì)象將SendMessage中的消息編號(hào)、消息參數(shù)1和消息參數(shù)2保存起來(lái)就是了,這沒(méi)什么大不了的;
策略:不過(guò)一個(gè)回調(diào)函數(shù),外加必要的參數(shù);
橋接:貌似沒(méi)什么必要;
……
沒(méi)有了!落得個(gè)一片白茫茫大地真干凈!
很久很久以前,世界上曾經(jīng)存在著這么一種語(yǔ)言,用它寫(xiě)出來(lái)的代碼,既能保證高效的運(yùn)行效率,又兼具優(yōu)雅十足的美感,并且,所有語(yǔ)言存在著的不足,都可以通過(guò)添加新的自定義的類(lèi)型來(lái)實(shí)現(xiàn)出來(lái),并且,新的類(lèi)型,只要做得好,可以與原有的類(lèi)型無(wú)縫交互。在它的世界里面,那是一切皆有可能。并且,它還承諾,雖然它的功能非常豐富,但各個(gè)功能特性都可以和睦相處,而且,你代碼中不必要用到某項(xiàng)功能時(shí),你完全不必為這項(xiàng)功能付出那怕一丁點(diǎn)的代價(jià),如果這個(gè)語(yǔ)言愿意作一點(diǎn)點(diǎn)妥協(xié),可能它就不是現(xiàn)在的這個(gè)樣子,至少將更加容易使用。所有它承諾的一切,乍聽(tīng)起來(lái)美妙無(wú)比,而且,它也似乎做到了,真的做到了馬兒既能跑,又能吃盡量少的草,前提是開(kāi)發(fā)人員要小心翼翼地用好這門(mén)語(yǔ)言的一切特性。只可惜事與愿違,這門(mén)語(yǔ)言確實(shí)看起來(lái),真的可以創(chuàng)造出完美的東西,但事實(shí)上,沒(méi)有人能用它寫(xiě)出過(guò)一個(gè)好東西,那怕是簡(jiǎn)簡(jiǎn)單單的字符串,要做得大多數(shù)人拍手叫好,也是千難萬(wàn)難,完美的字符串,從來(lái)就沒(méi)有出現(xiàn)過(guò)。各位同學(xué),自然知道在下說(shuō)的是什么語(yǔ)言,等千呼萬(wàn)喚的C++1X始出來(lái)之后, 真不知道還有什么東西,是C++沒(méi)法做的,要在C++中增加幾種新的編程模式,也不會(huì)有太多的困難。起碼,函數(shù)式的編程,使函數(shù)好像成為一等公民,絕不是什么難事。
C++本身自然不會(huì)有什么問(wèn)題,說(shuō)它復(fù)雜吧,它確實(shí)復(fù)雜,但是完全可以無(wú)視,異常不好,禁用就是;多繼承很壞,不用就是;模板過(guò)于復(fù)雜,忘了它吧;……,起碼它做到了,你不喜歡的特性,你可以不必付出任何代價(jià)。但是,成千上萬(wàn)的C++高手大牛巨俠,這么多年下來(lái),卻沒(méi)有搞出幾個(gè)似模似樣的類(lèi)庫(kù)出來(lái),除了STL(其實(shí)STL也有一些問(wèn)題),難道是程序員錯(cuò)了,難道是他們?nèi)e(cuò)了嗎?可是,C、JAVA、PYTHON它們,怎么就不存在這些問(wèn)題呢,里頭的好東西還真不少,起碼在它們的圈子里面,碼農(nóng)們都覺(jué)得類(lèi)庫(kù)沒(méi)問(wèn)題,很好,用起來(lái)很放心,可以放心的使用。又或者是,C++碼農(nóng)對(duì)自家的類(lèi)庫(kù)要求過(guò)高了,他們的眼光太挑剔了,大家人人都有多疑癥,但是,一旦他們使用其他的語(yǔ)言,又很樂(lè)意使用其他語(yǔ)言所提供的類(lèi)庫(kù)或者是框架,怎么就不會(huì)那樣疑神疑鬼,沒(méi)有各種并發(fā)癥,起碼在下是這樣的,你要說(shuō)C++沒(méi)問(wèn)題,那鬼才愿意相信呢?那么,假如C++有問(wèn)題,問(wèn)題又是那里呢?
我以為,C++的問(wèn)題,就在于它似乎很鼓勵(lì)碼農(nóng)制造輪子,撰寫(xiě)類(lèi)庫(kù),然后對(duì)所寫(xiě)的類(lèi)庫(kù)貌似沒(méi)有過(guò)多的要求,但實(shí)際上,C++對(duì)類(lèi)庫(kù)的要求,是所有語(yǔ)言中最高的。好比,要你寫(xiě)的軟件,需求很不明確,貌似用戶(hù)說(shuō),你隨便寫(xiě),然后它能完成任務(wù)就可以了,但實(shí)際上這樣的軟件最難開(kāi)發(fā)了。知道嗎?C++的類(lèi)庫(kù),有什么樣的要求:1、運(yùn)行的效率要高,處理一切細(xì)節(jié),高效源于對(duì)細(xì)節(jié)的特定處理;2、表現(xiàn)出來(lái)的接口和語(yǔ)義要無(wú)限接近原始內(nèi)嵌的類(lèi)型,值語(yǔ)義,吖的,這要求也太高了。3、要有足夠的通用性,滿(mǎn)足抽象的需要。這三條的任何一條都不是什么省油的燈,直接可以K死秒殺多少C++高手的幾千萬(wàn)只脆弱的腦細(xì)胞。這三條下來(lái),本身就差不多是一個(gè)難以完成的事情,但是,這還不是最要命。最能搞死人的,是C++又提供了太多的豐富特性,什么模板、操作符重載、異常、多繼承、內(nèi)聯(lián)、……,心理學(xué)的一項(xiàng)研究表明,當(dāng)選擇太多之時(shí),當(dāng)事人就會(huì)分散精力,不知道如何選擇。用C++開(kāi)發(fā)類(lèi)庫(kù),眼前存在著無(wú)限種選擇,貌似條條大路通羅馬,但實(shí)際上,卻是條條大路是死路。但這也沒(méi)什么,最后一擊,如果沒(méi)有必要,鬼才愿意費(fèi)心費(fèi)神寫(xiě)類(lèi)庫(kù),關(guān)鍵在于C++只是提供實(shí)現(xiàn)高標(biāo)準(zhǔn)類(lèi)庫(kù)的基本平臺(tái)而已,語(yǔ)言本身就沒(méi)有一點(diǎn)點(diǎn)能用的實(shí)在的東西,那怕一個(gè)簡(jiǎn)單的字符串類(lèi),一切都必須通過(guò)類(lèi)庫(kù)來(lái)完成,它一開(kāi)始一直都在鼓勵(lì)碼農(nóng)寫(xiě)類(lèi)庫(kù),試問(wèn)普天之下,有那個(gè)C++碼迷,能夠抑制造輪子的誘惑,于是,悲劇就誕生了。
反觀其他的語(yǔ)言,為什么就不存在這些問(wèn)題呢?
在C里面,是絕對(duì)不會(huì)出現(xiàn)C++的類(lèi)庫(kù)困境,首先,它只有一種選擇讓你開(kāi)發(fā)類(lèi)庫(kù),那就是函數(shù)和函數(shù)指針,外加結(jié)構(gòu)體、數(shù)組,或者,必要的時(shí)候,使用上點(diǎn)宏,嗯,就只有這么多。你要用就用,不用就拉倒,在這么有限的條件,你只能做出最簡(jiǎn)單但卻又是最精妙的設(shè)計(jì),都能直達(dá)問(wèn)題的本質(zhì)。基本上,C的設(shè)計(jì),不會(huì)存在一點(diǎn)點(diǎn)多余的抽象層次,一拳一拳,都打在要害上,針針見(jiàn)血。更何況,C的要求也不高,除了效率之外,就是要保持函數(shù)接口語(yǔ)義的清晰,而這并不難做到。
那么,在高級(jí)語(yǔ)言中,又是另一番天地了。當(dāng)然,它們也提供了多數(shù)C++的豐富特性,你自然有很多選擇,但是,這些語(yǔ)義卻屏蔽了很多底層的細(xì)節(jié),并且提供了豐富的語(yǔ)言平臺(tái)特性,什么反射啊、豐富的API框架,單是垃圾回收和單繼承,就功德無(wú)量,足以拯救多少弱小脆弱的靈魂。是的,人家效率確實(shí)不太高,也沒(méi)有要求值語(yǔ)義,但這不要緊,因?yàn)槿思艺Z(yǔ)言一開(kāi)始就沒(méi)有這樣的效率標(biāo)榜要求,它們一上來(lái)就讓碼農(nóng)直面慘淡的應(yīng)用,進(jìn)入問(wèn)題領(lǐng)域,不需要撰寫(xiě)什么高通用性的類(lèi),因?yàn)槠脚_(tái)本身就提供了,你要寫(xiě)的類(lèi),都是問(wèn)題領(lǐng)域中的東西。這實(shí)在能解救多少腦細(xì)胞啊,減少多少不必要的工作量啊呢。
沒(méi)有必要的類(lèi)庫(kù)來(lái)支持,C++做開(kāi)發(fā),工作量相當(dāng)浩大,并且還容易生產(chǎn)出一堆不成熟的通用類(lèi);但是,必要的成熟的類(lèi)庫(kù),市面上又沒(méi)有多少個(gè),目前的所謂的高級(jí)的C++類(lèi)庫(kù),大多數(shù)都在為了C++而C++,你們知道我在說(shuō)的是那一個(gè)的。當(dāng)然,俺是沒(méi)能力寫(xiě)出什么類(lèi)庫(kù)的,所謂的眼高手低,大概就是這樣吧。
或者,痛并快樂(lè)著,是C++碼迷的持久不退的熱情所在。只是,人生何其寶貴,將它們浪費(fèi)在C++上,實(shí)在有點(diǎn)不值得。當(dāng)然,活著只要快樂(lè)就好,C++也確實(shí)能給人帶來(lái)很多快樂(lè)。
謹(jǐn)以此文祭奠過(guò)去多少年下來(lái),日日夜夜學(xué)習(xí)MFC源代碼的寶貴時(shí)光。
曾經(jīng)以為MFC是世界上最神圣無(wú)比的東西,發(fā)誓在沒(méi)徹底弄透MFC之前,絕不越雷池半步,碰都不要去碰LISP、ERLANG、PYTHON、VCL、ATL、STL、LOKI、BOOST各種美妙的好東西。即使一再聽(tīng)到高人說(shuō)MFC設(shè)計(jì)得不好,非常糟糕,也都至死而不悔,然后只要稍微聽(tīng)到有人在說(shuō)MFC的偉大,心里就特別高興。買(mǎi)了各種各樣的VC方面的書(shū),什么四大天王、什么幾十百千例、什么VC項(xiàng)目實(shí)例,徹夜苦讀,單步調(diào)試,一次又一次地徘徊在MFC套來(lái)套去的各種美妙代碼之中,觀察里面各個(gè)對(duì)象的值。一次又一次地感嘆MFC的偉大,一直在想,這輩子,如果能徹底地掌握MFC里面的一切秘密,朕就心滿(mǎn)意足了。終于有一天,終于徹底醒悟,原來(lái)MFC真不是好東西,不過(guò)就是那些伎倆而已,真不該將它看得如此的偉大。這種念頭一起來(lái),再回過(guò)頭來(lái)看MFC的一坨一坨代碼,只覺(jué)得清晰無(wú)比,又感覺(jué)代碼之中充滿(mǎn)了各種無(wú)奈。接著再看看那些VC方面的書(shū),除了四大天王還稍微有些可取之處,其他的任何一本,奇臭無(wú)比,沒(méi)有一行代碼經(jīng)得起推敲, 文筆也糟糕無(wú)比,其內(nèi)容又一再重復(fù),抄來(lái)抄去,真正是一本又一本的狗屎。 感嘆一下:這些書(shū)犧牲了多少寶貴的樹(shù)木,又折磨死了多少讀者的腦細(xì)胞,實(shí)在罪大惡極。
在MFC大河日下的當(dāng)今的大環(huán)境之下,在下也不想對(duì)它落井添石,畢竟也在其上學(xué)到不少好東西,特別是閱讀代碼的能力,試問(wèn)這么糟糕復(fù)雜的框架都可以精通,天下還有什么代碼能看不懂(別自信滿(mǎn)滿(mǎn),你敢碰BOOST里面的那些奇技淫巧,匪夷所思的幻碼嗎)。但實(shí)在忍受不了軀體內(nèi)另一個(gè)人日日夜夜,旦夕不停的訴求,兼之又一直恨鐵不成鋼,又為自己失去的青春陣痛不已。而VC2008中BCG的出現(xiàn)之后,VC2010還依然保留著那些蠢笨無(wú)比的BCG文件,不思悔改,終于讓某徹底對(duì)MFC死心了,這貨沒(méi)得救了。
好吧,廢話(huà)少說(shuō),趕緊進(jìn)入正題。
國(guó)內(nèi)C++界中一大俠說(shuō)過(guò),任何東西,都要先抓住其主干,其他的一切細(xì)節(jié),都可以留待以后慢慢補(bǔ)充,否則,一開(kāi)始就陷入細(xì)節(jié),想跳出來(lái)就不容易了。大俠此語(yǔ),實(shí)在有道理。(不知為什么,此大俠后期一直在對(duì)C++表示不滿(mǎn),當(dāng)然,人家現(xiàn)在的境界,豈是我等小民所能體會(huì))。不說(shuō)其他,就說(shuō)在C++中,必須站在一個(gè)高度上,縱觀全局,才能將一切看得通通透透。于是,下面就嘗試爬上高峰,來(lái)俯瞰MFC中的眾生。MFC的主干,在下看來(lái),既不是什么六大關(guān)鍵技術(shù),也不是什么文檔視圖結(jié)構(gòu),更不是COM的封裝,而是為了將原始的API中的各種對(duì)象,好比窗口、GDI、設(shè)備環(huán)境等,都映射成相應(yīng)的多線程安全的C++對(duì)象,好比將HWND搞成CWnd,以期減輕WINDOWS下界面開(kāi)發(fā)的工作量。
這種想法,其實(shí)也無(wú)可厚非,如果真能封裝得好,確實(shí)可以提高生產(chǎn)代碼效率,但是從C++和WINDOWS的設(shè)計(jì)觀念中來(lái)看,這種映射,還是存在很大的問(wèn)題,稍后再述。問(wèn)題在于,MFC的實(shí)現(xiàn),異常丑陋呆板,通過(guò)幾個(gè)全局的線程安全局部變量的映射表,將句柄(Handle,此詞譯得相當(dāng)令人惡心)與其相應(yīng)的C++對(duì)象指針形成一一對(duì)應(yīng),并且指定,在大部分的消息處理和參數(shù)傳遞中,所有的參數(shù)由原本的句柄一律由對(duì)象指針來(lái)代替,然后就高高興興地宣稱(chēng)已經(jīng)完成任務(wù)了,這實(shí)在讓人哭笑不得。不說(shuō)別的,即使是消息處理,MFC即使已經(jīng)做了很大的努力,也都無(wú)法將句柄參數(shù)搞成對(duì)應(yīng)的對(duì)象指針,單是這一點(diǎn),就已經(jīng)失敗了。然后再看看,它最后封裝的做法,不過(guò)是將API函數(shù)中的第一個(gè)參數(shù)為句柄的都改成相應(yīng)的C++對(duì)象的函數(shù)成員,好比將SetWindowText搞成CWnd::SetWindowText,美其名曰,薄薄的封裝。這真是要笑掉大牙了,既然是這樣的薄薄的封裝,那還干脆不如不封裝了,我想破了頭,也看不出這樣的封裝,帶來(lái)了什么樣的好處。反而將原本全局函數(shù)的各種靈活性,都給葬送在類(lèi)成員函數(shù)這個(gè)監(jiān)獄中。全局函數(shù)的各種好處,除了是全局這個(gè)缺陷之外,實(shí)在都是類(lèi)成員函數(shù)沒(méi)法比的。而且,鑒于API中數(shù)量實(shí)在太多,于是搞出來(lái)的C++對(duì)象都是龐然大物,最臭名昭著的,當(dāng)然要數(shù)CWnd和CDC這對(duì)黑風(fēng)雙煞了,使用起來(lái),極不方便,極難學(xué)習(xí)。一個(gè)類(lèi)的函數(shù)過(guò)多,無(wú)論如何,都是失敗的設(shè)計(jì)。還有,如果WINDOWS系統(tǒng)新增了什么使用到句柄的函數(shù),這些類(lèi)也都要相應(yīng)地增加新成員函數(shù),再進(jìn)一步說(shuō),如果用戶(hù)自己因?yàn)樾枰惨_(kāi)發(fā)使用到句柄的全局函數(shù),要怎么想辦法整成員函數(shù)呢。其實(shí),MFC中的所謂的線程安全,其實(shí)也不過(guò)是自欺欺人的說(shuō)法而已,你要想在MFC中多線程的使用窗口對(duì)象,即使不是不可能,那也要花費(fèi)九牛二力氣才能在多線程中共享窗口對(duì)象,一切皆只因MFC已經(jīng)很努力地做到了不讓你在多線程下使用窗口對(duì)象。
好了,再進(jìn)一步考察,在這種思路的指導(dǎo)下,最后MFC被設(shè)計(jì)成一個(gè)什么樣的框架。一直很懷疑想,MFC的設(shè)計(jì)者,是否不是對(duì)WINDOWS API的精神了解得不透徹,又或者對(duì)C++不是很精通,否則,無(wú)論如何,斷斷都不會(huì)整出MFC這樣的一個(gè)人不象人、鬼不似鬼,精神分裂、做事虎頭蛇尾的鬼東西出來(lái)。要不然,我們用MFC開(kāi)發(fā),就不會(huì)覺(jué)得束手束腳,很不好施展拳腳,一再感覺(jué)一直受制于其可惡的呆板的框架之下,這并不是說(shuō)框架不好,只是MFC這套框架,實(shí)在很不好擴(kuò)充。這樣也罷了,問(wèn)題是,MFC在使用上,又很不人性化,你又要陷入各種各樣的細(xì)節(jié)之中,什么二段構(gòu)造,有些對(duì)象又要自己刪除,有些又不能自己刪除等,更要命的是,MFC的運(yùn)行效率又臭名昭著。有些同學(xué)就會(huì)問(wèn),貌似在MFC開(kāi)發(fā),其實(shí)也很強(qiáng)大很靈活,殊不知這種強(qiáng)大和靈活,那都是源于C++的強(qiáng)大靈活,也源于WINDOWS系統(tǒng)的強(qiáng)大靈活,源于MFC只對(duì)API只作了一層薄薄的封裝,與MFC本身的設(shè)計(jì)沒(méi)什么太多的關(guān)聯(lián)。當(dāng)拋開(kāi)MFC,直接用API和C++來(lái)寫(xiě)代碼,就體會(huì)到什么才叫做強(qiáng)大靈活,除了代碼比MFC多一點(diǎn)之外,一切都要比MFC來(lái)得清爽,也要來(lái)得容易維護(hù)。如果,如果設(shè)計(jì)做得好,可能總體的代碼量不會(huì)比用MFC的多也說(shuō)不定。
于是,這么一個(gè)偉大的框架,表現(xiàn)出來(lái)的,很多地方都C++的精神格格不入。承然,MFC剛開(kāi)始設(shè)計(jì)時(shí),缺乏太多的C++的特性的支持,而不得不另起爐灶。但是,就算沒(méi)有C++98中的功能支持,MFC也不該出現(xiàn)以下這么重大的設(shè)計(jì)問(wèn)題。
1、 不需要用到的特性,我們依然要付出種種代價(jià)。我們看到CCmdTarget集成了COM的功能,CWnd中對(duì)ActiveX的支持,就算我們的代碼不包含一丁點(diǎn)使用到COM的代碼,我們都要背負(fù)每個(gè)CWnd中將近100個(gè)字節(jié)的大小,運(yùn)行過(guò)程中一再要忍受框架代碼中對(duì)ActiveX變量的一次又一次地檢查。我們看到,就算不使用文檔視圖結(jié)構(gòu),CMainFrame中都要承受對(duì)它的支持代碼,和各個(gè)不必要的成員變量。總之,MFC中有很多特性,就算不愿意使用,它們還是在那里,不容許我們忽視。
2、 耦合太厲害。我們看到,CWnd中居然出現(xiàn)了對(duì)子類(lèi)CView和CFrameWnd的引用,CControlBar的成員函數(shù)中有對(duì)CToolBar的CAST,然后,CFrameWnd又引用到CControlBar和CView,CControlBar和CView也都用到CFrameWnd的函數(shù),各種各樣好厲害的循環(huán)依賴(lài),這么糟糕的設(shè)計(jì)竟然出現(xiàn)在C++的著名的界面框架,不得不令人嘆息不已。這樣的耦合,就是在MFC之中,一個(gè)類(lèi)設(shè)計(jì)得再好,也只能在某些特定的場(chǎng)合下使用。好比, CControlBar, CSplitterWnd這兩個(gè)好東西只能活在CFrameWnd,CView的環(huán)境下,想在其他的地方使用上就煞不容易了。就算是著名的文檔視圖結(jié)構(gòu),也都是問(wèn)題很大,如果我的類(lèi),想要訂閱文檔中的通知消息,還要求必須派生于CView中,這個(gè)限制也太厲害了,于是為了它這個(gè)死板的設(shè)計(jì),有多少次,我們必須改變?cè)O(shè)計(jì),以適應(yīng)MFC的無(wú)理取鬧的要求。
3、 功能太過(guò)單薄:MFC僅僅是作為一個(gè)界面的基本框架而已,僅僅實(shí)現(xiàn)了從句柄到對(duì)象的映射,然后,再加上一點(diǎn)點(diǎn)所謂的文檔視圖的MVC模式,必要的多視圖多子窗口的支持,基本的必要的COM支持,對(duì)API的薄薄封裝,僅此而已(至于SOCKET、WININET和數(shù)據(jù)庫(kù)的MFC類(lèi),又有多少可利用的價(jià)值)。有多少次,我們想對(duì)對(duì)話(huà)框進(jìn)行拉伸的操作;有多少次,我們需要用到表格控件;……,但是,這一切,想要MFC提供更加強(qiáng)大的控件,完全都沒(méi)有。必須求助于第三方控件公司庫(kù),好比,BCG、XTREME,你們知道,MFC的代碼就算再爛,也還是很有分寸,還能保住最基本的底線,但是,第三方控件的代碼,那真的是,讓人大驚失色,沒(méi)有做不到的,只有想不到的。要知道,C++可是很強(qiáng)大的,但是,用它做出來(lái)的框架,卻如此的功能之小。
4、 MFC中的類(lèi)的功能不單一,承載了太多的責(zé)任,導(dǎo)致很容易就出現(xiàn)了很多重復(fù)的代碼:好比CCmdTarget中原本只是為了可以處理消息,后來(lái)居然還兼具COM的功能。又好比,CList, CMapPtrToPtr里面又都包含了內(nèi)存池的實(shí)現(xiàn)代碼,原本這些內(nèi)存池的實(shí)現(xiàn)代碼就應(yīng)該剝離出來(lái)。
5、 行為錯(cuò)亂,神經(jīng)分裂,沒(méi)有統(tǒng)一的接口,以至于使用起來(lái),極易出錯(cuò)。很多地方,都努力地去做了,但是都做得不徹底。企圖封裝所有的WINDWOWS的界面的API,但無(wú)論如何,都做不到,而且它自己內(nèi)部也都知道沒(méi)辦法做到,但它還要努力地去做。
……
各種各樣的問(wèn)題,難怪MFC的書(shū)那么多,沒(méi)有一本可以作為面向?qū)ο蟮牡浞秾W(xué)習(xí),即使四大天王,也不過(guò)只是沒(méi)有那么惡臭而已。原來(lái)一切,皆只因?yàn)樵搭^本就臟了。之前還以為,MFC,還能算是一積心堆砌而成垃圾。總體設(shè)計(jì),雖然確實(shí)糟糕透頂,但細(xì)微上看,還能做到讓人擊節(jié)贊嘆,好比對(duì)于線程局部變量的封裝擴(kuò)展,消息映射表。可是,即使局部代碼的實(shí)現(xiàn),也都充滿(mǎn)了重重的缺陷。
如果說(shuō)MFC還有些可圈可點(diǎn)之處,VC2008之后引進(jìn)的BCG的界面補(bǔ)丁,完全就將這些可稍為搬上臺(tái)面的東西直接給淹沒(méi)掉。BCG的出現(xiàn),對(duì)MFC而言,根本就不是狗尾續(xù)貂,而是狗尾之上再綁上一只蠢笨無(wú)比的大豬。直接就將MFC推入萬(wàn)劫不復(fù)之地。
MFC,OH SHIT,你可以去死了!
突然驚覺(jué),C++的類(lèi)庫(kù)框架,無(wú)不充滿(mǎn)了各種各樣的缺陷,除了STL還差強(qiáng)人意之外(其實(shí)也問(wèn)題多多),沒(méi)有一個(gè)能夠讓人看著滿(mǎn)意,用著放心。于是,我要投入LISP和C的懷抱之中了,不,一定要頂住,C++是我的最?lèi)?ài)。
摘要: 書(shū)接上回,繼全排列之后,接下來(lái)的任務(wù)自然是要給我們的Permutation添加上部分排列的功能。所謂的部分排列,為了便于行文的展開(kāi),我也不怕再舉例,比如,針對(duì)0到4這5個(gè)數(shù)的P(3,5),其排列如下:012,013,014,021,023,024,……,234,240,241,…...
閱讀全文