栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++虚函数知识点详解

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++虚函数知识点详解

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

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

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

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

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

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

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

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

下图是测试的代码:

#include
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)如果定义的类被用作基类,则应将那些要在派生类中重新定义的类方法声明为虚。
构造函数不能为虚函数。
基类的析构函数应该为虚函数。
友元函数不能为虚,因为友元函数不是类成员,只有类成员才能是虚函数。
如果派生类没有重定义函数,则会使用基类版本。
重新定义继承的方法若和基类的方法不同(协变除外),会将基类方法隐藏;如果基类声明方法被重载,则派生类也需要对重载的方法重新定义,否则调用的还是基类的方法。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/983025.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号