C++虚函数知识点详解

1.虚函数的作用是什么?

C++中的虚函数的作用主要是实现了多态的机制。基类定义虚函数,子类可以重写该函数;在派生类中对基类定义的虚函数进行重写时,需要在派生类中声明该方法为虚方法(virtual关键字)。

函数的重载也可以认为是多态,但是是静态的,是编译时决定的,叫做静态联编。而虚函数是运行时决定的,是动态的,叫做动态联编。

2.虚函数的底层实现机制是什么?

实现原理:虚函数表+虚函数表指针。

编译器处理虚函数的方法是:为每个类对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针,称为虚表指针(vptr),这种数组成为虚函数表(virtual function table, vtbl),即,每个类(拥有虚函数的类才有)使用一个虚函数表,每个类对象(拥有虚函数的类才有)用一个虚表指针。

在vs2019中调试不含虚函数的类对象以及含有虚函数的类对象可以发现,只有含有虚函数的类对象才有虚函数表指针(__vfptr)。

基类的虚函数表和子类的虚函数表不是同一个表,因为虚函数表指针(__vfptr)指向的位置不一样。这是单继承的情况,如果继承了两个具有虚函数的基类,就有两个虚函数表和两个虚函数表的指针,分别在两个基类的内存空间上。

下图是测试的代码:

#include <iostream>
using namespace std;

//基类A
class BaseA {
private:
    int a;
public:
    BaseA()
    {
        cout << "construct basea" << endl;
    }
    virtual void funa()
    {
        cout << "funca" << endl;
    }
    virtual ~BaseA()
    {
        cout << "destruct basea" << endl;
    }
};


//基类B
class BaseB {
private:
    int b;
public:
    BaseB()
    {
        cout << "construct baseb" << endl;
    }
    virtual void funb()
    {
        cout << "funcb" << endl;
    }
    virtual ~BaseB()
    {
        cout << "destruct baseb" << endl;
    }
};

//无虚函数的类C
class NormalC {
private:
    int c;
public:
    NormalC()
    {
        cout << "construct NormalC" << endl;
    }
    ~NormalC()
    {
        cout << "destruct NormalC" << endl;
    }
};

//单继承子类D

class SingleDeriveD : public BaseA
{
private:
    int d;
public:
    SingleDeriveD()
    {
        cout << "construct SingleDeriveD" << endl;
    }
    ~SingleDeriveD()
    {
        cout << "destruct SingleDeriveD" << endl;
    }
    virtual void funa()
    {
        cout << "SingleDeriveD funa" << endl;
    }
};

//多继承子类E

class MultipleDeriveE : public BaseA,public BaseB
{
private:
    int e;
public:
    MultipleDeriveE()
    {
        cout << "construct MultipleDeriveE" << endl;
    }
    ~MultipleDeriveE()
    {
        cout << "destruct MultipleDeriveE" << endl;
    }
    virtual void funa()
    {
        cout << "MultipleDeriveE funa" << endl;
    }

    virtual void funb()
    {
        cout << "MultipleDeriveE funb" << endl;
    }
};


void main()
{
    BaseA* a = new BaseA();
    BaseB* b = new BaseB();
    NormalC* c = new NormalC();
    SingleDeriveD* d = new SingleDeriveD();
    MultipleDeriveE* e = new MultipleDeriveE();
    a->funa();
    b->funb();
    d->funa();
    e->funa();
    e->funb();

}

执行后的输出是:

在调试的时候,可以查看各个对象的__vfptr的指针以及指向的位置。

3.为什么析构函数可以是虚函数,构造函数却不能是虚函数?

因为虚函数的实现原理是:虚函数表+虚函数表指针,最关键的是寻函数表指针,这个指针是在对象构造的时候赋值的,一般是在构造函数初始化列表之后,函数体第一句代码之前赋值,因此,如果构造函数是虚函数,它的调用又要通过虚函数表指针(__vfptr)来寻找调用地址,而这个时候__vfptr还没有被赋值,这样就有问题了。

编译器处理虚函数的方法是:
给每个对象添加一个指针,存放了指向虚函数表的地址,虚函数表存储了为类对象进行声明的虚函数地址。比如基类对象包含一个指针,该指针指向基类所有虚函数的地址表,派生类对象将包含一个指向独立地址表的指针,如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址,如果派生类没有重新定义虚函数,该虚函数表将保存函数原始版本的地址。如果派生类定义了新的虚函数,则该函数的地址将被添加到虚函数表中。

使用虚函数后的变化:
(1) 对象将增加一个存储地址的空间(32位系统为4字节,64位为8字节)。
(2) 每个类编译器都创建一个虚函数地址表
(3) 对每个函数调用都需要增加在表中查找地址的操作。

虚函数的注意事项

总结前面的内容
(1) 基类方法中声明了方法为虚后,该方法在基类派生类中是虚的。
(2) 若使用指向对象的引用或指针调用虚方法,程序将根据对象类型来调用方法,而不是指针的类型。
(3)如果定义的类被用作基类,则应将那些要在派生类中重新定义的类方法声明为虚。
构造函数不能为虚函数。
基类的析构函数应该为虚函数。
友元函数不能为虚,因为友元函数不是类成员,只有类成员才能是虚函数。
如果派生类没有重定义函数,则会使用基类版本。
重新定义继承的方法若和基类的方法不同(协变除外),会将基类方法隐藏;如果基类声明方法被重载,则派生类也需要对重载的方法重新定义,否则调用的还是基类的方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程经验随笔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值