繼承(派生類的三種繼承方式,多重繼承,虛繼承)
繼承性是面向物件程式設計的第二大特性,它允許在既有類的基礎上建立新類,新類可以繼承既有類的資料成員和成員函式,可以新增自己特有的資料成員和成員函式,還可以對既有類中的成員函式重新定義。利用類的繼承和派生實現了更高層次的程式碼可重用性,符合現代軟體開發的思想。
C++語言同時支援單一繼承和多重繼承。單一繼承是指派生類只從一個基類繼承而來;相應的,多重繼承指派生類同時從兩個或更多的基類繼承而來。java只支援單一繼承。
一. 派生類
派生類的定義格式如下:
class <派生類名>:[繼承方式]<基類名1>
[,[繼承方式]<基類名2>,...,[繼承方式]<基類名n>]
{
<派生類新增的資料成員和成員函式定義>
};
說明:
(1)定義派生類關鍵字可以是class或者是struct,兩者區別是:用class定義派生類,預設的繼承方式是private,用struct定義派生類,預設的繼承方式為public。新增加的成員預設屬性也是class對應private屬性,struct對應public屬性。
(2)基類不能被派生類繼承的兩類函式是建構函式和解構函式。
二. 3種繼承方式下基類成員在派生類中的訪問屬性
繼承描述符 | 父public成員 | 父protected成員 | 父private成員 |
public | 子public成員 | 子protected成員 | - |
protected | 子protected成員 | 子protected成員 | - |
private | 子private成員 | 子private成員 | - |
用下面的程式碼簡單理解一下:
1 #include "stdafx.h" 2 #include<iostream> 3 using namespace std; 4 5 class Base 6 { 7 private: 8 int priData; 9 protected: 10 int proData; 11 public: 12 int pubData; 13 }; 14 15 class D1:private Base//私有繼承 16 { 17 void f1() 18 { 19 //priData=1;//基類private成員在派生類中不可直接訪問 20 proData=2;//基類的protected成員在派生類中為private訪問屬性 21 pubData=3;//基類的public成員在派生類中為private訪問屬性 22 } 23 }; 24 25 class D2:protected Base//保護繼承 26 { 27 void f2() 28 { 29 //priData=1;//基類private成員在派生類中不可直接訪問 30 proData=2;//基類的protected成員在派生類中為protected訪問屬性 31 pubData=3;//基類的public成員在派生類中為protected訪問屬性 32 } 33 }; 34 35 class D3:public Base//公有繼承 36 { 37 void f3() 38 { 39 //priData=1;//基類private成員在派生類中不可直接訪問 40 proData=2;//基類的protected成員在派生類中為protected訪問屬性 41 pubData=3;//基類的public成員在派生類中為public訪問屬性 42 } 43 }; 44 45 int main() 46 { 47 Base obj; 48 //obj.priData=1;//物件不可訪問Base類中private成員 49 //obj.proData=2;//物件不可訪問Base類中protected成員 50 obj.pubData=3; 51 D1 objD1; 52 //objD1.pubData=3;//private屬性,不可訪問 53 D2 objD2; 54 //objD2.pubData=3;//protected屬性,不可訪問 55 D3 objD3; 56 objD3.pubData=3;//public屬性,可以訪問 57 return 0; 58 }
基類的private成員函式雖然在派生類的成員函式中不可直接訪問,但派生類的成員函式可以通過呼叫基類被繼承的函式來間接訪問這些成員。如果基類的函式被繼承後在派生類中仍為public成員,則可以通過派生類物件直接呼叫。
先來看一下類成員的訪問屬性及作用:
訪問屬性 | 作用 |
private | 只允許該類的成員函式及友元函式訪問,不能被其他函式訪問 |
protected | 既允許該類的成員函式及友元函式訪問,也允許其派生類的成員函式訪問 |
public | 既允許該類的成員函式訪問,也允許類外部的其他函式訪問 |
好了,繼續通過程式碼來理解:
1 #include "stdafx.h" 2 #include<iostream> 3 using namespace std; 4 5 class Base 6 { 7 private: 8 int priData; 9 protected: 10 int proData; 11 public: 12 int pubData; 13 //在類的定義中不能對資料成員進行初始化 14 void SetData()//為基類中的資料成員賦值 15 { 16 priData=100; 17 proData=200; 18 pubData=300; 19 } 20 void Print() 21 { 22 cout<<"priData="<<priData<<endl; 23 cout<<"proData="<<proData<<endl; 24 cout<<"pubData="<<pubData<<endl; 25 } 26 }; 27 28 class Derived:public Base 29 { 30 public: 31 void ChangeData() 32 { 33 SetData(); 34 proData=12;//在派生類的成員函式類可以訪問基類的非私有成員 35 } 36 }; 37 38 int main() 39 { 40 Base b; 41 b.SetData(); 42 b.Print(); 43 44 Derived d1; 45 d1.ChangeData(); 46 d1.pubData=13; 47 d1.Print(); 48 49 return 0; 50 }
程式執行結果如下:
三. 派生類的建構函式和解構函式
在定義一個派生類的物件時,在派生類中新增加的資料成員當然用派生類的建構函式初始化,但是對於從基類繼承來的資料成員的初始化工作就必須由基類的建構函式完成,這就需要在派生類的建構函式中完成對基類建構函式的呼叫。同樣,派生類的解構函式值能完成派生類中新增加資料成員的掃尾、清理工作,而從基類繼承來的資料成員的掃尾工作也應有基類的解構函式完成。由於解構函式不能帶引數,因此派生類的解構函式預設直接呼叫了基類的解構函式。
派生類建構函式定義格式如下:
<派生類名>(<總形式引數表>):<基類名1>(<引數表1>),
<基類名2>(<引數表2>),[...,<基類名n>(<引數表n>),其他初始化項>]
{
[<派生類自身資料成員的初始化>]
}
說明:
(1)總形式表給出派生類建構函式中所有的形式引數,作為呼叫基類帶參建構函式的實際引數以及初始化本類資料成員的引數;
(2)一般情況下,基類名後面的引數表中的實際引數來自前面派生類建構函式形式引數總表,當然也可能是與前面形式引數無關的常量;
(3)在多層次繼承中,每一個派生類只需要負責向直接基類的建構函式提供引數;如果一個基類有多個派生類,則每個派生類都要負責向該積累的建構函式提供引數。
1.單一繼承
1 #include"stdafx.h" 2 #include<iostream> 3 using namespace std; 4 5 class Other 6 { 7 public: 8 Other() 9 { 10 cout<<"constructing Other class"<<endl; 11 } 12 ~Other() 13 { 14 cout<<"destructing Other class"<<endl; 15 } 16 }; 17 18 class Base 19 { 20 public: 21 Base() 22 { 23 cout<<"constructing Base class"<<endl; 24 } 25 ~Base() 26 { 27 cout<<"destructing Base class"<<endl; 28 } 29 }; 30 31 class Derive:public Base 32 { 33 private: 34 Other ot; 35 public: 36 Derive() 37 { 38 cout<<"constructing Derive class"<<endl; 39 } 40 ~Derive() 41 { 42 cout<<"destructing Derive class"<<endl; 43 } 44 }; 45 46 int main() 47 { 48 Derive d; 49 50 return 0; 51 }
程式執行結果如下:
可以看到定義派生類物件時,建構函式的呼叫順序:
a.先呼叫基類的建構函式
b.然後呼叫派生類物件成員所屬類的建構函式(如果有物件成員)
c.最後呼叫派生類的建構函式
解構函式的呼叫順序正好與建構函式呼叫順序相反。
2.多重繼承
1 #include"stdafx.h" 2 #include<iostream> 3 using namespace std; 4 5 class Grand 6 { 7 int g; 8 public: 9 Grand(int n):g(n) 10 { 11 cout<<"Constructor of class Grand g="<<g<<endl; 12 } 13 ~Grand() 14 { 15 cout<<"Destructor of class Grand"<<endl; 16 } 17 }; 18 19 class Father:public Grand 20 { 21 int f; 22 public: 23 Father(int n1,int n2):Grand(n2),f(n1) 24 { 25 cout<<"Constructor of class Father f="<<f<<endl; 26 } 27 ~Father() 28 { 29 cout<<"Destructor of class Father"<<endl; 30 } 31 }; 32 33 class Mother 34 { 35 int m; 36 public: 37 Mother(int n):m(n) 38 { 39 cout<<"Constructor of class Mother m="<<m<<endl; 40 } 41 ~Mother() 42 { 43 cout<<"Destructor of class Mother"<<endl; 44 } 45 }; 46 47 class Son:public Father,public Mother 48 { 49 int s; 50 public: 51 Son(int n1,int n2,int n3,int n4):Mother(n2),Father(n3,n4),s(n1) 52 { 53 cout<<"Constructor of class Son s="<<s<<endl; 54 } 55 ~Son() 56 { 57 cout<<"Destructor of class Son"<<endl; 58 } 59 }; 60 61 int main() 62 { 63 Son s(1,2,3,4); 64 return 0; 65 }
程式執行結果如下:
可以看到,與單一繼承不同的是:在多重繼承中,派生類有多個平行的基類,這些處於同一層次的基類建構函式的呼叫順序,取決於宣告派生類時所指定的各個基類的順序,而與派生類建構函式的成員初始化列表中呼叫基類建構函式的順序無關。
派生類的3種繼承方式總結如下:
1.公有繼承
基類成員對其物件的可見性與一般類及其物件的可見性相同,公有成員可見,其他成員不可見。這裡保護成員和私有成員相同。
基類成員對派生類的可見性對派生類來說,基類的公有成員和保護成員可見,基類的公有成員和保護成員作為派生類的成員時,它們都保持原有的狀態;基類的私有成員不可見,基類的私有成員仍然是私有的,派生類不可訪問基類中的私有成員。
基類成員對派生類物件的可見性對派生類物件來說,基類的公有成員是可見的,其他成員是不可見的。
所以,在公有繼承時,派生類的物件可以訪問基類中的公有成員,派生類的成員函式可以訪問基類中的公有成員和保護成員。
2.私有繼承
基類成員對其物件的可見性與一般類及其物件的可見性相同,公有成員可見,其他成員不可見。
基類成員對派生類的可見性對派生類來說,基類的公有成員和保護成員是可見的,基類的公有成員和保護成員都作為派生類的私有成員,並且不能被這個派生類的子類所訪問;基類的私有成員是不可見的,派生類不可訪問基類中的私有成員。
基類成員對派生類物件的可見性對派生類物件來說,基類的所有成員都是不可見得。
所以,在私有繼承時,基類的成員只能由直接派生類訪問,而無法再往下繼承。
3.保護繼承
與私有繼承的區別僅在於對派生類的成員而言,基類成員對其物件的可見性與一般類及其物件的可見性相同,公有成員可見,其他成員不可見。
基類成員對派生類的可見性對派生類來說,基類的公有成員和保護成員是可見的,基類的公有成員和保護成員都作為派生類的保護成員,並且不能被這個派生類的子類的物件所訪問,但可以被派生類的子類的所訪問,;基類的私有成員是不可見的,派生類不可訪問基類中的私有成員。
基類成員對派生類物件的可見性對派生類物件來說,基類的所有成員都是不可見的。
虛繼承是多重繼承中特有的概念。虛基類是為解決多重繼承而出現的。
類D繼承自B和C,類B,C都繼承自A,所以在類D中會出現兩次A,為了節省空間,可以將B,C對A的繼承定義為虛擬繼承,而A就成了虛擬基類
class A;
class B:public virtual A;
class C:public virtual A;
class D:public B,public C;