說明:在C++學習的過程中,虛繼承-虛函數經常是初學者容易產生誤解的兩個概念,它們與C++中多態形成的關系,也是很多初學者經常產生困惑的地方,這篇文章將依次分別對三者進行解析,並講述其之間的聯系與不同。
一.虛繼承
1.在多繼承中,對於多個父類的數據及函數成員,雖然有時候把他們全部繼承下來是有必要的,比如當這些成員都不同的時候。但在大多數的情況下,比如當多個父類之中的成員有重疊的地方時,因為保留多份數據成員的拷貝,不僅占有了較多的存儲空間,還增加了訪問的難度(由於繼承了來自多個父類的同名數據成員,訪問時需要加上父類的作用域,比如“父類名::成員”),因此,在實際的繼承中是沒必要的。而虛繼承則可以完美的解決這一問題。
2.在虛繼承中,被虛繼承的類叫做虛基類,虛基類是需要設計和抽象的,它應當提取多繼承父類中重疊的部分作為成員,虛繼承是對繼承的一種擴展。
示例1:

1 #include<iostream> 2 using namespace std; 3 4 class furniture 5 { 6 public: 7 furniture(float l,float wi,float we) 8 :len(l),wid(wi),weight(we) 9 {} 10 void dis() 11 { 12 cout<<"len = "<<len<<endl; 13 cout<<"wid = "<<wid<<endl; 14 cout<<"weight="<<weight<<endl; 15 } 16 protected: 17 float len; 18 float wid; 19 float weight; 20 }; 21 22 //+++++++++++++++++++++++++ 23 24 class bed:virtual public furniture 25 { 26 public: 27 bed(float l,float wi,float we) 28 :furniture(l,wi,we) 29 {} 30 31 void sleep(){cout<<"go to sleep!!!!!"<<endl;} 32 }; 33 34 //+++++++++++++++++++++++++ 35 36 class sofa:virtual public furniture 37 { 38 public: 39 sofa(float l,float wi,float we) 40 :furniture(l,wi,we) 41 {} 42 43 void sit(){cout<<"go to have a rest!!!!!"<<endl;} 44 }; 45 46 //+++++++++++++++++++++++++ 47 48 class sofabed:public bed,public sofa 49 { 50 public: 51 sofabed(float l,float wi,float we) 52 :furniture(l,wi,we),bed(1,2,3),sofa(1,2,3) 53 {} 54 }; 55 56 int main() 57 { 58 bed b(1,2,3); 59 b.sleep(); 60 b.dis(); 61 sofa s(2,3,4); 62 s.sit(); 63 s.dis(); 64 sofabed sb(4,5,6); 65 sb.sleep(); 66 sb.sit(); 67 sb.dis(); 68 return 0; 69 } 70
程序運行結果:
在本例中,如果僅僅采用的是多繼承而非虛繼承,如下代碼所示:

1 #include<iostream> 2 using namespace std; 3 4 class bed 5 { 6 public: 7 bed(float l,float wi,float we) 8 :len(l),wid(wi),weight(we) 9 {} 10 11 void sleep() 12 { 13 cout<<"go to sleep!!!!!"<<endl; 14 } 15 16 void dis() 17 { 18 cout<<"len = "<<len<<endl; 19 cout<<"wid = "<<wid<<endl; 20 cout<<"weight = "<<weight<<endl; 21 } 22 protected: 23 float len; 24 float wid; 25 float weight; 26 }; 27 //+++++++++++++++++++++++++++++ 28 class sofa 29 { 30 public: 31 sofa(float l,float wi,float we) 32 :len(l),wid(wi),weight(we) 33 {} 34 void sit() 35 { 36 cout<<"go to have a rest!!!!!"<<endl; 37 } 38 void dis() 39 { 40 cout<<"len = "<<len<<endl; 41 cout<<"wid = "<<wid<<endl; 42 cout<<"weight = "<<weight<<endl; 43 } 44 protected: 45 float len; 46 float wid; 47 float weight; 48 49 }; 50 //+++++++++++++++++++++++++++ 51 class sofabed:public bed,public sofa 52 { 53 public: 54 sofabed(float l,float wi,float we) 55 :bed(l,wi,we),sofa(l,wi,we) 56 {} 57 }; 58 //+++++++++++++++++++++++++++ 59 int main() 60 { 61 bed b(1,2,3); 62 b.sleep(); 63 b.dis(); 64 sofa s(2,3,4); 65 s.sit(); 66 s.dis(); 67 sofabed sb(5,6,7); 68 sb.sit(); 69 sb.sleep(); 70 sb.sofa::dis(); 71 sb.bed::dis(); 72 return 0; 73 }
則sb.dis()就有問題了;因為它產生了二義性,編譯器不知道該調用哪一個父類的成員函數,而正確做法是加上父類的作用域,這無疑是增加了訪問了難度。
結論:多繼承帶來的數據存儲多份,占用內存空間較多,並且訪問不便(作用域的增加),采用虛繼承可以解決這一問題。
二.純虛函數
1.純虛函數的格式:
1 class A 2 { 3 virtual void func() = 0; 4 }
2.含有純虛函數的類為抽象基類,不可創建對象,其存在的意義就是被繼承,提供族類的公共接口,
3.純虛函數只有聲明,沒有實現,被初始化為0,
4.如果一個類中聲明了純虛函數,而在派生類中沒有對該函數定義,則該函數在派生類中仍然為純虛函數,派生類仍然為純虛基類,
5.含有虛函數的類,析構函數也應該聲明為虛函數,這樣在delete父類指針的時候,才會調用子類的析構函數,實現完整析構,

1 #include<iostream> 2 using namespace std; 3 4 class A 5 { 6 public: 7 A() 8 { 9 cout<<"A(){}"<<endl; 10 } 11 virtual ~A() 12 { 13 cout<<"~A(){}"<<endl; 14 } 15 virtual void func() = 0; 16 }; 17 class B:public A 18 { 19 public: 20 B(){cout<<"B(){}"<<endl;} 21 ~B(){cout<<"~B(){}"<<endl;} 22 virtual void func() 23 { 24 cout<<"B.func(){}"<<endl; 25 } 26 }; 27 int main() 28 { 29 A*pa = new B; 30 pa->func(); 31 delete pa; 32 return 0; 33 } 34
程序運行結果:
注意:若在此例中,沒有將含有虛函數的父類析構函數聲明為虛函數,則將不會調用子類的析構函數~B()實現完整析構。
三.多態的實現
1.C++中的多態指的是由於繼承而產生的相關的不同的類,其對象對同一消息會做出不同的反應。
2.多態實現的前提是賦值兼容,賦值兼容的內容如下:
a.子類的對象可以賦值給基類的對象,
b.子類的對象可以賦值給基類的引用,
c.子類對象的地址可以賦值給基類的指針(一般用於動多態的實現),
d.在賦值后,子類對象就可以作為基類對象使用,但只能訪問從基類繼承的成員.
3.動多態的實現條件:
a.父類中有虛函數,
b.子類override(覆寫)父類中的虛函數,
c.將子類的對象賦值給父類的指針或引用,由其調用公用接口.

1 #include<iostream> 2 using namespace std; 3 4 class Shape 5 { 6 public: 7 virtual void draw() = 0; 8 }; 9 //+++++++++++++++++++ 10 class Circle:public Shape 11 { 12 public: 13 void draw() 14 { 15 cout<<"Circle"<<endl; 16 } 17 }; 18 //+++++++++++++++++++ 19 class Rect:public Shape 20 { 21 public: 22 void draw() 23 { 24 cout<<"Rect"<<endl; 25 } 26 }; 27 int main() 28 { 29 Circle c; 30 Rect r; 31 Shape *p = &c; 32 p->draw(); 33 p = &r; 34 p->draw(); 35 return 0; 36 } 37
注意:C++中的多態一般指動多態,其實C++中函數的重載也是一種多態現象,其通過命名傾軋在編譯階段決定,故稱為靜多態;而動多態一般是在父子類中在運行階段決定的。