在《基類和衍生類別》中講述了單繼承的基本概念,這節著重講述繼承的具體應用。
在單繼承中,每個類可以有多個衍生類別,但是每個衍生類別只能有一個基類,從而形成樹形結構。
成員存取權限的控制
在《基類和衍生類別》一講中,我們講述了衍生類別和衍生類別的對象對基類成員的存取權限的若干規定,這裡通過一個執行個體進一步討論存取權限的具體控制,然後得出在使用三種繼承方式時的調用方法。
//繼承性的public繼承方式的存取權限的例子 #include file://定義基類A class A { public: A() { cout<<"類A的建構函式!"< A(int a) { Aa = a, aa = a, aaa = a; } void Aprint() { cout<<"類A列印自己的private成員aa:"< int Aa; private: int aa; protected: int aaa; }; file://定義由基類A派生的類B class B : public A { public: B() { cout<<"類B的建構函式!"< B(int i, int j, int k); void Bprint() { cout<<"類B列印自己的private成員bb和protected成員bbb:"< void B_Aprint() { cout<<"類B的public函數訪問類A的public資料成員Aa:"< cout<<"類B的public函數訪問類A的protected資料成員aaa:"< GetAaaa(); GetAaaa1();} private: int bb; void GetAaaa() { cout<<"類B的private函數訪問類A的public資料成員Aa:"< cout<<"類B的private函數訪問類A的protected資料成員aaa:"< protected: int bbb; void GetAaaa1() { cout<<"類B的protected函數訪問類A的public資料成員Aa:"< cout<<"類B的protected函數訪問類A的protected資料成員aaa:"< }; file://基類B的建構函式,需負責對基類A的建構函式的初始化 B::B(int i, int j, int k):A(i), bb(j), bbb(k) {} file://程式主函數 void main() { B b1(100, 200, 300); file://定義類B的一個對象b1,並初始化建構函式和基類建構函式 b1.B_Aprint(); file://類B調用自己的成員函數B_Aprint函數 b1.Bprint(); file://類B對象b1訪問自己的private和protected成員 b1.Aprint(); file://通過類B的對象b1調用類A的public成員函數 } |
該程式的輸出結果為:
類B的public函數訪問類A的public資料成員Aa:100
類B的public函數訪問類A的protected資料成員aaa:100
類B的private函數訪問類A的public資料成員Aa:100
類B的private函數訪問類A的protected資料成員aaa:100
類B的protected函數訪問類A的public資料成員Aa:100
類B的protected函數訪問類A的protected資料成員aaa:100
類B列印自己的private成員bb和protected成員bbb:200,300
類A列印自己的private成員aa:100
上述是屬public繼承方式,我們可以得出以下結論:
在公有繼承(public)時,衍生類別的public、private、protected型的成員函數可以訪問基類中的公有成員和保護成員;衍生類別的對象僅可訪問基類中的公有成員。
讓我們把繼承方式public改為private,編譯結果出現1處如下錯誤:
'Aprint' : cannot access public member declared in class 'A'
出錯語句在於:b1.Aprint();,因此,我們可以得出以下結論:
在公有繼承(private)時,衍生類別的public、private、protected型的成員函數可以訪問基類中的公有成員和保護成員;但衍生類別的對象不可訪問基類中的任何成員。另,使用class關鍵字定義類時,預設的繼承方式是private,也就是說,當繼承方式為私人繼承時,可以省略private。
讓我們把繼承方式public改為protected,可以看出,結果和private繼承方式一樣。
建構函式和解構函式
衍生類別的建構函式和解構函式的構造是討論的主要問題,讀者要掌握它。
1. 建構函式
我們已知道,衍生類別的對象的資料結構是由基類中說明的資料成員和衍生類別中說明的資料成員共同構成。將衍生類別的對象中由基類中說明的資料成員和操作所構成的封裝體稱為基類子物件,它由基類中的建構函式進行初始化。
建構函式不能夠被繼承,因此,衍生類別的建構函式必須通過調用基類的建構函式來初始化基類子物件。所以,在定義衍生類別的建構函式時除了對自己的資料成員進行初始化外,還必須負責調用基類建構函式使基類資料成員得以初始化。如果衍生類別中還有子物件時,還應包含對子物件初始化的建構函式。
衍生類別建構函式的一般格式如下:
<衍生類別名>(<衍生類別建構函式總參數表>):<基類建構函式>(參數表1),<子物件名>(<參數表2>)
{
<衍生類別中資料成員初始化>
};
衍生類別建構函式的調用順序如下:
· 基類的建構函式
· 子物件類的建構函式(如果有的話)
· 衍生類別建構函式
在前面的例子中,B::B(int i, int j, int k):A(i), bb(j), bbb(k)就是衍生類別建構函式的定義,下面再舉一個構造衍生類別建構函式的例子。
#include class A { public: A() { a=0; cout<<"類A的預設建構函式./n"; } A(int i) { a=i; cout<<"類A的建構函式./n"; } ~A() { cout<<"類A的解構函式./n"; } void Print() const { cout< int Geta() { reutrn a; } private: int a; } class B : public A { public: B() { b=0; cout<<"類B的預設建構函式./n"; } B(int i, int j, int k); ~B() { cout<<"類B的解構函式./n"; } void Print(); private: int b; A aa; } B::B(int i, int j, int k):A(i), aa(j) { b=k; cout<<"類B的建構函式./n"; } void B::Print() { A::Print(); cout< }void main() { B bb[2]; bb[0] = B(1, 2, 5); bb[1] = B(3, 4, 7); for(int i=0; i<2; i++) bb[i].Print(); } |
2. 建構函式
當對象被刪除時,衍生類別的解構函式被執行。由於解構函式也不能被繼承,因此在執行衍生類別的解構函式時,基類的解構函式也將被調用。執行順序是先執行衍生類別的建構函式,再執行基類的解構函式,其順序與執行建構函式時的順序正好相反。這一點從前面講過的例子可以看出,請讀者自行分析。
3. 衍生類別建構函式使用中應注意的問題
(1) 衍生類別建構函式的定義中可以省略對基類建構函式的調用,其條件是在基類中必須有預設的建構函式或者根本沒有定義建構函式。當然,基類中沒有定義建構函式,衍生類別根本不必負責調用基類的解構函式。
(2) 當基類的建構函式使用一個或多個參數時,則衍生類別必須定義建構函式,提供將參數傳遞給基類建構函式途徑。在有的情況下,衍生類別建構函式的函數體可能為空白,僅起到參數傳遞作用。如本講第一個例子就屬此種情況。
子類型化和類型適應
1. 子類型化
子類型的概念涉及到行為共用,它與繼承有著密切關係。
有一個特定的類型S,若且唯若它至少提供了類型T的行為,由稱類型S是類型T的子類型。子類型是類型之間的一般和特殊的關係。
在繼承中,公有繼承可以實現子類型。例如:
class A { public: void Print() const { cout<<"A::print() called./n"; } }; class B : public A { public: void f() {} }; |
類B繼承了類A,並且是公有繼承方式。因此,可以說類B是類A的一個子類型。類A還可以有其他的子類型。類B是類A的子類型,類B具備類A中的操作,或者說類A中的操作可被用於操作類B的對象。
子類型關係是無法復原的。這就是說,已知B是A的子類型,而認為A也是B的子類型是錯誤的,或者說,子類型關係是不對稱不。
因此,可以說公有繼承可以實現子類型化。
2. 類型適應
類型適應是指兩種類型之間的關係。例如,B類型適應A類型是指B類型的對象能夠用於A類型的對象所能使用的場合。
前面講過的衍生類別的對象可以用於基類對象所能使用的場合,我們說衍生類別適應於基類。
同樣道理,衍生類別對象的指標和引用也適應於基類對象的指標和引用。
子類型化與類型適應是致的。A類型是B類型的子類型,那麼A類型必將適應於B類型。
子類型的重要性就在於減輕程式人員編寫程式碼的負擔。因為一個函數可以用於某類型的對象,則它也可以用於該類型的各個子類型的對象,這樣就不必為處理這些子類型的對象去重載該函數。