C++虚函数表的使用小结

 更新时间:2026年02月08日 15:15:40   作者:yuanhao  
本文主要介绍了C++虚函数表的使用小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前言

使用过Java的同学应该特别熟悉多态,即耳熟能详的一句话:可以使用父类引用来指向子类对象,而实现起来非常容易,只需要重写父类的方法或者实现接口的方法即可:

//Java代码

class Parent {
    void show() {
        System.out.println("Inside Parent");
    }
}

class Child extends Parent {
    void show() {
        System.out.println("Inside Child");
    }
}

public class Main {
    public static void main(String[] args) {
        Parent obj = new Child();
        obj.show(); // 调用的是子类的 show() 方法
    }
}

在Java中,这里是使用父类Parent的引用obj,指向了子类对象,然后调用的是子类的show()方法。

但是在C++中,如果按同样类似的写法,却得到的结果不一样:

//C++代码

#include <iostream>

class Parent {
public:
    void show() {
        std::cout << "Inside Parent" << std::endl;
    }
};

class Child : public Parent {
public:
    void show() {
        std::cout << "Inside Child" << std::endl;
    }
};

int main() {
    Parent* obj = new Child();
    obj->show(); // 调用的是父类的 show() 方法
    delete obj;
    return 0;
}

在C++中,这里使用父类的指针,指向子类对象,但是最终调用的是父类的show()方法。那如何实现像Java那种效果呢?就需要使用虚函数,本篇文章深入探究虚函数以及虚函数的原理--虚函数表(后面简称虚表)的相关知识。

正文

关于虚函数的使用,以及纯虚函数定义接口等,这些基础知识,暂时就不详细说明了,重点关注虚表。

虚表是属于类

虚表是函数指针类型的数组,指向该类定义的虚函数。

在C++中,只要一个类包含了一个虚函数,那么就有由一个虚表来维护。虚表是属于类的,而非属于某个对象,一个类只需要一个虚表,所以该类对象共用一个虚表。

当类B继承至类A,继承类也可以调用A的函数,如果A是一个包含虚表的基类,那么继承类B也拥有自己的虚表

理解类的虚表非常重要,比如有代码:

class A {
public:
    A();
    virtual void vfunc1() { qDebug() << "A vfunc1()"; }
    virtual void vfunc2() { qDebug() << "A vfunc2()"; }
    void func1() { qDebug() << "A func1()"; }
    void func2() { qDebug() << "A func2()"; }

private:
    int m_data1, m_data2;
};

那么类A的虚表如图:

虚表是一个指针数组,其元素是虚函数的指针,每个元素对应一个虚函数的函数指针。非虚函数的函数,调用不需要虚表,所以虚表中不会保存。虚表的创建在编译时期,即在编译时期,属于一个类的虚表就构造出来了。

对象的虚表指针

我们使用IDE调试模式,来看一下类A的对象包含哪些东西:

可以发现除了其数据成员外,还有一个__vptr指针,这个指针是虚表指针,指向类的虚表。为了指定对象的虚表,编译器在类中添加了__vptr指针,专门用来指向虚表。

这里可以知道,同一个类的多个对象,其虚表指针所指向的虚表是一样的:

效果如图:

动态绑定

重点来了,C++是如何通过虚表实现多态的,即动态绑定,有如下3个类:

class A {
public:
    A();
    virtual void vfunc1() { qDebug() << "A vfunc1()"; }
    virtual void vfunc2() { qDebug() << "A vfunc2()"; }
    void func1() { qDebug() << "A func1()"; }
    void func2() { qDebug() << "A func2()"; }

private:
    int m_data1, m_data2;
};

class B : public A {
public:
    B();
    virtual void vfunc1() { qDebug() << "B vfunc1()"; }
    void func1() { qDebug() << "B func1()"; }

private:
    int m_data3;
};

class C : public B {
public:
    C();
    virtual void vfunc2() { qDebug() << "c vfunc2()"; }
    void func2() { qDebug() << "C func2()"; }

private:
        int m_data1, m_data4;
};

记住:虚表是属于类的,上面代码的虚表通过IDE调试模式如下:

虚表关系如图:

我们来仔细分析:

  1. 类A包含2个虚函数,所以A的虚表元素是2个。

  2. 类B继承至A,但是重写了虚函数,对于重写的虚函数,会增加新的地址来保存新的函数指针。所以对于类B的虚表,有一个是继承至A,即A::vfunc2*(),还有一个是新的函数,即B::vfunc1()

  3. 而对于C来说,它继承至B,但是重写了虚函数,所以会多出一个C::vfunc2(),那么另一个虚函数指针是执行类B的vfunc1()还是类A的vfunc1()呢?

    这里会指向B::vfunc1(),规律非常简单:对象的虚表指针用来指向类的虚表,虚表中的指针会指向其继承的最近的一个类的虚函数,所以C的虚表先是指向B的虚表,然后由于重写虚函数的原因,会修改其中一个指针的值。

基类指针指向子类对象

搞明白上面的继承关系后,多态就非常容易理解了,比如下面代码:

B b;
A *p = &b;
p->vfunc1();

这里使用p来调用vfunc1(),最终会调用类B中的虚函数,过程分析如下:

  1. 程序执行p->vfunc1()时,会发现p是一个指针,而且调用的是虚函数。
  2. 根据虚表指针p->__vptr访问对象b对应的虚表。虽然指针p是基类A的类型,但是__vptr也是基类的一部分,是编译器加的,所以p->__ptr可以访问对象的虚表
  3. 虚表中查找调用的函数,由于虚表是在编译时就确定,所以p->vfunc1()根据上面的图可知会调用B的虚函数,即重写的虚函数B::vfunc1()

这个过程就是动态绑定来实现的多态,非常容易理解。

总结

什么情况下会发生动态绑定呢?有如下几个条件:

  1. 通过指针调用函数。
  2. 指针upcast向上转型,比如继承类向基类的转换。
  3. 调用的是虚函数。

到此这篇关于C++虚函数表的使用小结的文章就介绍到这了,更多相关C++虚函数表内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++从txt文件中读取二维的数组方法

    C++从txt文件中读取二维的数组方法

    今天小编就为大家分享一篇C++从txt文件中读取二维的数组方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-07-07
  • 简介C/C++预处理器的一些工作

    简介C/C++预处理器的一些工作

    这篇文章主要介绍了C/C++预处理器的一些工作,有助于理解编译器底层的工作流程,需要的朋友可以参考下
    2015-07-07
  • 一篇文章带你了解C语言二分查找

    一篇文章带你了解C语言二分查找

    这篇文章主要为大家详细介绍了C语言二分查找法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • C/C++常用函数易错点分析

    C/C++常用函数易错点分析

    这篇文章主要介绍了C/C++常用函数易错点分析,包含了memset、sizeof、getchar三个常用函数的分析,需要的朋友可以参考下
    2014-08-08
  • C++代码实现逆波兰表达式

    C++代码实现逆波兰表达式

    这篇文章主要为大家详细介绍了C++代码实现逆波兰表达式,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-11-11
  • Qt使用HTTP上传json格式数据

    Qt使用HTTP上传json格式数据

    这篇文章主要为大家详细介绍了Qt如何使用HTTP上传json格式数据,文中的示例代码讲解详细,具有一定的借鉴价值,有需要的小伙伴可以参考一下
    2024-12-12
  • 一篇文章带你了解C语言二分查找的简单应用

    一篇文章带你了解C语言二分查找的简单应用

    这篇文章主要介绍了二分查找算法在C语言程序中的使用示例,文中最后提到了使用二分查找法一个需要注意的地方,需要的朋友可以参考下
    2021-08-08
  • C语言实现简易网络聊天室

    C语言实现简易网络聊天室

    这篇文章主要为大家详细介绍了C语言实现简易网络聊天室,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-06-06
  • C语言中花式退出程序的方式总结

    C语言中花式退出程序的方式总结

    在本篇文章当中主要给大家介绍C语言当中一些不常用的特性,比如在main函数之前和之后设置我们想要执行的函数,以及各种花式退出程序的方式,需要的可以参考一下
    2022-10-10
  • Qt线程QtConcurrent模块的使用

    Qt线程QtConcurrent模块的使用

    QtConcurrent是Qt特有的一个用于实现并发任务的模块,本文主要介绍了Qt线程QtConcurrent模块的使用,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2026-01-01

最新评论