1. 程式人生 > >31、不一樣的C++系列--多重繼承

31、不一樣的C++系列--多重繼承

多重繼承

  • C++支援編寫多重繼承的程式碼
    • 一個子類可以擁有多個父類
    • 子類擁有所有父類的成員變數
    • 子類繼承所有父類的成員函式
    • 子類物件可以當作任意父類物件使用
  • 多重繼承的語法規則
//多重繼承的本質與單繼承相同
class Derived : public BaseA,
                public BaseB,
                public BaseC
{
        // ......
};

多重繼承的問題一

這裡首先看一段程式碼:

#include <iostream>
#include <string>

using
namespace std; //定義父類BaseA class BaseA { //成員變數 int ma; public: //帶參建構函式 BaseA(int a) { ma = a; } //成員函式 int getA() { return ma; } }; //定義父類BaseB class BaseB { //成員變數 int mb; public: //帶參建構函式 BaseB(int b) { mb = b; } //成員函式
int getB() { return mb; } }; //子類Derived同時繼承BaseA 和 BaseB class Derived : public BaseA, public BaseB { //子類的成員變數 int mc; public: //子類帶參建構函式 初始化列表中初始化2個父類 Derived(int a, int b, int c) : BaseA(a), BaseB(b) { mc = c; } //成員函式 int getC() { return
mc; } //成員函式 void print() { cout << "ma = " << getA() << ", " << "mb = " << getB() << ", " << "mc = " << mc << endl; } }; int main() { cout << "sizeof(Derived) = " << sizeof(Derived) << endl; // 12 Derived d(1, 2, 3); d.print(); cout << "d.getA() = " << d.getA() << endl; cout << "d.getB() = " << d.getB() << endl; cout << "d.getC() = " << d.getC() << endl; cout << endl; return 0; }

上述程式碼是一個非常標準的多重繼承的示例,輸出結果如下:

sizeof(Derived) = 12
ma = 1, mb = 2, mc = 3
d.getA() = 1
d.getB() = 2
d.getC() = 3

因為子類初始化時在初始化列表中也初始化了2個父類,所以呼叫父類函式可以輸出從父類繼承的成員變數。此時,我們再做一點其它操作:

    //使用子類物件指標分別賦值給父類指標
    BaseA* pa = &d;
    BaseB* pb = &d;

    //分別呼叫函式
    cout << "pa->getA() = " << pa->getA() << endl;
    cout << "pb->getB() = " << pb->getB() << endl;

    cout << endl;

    //使用2個指標分別指向父類指標
    void* paa = pa;
    void* pbb = pb;

    //判斷2個指標是否相等
    if( paa == pbb )
    {
        cout << "Pointer to the same object!" << endl; 
    }
    else
    {
        cout << "Error" << endl;
    }
    //再分別輸出4個指標
    cout << "pa = " << pa << endl;
    cout << "pb = " << pb << endl;
    cout << "paa = " << paa << endl;
    cout << "pbb = " << pbb << endl; 

輸出結果為:

sizeof(Derived) = 12
ma = 1, mb = 2, mc = 3
d.getA() = 1
d.getB() = 2
d.getC() = 3

pa->getA() = 1
pb->getB() = 2

Error
pa = 0x7fff58957a00
pb = 0x7fff58957a04
paa = 0x7fff58957a00
pbb = 0x7fff58957a04

有沒有發現一個奇怪的事情,對子類物件d取地址後分別賦值給2個父類指標,此時竟然會得到2個不同的地址。也就是說 **通過多重繼承得到的物件可能擁有不同的地址** !如下所示:

Derived d(1, 2, 3);
BaseA* pa = &d;
BaseB* pb = &d;

pa  ====>   int ma;
            int mb;      <====  pb
            int mc;

多重繼承的問題二

接下來再看一段程式碼:

#include <iostream>
#include <string>

using namespace std;

class People
{
    string m_name;
    int m_age;
public:
    People(string name, int age)
    {
        m_name = name;
        m_age = age;
    }
    void print()
    {
        cout << "Name = " << m_name << ", "
             << "Age = " << m_age << endl;
    }
};

class Teacher : virtual public People
{
public:
    Teacher(string name, int age) : People(name, age)
    {
    }
};

class Student : virtual public People
{
public:
    Student(string name, int age) : People(name, age)
    {
    }
};

class Doctor : public Teacher, public Student
{
public:
    Doctor(string name, int age) : Teacher(name, age), Student(name, age), People(name, age)
    {
    }
};

int main()
{
    Doctor d("Delphi", 33);

    d.print();

    return 0;
}

在上述程式碼中,Teacher類和Student類都是People類的子類,然後Doctor類又同時繼承Student類和Teacher類,這裡會引起歧義。先看下輸出結果:

Name = Delphi, Age = 33

看到結果,可以自問一下,d呼叫print函式,這個print函式是Teacher類的還是Student類的呢?其實這裡反映出多重繼承的第二個問題: 當多重繼承關係出現閉合時將產生資料冗餘的問題!

那如何解決呢? 可是使用 虛繼承

class People{ };
class Teacher : virtual public People{};
class Student : virtual public People{};
class Doctor : public Teacher, public Student
{

};
  • 虛繼承能夠解決資料冗餘問題
  • 中間層父類不再關係頂層父類的初始化
  • 最終子類必須直接呼叫頂層父類的建構函式

多重繼承的問題三

在問題二中,使用虛繼承的方式來解決多繼承閉合造成的資料冗餘問題,但是又引發了另一個問題:

當架構設計中需要繼承時,無法確定使用直接繼承還是虛繼承!!

多重繼承的問題四

接下來看第四個問題,先看一段程式碼:

#include <iostream>
#include <string>

using namespace std;

//定義BaseA類
class BaseA
{
public:
    //定義虛擬函式funcA()
    virtual void funcA()
    {
        cout << "BaseA::funcA()" << endl;
    }
};
//定義BaseB類
class BaseB
{
public:
    //定義虛擬函式funcB()
    virtual void funcB()
    {
        cout << "BaseB::funcB()" << endl;
    }
};
//定義Derived類 並同時繼承BaseA和BaseB類
class Derived : public BaseA, public BaseB
{

};

int main()
{
    Derived d;
    //用子類物件d分別賦值給2個父類指標
    BaseA* pa = &d;
    BaseB* pb = &d;
    //把BaseA指標直接強轉為BaseB指標
    BaseB* pbe = (BaseB*)pa;
    //使用dynamic_cast來轉換指標
    BaseB* pbc = dynamic_cast<BaseB*>(pa);

    cout << "sizeof(d) = " << sizeof(d) << endl;

    cout << "Using pa to call funcA()..." << endl;
    pa->funcA();

    cout << "Using pb to call funcB()..." << endl;
    pb->funcB();

    cout << "Using pbe to call funcB()..." << endl;
    pbe->funcB();

    cout << "Using pbc to call funcB()..." << endl;
    pbc->funcB();

    cout << endl;

    cout << "pa = " << pa << endl;
    cout << "pb = " << pb << endl;
    cout << "pbe = " << pbe << endl;
    cout << "pbc = " << pbc << endl;

    return 0;
}

執行結果如下:

sizeof(d) = 16
Using pa to call funcA()...
BaseA::funcA()
Using pb to call funcB()...
BaseB::funcB()
Using pbe to call funcB()...
BaseA::funcA()
Using pbc to call funcB()...
BaseB::funcB()

pa = 0x7fff59f519f8
pb = 0x7fff59f51a00
pbe = 0x7fff59f519f8
pbc = 0x7fff59f51a00

有沒有發現在轉換指標時,如果直接進行強制轉換,呼叫的還是原指標指向的方法,也就是說pbe指標雖然是父類指標BaseB型別,但是實際是父類指標BaseA型別,所以呼叫funcB函式時輸出funcA。但如果使用dynamic_cast的時候,就不會存在這個問題。

所以在需要進行強制型別轉換時,C++中推薦使用新式型別轉換關鍵字: dynamic_cast

正確使用多重繼承

  • 工程開發中的 “多重繼承” 方式:
    • 單繼承某個類 + 實現(多個)介面

就像這樣:

#include <iostream>
#include <string>

using namespace std;

//定義父類Base
class Base
{
protected:
    //成員變數mi,子類可訪問,外界不可訪問
    int mi;
public:
    //帶參建構函式
    Base(int i)
    {
        mi = i;
    }
    //成員函式
    int getI()
    {
        return mi;
    }
    //這個函式的作用是判斷obj是否是當前物件
    bool equal(Base* obj)
    {
        return (this == obj);
    }
};

//定義介面Interface1
class Interface1
{
public:
    //2個純虛擬函式
    virtual void add(int i) = 0;
    virtual void minus(int i) = 0;
};

//定義介面Interface2
class Interface2
{
public:
    //2個純虛擬函式
    virtual void multiply(int i) = 0;
    virtual void divide(int i) = 0;
};

//定義子類 並繼承父類Base,並實現2個介面
class Derived : public Base, public Interface1, public Interface2
{
public:
    //初始化時 初始化Base類
    Derived(int i) : Base(i)
    {
    }
    void add(int i)
    {
        mi += i;
    }
    void minus(int i)
    {
        mi -= i;
    }
    void multiply(int i)
    {
        mi *= i;
    }
    void divide(int i)
    {
        if( i != 0 )
        {
            mi /= i;
        }
    }
};

int main()
{
    Derived d(100);
    Derived* p = &d;
    Interface1* pInt1 = &d;
    Interface2* pInt2 = &d;

    cout << "p->getI() = " << p->getI() << endl;    // 100

    pInt1->add(10);
    pInt2->divide(11);
    pInt1->minus(5);
    pInt2->multiply(8);

    cout << "p->getI() = " << p->getI() << endl;    // 40

    cout << endl;

    cout << "pInt1 == p : " << p->equal(dynamic_cast<Base*>(pInt1)) << endl;
    cout << "pInt2 == p : " << p->equal(dynamic_cast<Base*>(pInt2)) << endl;

    return 0;
}

輸出結果為:

p->getI() = 100
p->getI() = 40

pInt1 == p : 1
pInt2 == p : 1
  • 一些建議
    • 先繼承自一個父類,然後實現多個介面
    • 父類中提供equal( )成員函式
    • equal( ) 成員函式用於判斷指標是否指向當前物件
    • 與多重繼承相關的強制型別轉換用 dynamic_cast 完成