C++筆記(十一)——多型的概念和作用(深入理解)
多型是面向物件的重要特性,簡單點說:“一個介面,多種實現”,就是同一種事物表現出的多種形態。 程式設計其實就是一個將具體世界進行抽象化的過程,多型就是抽象化的一種體現,把一系列具體事物的共同點抽象出來, 再通過這個抽象的事物, 與不同的具體事物進行對話。 對不同類的物件發出相同的訊息將會有不同的行為。比如,你的老闆讓所有員工在九點鐘開始工作, 他只要在九點鐘的時候說:“開始工作”即可,而不需要對銷售人員說:“開始銷售工作”,對技術人員說:“開始技術工作”, 因為“員工”是一個抽象的事物, 只要是員工就可以開始工作,他知道這一點就行了。至於每個員工,當然會各司其職,做各自的工作。 多型允許將子類的物件當作父類的物件使用,某父型別的引用指向其子型別的物件,呼叫的方法是該子型別的方法。這裡引用和呼叫方法的程式碼編譯前就已經決定了,而引用所指向的物件可以在執行期間動態繫結。再舉個比較形象的例子: 比如有一個函式是叫某個人來吃飯,函式要求傳遞的引數是人的物件,可是來了一個美國人,你看到的可能是用刀和叉子在吃飯,而來了一箇中國人你看到的可能是用筷子在吃飯,這就體現出了同樣是一個方法,可以卻產生了不同的形態,這就是多型! 多型的作用: 1. 應用程式不必為每一個派生類編寫功能呼叫,只需要對抽象基類進行處理即可。大大提高程式的可複用性。//繼承 2. 派生類的功能可以被基類的方法或引用變數所呼叫,這叫向後相容,可以提高可擴充性和可維護性。 //多型的真正作用,以前需要用switch實現
多型是面向物件程式設計和麵向過程程式設計的主要區別之一,何謂多型?記得在CSDN裡一篇論C++多型的文章裡有一名話:“龍生九子,子子不同”多型就是同一個處理手段可以用來處理多種不同的情況,在錢能老師的《C++程式設計教程》書中有這樣一個例子: 定義了一個小學生類
//[本文全部程式碼均用偽碼]
class Student
{
public:
Student(){}
~Student(){}
void 交學費(){}
//......
};
裡面有一個 “交學費”的處理函式,因為大學生和小學生一些情況類似,我們從小學生類中派生出大學生類:
class AcadStudent:public Student { public: AcadStudent(){} ~ AcadStudent(){} void 交學費(){} //....... };
我們知道,中學生交費和大學生交費情況是不同的,所以雖然我們在大學生中繼承了中學生的"交學費"操作,但我們不用,把它過載,定義大學生自己的交學費操作,這樣當我們定義了一個小學生,一個大學生後:
Student A;
AcadStudent B;
A.交學費(); 即呼叫小學生的,B.交學費();是呼叫大學生的,功能是實現了,但是你要意識到,可能情況不僅這兩種,可能N種如:小學生、初中生、高中生、研究生..... 它們都可以以Student[小學生類]為基類。
如果系統要求你在一群這樣的學生中,隨便抽出一位交納學費,你怎麼做?
: //A為抽出來的要交學費的同學 { switch(typeof(A)) { case 小學生:A.小學生:: 交學費 ();break; case 初中生:A.初學生:: 交學費 ();break; case 高中生:A.高學生:: 交學費 ();break; default: ............. } }
首先,我們要在每個類中定義一個 typeof()用來識別它的型別,然後還要在程式中進行區別,這樣一來,雖然也行,但是,如果再增加型別則要改動switch,又走了面向過程的老路,而且想通過一個模組進行操作實現起來也有難度。所以C++中提供了多型,即能通過遲後聯編的技術實現動態的區分。 在基類的"交學費"前加個Virtual 用來告訴系統,遇到這個處理過程要等到執行時再確定到底呼叫哪個類的處理過程。這樣一來就可以:
void 通用的交學費操作 (Student &A)
{
A.交學費();
}
一下全部搞定,你再加新的型別我也不怕!!![具體的實現原理參考:《Inside The C++ Object Model》]。如果沒有 virtual這一宣告,那麼,系統在執行前就確定了操作,比如把“大學生”傳給
void 通用的交學費操作 (Student &A)
{
A.交學費();
}
,則A.交學費();呼叫的是大學生類中繼承於Student類中的“交學費操作” 所以虛擬函式對多型的實現是必要的。
為什麼會出現純虛擬函式呢?
如果按上面的例子進行程式設計,所有型別都繼承小學生類,我們會發現一此小學生自己特定的東東[比如 void 上美術課();],也都被大學生繼承來了,雖然不影響大學生的操作,但是隨時間的加長,小學生類中自已所特定的東東越來越多,這樣下去,大學生中冗餘的資料就多了,有什麼辦法可以解決????
就是定義基類時定義一個抽象類,如學生類,在學生類中實現一此大家都有的操作。這個過程就叫分解。這個類子對純虛擬函式的說明還不夠明顯,換個例子比如:
class 人()
{
public :
//......
void 吃()
{
人吃飯;
}
//......
char *Name;
int age;
};
class 狗()
{
public :
//......
void 吃()
{
狗吃屎;
}
//......
char *Name;
int age;
};
人類、狗類有一些相同的東東,如名字,年紀,吃的動作等,有人想到了為了程式碼的重用,讓人類繼承狗類,可是資料的冗餘讓這個想法完蛋了,所以有人又想出可不可以定義一個這樣的類: 這個類界於人類狗類之間,有人類和狗類共有的一些東東,比如年紀,名字,體重什麼的,但它是不存在例項的,它的存在意義也是隻是為了派生其它類,如人類、狗類,這樣可以使系統清淅、。。。。。、、反正好處多多。
在這個世界上根本不存在界於人狗之間的東東,所以這個“人狗之間類”中的“吃”也是沒什麼意義,你也很難為它的內容下個定義,況且也沒必要,所以定義它為純虛擬函式,形式為:virtual void 吃()=0; 用來告訴系統: 1、這個函式可以沒有函式定義; 2、擁有本函式的類是抽象類; 你可能會說,即然純虛擬函式沒什麼意義,為什麼還要它,它的作用是什麼呢? 為實現多型作準備!!!
由於抽象類的例項沒意義,所以C++中不允許定義它的例項。(如果定義了這樣的例項A,那麼你呼叫A.吃()怎麼辦?)當然了,你也可以在基類中,virtual 吃(){};這樣一來,基類就不是抽象類了,可以有例項,而且對於其它方面都不影響。
但你也要承認這樣的物件是沒有什麼意識的,它的存在只能使你思考上增加負擔,除錯時還要考慮到是不是有這樣類的物件在作怪,所以C++乾脆提供了“虛擬函式”、抽象類,的機制,給我們操作時加了限制也可以認為是保險[不可以有抽象類的物件],試想當代碼變得非常之龐大時,它的存在是多麼有必要啊!!!