C++多繼承


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM