C++虚函数详解

C语言题库

共 3985字,需浏览 8分钟

 ·

2021-09-02 03:25



01

C++虚函数探索


C++是一门面向对象语言,在C++里运行时多态是由虚函数和纯虚函数实现的,现在我们看下在C++中如何用虚函数实现多态。先来看一段代码。


// virtual_function.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>

class Base {
public:
    Base() {
        std::cout << "Base::constructor run" << std::endl;
    }

    virtual void fun1() {
        std::cout << "Base::fun1 run" << std::endl;
    }

    virtual void fun2() {
        std::cout << "Base::fun2 run" << std::endl;
    }

    virtual ~Base() {
        std::cout << "Base::desconstructor run" << std::endl;
    }
};

class Derive : public Base {
public:
    Derive() {
        std::cout << "Derive::constructor run" << std::endl;
    }

    void fun1() {
        std::cout << "Derive::fun1 run" << std::endl;
    }

    void fun3() {
        std::cout << "Derive::fun3 run" << std::endl;
    }

    ~Derive() {
        std::cout << "Derive::desconstructor run" << std::endl;
    }
};

int main()
{
    Derive* d = new Derive();

    d->fun1();
    d->fun2();
    d->fun3();

    delete d;
}

这段代码编译运行后输出了:

Base::constructor run
Derive::constructor run
Derive::fun1 run
Base::fun2 run
Derive::fun3 run
Derive::desconstructor run
Base::desconstructor run

这段代码里基类Base定义了虚函数fun1和fun2,派生类Derive有成员函数fun1和fun3,其中派生类覆盖了继承而来的基类虚函数fun1。在主函数里创建Derive类型对象指针d指向Derive类型对象。由于派生类Derive成员函数fun1覆盖了基类Base成员函数fun1,因此通过d调用fun1实际调用的是派生类Derive类的成员函数fun1,而继承而来的成员函数fun2没有被覆盖,则通过指针d调用fun2实际调用的是基类成员函数fun2。这里好像让看不出虚函数有什么作用,那么我们将主函数修改如下:

int main()
{
    Base* b = new Derive();

    b->fun1();
    b->fun2();

    delete b;
}

在程序里我们将创建一个基类指针b并指向的是派生类,并且调用delete释放内存时使用的是基类指针b。编译运行输出结果如下:

Base::constructor run
Derive::constructor run
Derive::fun1 run
Base::fun2 run
Derive::desconstructor run
Base::desconstructor run
通过基类指针b调用fun1函数,实际调用的是派生类Derive的成员函数fun1,由于在派生类Derive中没有定义成员函数fun2,因此通过基类指针b调用fun2函数,实际调用的依旧是基类Base的成员函数fun2。代码里虽然我们没有对派生类的成员函数fun1加virtual,实际上派生类的成员函数fun1是虚函数。但是对于大多数C++初学者会有2个疑问的地方。
1、通过基类指针b调用fun1函数,实际调用的是派生类的成员函数fun1。
2、通过delete释放内存使用的是基类指针b,会调用派生类析构函数和基类析构函数,成功释放内存,不会存在内存泄露问题。

带有虚函数的类称为虚基类,子类继承虚基类。在C++中虚基类有一个虚函数表指针保存虚函数表地址,而虚函数表保存函数地址,虚函数表并不在虚基类里,但是虚函数表指针在虚基类里,子类继承虚基类,子类也就有了虚函数表指针。那么C++是如何通过虚函数表和虚函数表指针实现多态呢?打开VS2019,并用管理员身份运行"2019开发人员命令提示符"工具,如下图所示:

输入:cl /d1 reportSingleClassLayoutXXX  [filename],XXX表示类名,[filename]表示类所在的.cpp文件路径。这里我输入源文件的派生类名和源文件路径,回车输出如下:

从输出可以看出派生类从基类继承了虚函数表指针vfptr,且占用字节数大小是4字节,刚好就是一个指针占用字节数。虚函数表vftable里保存了派生类成员函数fun1,基类成员函数fun2的地址,由于派生类成员函数fun3不是虚函数,因此虚函数表里没有fun3的地址。看到这里我们就明白了,通过基类指针b调用fun1的过程:通过虚函数表指针vfptr找到虚函数表vftable,再通过虚函数表找到派生类成员函数fun1的地址,调用派生类成员函数fun1。而通过基类指针b调用fun2的过程则是:通过虚函数表指针vfptr找到虚函数表vftable,再通过虚函数表找到基类成员函数fun2的地址,调用基类成员函数fun2。看到这里,第一个疑问已经解开了,关键在于虚函数表指针和虚函数表。

在C++中有虚函数的类,其析构函数默认是虚析构函数,只要是虚函数就会在虚函数表里有相应的函数地址,因此派生类里的虚函数表指针vfptr指向的虚函数表vftable必然保存着派生类析构函数的地址,类的析构过程:从继承链的最底端到最顶端依次调用析构函数,因此delete b调用过程:通过虚函数表指针vfptr找到虚函数表vftable,再通过虚函数表找到派生类析构函数地址,调用析构函数。


浏览 37
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报