向上/下造型

发布时间 2023-11-13 16:07:18作者: ZTer
  • 孔子云:杀鸡用牛刀者,所谓向上造型也。

向上造型

显然孔子没说过开头那段话。

假设我们有一个父类 A 类,一个子类 B 类。

class A
{
public:
  A() : i(10) {}
  void Print()
  {
    cout << "a.i = " << i << endl;
  }
private:
  int i;
};

class B : public A
{
public:
  B() : j(10) {}
  void Print()
  {
    cout << "b.j = " << j << endl;
  }
private: 
  int j;
};

在类的组合与继承一节中,我们了解到,B 类继承了 A 类所有的东西。

那么实际上,我们可以把 B 类看成是一种特殊的 A 类。

打个比方:

  • 子类是学生,父类是人。你可以认为我不是学生,而从人的角度来审视我。人会做的事情学生都会做。
  • 同样的,子类是 B,父类是 A,你可以把 B 看成一种特殊的 A,A 会做的事情 B 也都会做,A 不会做的事情 B 也会做,所以我们把 B 当作 A 类使用是完全合理的 (这就解释了孔子为什么说杀鸡用牛刀)

所谓向上造型,就是把子类当作父类来使用的意思。

因此我们把父类的指针指向子类的对象是合法并且安全的。

int main()
{
  A a;
  B b;
  A* p = &b;
}

p 指针可以调用 b 中所有从父类继承来的 public 变量和方法。

向上造型会屏蔽子类函数

我们现在来做一个实验,注意到父类和子类中均有 Print 的方法,现在我们执行 p -> Print();

程序输出了 a.i = 10 ,而不是 b.j = 10

按照类的继承中的函数名隐藏原则,子类会屏蔽掉父类的同名(及其所有重载)函数。

p 指针是把 b 视为一个 A 类,而 A 类中只有一个 Print 函数,这时反而是子类的函数被直屏蔽掉了,因此 p 指针调用的是父类 A 中的 Print 方法。

同样的理由,p 指针也无法调用子类 public 中的方法和变量,因为它们全部被屏蔽掉了。

int main()
{
  A a;
  B b;
  A* p = &b;
  //p -> B::Print();
  //提示报错 “B::Print不是 A 或 A 的父类中的成员”
}

当我们选择向上造型的时候,父类的父类以及之前的所有父类中 public 的方法都可以被调用,父类的子类及之后的所有子类中 public 的方法全部无法调用。

向下造型

  • 人是父类,学生是子类,人不会写作业,学生会写作业。你在人群里抓了一个人出来,让他写作业。

这显然有两种可能,第一种:你抓住的这个人正好是学生,那么他可以执行你“写作业”的命令。

第二种:他不是个学生,无法执行“写作业”的命令,这就比较危险了。

向下造型是不安全的,因为你不知道一个对象到底会不会做某件它的子类会做的事。

因此,我们不建议在程序中使用向下造型的方法。

但是向下造型可以让我们实现一些 邪恶的 操作,比如这样:

int main()
{
  B b;

  int* p = (int*) &b;
  *p = 15;
  p ++;
  *p = 20;
  b.A::Print();
  b.Print();

  return 0;
}

我们强制把对象 b 向下造型为 int 类型,然后通过 int* 类型的指针调用它。

然后我们尝试输出 b 类中的私有变量 i 和 j,看看会发生什么。

output:
a.i = 15
b.j = 20

发现 private 中的数据被成功从外部修改了,这说明两个问题:

  1. 我们通过向下造型取消了私有变量的访问限制。
  2. 我们通过指针自加的操作从父类中的 i 移动到了子类中的 j ,这说明在继承父类的时候,子类的变量和从父类继承来的变量之间地址是连续的。