四、多態(tài)與虛函數
一.構成多態(tài)的先決條件
1.必須存在繼承關系;(派生類:基類)
2.繼承關系中必須有同名的虛函數,并且它們是覆蓋關系(函數原型相同)。(基類:virtual void fun() 派生類:void fun()或者基類:virtual?void fun(int a) 派生類:void fun(int b))
為了方便,你可以只將基類中的函數聲明為虛函數,這樣所有派生類中具有遮蔽關系的同名函數都將自動成為虛函數
3.存在基類的指針,通過該指針調用虛函數。(基類指針訪問派生類的虛函數)
二.了解多態(tài)(多態(tài)的本質,核心)
C++多態(tài)的本質(核心):可以通過基類指針對所有派生類(包括直接派生和間接派生)的成員變量和成員函數進行“全方位”的訪問,尤其是成員函數。如果沒有多態(tài),我們只能訪問成員變量。雖然基類指針指能夠訪問派生類的成員,但是只能訪問繼承下來的成員,不可訪問派生類新增成員。
三.基類指針訪問派生類成員
1.無Virtual關鍵字

上述例子表明:當基類指針指向派生類時,調用了Student的成員變量,但是沒有調用Student的成員函數,導致了結果不倫不類("hi,大家好!我叫李四?,今年18歲了。但是考了100分。");換句話說,通過基類指針只能訪問派生類的成員變量,但是不能訪問派生類的成員函數。
2.有Virtual關鍵字
為了消除這種尷尬,讓基類指針能夠訪問派生類的成員函數,C++?增加了虛函數(Virtual Function)。使用虛函數非常簡單,只需要在函數聲明前面增加?virtual?關鍵字。

有了虛函數,基類指針指向基類對象時就使用基類的成員(包括成員函數和成員變量),指向派生類對象時就使用派生類的成員。換句話說,基類指針可以按照基類的方式來做事,也可以按照派生類的方式來做事,它有多種形態(tài),或者說有多種表現方式,我們將這種現象稱為多態(tài)(Polymorphism)。
四.借助引用實現多態(tài)

五.多態(tài)的一個典型混合案例

在基類 Base 中我們將void func()
聲明為虛函數,這樣派生類 Derived 中的void func()
就會自動成為虛函數。p 是基類 Base 的指針,但是指向了派生類 Derived 的對象。
語句p -> func()/fun(char* name1, int a1);
調用的是派生類的虛函數,構成了多態(tài)。
語句p -> func(10);
調用的是基類的虛函數,因為派生類中沒有函數覆蓋它。
語句p -> func("http://c.biancheng.net");
出現編譯錯誤,因為通過基類的指針只能訪問從基類繼承過去的成員,不能訪問派生類新增的成員。
六.純虛函數與抽象類
1.什么是純虛函數?
virtual 返回值類型 函數名 (函數參數) = 0;純虛函數沒有函數體,只有函數聲明,在虛函數聲明的結尾加上=0
,表明此函數為純虛函數;最后的=0
并不表示函數返回值為0,它只起形式上的作用,告訴編譯系統(tǒng)“這是純虛函數”。
2.什么是抽象類?
包含純虛函數的類稱為抽象類(Abstract Class)。之所以說它抽象,是因為它無法實例化,也就是無法創(chuàng)建對象。原因很明顯,純虛函數沒有函數體,不是完整的函數,無法調用,也無法為其分配內存空間。
抽象類通常是作為基類,讓派生類去實現純虛函數。派生類必須實現純虛函數才能被實例化。
3.派生類必須重寫基類中全部的虛函數后,才能夠實例化

本例中定義了四個類,它們的繼承關系為:Line --> Rec --> Cuboid --> Cube。
Line 是一個抽象類,也是最頂層的基類,在 Line 類中定義了兩個純虛函數 area() 和 volume()。
在 Rec 類中,實現了 area() 函數;所謂實現,就是定義了純虛函數的函數體。但這時 Rec 仍不能被實例化,因為它沒有實現繼承來的 volume() 函數,volume() 仍然是純虛函數,所以 Rec 也仍然是抽象類。
直到 Cuboid 類,才實現了 volume() 函數,才是一個完整的類,才可以被實例化。
可以發(fā)現,Line 類表示“線”,沒有面積和體積,但它仍然定義了 area() 和 volume() 兩個純虛函數。這樣的用意很明顯:Line 類不需要被實例化,但是它為派生類提供了“約束條件”,派生類必須要實現這兩個函數,完成計算面積和體積的功能,否則就不能實例化。