(C/C++學習)5.C++中的虛繼承-虛函數-多態解析


說明:在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獲

在本例中,如果僅僅采用的是多繼承而非虛繼承,如下代碼所示:

  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 
查看代碼

程序運行結果:

2獲

注意:若在此例中,沒有將含有虛函數的父類析構函數聲明為虛函數,則將不會調用子類的析構函數~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++中函數的重載也是一種多態現象,其通過命名傾軋在編譯階段決定,故稱為靜多態;而動多態一般是在父子類中在運行階段決定的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM