七、C++繼承與多型-多重繼承的那些坑該怎麼填
阿新 • • 發佈:2022-05-18
理解虛基類和虛繼承
多重繼承:程式碼複用,一個派生類有多個基類。如: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()*/