[英]Difference in usage of pointer downcast & upcast?
我想知道當我們使用向下轉換和向上轉換時指針轉換中究竟發生了什么。 我有2個問題。 其中前兩個是評論。 Q3結束了。
#include<iostream>
using namespace std;
class A{
public:
virtual void f()
{
cout<<"A"<<endl;
}
};
class B: public A{
public:
virtual void f()
{
cout<<"B"<<endl;
}
};
int main()
{
A* pa =new A();
B* pb =new B();
A* paUpcast= new B();
/*
Q1:
Is the line above equivalent to the following?
A* paUpcast = (A*) B;
*/
B* pbDowncast=(B*)paUpcast;
/*
Q2:Why we cannot use the following 2 code;
B* pbDowncast=new A();
B* pbDowncast = (B*) pa;
*/
pa->f();
pb->f();
paUpcast->f();
pbDowncast->f();
return 1;
}
問題3:我正在嘗試總結一條規則,以推斷如果我們使用虛擬函數和指針一起投射會發生什么,但我無法弄明白。
最初,我認為虛函數將引導我們到指針真正指出的位置。 因此,當我們打字
A* paUpcast= new B();
paUpcast->f();
如果Af()是虛函數,則第二行將顯示“B”,因為paUpcast實際上指向B對象
但是,當我們打字
B* pbDowncast=(B*)pa;
pbDowncast->f();
並且它將顯示“A”而不是“B”,這使矛盾發生。
誰能解釋或給我一些提示? 非常感謝
我會嘗試解釋我是如何理解它的。 幫助我的小貼士是考慮樂高積木。
在你的情況下,我們有兩個lego片,一個名為A
,另一個名為B
......但是想象一下B
片是通過連接兩個片形成的片,其中一個片是相同類型的A
:
A B
+-+ +-+-+
|a| |a|b|
+-+ +-+-+
然后,你使用指針來評估每個樂高積木,但每件都有自己的形狀,所以,想象一下:
A* pa =new A();
B* pb =new B();
A* paUpcast= new B();
A *pa --> +-+ new A()
|a|
+-+
B* pb --> +-+-+ new B()
|a|b|
+-+-+
A* paUpcast --> +-+-+ new B()
|a|b|
+-+-+
請注意, paUpcast
指針是A
類型的指針但是拿着一塊B
型, B
塊與A
不同,正如您所看到的那樣,它是一塊略大於它的基礎。
這是你正在談論的upcasting,基指針就像一個通配符,可以在繼承樹上保存任何相關的東西。
A * paUpcast = new B();
上面的線是否等於以下?
A * paUpcast =(A *)B;
好吧,假設你真的想寫這個: A* paUpcast = (A*) new B();
是的。 您創建B
類的新實例並將其存儲到指向A
類的指針中,在分配到指針之前轉換新實例不會改變它無論如何都將存儲到基類指針中的事實。
為什么我們不能使用以下2個代碼;
B * pbDowncast = new A();
B * pbDowncast =(B *)A;
記住樂高積木。 做B* pbDowncast=new A()
時會發生什么?:
B* pbDowncast --> +-+ new A()
|a|
+-+
創建一個新的base clas實例並將其存儲到指向派生類的指針中,如果仔細觀察lego文件是否合適,你會嘗試將基數視為派生的! A
片缺少必須考慮B
類的額外東西 ; 所有這些東西“存儲”到樂高積木的額外部分, B = all the A stuff plus something more
:
B
+-+-----------------------------------+
|a|extra stuff that only B pieces have|
+-+-----------------------------------+
如果你試圖調用只有B
類的方法會發生什么? 有一個B
指針你可以調用所有的B
方法,但是你創建的實例來自A
類,它沒有B
方法,它不是用所有這些額外的東西創建的。
但是,當我們打字
B * pbDowncast =(B *)pa;
pbDowncast-> F();
顯示“A”而不是“B”,這使矛盾發生。
它沒有解釋我的矛盾,記住lego片段, pa
指針指向A
型片:
A *pa --> +-+
|a|
+-+
這篇文章缺少所有B
東西,事實是缺少在標准輸出上打印B
的f()
方法......但它有一個方法f()
在輸出上打印A
我希望它有所幫助!
編輯:
看來你也同意使用垂頭喪氣是不合適的嗎?
不,我不同意。 轉發根本不是不合適的,但取決於它的用途它將是不合適的。 與所有C ++工具一樣,向下轉換具有實用性和使用范圍; 所有尊重良好用途的詭計都是合適的。
那么什么是良好的使用向下轉換工具呢? 恕我直言,任何不會破壞代碼或程序流程的東西,如果程序員知道他在做什么,維護代碼盡可能可讀(對我來說最重要)。
傾向於采用可能的繼承分支是一種常見的做法:
A* paUpcast = new B();
static_cast<B*>(paUpcast)->f();
但是對於更復雜的繼承樹來說會很麻煩:
#include<iostream>
using namespace std;
class A{
public:
virtual void f()
{
cout<<"A"<<endl;
}
};
class B: public A{
public:
virtual void f()
{
cout<<"B"<<endl;
}
};
class C: public A{
public:
virtual void f()
{
cout<<"C"<<endl;
}
};
A* paUpcast = new C();
static_cast<B*>(paUpcast)->f(); // <--- OMG! C isn't B!
要解決這個問題,您可以使用dynamic_cast
A* paUpcast = new C();
if (B* b = dynamic_cast<B*>(paUpcast))
{
b->f();
}
if (C* c = dynamic_cast<C*>(paUpcast))
{
c->f();
}
但是dynamic_cast
因其缺乏性能而眾所周知,您可以研究dynamic_cast
一些替代方案,比如內部對象標識符或轉換運算符,但為了堅持這個問題,如果正確使用,那么向下轉換就不錯了。
不要在C ++中使用C風格的強制轉換! 沒有理由這樣做,它會模糊你想要做的事情的意義,更重要的是,如果你沒有投出你認為自己正在施展的東西,它會產生有趣的結果。 在上面的情況中,你總是可以使用static_cast<T*>()
,盡管所有的向下轉換應該更好地是dynamic_cast<T*>()
,而是:后者只有在static_cast<T*>()
轉換才真正意味着工作的情況下才能成功。返回指向正確對象的指針,可能根據需要調整指針值(指針值可能會在涉及多重繼承的情況下發生變化)。 如果dynamic_cast<T*>(x)
失敗,即x
實際上不是指向類型為T
(或派生)的對象的指針,則返回null。
現在,關於問題:指針強制轉換你只會影響指針而不影響對象。 也就是說,指向對象的類型永遠不會改變。 在問題1的場景中,您創建一個指向派生類型的指針,並將其指定給指向基類型的指針。 由於派生類型是基類型,因此此轉換是隱式的,但它等效於
A* paUpcast = static_cast<A*>(pb);
在第二個場景中,您將paUpcast
轉換為B*
。 由於paUpcast
是將B*
轉換為A*
的結果,因此轉換回B*
使用static_cast<T*>(x)
:當使用static_cast<>()
轉換指針或引用時,可以反轉隱式轉換。 如果要以反轉隱式轉換效果的任何其他方式導航類層次結構,則需要使用dynamic_cast<>()
。 在這種情況下你也可以使用dynamic_cast<>()
,但dynamic_cast<>()
有一些成本。
現在,對於第三種情況,你有pa
實際上指向一個A
對象,它是不是B
對象。 編譯器將允許您將pa
為B*
但是您對編譯器撒謊: pa
不是將B*
隱式轉換為A*
並使用static_cast<B*>(pa)
(或(B*)pa
在這種情況下是等價的,但C風格的強制轉換並不總是等同於static_cast<>()
s)導致未定義的行為:編譯器會做它想做的任何事情。 由於A
和B
對象的布局類似,它最終調用A
的虛函數,但這只是許多可能結果之一,並且不能保證會發生什么。 如果您使用過dyanmic_cast<B*>(pa)
,結果將是一個空指針,表示從pa
到B*
dyanmic_cast<B*>(pa)
不起作用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.