深入理解C++多态

在之前发的一篇文章《虚函数表分析-C++多态的实现》中,已经分析过C++多态的实现原理。这篇文章来看一个具体的例子,这个例子来源于一道经典的C++面试题,看起来蛮简单,但是相当多的人都在这里翻车了。这篇文章希望能把这道题阐述清楚,给大家带来一些帮助。题目给了下面这样一段代码(代码我略有更改),问程序的输出是什么,并解释输出结果。

class A {
public:
    int a;
    virtual void x() {
        cout << "A::x()" << endl;
    }
    void y() {
        x();
        cout << "A::y()" << endl;
    }
};
class B:public A {
public:
    int b;
    virtual void x() {
        cout << "B::x()" << endl;
    }
    virtual void y() {
        x();
        cout << "B::y()" << endl;
    }
};
int main()
{
    A* p = new B;
    p->y();
    return 0;
}

大家可以先自己想一想这段代码的执行结果是什么,再看后面的内容。

这段代码的打印结果是:

B::x()
A::y()

如何解释这个执行结果呢?我们先来分析一下A,B两个对象的内存布局是什么样子。A定义了一个int型的成员变量a,一个虚函数x(),一个普通成员函数y()。那么根据我们之前的分析可以知道,A对象的内存中应该包含一个虚函数指针和一个int类型的变量a。虚函数表指针指向一张虚函数表,虚函数表中保存着A::x()的函数地址。由于A::y()不是虚函数,因此y()的地址不会写入虚函数表,调用y()函数是编译器静态联编实现的。如果在32位系统上,那么一个A对象的实例应该占用8个字节(一个指针占用4字节,一个int变量占用4个字节)。其内存布局如图所示:

B对象公有继承了A对象,那么B对象的内存中同样有一个虚函数表指针,一个int型变量a。B中的虚函数表的内容和A的虚函数表内容是一模一样。但是要注意由于B重写了A的x()函数,那么B的虚函数表中,本来存放A::x()函数地址的地方现在被替换为B::x()函数地址。同时因为B::y()函数也是一个虚函数,因此B::y()的函数地址被追加到B的虚函数表的末尾。B继承了A的a变量,同时自己也定义了一个int型的变量b。如果是在一个32位系统上,那么一个B对象的实例将占用12个字节(一个指针,两个int类型变量)。B的内存布局如下图所示:

现在回到代码上来,为什么执行p->y()打印出来的结果是调用了B的x函数,A的y函数呢?在main程序中,我们new了一个B对象,用一个A指针来指向它。那么A指针在B的内存当中可访问的范围如下图:

因此通过p指针能访问的函数只有B::x()和A::y(),通过p能访问的成员变量只有a。调用p->y()函数的时候,由于p是一个A*指针,此时调用的是A的普通成员函数A::y(),这个函数调用是在编译器编译的时候已经确定的。我们知道当一个成员函数被调用时,编译程序会向它传递一个隐含的参数,这个参数是一个指向这个成员函数所在的对象的指针,这个指针的值会被赋值给this指针。

在此例当中就是说执行p->y()时,会向y函数传递p指针赋值给this指针。在A::y()中调用x()函数,相当于调用this->x(),调用this->x()也就是调用p->x()。到这里就很清楚了,调用p->x()的时候,编译器从p的虚函数表中寻找x()函数的地址执行,显然找到的是B::x()的地址。因此,这里执行B的x()函数,打印出B::x(),随后接着执行A的y()函数,打印出A::y()。

全部评论

相关推荐

vegetable_more_exercise:1-1.5万,没错啊,最少是1人民币,在区间内
点赞 评论 收藏
分享
最近和朋友聊天,她说了句让我震惊的话:"我发现我连周末点外卖都开始'最优解'了,一定要赶在高峰期前下单,不然就觉得自己亏了。"这不就是典型的"班味入侵"吗?工作思维已经渗透到生活的方方面面。
小型域名服务器:啊?我一直都这样啊?我还以为是我爱贪小便宜呢?每次去实验室都得接一杯免费的开水回去,出门都得规划一下最短路径,在宿舍就吃南边的食堂,在实验室就吃北边的食堂,快递只有顺路的时候才取。
点赞 评论 收藏
分享
点赞 1 评论
分享
牛客网
牛客企业服务