31、不一樣的C++系列--多重繼承
阿新 • • 發佈:2019-01-01
多重繼承
- 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 完成