1.繼承的三種方式:
公有繼承(public),私有繼承(private),保護繼承(protected)
三種繼承方式的說明,如下表所示:
特征 | 公有繼承 | 保護繼承 | 私有繼承 |
公有成員變成 | 派生類的公有成員 | 派生類的保護成員 | 派生類的私有成員 |
保護成員變成 | 派生類的保護成員 | 派生類的保護成員 | 派生類的私有成員 |
私有成員變成 | 只能通過基類接口訪問 | 只能通過基類接口訪問 | 只能通過基類接口訪問 |
能否隱式向上轉換 | 是 | 是 | 否 |
2.什么是多繼承
一個類有多個基類,那么這種繼承關系就叫做多繼承。
比如有兩個類,服務員類Waiter,歌手類Singer,我們有一個類既是服務員,又是歌手,
那么我們可以定義類的多繼承關系如下:
class Waiter {}; class Singer {}; class SingerWaiter:public Waiter,public Singer {};
3.使用多繼承會帶來哪些問題
多繼承比單繼承復雜,也更容易出現問題,因此我們不建議使用多繼承。
多繼承的兩個主要問題是:
1)從兩個不同的基類,繼承同名方法
如下例所示:
class Waiter { public: void work(){std::cout<<"Service"<<std::endl;} }; class Singer { public: void work(){std::cout<<"Sing"<<std::endl;} }; class SingerWaiter:public Waiter,public Singer {}; int main() { SingerWaiter singerWaiter; singerWaiter.work(); return 0; }
編譯器不知道該調用哪個基類的work方法,所以會報singerWaiter.work();不明確錯誤。
2)從多個基類間接繼承同一個類的多個實例
如下例所示:
class Worker {}; class Waiter:public Worker {}; class Singer:public Worker {}; class SingerWaiter:public Waiter,public Singer {}; int main() { SingerWaiter singerWaiter; Worker *pw = &singerWaiter; return 0; }
會報錯Worker *pw = &singerWaiter;基類Worker不明確。
這是因為SingerWaiter對象創建時,會分別調用Waiter類和Singer類的構造函數,
Waiter類和Singer類又會分別調用Worker類的構造函數,生成了兩份Worker類的實例,
所以pw指針,不知道該指向哪一份Worker實例。
4.如何解決多繼承帶來的問題
對於問題一,我們在調用時,需要明確指出具體要調用哪個類的方法,如下所示:
int main() { SingerWaiter singerWaiter; singerWaiter.Waiter::work();//調用Waiter類的方法 singerWaiter.Singer::work();//調用Singer類的方法 return 0; }
對於問題二,我們引入了虛基類的概念。如下所示:
class Worker {}; class Waiter:virtual public Worker {}; class Singer:virtual public Worker {}; class SingerWaiter:public Waiter,public Singer {}; int main() { SingerWaiter singerWaiter; Worker *pw = &singerWaiter; return 0; }
我們在子類繼承時,聲明一個virtual關鍵字,這時,就表明基類Worker是一個虛基類。
實例化SingerWaiter時產生的Waiter對象和Singer對象,共享一個基類Worker對象。
5.使用虛基類需要注意的問題
我們知道繼承關系中,類的構造函數具有傳遞性,如以下代碼所示:
class A { private: int a; public: A(int n=0):a(n){} int get(){return a;} }; class B:public A { private: int b; public: B(int m=0,int n=0):A(n),b(m){} int get(){return b;} }; class C:public B { private: int c; public: C(int q=0,int m=0,int n=0):B(m,n),c(q){} int get(){return c;} void Show() { std::cout<<A::get()<<" "<<B::get()<<" "<<C::get()<<std::endl; } }; int main() { C c(1,2,3); c.Show(); return 0; }
輸出結果為: 3 2 1
調用C的構造函數,則B,A構造函數都將使用傳入的參數進行初始化。
我們再看使用虛基類的情況:
class A { private: int a; public: A(int n=0):a(n){} int get(){return a;} }; class B:virtual public A { private: int b; public: B(int m=0,int n=0):A(n),b(m){} int get(){return b;} }; class C:public B { private: int c; public: C(int q=0,int m=0,int n=0):B(m,n),c(q){} int get(){return c;} void Show() { std::cout<<A::get()<<" "<<B::get()<<" "<<C::get()<<std::endl; } }; int main() { C c(1,2,3); c.Show(); return 0; }
輸出結果為: 0 2 1
說明A並沒有使用傳入的參數進行初始化,
這是因為在虛基類中,我們假想會有多個子類向虛基類傳遞參數,為了避免這種情況,
在虛基類的情況下,禁止了子類C使用中間類B向虛基類A傳遞參數,此時調用的是A的默認構造函數。
那么我們該如何使用參數,初始化虛基類呢?
答案是我們可以在子類C中,直接調用虛基類A的構造函數進行初始化,如以下代碼所示:
class A { private: int a; public: A(int n=0):a(n){} int get(){return a;} }; class B:virtual public A { private: int b; public: B(int m=0,int n=0):A(n),b(m){} int get(){return b;} }; class C:public B { private: int c; public: C(int q=0,int m=0,int n=0):A(m),B(m,n),c(q){} int get(){return c;} void Show() { std::cout<<A::get()<<" "<<B::get()<<" "<<C::get()<<std::endl; } }; int main() { C c(1,2,3); c.Show(); return 0; }
輸出結果為: 3 2 1
跳過中間類,直接調用基類的構造函數,這種方式只適合虛基類。
在非虛基類中會報錯“不允許使用間接非虛擬基類”。
參考資料:《C++ Primer.Plus》 pp.551-567