我想知道当我们使用向下转换和向上转换时指针转换中究竟发生了什么。 我有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();
如果A.f()是虚函数,第二行将显示“B”,因为paUpcast实际上指向B对象
然而,当我们输入
时B* pbDowncast=(B*)pa;
pbDowncast->f();
它将显示“A”而不是“B”,这使矛盾发生。
任何人都可以解释或给我一些提示吗?非常感谢
答案 0 :(得分:8)
我会尝试解释我是如何理解它的。帮助我的提示就是考虑乐高积木。
在你的情况下,我们有两个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; </ p>
好吧,假设你真的想写这个: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
种类;所有这些东西“存储”到lego作品的额外部分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-&GT; F();
显示“A”而不是“B”,这使矛盾发生。
它没有解释我的矛盾,记住lego片段,pa
指针指向A
类型的片段:
A *pa --> +-+
|a|
+-+
这篇文章缺少所有B
内容,事实是缺少在标准输出上打印f()
的{{1}}方法......但它有一个方法{{ 1}}在输出上打印B
。
我希望它有所帮助!
编辑:
似乎你也同意使用垂头丧气是不合适的吗?
不,我不同意。转发根本不是不合适的,但取决于它的用途它将是不合适的。与所有C ++工具一样,向下转换具有实用性和使用范围;尊重良好用途的所有诡计都是恰当的。
那么对于向下转换工具有什么用呢?恕我直言,任何不会破坏代码或程序流程的东西,如果程序员知道他在做什么,保持代码尽可能可读(对我来说最重要)。
倾向于采用可能的继承分支是一种常见的做法:
f()
但是使用更复杂的继承树会很麻烦:
A
要解决此问题,您可以使用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
已为lack of performance所熟知,您可以研究A* paUpcast = new C();
if (B* b = dynamic_cast<B*>(paUpcast))
{
b->f();
}
if (C* c = dynamic_cast<C*>(paUpcast))
{
c->f();
}
的一些替代方案,例如内部对象标识符或转换运算符,但为了坚持这个问题,请继续关注如果使用得当,一点都不错。
答案 1 :(得分:3)
不要在C ++中使用C风格的强制转换!没有理由这样做,它会模糊你想要做的事情的意义,更重要的是,如果你没有投出你认为自己正在施展的东西,它会产生有趣的结果。在上面的情况中,你总是可以使用static_cast<T*>()
,尽管所有的向下转换应该更好地是dynamic_cast<T*>()
,而是:后者只有在强制转换才能工作的情况下才能成功,在这种情况下它会返回一个指向正确的对象,可能根据需要调整指针值(指针值可能会在涉及多重继承的情况下发生变化)。如果dynamic_cast<T*>(x)
失败,即x
实际上不是指向T
(或派生)类型的对象的指针,则返回null。
现在,关于问题:指针强制转换你只会影响指针而不影响对象。也就是说,指向对象的类型永远不会改变。在问题1的场景中,您创建一个指向派生类型的指针,并将其指定给指向基类型的指针。由于派生类型是-a 基类型,因此此转换是隐式的,但它等效于
A* paUpcast = static_cast<A*>(pb);
在第二种情况下,您将paUpcast
投射到B*
。由于paUpcast
是将B*
转换为A*
的结果,因此使用B*
转换回static_cast<T*>(x)
:使用{{转换指针或引用时1}}你可以扭转隐式转换的效果。如果要以反转隐式转换效果的任何其他方式导航类层次结构,则需要使用static_cast<>()
。您也可以在这种情况下使用dynamic_cast<>()
,但dynamic_cast<>()
会有一些费用。
现在,对于第三种情况,您dynamic_cast<>()
实际上指向的pa
对象不是A
对象。编译器允许您将B
强制转换为pa
,但是您对编译器撒谎:B*
不隐式转换{{}的结果1}}到pa
并使用B*
(或A*
在这种情况下等效但C风格的强制转换不始终等同于{{1 s)导致未定义的行为:编译器执行它想要做的任何事情。由于static_cast<B*>(pa)
和(B*)pa
对象的布局类似,它最终会调用static_cast<>()
的虚函数,但这只是许多可能结果之一,并且无法保证会发生什么。如果您使用过A
,那么结果将是一个空指针,表示从B
到A
的强制转换不起作用。