【C++】C++虚函数表和多态

        对于虚函数表和多态的掌握,意味着在实际的开发过程中,程序员能够充分利用C++面向对象编程中的多态技术,写出高拓展性以及耦合度比较低的优质代码。

        另外,在C++面试的过程中,这个问题也是问的比较多的,如果不懂这个问题,就代表面试者对C++的掌握还是比较初级的阶段。

        接下来,我们就站在编译器的角度去探究C++幕后的秘密。

        首先,我们观察一下,虚函数引入之后,类会产生什么变化。接着,就会引出虚函数表的生成时机和生成原因。然后再涉及到虚函数表指针被赋值的时机,之后,就要看一看类对象在内存中的布局,最后把这些知识点串起来,再引出 虚函数表在支持多态方面的工作原理

虚函数

       首先创建一个空类A。

#include <iostream>

class A{
    
};

int main() {
    A a;
    cout<<"sizeof(a) = "<<sizeof(a)<<endl;
    return 0;
}

        运行结果:

sizeof(a) = 1

        千万不要以为一个空类的sizeof值为0,一个对象,只要它占用内存空间, 那么这个对象占用的内存空间至少是1,哪怕是空类。接下来,继续向类A中加入两个普通的成员函数。

#include <iostream>

using namespace std;

class A{
public:
    void func1(){}
    void func2(){}
};

int main() {
    A a;
    cout<<"sizeof(a) = "<<sizeof(a)<<endl;
    return 0;
}

        再次执行程序,运行结果:

sizeof(a) = 1

         这时,发现A类对象a的sizeof值还是1,这说明这个类A的普通成员函数,它并不占用类对象的内存空间。接着继续再向类A中放入一个虚函数。

#include <iostream>

using namespace std;

class A{
public:
    void func1(){}
    void func2(){}
public:
    virtual void vFunc(){}
};

int main() {
    A a;
    cout<<"sizeof(a) = "<<sizeof(a)<<endl;
    return 0;
}

        再次运行,运行结果:

sizeof(a) = 8

         发现,对象a的sizeof值,突然变成8了。

        要想弄明白sizeof(a)的值为什么从1变成了8,可以分析一下,正是由于加入虚函数 virtual void vFunc(){} 才引起的变化。

        虚函数引入之后,类会发生一系列的变化。比如刚才的sizeof(a)的值改变了,这个其实就是属于C++对象模型知识的一小部分。

        当一个或多个虚函数加入到一个类中之后,编译器就会向类中插入一个看不见的成员变量。

虚函数表

        当类中虚函数大于等于1个的时候,编译器就会为类生成一个虚函数表(virtual table),简称vtbl,这个虚函数表会一直伴随着类A。

        在经过编译、链接,直到生成一个可执行文件后,这个类A以及伴随类A的虚函数表都会保存到这个可执行文件中,在这个可执行文件在执行的时候,也会被一并装在到内存中来。

虚函数表指针

        那么对于这种有虚函数的类A,在编译的时候,编译器会向类A的构造函数中,安插为vptr赋值的语句。

A(){
    vptr=&A::vftable;//编译器在编译期间做的
    //...    
}

         这个是编译器是在编译期间做的,是编译器默默在背后为程序员所做的事情。伪代码大概是上面的感觉。

        程序运行起来之后,当创建一个类A对象的时候,会执行类A的构造函数,因为构造函数中,有给vptr赋值的语句,从而呢,能够使vptr指向类A的vtbl。

        当然,如果程序员没有书写自己的关于类A的构造函数的话,这个时候编译器就会默默为程序员生成一个类A的构造函数,并会默默地在这个构造函数中,安插给vptr赋值语句,只不过这种动作是背着程序员进行的,程序员看不到这个构造函数。

类对象在内存中的布局

        以上A类中的内容太简单,我们添加两个成员变量,如下:

#include <iostream>

using namespace std;

class A{
public:
    void func1(){}
    void func2(){}
public:
    virtual void vFunc(){}
private:
    int m_a;
    int m_b;
};

int main() {
    A a;
    cout<<"sizeof(a) = "<<sizeof(a)<<endl;
    return 0;
}

         运行结果:

sizeof(a) = 16

        再添加两个虚函数,如下:

#include <iostream>

using namespace std;

class A{
public:
    void func1(){}
    void func2(){}
public:
    virtual void vFunc(){}
    virtual void vFunc1(){}
    virtual ~A(){}
private:
    int m_a;
    int m_b;
};

int main() {
    A a;
    cout<<"sizeof(a) = "<<sizeof(a)<<endl;
    return 0;
}

        运行结果:

sizeof(a) = 16

        在有m_a、m_b的前提下,可以发现,不论类A中的虚函数有几个,类A的实例a的sizeof值都为16,其中m_a、m_b都为int类型,每个int类型占4个字节,两个即为8个字节,虚函数表指针占8个字节。共16个字节。 

        此时类A对象a的内存布局:

虚函数表指针vptr8字节
int m_a;4字节
int m_b;4字节

        如图所示:

        普通成员函数属于类A的组成部分,并不占用类A对象的内存空间。 

虚函数表在支持多态方面的工作原理

        多态性:父类中有一个虚函数,子类中也有一个同名的虚函数,当通过父类指针new一个子类对象的时候,或者是通过父类引用来绑定一个子类对象的时候,如果用这个父类指针来调用虚函数,调用的其实是子类的虚函数。

        谈到多态,多态必须存在虚函数,没有虚函数,绝不可能存在多态。那么类中定义了虚函数,并且要调用虚函数,才存在多态性的可能,仅仅是可能。

        当调用虚函数的时候,可以看一下调用路线,是不是利用vptr找到vtbl,然后通过查询vtbl来找到虚函数表的入口地址并执行虚函数。如果调用虚函数的路线是这个路线,那么就是多态,如果走的不是这个路线,而是像调用普通成员函数那样直接调用,那么就不是多态。所以,从这个角度来讲,就不用管有没有继承关系,也不用管什么子类。

class Base{
public:
    virtual void virFuc(){}
};

int main() {
    Base* ba=new Base();
    ba->virFuc();    //是多态

    Base base;
    base.virFuc();    //不是多态

    Base* bas=&base;
    bas->virFuc();    //是多态
}

1.程序中既存在父类,也存在子类,父类中必须含有虚函数,子类中也必须重写父类中的虚函数。

2.父类指针指向子类对象,或者父类引用绑定(指向)子类对象。

3.当通过父类的指针,或者引用,调用子类中重写的虚函数时,就能看出多态性的表现了。最终调用的是子类的虚函数。 

class Base{
public:
    virtual void virFunc(){}
};

class Device:public Base{
public:
    virtual void virFunc(){}
};

int main() {
    Device device;
    Base* pBase = &device;    //父类指针指向子类对象
    pBase->virFunc();   //调用Device::virFunc()

    Base* pBase1 = new Device();
    pBase1->virFunc();  //调用Device::virFunc()
    delete pBase1;

    Device device2;
    Base& ba = device2; //父类引用绑定子类对象
    ba.virFunc();   //调用Device::virFunc()
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值