C++類的多繼承
在前面的例子中,派生類都只有一個基類,稱為單繼承。除此之外,C++也支持多繼承,即一個派生類可以有兩個或多個基類。
多繼承容易讓代碼邏輯復雜、思路混亂,一直備受爭議,中小型項目中較少使用,后來的 Java、C#、PHP 等干脆取消了多繼承。想快速學習C++的讀者可以不必細讀。
多繼承的語法也很簡單,將多個基類用逗號隔開即可。例如已聲明了類A、類B和類C,那么可以這樣來聲明派生類D:
class D: public A, private B, protected C{ //類D新增加的成員}
D是多繼承的派生類,它以共有的方式繼承A類,以私有的方式繼承B類,以保護的方式繼承C類。D根據不同的繼承方式獲取A、B、C中的成員,確定各基類的成員在派生類中的訪問權限。
多繼承下的構造函數
多繼承派生類的構造函數和單繼承類基本相同,只是要包含多個基類構造函數。如:
D類構造函數名(總參數表列): A構造函數(實參表列), B類構造函數(實參表列), C類構造函數(實參表列){ 新增成員初始化語句}
各基類的排列順序任意。
派生類構造函數的執行順序同樣為:先調用基類的構造函數,再調用派生類構造函數?;悩嬙旌瘮档恼{用順序是按照聲明派生類時基類出現的順序。
下面的定義了兩個基類,BaseA類和BaseB類,然后用多繼承的方式派生出Sub類。
#include <iostream>using namespace std;//基類class BaseA{protected: int a; int b;public: BaseA(int, int);};BaseA::BaseA(int a, int b): a(a), b(b){}//基類class BaseB{protected: int c; int d;public: BaseB(int, int);};BaseB::BaseB(int c, int d): c(c), d(d){}//派生類class Sub: public BaseA, public BaseB{private: int e;public: Sub(int, int, int, int, int); void display();};Sub::Sub(int a, int b, int c, int d, int e): BaseA(a, b), BaseB(c, d), e(e){}void Sub::display(){ cout<<"a="<<a<<endl; cout<<"b="<<b<<endl; cout<<"c="<<c<<endl; cout<<"d="<<d<<endl; cout<<"e="<<e<<endl;}int main(){ (new Sub(1, 2, 3, 4, 5)) -> display(); return 0;}
運行結果:
a=1b=2c=3d=4e=5
從基類BaseA和BaseB繼承來的成員變量,在 Sub::display() 中都可以訪問。
命名沖突
當兩個基類中有同名的成員時,就會產生命名沖突,這時不能直接訪問該成員,需要加上類名和域解析符。
假如在基類BaseA和BaseB中都有成員函數 display(),那么下面的語句是錯誤的:
Sub obj;obj.display();
由于BaseA和BaseB中都有display(),系統將無法判定到底要調用哪一個類的函數,所以報錯。
應該像下面這樣加上類名和域解析符:
Sub obj;obj.BaseA::display();obj.BaseB::display();
通過這個舉例可以發現:在多重繼承時,從不同的基類中會繼承一些重復的數據。如果有多個基類,問題會更突出,所以在設計派生類時要細致考慮其數據成員,盡量減少數據冗余。
C++多重繼承的二義性問題
多重繼承可以反映現實生活中的情況,能夠有效地處理一些較復雜的問題,使編寫程序具有靈活性,但是多重繼承也引起了一些值得注意的問題,它增加了程序的復雜度,使 程序的編寫和維護變得相對困難,容易出錯。其中最常見的問題就是繼承的成員同名而產生的二義性(ambiguous)問題。
如果類A和類B中都有成員函數display和數據成員a,類C是類A和類B的直接派生類。分別討論下列3種情況。
1) 兩個基類有同名成員
代碼如下所示:
class A{public: int a; void display();};class B{public: int a; void display ();};class C: public A, public B{public: int b; void show();};
如果在main函數中定義C類對象cl,并調用數據成員a和成員函數display :
C cl; cl.a=3; cl.display();
由于基類A和基類B都有數據成員a和成員函數display,編譯系統無法判別要訪問的是哪一個基類的成員,因此程序編譯出錯。那么,應該怎樣解決這個問題呢?可以用基類名來限定:
cl.A::a=3; //引用cl對象中的基類A的數據成員a cl.A::display(); //調用cl對象中的基類A的成員函數display
如果是在派生類C中通過派生類成員函數show訪問基類A的display和a,可以不 必寫對象名而直接寫
A::a = 3; //指當前對象 A::display();
2) 兩個基類和派生類三者都有同名成員
將上面的C類聲明改為:
class C: public A, public B { int a; void display(); };
如果在main函數中定義C類對象cl,并調用數據成員a和成員函數display:
C cl; cl.a = 3; cl.display();
此時,程序能通過編譯,也可以正常運行。請問:執行時訪問的是哪一個類中的成員?答案是:訪問的是派生類C中的成員。規則是:基類的同名成員在派生類中被屏蔽,成為“不可見”的,或者說,派生類新增加的同名成員覆蓋了基類中的同名成員。因此如果在定義派生類對象的模塊中通過對象名訪問同名的成員,則訪問的是派生類的成員。請注意:不同的成員函數,只有在函數名和參數個數相同、類型相匹配的情況下才發生同名覆蓋,如果只有函數名相同而參數不同,不會發生同名覆蓋,而屬于函數重載。
有些讀者可能對同名覆蓋感到不大好理解。為了說明問題,舉個例子,例如把中國作為基類,四川則是中國的派生類,成都則是四川的派生類。基類是相對抽象的,派生類是相對具體的,基類處于外層,具有較廣泛的作用域,派生類處于內層,具有局部的作用域。若“中國”類中有平均溫度這一屬性,四川和成都也都有平均溫度這一屬性,如果沒有四川和成都這兩個派生類,談平均溫度顯然是指全國平均溫度。如果在四川,談論當地的平均溫度顯然是指四川的平均溫度;如果在成都,談論當地的平均溫度顯然是指成都的平均溫度。這就是說,全國的“平均溫度”在四川省被四川的“平均溫度”屏蔽了,或者說,四川的“平均溫度”在當地屏蔽了全國的“平均溫度”。四川人最關心的是四川的溫度,當然不希望用全國溫度覆蓋四川的平均溫度。
如果在四川要查全國平均溫度,一定要聲明:我要查的是全國的平均溫度。同樣,要在派生類外訪問基類A中的成員,應指明作用域A,寫成以下形式:
cl.A::a=3; //表示是派生類對象cl中的基類A中的數據成員a cl.A::display(); //表示是派生類對象cl中的基類A中的成員函數display
3) 類A和類B是從同一個基類派生的
代碼如下所示:
class N{public: int a; void display(){ cout<<"A::a="<<a<<endl; }};class A: public N{public: int al;};class B: public N{public: int a2;};class C: public A, public B{public: int a3; void show(){ cout<<"a3="<<a3<<endl; }}int main(){ C cl; //定義C類對象cl // 其他代碼}
在類A和類B中雖然沒有定義數據成員a和成員函數display,但是它們分別從類N繼承了數據成員a和成員函數display,這樣在類A和類B中同時存在著兩個同名的數據成員a和成員函數display。它們是N類成員的拷貝。類A和類B中的數據成員a代表兩個不同的存儲單元,可以分別存放不同的數據。在程序中可以通過類A和類B的構造函數去調用基類N的構造函數,分別對類A和類B的數據成員a初始化。
怎樣才能訪問類A中從基類N繼承下來的成員呢?顯然不能用
cl.a = 3; cl.display();
或
cl.N::a = 3; cl. N::display();
因為這樣依然無法區別是類A中從基類N繼承下來的成員,還是類B中從基類N繼承下來的成員。應當通過類N的直接派生類名來指出要訪問的是類N的哪一個派生類中的基類成員。如
cl.A::a=3; cl.A::display(); //要訪問的是類N的派生類A中的基類成員
新聞熱點
疑難解答
圖片精選