下面說說C++多重繼承中關於指針的一些問題。
指針指向問題
先看下面的程序:
class Base1
{
public:
virtual void fun1() {cout << "Base1::fun1" << endl;};
};
class Base2
{
public:
virtual void fun2() {cout << "Base2::fun1" << endl;};
};
class Derive : public Base1, public Base2
{
public:
virtual void fun1 () {cout << "Derive::fun1" << endl;}
virtual void fun2 () {cout << "Derive::fun2" << endl;}
};
int main()
{
Derive oD;
Base1 *pB1 = (Base1*)(&oD);
Base2 *pB2 = (Base2*)(&oD);
cout << "&oD=" << &oD << '\n';
cout << "pB1=" << pB1 << '\n';
cout << "pB2=" << pB2 << '\n';
if (&oD == pB1) cout << "&oD == pB1" << '\n';
if (&oD == pB2) cout << "&oD == pB2" << '\n';
}
我電腦上的運行結果:
首先,可以看到&oD和pB1指針指向相同的存儲地址。為什麼?
這是因為當我們new一個Derive類的時候,計算機給Derive類分配的空間可以分為三部分:首先是類Base1的部分,然後是Base2的部分,然後是Derive中除去Base和Base2剩余部分,如下圖。
所以&oD肯定保存的是整體的首地址,而pB1指向的是Base1的首地址,恰好也是整體的首地址,所以有&oD和pB1的值剛好相等。pB2則指向的是Base2的首地址。 猜猜執行結果: 下面我們探索下為什麼輸出的是Derive::fun1。 給添加一個函數fun3,然後再次執行上面的main函數,結果如下: 上面強制將Base1轉為Base2不會報錯,但是不能運行處正確結果。而我們強制將Base2轉為Base1呢? 這樣程序執行到第6行的時候會直接奔潰。
可是後面為什麼會有&oD == pB2呢?這是因為當編譯器發現一個指向派生類的指針和指向其某個基類的指針進行==運算時,會自動將指針做隱式類型提升已屏蔽多重繼承帶來的指針差異。因為兩個指針做比較,目的通常是判斷兩個指針是否指向了同一個內存對象實例,在上面的場景中,&oD和pB2雖然指針值不等,但是他們確確實實都指向了同一個內存對象(即new Derive產生的內存對象)。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxociAvPg0KPHA+PHN0cm9uZz7WuNXrwODQzdequ7vOysziPC9zdHJvbmc+PGJyIC8+DQq7ucrHyrnTw8nPw+a1xMDgo6y/tNb3uq/K/aO6PC9wPg0KPHByZSBjbGFzcz0="brush:java;">
int main(){
Derive oD;
cout << "&oD=" << &oD << '\n';
Base1 *pB1 = &oD;
cout << "pB1=" << pB1 << '\n';
pB1->fun1();
cout << endl;
Base2 *pB2 = (Base2*)(pB1); // 指針強行轉換,沒有偏移
cout << "pB2=" << pB2 << '\n';
pB2->fun2();
cout << endl;
pB2 = dynamic_cast
是不是很意外,為什麼pB2->fun2()的結果是Derive::fun1。這裡我們看到的是使用強制類型轉換是不能把Base1類型的指針轉成Base2類型的指針的,必須使用dynamic_cast的形式進制轉換才奏效。
我們修改Base1的定義:
class Base1
{
public:
virtual void fun3() {cout << "Base1::fun3" << endl;};
virtual void fun1() {cout << "Base1::fun1" << endl;};
};
我們可以發現強制轉換不會成功,也不會報錯,你調用Base2的fun2函數,因為強制轉換不成功,所以指針仍然指向Base1,而Base1中沒有fun2,所以就會自動調用聲明的函數中的第一個函數。(不知道為什麼會這樣設計!)
int main()
{
Derive *pD = new Derive();
Base2 *pB2 = pD;
pB2->fun2();
Base1 *pB1 = (Base1*)(pB2);
pB1->fun1();
return 0;
}
所以:
1. C++多重繼承需要慎用
2. 類型轉換盡量采用c++內置的類型轉換函數,而不要強行轉換。