As We All Know...
繼承和多態是C++語言的兩個重要特性,其中,繼承實現了派生類對基類的復用,而多態則是通過虛函數的重寫來進行。
基本概念
菱形繼承是一種比較復雜的繼承關系。
比如,學校里的人(Person)主要分為老師(Teacher)和學生(Student),這兩者都有人的特性;而其中有的研究生在學校任職,這就
是教助(Assistant),這種身份同時具有老師和學生兩類對象的特性,所以,學生、老師、教助就構成了一個菱形關系。
這種繼承方式的關系圖比較像菱形圖案:class Person{public: char _name;};class Student : public Person{public: int _num;};class Teacher : public Person{public: int _id;};class Assistant :virtual public Student, virtual public Teacher{public: char MajorCourse;};其對應的對象模型為:
很明顯,Assistant的對象中有兩個Person成員,所以菱形繼承存在二義性和數據冗余的問題。
解決方法
虛繼承--解決菱形繼承的二義性和數據冗余的問題。
在繼承關系前面加virtual關鍵字,就成為虛繼承。比如:
class Assistant :virtual public Student, virtual public Teacher{public: string MajorCourse;};我們可以看到,變為虛繼承前后類的大小有所變化:
為什么加入虛繼承后,類對象的大小增加了4個字節呢,
對于虛繼承,我們需要知道:
1.%20虛繼承解決了在菱形繼承體系里面子類對象包含多份父類對象的數據冗余&浪費空間的問題。
2.%20虛繼承體系看起來好復雜,在實際應用我們通常不會定義如此復雜的繼承體系。一般不到萬不得已都不要定義菱形結構的虛繼承
體系結構,因為使用虛繼承解決數據冗余問題也帶來了性能上的損耗。
——>多態
虛函數--類的成員函數前面加virtual關鍵字,則這個成員函數稱為虛函數。
虛函數重寫--當在子類的定義了一個與父類完全相同的虛函數時,則稱子類的這個函數重寫(也稱覆蓋)了父類的這個虛函數。
多態--使用的指針或引用調用重寫的虛函數時,指向父類調用的就是父類的虛函數,指向子類則調用子類的虛函數。比如:
class%20Person%20{public:%20%20%20 virtual%20void%20BuyTickets()%20%20 {%20cout%20<<%20"%20買票"%20<<%20endl;%20}PRotected:%20%20 string%20_name;%20%20%20//%20姓名%20};class%20Student%20:%20public%20Person{public: virtual%20void%20BuyTickets()%20 {%20cout%20<<%20"%20買票-半價%20"%20<<%20endl;%20}protected: int%20_num;%20%20%20//學號}; void%20Fun%20(Person&%20p)%20 { p.BuyTickets(); } void%20Test() { Person%20p; Student%20s; Fun(p); Fun(s); }運行結果為:
對于虛函數,我們需要知道:
1. 派生類重寫基類的虛函數實現多態,要求函數名、參數列表、返回值完全相同。(協變除外)
2. 基類中定義了虛函數,在派生類中該函數始終保持虛函數的特性。
3. 只有類的成員函數才能定義為虛函數。
4. 靜態成員函數不能定義為虛函數。
5. 如果在類外定義虛函數,只能在聲明函數時加virtual,類外定義函數時不能加virtual。
6. 構造函數不能為虛函數,雖然可以將Operator=定義為虛函數,但是最好不要將operator=定義為虛函數,因為容易使用時容易引
起混淆。
7. 不要在構造函數和析構函數里面調用虛函數,在構造函數和析構函數中,對象是不完整的,可能會發生未定義的行為。
8. 最好把基類的析構函數聲明為虛函數。因為盡管派生類的析構函數跟基類的析構函數名稱不一樣,但是兩者構成覆蓋,這里是因
為編譯器做了特殊處理。
新聞熱點
疑難解答
圖片精選