看著窗明幾凈的實(shí)驗(yàn)室,清理得整整齊齊的網(wǎng)線和交換機(jī),兩個(gè)人都覺得心情很舒暢。吃完了晚飯,兩個(gè)人又坐到了白板旁邊。
“老C,今天早上的話題還沒有談?wù)撏昴亍?#8221;小P總是充滿好奇心,老C覺得小家伙是個(gè)人才。
“稍等稍等,等我把這一點(diǎn)看完。”老C用目光匆匆掠過一篇起點(diǎn)上的穿越文,戀戀不舍的轉(zhuǎn)過座椅,“好吧,我們?cè)賮?lái)review一下我們第二版的C代碼。”
“我們來(lái)看看
PrintFruitName()函數(shù)的問題,它的一個(gè)比較明顯的缺陷是效率。”老C用筆指著代碼說(shuō)道。
“是啊是啊,”有機(jī)會(huì)表現(xiàn)一把小P很是高興,“這個(gè)函數(shù)做了線性查找,因此它的時(shí)間效率應(yīng)當(dāng)是n。”
“對(duì),因此對(duì)比原版的switch...case...語(yǔ)句的常數(shù)時(shí)間效率,我們現(xiàn)在的設(shè)計(jì)在時(shí)間效率上有損失,你有什么好辦法解決這個(gè)問題嗎?”
“讓我想想……”小P又開始發(fā)動(dòng)腦筋,“我覺得我們可以使用一個(gè)散列表來(lái)儲(chǔ)存這些信息,那么在查找的時(shí)候算法的時(shí)間復(fù)雜度就是常數(shù)了。”小P找到了解決辦法很是高興,就想著去白板上寫下自己的得意之作。
“等等別急。”老C攔住小P,“只要知道了解決之道,代碼是微不足道的,你先不要著急寫代碼……”老C對(duì)小P的反應(yīng)速度有些驚訝,“我覺得你的基礎(chǔ)挺好的啊,就是缺乏正確的引導(dǎo)……”
“呵呵,謝謝啊,其實(shí)我就是比較油菜而已。”聽了老C的評(píng)論小P有些囧,不知道他是表?yè)P(yáng)還是批評(píng)。
“我們不著急對(duì)這一版進(jìn)行改寫,現(xiàn)在你試著使用C++來(lái)完成上面的代碼。”老C給小P出了新的題目。
“嗯,我看看啊。”小P開始思索起來(lái),“但是有什么不同嗎?如果使用C++的話,我可能還是會(huì)寫出第一版那樣的代碼啊……”
“所以我說(shuō)你缺乏正確的引導(dǎo)啊,”老C有些感慨,“因?yàn)镃++包含了C的部分,因此你完全可以用C的思維方式在C++下編碼,反應(yīng)到代碼風(fēng)格上,就是第一版風(fēng)格的代碼在C++代碼中頻繁出現(xiàn)……”
“槑,”小P有些莫名其妙,“那么應(yīng)該怎么做呢?”
“嗯,我先簡(jiǎn)單的寫寫,然后我們來(lái)review。”老C在白板上劃了一道線,將白板分為兩部分,在原來(lái)的代碼部分上面寫下C,然后在另一個(gè)空白部分的頭部寫下C++。
class Fruit
{
public:
virtual ~Fruit() {}
public:
virtual void printName() = 0;
}
class Orange : public Fruit
{
public:
virtual void printName() { cout << "orange" << endl;}
}
class Apple : public Fruit
{
public:
virtual void printName() { cout << "apple" << endl; }
}
class Banana : public Fruit
{
public:
virtual void printName() { cout << "bnana" << endl; }
}
void PrintFruitName(const Fruit& fruit)
{
fruit.printName();
}
int main()
{
/* Fruit factory function. */
Fruit* fruit = GetFruit();
PrintFruitName(*fruit);
delete fruit;
return 0;
}
“唔,”老C揉揉手,“差不多就是這樣啦,一個(gè)很簡(jiǎn)單的實(shí)現(xiàn)……與現(xiàn)實(shí)代碼相去甚遠(yuǎn)……”他又想了想,“在實(shí)際情況下我們是不會(huì)這樣編碼的,但在這里只是說(shuō)明一下思維的差異性。”
“給我講講吧。”小P等待下文。
“在這個(gè)實(shí)現(xiàn)里我們使用了C++的多態(tài)特性,也有一種說(shuō)法是晚綁定……但是無(wú)論怎么說(shuō),其根源也是信息隱藏。”老C開始比較C和C++的實(shí)現(xiàn),“我們已經(jīng)比較過第一版和第二版的C實(shí)現(xiàn),發(fā)現(xiàn)信息隱藏是進(jìn)行設(shè)計(jì)的一個(gè)關(guān)鍵點(diǎn)……”
“等等老C,什么叫多態(tài)?什么叫晚綁定?”
“哦,我們來(lái)看看PrintFruitName()函數(shù),你能說(shuō)清楚這個(gè)函數(shù)具體實(shí)現(xiàn)了哪些需求?”如何簡(jiǎn)單的解釋這些術(shù)語(yǔ)讓老C覺得有些頭痛。
“從代碼看它實(shí)現(xiàn)了對(duì)fruit對(duì)象名稱的打印……”小P看了看代碼,“但是具體如何做的我看不出來(lái),而且打印的內(nèi)容與函數(shù)的輸入?yún)?shù)有關(guān),不同的參數(shù)會(huì)有不同的結(jié)果……”
“沒錯(cuò)!保持統(tǒng)一的接口,而具體行為依照對(duì)象而定,不同的對(duì)象有不同的行為,這個(gè)就是對(duì)多態(tài)的簡(jiǎn)單解釋。”老C覺得小P還是有些悟性的,“具體在C++語(yǔ)
言中,多態(tài)通過指針和引用來(lái)表現(xiàn)。即接口使用父類的指針或引用來(lái)表明抽象的統(tǒng)一的接口,而行為根據(jù)父類指針或引用所指向具體子類對(duì)象,不同的對(duì)象表現(xiàn)出不
同的行為,此乃C++實(shí)現(xiàn)多態(tài)的風(fēng)格,具體來(lái)說(shuō)我們?cè)诰幊痰臅r(shí)候需要使用指向父類的指針或者引用,然后在子類中改寫父類中的虛函數(shù),最后再把子類對(duì)象賦值
到父類指針或引用上。如果現(xiàn)在我們需要再增加一個(gè)梨這樣的水果,你會(huì)怎么做?”
“好像很簡(jiǎn)單了?”小P試著在白板上寫下如下代碼。
class Pear : public Fruit
{
public:
virtual void printName() { cout << "pear" << endl; }
}
"然后怎么辦?我可能還需要改寫GetFruit()函數(shù),使得他可以增加一個(gè)返回的對(duì)象類型?"小P很細(xì)心的發(fā)現(xiàn)一個(gè)問題,“但是我覺得我們很難在
GetFruit()函數(shù)中避免類似if..else...或者switch...case..之類的邏輯選擇分支……等等,好像我們實(shí)現(xiàn)的第二版C語(yǔ)言
代碼也存在類似的問題,就是GetFruitInfo()中也無(wú)法避免多選擇的判斷分支……”
"沒有錯(cuò)!但是起碼我們對(duì)PrintFruitName()函數(shù)的維護(hù)會(huì)好很多。"老C很是贊同小P的觀察力,“這個(gè)是另外的問題,涉及到一些
factory模式,我們以后再討論……無(wú)論怎么樣,我們先來(lái)評(píng)判一下現(xiàn)在C++的實(shí)現(xiàn)。因?yàn)镃++的虛函數(shù)實(shí)際上采用散列表的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn),所以我們的
C++程序執(zhí)行效率會(huì)比白板另一邊的第二版C代碼好一些。我覺得我以后一定會(huì)和你討論一下C++的虛函數(shù)的實(shí)現(xiàn)的,但是今天我們先把這個(gè)問題放一邊。”
“看來(lái)C++還有很多東西是我不了解的啊。”小P開始覺得自己C++課程好像是白上了。
“我們來(lái)比較一下三個(gè)版本的實(shí)現(xiàn)。”老C開始回顧早上的討論,“你發(fā)現(xiàn)什么問題沒有?”
“好像一段代碼對(duì)其具體實(shí)現(xiàn)了解的越少,它的維護(hù)性就會(huì)越好?”小P有些猜測(cè)。
“呵呵,的確,那么我們通過各種不同的方法達(dá)到了什么樣的看似相同的目的?”老C開始掉小P的胃口。
“信息隱藏?”小P不太確定。
“信息隱藏是手段,但不是目的。”老C很確定的否決掉小P,“我們達(dá)到的目的是
控制問題的規(guī)模!”
老C覺得有必要給小P講講哲學(xué):“我們寫軟件的目的是為了解決現(xiàn)實(shí)生活中的具體問題,沒錯(cuò)吧?”
“沒錯(cuò),的確是這樣,可是這個(gè)和C++有什么關(guān)系的?”小P覺得有些莫名其妙。
“那么你覺得使用高級(jí)的語(yǔ)言、先進(jìn)的設(shè)計(jì)和合理的開發(fā)流程,問題的復(fù)雜度會(huì)降低嗎?”
“那是啊,問題的復(fù)雜度當(dāng)然會(huì)降低啊。”
“唉,錯(cuò)了,問題的復(fù)雜度不會(huì)降低的,因?yàn)閱栴}的復(fù)雜度是客觀存在,不會(huì)因?yàn)槿酥饔^的原因而改變!”
“槑!”小P有些被震住了,他以前還真是沒有考慮過這樣的問題,“那么為什么我覺得C解決問題比匯編簡(jiǎn)單呢?”
“那是因?yàn)閱栴}的規(guī)模被控制了!”老C開始強(qiáng)調(diào),“因?yàn)镃的編連器暗地里幫你做了很多事情來(lái)控制問題表現(xiàn)給你的規(guī)模,使你感覺好像問題變簡(jiǎn)單了——其實(shí)是你面對(duì)的問題規(guī)模變小了。打個(gè)比方,”老C在白板上找出了上午最早的程序。
typedef enum tagFRUIT{ORANGE, APPLE, BANANA} FRUIT;
void PrintFruitName(FRUIT fruit)
{
switch (fruit)
{
case ORANGE:
pirntf("orange\n");
break;
case APPLE:
printf("apple\n");
break;
case BANANA:
printf("banana\n");
break;
default:
return;
}
}
int main()
{
FRUIT fruit;
/* Get the information of fruit. */
fruit = GetFruitInfo();
/* Print the name of the fruit. */
PrintFruitName(fruit);
return 0;
}
“看看吧,根據(jù)你剛才的發(fā)現(xiàn),是不是如果需求發(fā)生變化時(shí),GetFruitInfo()和PrintFruitName()這兩個(gè)函數(shù)都要發(fā)生變更?”老C指著代碼問小P。
“是啊,沒有錯(cuò)。”
“現(xiàn)在我們代碼的規(guī)模還很小,如果我們?cè)诙嗵幮枰婕暗剿男畔ⅲ敲锤鶕?jù)這種風(fēng)格,如果需求發(fā)生變更時(shí),是不是每個(gè)地方都需要對(duì)源代碼進(jìn)行修改?”老C開始循循善誘。
“嗯,好像是的。我們這里只是打印了水果的名稱,如果我們還需要水果的形狀,水果的顏色,哦,好像我們維護(hù)時(shí)候的復(fù)雜度會(huì)按照問題的規(guī)模成倍的增長(zhǎng)。”
“是啊是啊,這就叫牽一發(fā)而動(dòng)全身。”老C總結(jié)道,“那么第二個(gè)做法呢?”
“好像可以好一些,起碼我們只用修改某些表格,哦,還要修改哪個(gè)GetFruitInfo()的函數(shù),但是起碼問題的擴(kuò)散沒有那么嚴(yán)重了……”小P現(xiàn)在隱約覺得自己好像腦袋里面有只手,快要抓住什么東西卻又抓不到,有些迷蒙起來(lái)。
“但是維護(hù)表格的工作量也不小啊。”老C補(bǔ)充道,“那么最后一種做法呢?”
“我個(gè)人感覺好像C++編連器在幫助我們維護(hù)這些表格?”小P好像猛的明白過來(lái),“比如將虛函數(shù)的實(shí)現(xiàn)隱藏在編連器的后面……”他又開始有些迷蒙……
“是的,是這樣的。”老C點(diǎn)點(diǎn)頭,“我們實(shí)際上面對(duì)問題的復(fù)雜度并沒有改變,只是由于語(yǔ)言的幫助,我們可以設(shè)計(jì)出一些代碼來(lái)限制我們接觸問題的規(guī)模,把一
個(gè)復(fù)雜的問題逐步劃分到我們自己可以理解的規(guī)模上來(lái),這樣好像問題變簡(jiǎn)單了一樣。”老C接著說(shuō)道,“這就是我為什么說(shuō)我們所使用的語(yǔ)言會(huì)影響到我們思考問
題的方法,而我們思考問題的方法會(huì)反過來(lái)影響我們編碼的風(fēng)格。”
小P突然感覺自己來(lái)到了一個(gè)更寬闊的世界,好像自己突然明白了什么又好像不是很明白……小P開始覺得C++充滿了神秘和樂趣,下決心一定要把C++學(xué)好。
“老C,和你聊聊太有收獲了,我要把這些代碼抄下來(lái)回去再看看,體會(huì)體會(huì),”小P做激昂狀,“我一定要在3年內(nèi)學(xué)好C++……”
“等等,3年時(shí)間好像太短了吧?”老C有些被雷到了。
“?”
“建議你看看《Teach Yourself Programming in Ten Years》這篇文章吧,急是急不來(lái)的。”老C覺得年輕人就是浮躁。
“哦?好,那我回宿舍后查查這篇文章。但是以后你要多教教我啊。”
“互相討論,互相學(xué)習(xí)。”老C謙虛道,“C++還有template種類的編程風(fēng)格,但是我想我們還是討論到此吧,時(shí)間也不早了,我們回去吧。”
“好啊,回去打一盤魔獸,看看誰(shuí)更厲害!”小P決定也給老C當(dāng)一回老師過過癮!
(欲知后事如何,且聽下回分解)