游戏开发工具

将派生类指针赋值给基类指针时到底发生了什么?

通过上节最后一个例子我们发现,将派生类的指针赋值给基类的指针后,它们的值有可能相等,也有可能不相等。例如执行pc = pd;语句后,pc 的值为 0x9b1800,pd 的值为 0x9b17f8,它们不相等。

我们通常认为,赋值就是将一个变量的值交给另外一个变量,这种想法虽然没错,但是有一点要注意,就是赋值以前编译器可能会对现有的值进行处理。例如将 double 类型的值赋给 int 类型的变量,编译器会直接抹掉小数部分,导致赋值运算符两边变量的值不相等。请看下面的例子:

#include <iostream>
using namespace std;

int main()
{
    double pi = 3.14159;
    int n = pi;
    cout<<pi<<", "<<n<<endl;
    return 0;
}

运行结果:

3.14159, 3


pi 的值是 3.14159,执行int n = pi;后 n 的值变为 3,虽然是赋值,但是 pi 和 n 的值并不相等。

将派生类的指针赋值给基类的指针时也是类似的道理,编译器也可能会在赋值前进行处理。要理解这个问题,首先要清楚 D 类对象的内存模型,如下图所示:

1.jpg


首先要明确的一点是,对象的指针必须要指向对象的起始位置。对于 A 类和 B 类来说,它们的子对象的起始地址和 D 类对象一样,所以将 pd 赋值给 pa、pb 时不需要做任何调整,直接传递现有的值即可;而 C 类子对象距离 D 类对象的开头有一定的偏移,将 pd 赋值给 pc 时要加上这个偏移,这样 pc 才能指向 C 类子对象的起始位置。也就是说,执行pc = pd;语句时编译器对 pd 的值进行了调整,才导致 pc、pd 的值不同。

下面的代码演示了将 pd 赋值给 pc 时编译器的调整过程:

pc = (C*)( (int)pd + sizeof(B) );

如果我们把 B、C 类的继承顺序调整一下,让 C 在 B 前面,如下所示:

class D: public C, public B

那么输出结果就会变为:

pa=0x317fc
pb=0x317fc
pc=0x317f8
pd=0x317f8

相信聪明的你能够自行分析出来。