1、思考,不同类型的指针,到底有什么区别?
指向Animal的指针和指向Dog的指针,到底有什么区别?首先,指针的表示方法相同,指针的内容相同,都是一个int,表示地址。区别只是指向对象的类型不同(好像是废话)。这有什么意义呢? 这其实是告诉了编译器如何解释这个地址中的内存内容以及大小。也就是说,对于指向Animal 的指针,编译器把指向内容当作一个Animal,对于指向Dog 指针,编译器把指向的内容当作Dog。
2、那么,问题来了,Animal* pa = new Dog(),是怎么做到运行期多态的呢?
pa的表面类型是Animal,真实类型是Dog,编译器在编译时只知道pa的表面类型,那么把pa当作Animal来解释,能不能做到运行时多态呢?简单回忆一下,Animal对应一个虚方法表(虚方法表可认为一个数组,元素是方法指针),Dog对应一个虚方法表,可以认为Dog中的虚方法表是整体拷贝了Animal 的虚方法表,对于重写的方法在对应的位置换上重写后的方法,对于新增的虚方法,加在虚方法表的最后。对象的内存中只有实例字段和vptr,vptr指向vtbl(虚方法表),根据对应的方法,在vtbl对应的位置上,找到方法指针,进而找到方法。
对于Animal对象和Dog对象,可认为在相同位置都放着vptr。这样的话,pa指向内存中的一个Dog对象,我把指向的内容当作Animal来解释,也能找到vptr,但是这个vptr指向Dog类的虚方法表。由于Dog类中虚方法表,是在Animal虚方法表的基础上,进行了替换,这就构成了运行时多态。
3、现在考虑,Animal和Dog都有一个Name字段,pa->Name 访问是Animal的字段,还是Dog 的字段呢?
Animal对象和Dog对象的内存分布,举例来说,Animal对象为:age,Name,vptr,那么Dog对象的内存分布就是age,Name,vptr,Name。前面Name的作用域是父类,后面Name的作用域是子类,子类作用域小于父类作用域。
编译器把pa指向的内容,当作Animal来解释,取的是Animal的字段。如果想要取出Dog 的Name字段怎么办呢?当然是,告诉编译器,把pa指向的内容当作Dog来解释,强制类型转化为Dog指针。