1.繼承的三種方式:函數
公有繼承(public),私有繼承(private),保護繼承(protected)
三種繼承方式的說明,以下表所示:spa
特徵 | 公有繼承 | 保護繼承 | 私有繼承 |
公有成員變成 | 派生類的公有成員 | 派生類的保護成員 | 派生類的私有成員 |
保護成員變成 | 派生類的保護成員 | 派生類的保護成員 | 派生類的私有成員 |
私有成員變成 | 只能經過基類接口訪問 | 只能經過基類接口訪問 | 只能經過基類接口訪問 |
可否隱式向上轉換 | 是 | 是 | 否 |
2.什麼是多繼承指針
一個類有多個基類,那麼這種繼承關係就叫作多繼承。
好比有兩個類,服務員類Waiter,歌手類Singer,咱們有一個類既是服務員,又是歌手,
那麼咱們能夠定義類的多繼承關係以下:code
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)從多個基類間接繼承同一個類的多個實例
以下例所示:blog
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; }
對於問題二,咱們引入了虛基類的概念。以下所示:get
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