C++基本概念复习之二:多重继承、虚继承、纯虚函数(抽象类)
一、多重繼承:
#include <iostream> using namespace std; class Horse { public: Horse(){cout<<"Horse constructor...";} virtual ~Horse(){cout<<"Horse destructor...";} virtual void whinny()const{cout<<"Whinny!..";} private: int itsAge; }; class Bird { public: Bird(){cout<<"Bird constructor...";} virtual ~Bird(){cout<<"Bird destructor...";} virtual void chirp()const{cout<<"Chirp!...";} virtual void fly(){cout<<"Fly!!!";} private: int itsWeight; }; class Pegasus:public Horse,public Bird { public: Pegasus(){cout<<"Pegasus constructor...";} //飛馬也以鳥鳴的方式叫,但叫出來的聲音是馬的聲音 //注意const不能少,否則就不能覆蓋父類中的虛方法//void chirp()const,從而多態調用失敗
//掉了const導致的后果是隱藏,將來通過子類
//對象的指針、引用或者子類對象本身,都無法調用父類的
//同名方法,另一方面,通過指向子類對象的父類類型的指針
//也不能達到動態綁定從而多態調用,調用的永遠是父類的
//方法
virtual void chirp()const{whinny();} virtual ~Pegasus(){cout<<"Pegasu destructor...";} }; int main() { Horse * ranch[2]; Bird * aviary[2];//鳥籠和農場的容量都是2 //觀察打印的每一行可以看出每個對象的創建過程 ranch[0]=new Horse;//初始化農場 cout<<endl; ranch[1]=new Pegasus; cout<<endl; aviary[0]=new Bird;//初始化鳥籠 cout<<endl; aviary[1]=new Pegasus; cout<<endl; cout<<"*********************"<<endl; ranch[0]->whinny();cout<<endl; ranch[1]->whinny();cout<<endl; aviary[0]->chirp();aviary[0]->fly();cout<<endl; aviary[1]->chirp();aviary[1]->fly();cout<<endl; cout<<"*********************"<<endl; //觀察打印的每一行可以看出每個對象的銷毀過程 delete ranch[0];//清理農場 cout<<endl; delete ranch[1]; cout<<endl; delete aviary[0];//清理鳥籠 cout<<endl; delete aviary[1]; cout<<endl; //上面的子類對象能得以正確析構,要歸功于虛析構函數 return 0; }打印結果:
避免歧義:
上面的代碼中,若Horse和Bird類中都有getColor方法(可能是虛方法,也可能不是),且該方法在兩個類中的簽名完全一致,對子類來說,這兩個方法都繼承了,問題也隨之而來:
1、當我們在客戶端使用子類對象來調用該方法時:
Pegasus pgs;
pgs.getColor();
會告訴錯誤,因為編譯器不知道該調用哪個getColor。解決辦法,要么讓子類有自己的getColor方法(覆蓋父類的getColor),我們可以在子類自己的getColor方法指定調用哪個父類的getColor,或者都不調用,子類的getColor有自己全新的邏輯;要么在調用的時候指定調誰的:pgs.Bird::getColor()。
2、如果我們在客戶端用這樣調用:
Pegasus pgs;
Horse h=pgs;
h.getColor();
則不會有任何歧義,這種調用方式不會引入多態機制,永遠調用父類的方法——與java不同。
3、如果這樣調用:
Pegasus pgs;
Horse* ph=&pgs;//或者通過引用
ph->getColor();
也不會有任何歧義,這也不會引入多態機制,僅僅是在調用父類方法。
注意,上面所說的歧義,與getColor是不是虛方法沒有關系,即,僅僅靠虛方法不能避免歧義,解決辦法與1類似。
另一種歧義:
現在的情況是:馬和鳥都繼承自動物類Animal,由于飛馬從馬和鳥多繼承,導致飛馬對象的內存結構是這樣的:
這五塊內容,構成一個完整的飛馬Pegasu對象,其中有兩塊一模一樣的數據:Animal。這是飛馬的兩個直接父類從共同的基類Animal繼承而來的。代碼如下:
#include <iostream> using namespace std; enum COLOR{BLACK,RED,BLUE}; class Animal { public: void baseFunc(){cout<<"Animal func..."<<endl;} }; class Horse:public Animal { }; class Bird:public Animal { }; class Pegasus:public Horse,public Bird { }; int main() { Pegasus pgs; Horse* ph=&pgs; ph->baseFunc();//無歧義 pgs.baseFunc();//有歧義 return 0; }二、虛繼承解決歧義:
虛繼承得到的最終子類對象的內存模型:
從而能消除上面引起的歧義。代碼:
#include <iostream> using namespace std; enum COLOR{BLACK,RED,BLUE}; class Animal { public: void baseFunc(){cout<<"Animal func..."<<endl;} }; class Horse:virtual public Animal { }; class Bird:virtual public Animal { }; class Pegasus:public Horse,public Bird { }; int main() { Pegasus pgs; Horse* ph=&pgs; ph->baseFunc();//無歧義 pgs.baseFunc();//無歧義,虛繼承解決歧義 return 0; }虛繼承引進的一個注意事項是:共同基類(這里指Animal)的初始化任務,由最終子類完成。——上述代碼沒有體現這一點,但要注意。所謂的“類的初始化”這種說法,嚴格來說,并不確切。對于上面的內存模型,它是一個最終子類對象的內存模型,即是一個Pegasu對象,我們給其所占內存里的內容編上號,從上到下從左至右,分別為0,1,2,3:
可以這樣理解:整個(0,1,2,3)構成的是一個完整的飛馬對象,而0塊內容是一個Animal對象,1和0是一個Horse對象,2和0是一個Bird對象。上面所說的“基類的初始化”,意指0塊內容的初始化。不涉及虛繼承的情況下,父類的初始化,都在直接子類完成——通過在子類構造的參數列表后面手動調用父類相應的構造來完成。
經驗總結:在單繼承可行的情況,不要使用多繼承,避免帶來的開銷及額外的風險。
三、純虛函數(抽象類):
有純虛函數的類是抽象類。抽象類不能實例化,它存在的意義類似java中的接口和抽象類,將后代共同的操作統一聲明,至于具體如何實現,后代自己決定。一旦在父類中定義一個純虛函數,就對子類提出這樣的要求:要想使子類不是抽象類,就必須要覆蓋實現這個純虛函數,要覆蓋父類中的每個純虛函數。
虛函數可以有自己的實現。往往它完成的是通用的、基本的工作,通常的做法是:在子類具體的覆蓋實現中,調用父類純虛函數的實現來完成共同的基本操作。其實虛函數也能做到這一點,不過父類必須給出虛函數實現,而不能僅僅是聲明。從抽象的角度來講,具有純虛函數的類抽象程度更大一些,它僅有通用方法的聲明即可,且不能被實例化。
轉載于:https://www.cnblogs.com/qzhforthelife/archive/2012/11/04/2753610.html
總結
以上是生活随笔為你收集整理的C++基本概念复习之二:多重继承、虚继承、纯虚函数(抽象类)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python与C之间的相互调用(Pyth
- 下一篇: linux下的定时或计时操作(getti