7

我想知道当我们使用向下转换和向上转换时,指针转换到底发生了什么。我有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;
}

Q3:我想总结一个规则来推断如果我们一起使用虚函数和指针转换会发生什么,但我就是想不通。

本来,我认为虚函数会把我们引向指针真正指出的地方。因此,当我们键入

A* paUpcast= new B();
paUpcast->f();

如果 Af() 是虚函数,第二行将显示“B”,因为 paUpcast 实际上是指向 B 对象

但是,当我们键入

B* pbDowncast=(B*)pa;
pbDowncast->f();

它会显示“A”而不是“B”,这会导致矛盾发生。

谁能解释或给我一些提示?非常感谢

4

2 回答 2

10

我会尝试解释我是如何理解它的。帮助我的提示是考虑乐高积木。

在你的情况下,我们有两个乐高积木,一个命名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指针是 type 的指针,A但持有一块 type B,这块与B那块不同A,正如您所看到的,它比它的基数略大。

这就是你所说的向上转换,基指针就像一个通配符,可以在继承树上向下保存任何相关的东西。

A* paUpcast= 新 B();

上面的行是否等同于以下行?

A* paUpcast = (A*) B;

好吧,假设你真的想写这个:A* paUpcast = (A*) new B();是的,它是。您创建B类的新实例并将其存储到指向A类的指针中,在分配到指针之前转换新实例不会改变它将存储到基类指针中的事实。

为什么我们不能使用以下2个代码;

B* pbDowncast=新 A();

B* pbDowncast = (B*) A;

记住乐高积木。做的时候会发生什么B* pbDowncast=new A()?:

B* pbDowncast --> +-+ new A()
                  |a|
                  +-+

创建一个新的基类实例并将其存储到派生类的指针中,如果您仔细观察乐高积木不合适,您会尝试将基类视为派生类!该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”,这使矛盾发生。

这对我来说并不矛盾,记住乐高积木,pa指针指向一块类型A

A *pa --> +-+
          |a|
          +-+

这篇文章缺少所有的B东西,事实是缺少在标准输出上f()打印的方法B……但它有一个在输出上f()打印的方法A

我希望它有帮助!

编辑:

看来您也同意使用downcast是不合适的不是吗?

不,我不同意。向下转换根本不是不合适的,但根据它的用途它会是不合适的。与所有 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,例如内部对象标识符或转换运算符,但为了解决这个问题,如果正确使用向下转换,它一点也不坏。

于 2012-10-18T10:18:11.057 回答
3

不要在 C++ 中使用 C 风格的强制转换!没有理由这样做,它会混淆你正在尝试做的事情的含义,更重要的是,如果你不投射你认为你正在投射的东西,它会产生有趣的结果。在上述情况下,您总是可以使用static_cast<T*>(),尽管所有向下转换可能最好是dynamic_cast<T*>(),而不是:后者只有在转换实际上意味着工作时才会成功,在这种情况下它返回一个指向正确对象的指针,可能将指针值调整为需要(在涉及多重继承的情况下,指针值可能会改变)。如果dynamic_cast<T*>(x)失败,即x不是真正指向类型T(或派生)对象的指针,则返回 null。

现在,关于问题:指针强制转换只会影响指针,不会影响对象。也就是说,指向对象的类型永远不会改变。在问题 1 的场景中,您创建一个指向派生类型的指针并将其分配给指向基类型的指针。由于派生类型基类型,因此这种转换是隐式的,但它等效于

A* paUpcast = static_cast<A*>(pb);

在第二种情况下,您强制paUpcast转换为B*. 由于paUpcast将 a 转换回 a 的结果是B*使用A*:B*转换static_cast<T*>(x)指针或引用时,static_cast<>()您可以反转隐式转换的效果。如果您想以任何其他方式导航类层次结构,而不是反转隐式转换的效果,您需要使用dynamic_cast<>(). 您也可以dynamic_cast<>()在这种情况下使用,但dynamic_cast<>()要付出一些代价。

现在,对于第三种情况,您pa实际上指向了一个A不是对象的B对象。编译器将允许您转换pa为,B*但您对编译器撒谎:pa不是a到 an和 using的隐式转换的结果或者在这种情况下是等效的,但 C 风格的转换并不总是等同于s)导致未定义的行为:编译器做它想做的任何事情。由于 a和object 的布局相似,它最终调用了一个虚函数,但这只是许多可能的结果之一,并不能保证会发生什么。如果你用过B*A*static_cast<B*>(pa)(B*)pastatic_cast<>()ABAdyanmic_cast<B*>(pa)结果将是一个空指针,表示从pato的转换B*不起作用。

于 2012-10-18T07:01:11.027 回答