1. 程式人生 > 其它 >七、C++繼承與多型-多重繼承的那些坑該怎麼填

七、C++繼承與多型-多重繼承的那些坑該怎麼填

理解虛基類和虛繼承

多重繼承:程式碼複用,一個派生類有多個基類。如:class C: public A,public B{};

虛基類:virtual可以修飾繼承方式,是虛繼承,被虛繼承的類,稱作虛基類。class A:virtual public B{};

虛繼承的類中會多一個vbptr指向vbtable,Vbtable中儲存的是虛基類中資料在派生類中的記憶體偏移量,從虛基類中繼承的成員變數會被放在派生類記憶體的最下端。

虛擬函式和虛基類在呼叫的時候是沒有問題的,

但是在delete的時候會發生堆報錯

原因是:基類指標型別的成員p指向派生類物件,永遠指向的是派生類基類部分資料的起始地址,這裡的基類A的起始位置就是vfptr。但是這裡的派生類B是虛繼承的A,虛繼承的部分會放到派生類記憶體的後面,p指向的就是派生類後面的記憶體,這種情況Delete p就不會刪除派生類中的記憶體,造成了上圖中的問題。

class A{
public:
    virtual void func(){cout<<"call A:func"<<endl;}
    void operator delete (void* ptr){
        cout<<"operator delete:"<<ptr<<endl;
        free(ptr);
    }
private:
    int ma;
};
class B:virtual public A{
public:
    void func(){cout<<"call B:func"<<endl;}
    void* operator new (size_t size){
        void *p= malloc(size);
        cout<<"operator new:"<<p<<endl;
        return p;
    }
private:
    int mb;
};
int main(){
    A *p=new B();
    cout<<"main:p"<<p<<endl;
    p->func();
    delete p;
}

delete的記憶體地址與new的記憶體地址不同,所以會造成問題。

菱形繼承問題

繼承的樣子像菱形,叫菱形繼承。類D中會繼承兩個類A中的成員。儘量避開多重繼承。

多重繼承的好處:可以做更多程式碼的複用。

用虛繼承解決上面的問題。

class A{
public:
    A(int data):ma(data){
        cout<<"A()"<<endl;
    }
    ~A(){
        cout<<"~A()"<<endl;
    }
protected:
    int ma;
};

class B:public A{
public:
    B(int data):A(data),mb(data){
        cout<<"B()"<<endl;
    }
    ~B(){
        cout<<"~B()"<<endl;
    }
protected:
    int mb;
};

class C:public A{
public:
    C(int data):A(data),mc(data){
        cout<<"C()"<<endl;
    }
    ~C(){
        cout<<"~C()"<<endl;
    }
protected:
    int mc;
};

class D:public B,public C{
public:
    D(int data):B(data),C(data),md(data){
        cout<<"D()"<<endl;
    }
    ~D(){
        cout<<"~D()"<<endl;
    }
protected:
    int md;
};

int main(){
    D d(10);
    return 0;
}
/*
輸出
A()
B()
A()
C()
D()
~D()
~C()
~A()
~B()
~A()*/

如果虛繼承就會在B和C中構造出vbptr指標,在D中指向類A中的成員變數。

使用虛繼承避免繼承多次的問題:

class B:virtual public A{//使用虛繼承避免菱形繼承的問題
public:
    B(int data):A(data),mb(data){
        cout<<"B()"<<endl;
    }
    ~B(){
        cout<<"~B()"<<endl;
    }
protected:
    int mb;
};

class C:virtual public A{
public:
    C(int data):A(data),mc(data){
        cout<<"C()"<<endl;
    }
    ~C(){
        cout<<"~C()"<<endl;
    }
protected:
    int mc;
};

class D:public B,public C{
public:
    D(int data):A(data),B(data),C(data),md(data){
        cout<<"D()"<<endl;
    }
    ~D(){
        cout<<"~D()"<<endl;
    }
protected:
    int md;
};

/*
輸出結果:
A()
B()
C()
D()
~D()
~C()
~B()
~A()*/

C++的四種類型轉換

const_cast:去掉(指標或者引用)常量屬性的一個型別轉換

static_cast:提供編譯器認為安全的型別轉換 基類和派生類可以通過static_cast進行轉換

reinterpret_cast:類似於c風格的強制型別轉換(不安全)

dynamic_cast:主要用在繼承結構中,可以支援RTTI型別識別的上下轉換

解釋一下dynamic_cast的用法:

class Base{
public:
    virtual void func()=0;
};

class Derive1:public Base{
public:
    void func() override {
        cout<<"Derive1::func()"<<endl;
    }
};

class Derive2:public Base{
public:
    void func() override {
        cout<<"Derive2::func()"<<endl;
    }
    //如果想要在這個類裡新增一個新業務
    void funcDerive2(){
        cout<<"Derive2::funcDerive2()"<<endl;
    }
};

/**
 * 外部呼叫上面兩個類的介面
 * @param p
 */
void show(Base* p){
    //dynamic_cast會檢查p指標是否指向的是一個Derive2型別的物件
    //如果是,dynamic_cast轉換型別成功,返回Derive2物件的地址給dp2;否則返回nullptr
    Derive2 *dp2=dynamic_cast<Derive2*> (p);
    if(dp2!= nullptr){
        dp2->funcDerive2();
    }else
        p->func();
}

int main(){
    Derive1 d1;
    Derive2 d2;
    show(&d1);
    show(&d2);

    return 0;
}

/*
輸出結果:
Derive1::func()
Derive2::funcDerive2()*/