向上/下造型
- 孔子云:杀鸡用牛刀者,所谓向上造型也。
向上造型
显然孔子没说过开头那段话。
假设我们有一个父类 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 中的数据被成功从外部修改了,这说明两个问题:
- 我们通过向下造型取消了私有变量的访问限制。
- 我们通过指针自加的操作从父类中的 i 移动到了子类中的 j ,这说明在继承父类的时候,子类的变量和从父类继承来的变量之间地址是连续的。