一文搞懂C++虚函数的实现原理

虚函数的概念

虚函数是实现C++中面向对象的三大特性之一——多态的一种技术手段。

什么是多态呢?
在基类的成员函数前,使用virtual修饰得到的就是虚函数。在派生类中,对继承得到的虚函数,进行重写,使得派生类对继承的方法有了自己的具体实现。在外部函数中,通过基类指针调用类的接口,在程序运行时,会根据指针绑定的对象选择对应的接口,这便是多态。多态简单来说,是让同一种事物有了多种不同的表现。

虚函数的具体实现

基类指针绑定了类对象后,程序运行时,是怎么知道该运行哪个类的接口函数呢?这便是本篇文章要探究的问题。

虚函数表

C++为了实现虚函数的动态绑定,在每一个类(当然是含有虚函数的类)中,都有一张虚函数表(Virtual Table),通过指向虚函数表的指针(Virtual Table Pointer)来选择执行的虚函数。

我将从以下三种情况,和大家一起研究虚函数表的分布:

  • 派生类不重写基类虚函数
  • 派生类重写了基类的虚函数
  • 多重继承下虚函数表的分布

1.派生类不重写基类虚函数

有如下的基类和派生类

class Base{
public:
    virtual void f1(){ cout << "Base 的 f1" << endl; }
    virtual void f2(){ cout << "Base 的 f2" << endl; }
};
class Derived : public Base{
public:
	// 不重写基类的虚函数 f1 和 f2
    // void f1(){ cout << "Derived 的 f1" << endl; }
    // void f2(){ cout << "Derived 的 f2" << endl; }
    // 派生类自己的虚函数 f3
    virtual void f3(){ cout << "Derived 的 f3" << endl;}
};
// 使用typedef定义一个函数指针
typedef void(*Fun)(void);
// 测试函数
void vptrTest(){
    Derived d;
    Base *pb = &d;
    cout << "Base的大小:" << sizeof(Base) << " Derived的大小:" << sizeof(Derived) << endl;
    cout << "Derived的首地址" << (int *)&d << endl;
    int *pfun = ( (int *)*((int *)&d) );
    cout << "虚函数表的第一个函数的地址" << pfun << endl;

    Fun fun1 = (Fun)*pfun;
    fun1();
    pfun+=2; // 此时指针是8字节的,64位机器嘛
    Fun fun2 = (Fun)*pfun;
    fun2();
    pfun+=2;
    Fun fun3 = (Fun)*pfun;
    fun3();
}

程序的运行结果:
在这里插入图片描述
没有被重写基类的虚函数f1(), f2(), 派生类自己的虚函数f3()分布情况:
在这里插入图片描述

2.派生类重写基类虚函数

派生类重写继承的f1()函数

class Base{
public:
    virtual void f1(){ cout << "Base 的 f1" << endl; }
    virtual void f2(){ cout << "Base 的 f2" << endl; }
};
class Derived : public Base{
public:
	// 不重写基类的虚函数 f1 和 f2
    void f1(){ cout << "Derived 的 f1" << endl; }
    // void f2(){ cout << "Derived 的 f2" << endl; }
    // 派生类自己的虚函数 f3
    virtual void f3(){ cout << "Derived 的 f3" << endl;}
};
// 使用typedef定义一个函数指针
typedef void(*Fun)(void);
// 测试函数
void vptrTest(){
    Derived d;
    Base *pb = &d;
    cout << "Base的大小:" << sizeof(Base) << " Derived的大小:" << sizeof(Derived) << endl;
    cout << "Derived的首地址" << (int *)&d << endl;
    int *pfun = ( (int *)*((int *)&d) );
    cout << "虚函数表的第一个函数的地址" << pfun << endl;

    Fun fun1 = (Fun)*pfun;
    fun1();
    pfun+=2; // 此时指针是8字节的,64位机器嘛
    Fun fun2 = (Fun)*pfun;
    fun2();
    pfun+=2;
    Fun fun3 = (Fun)*pfun;
    fun3();
}

程序的运行结果:
在这里插入图片描述
在这里插入图片描述
所以,对于单继承模式,虚函数表的分布,可以总结为:

  • case1:派生类不重写基类虚函数:虚函数表中只有基类的虚函数,存储的顺序按照函数定义的顺序来。如果派生类还有自己的虚函数,存放在基类的虚函数之后。
  • case2:派生类重写了基类的虚函数:基类的虚函数被派生类的重写函数取代。位置顺序不发生改变

3.多继承情况
不重写虚函数的情况:

class Base1{
public:
    virtual void f1(){ cout << "Base1 的 f1" << endl; }
    virtual void f2(){ cout << "Base1 的 f2" << endl; }
};
class Base2{
public:
    virtual void f1(){ cout << "Base2 的 f1" << endl; }
    virtual void f2(){ cout << "Base2 的 f2" << endl; }
};
class Derived : public Base1, public Base2{
public:
	// 不重写基类的虚函数 f1 和 f2
    // void f1(){ cout << "Derived 的 f1" << endl; }
    // void f2(){ cout << "Derived 的 f2" << endl; }
    // 派生类自己的虚函数 f3
    virtual void f3(){ cout << "Derived 的 f3" << endl;}
};
typedef void(*Fun)(void);
void vptrTest(){
    Derived d;
    Base1 *pb = &d;
    cout << "Base的大小:" << sizeof(Base1) << " Derived的大小:" << sizeof(Derived) << endl;
    cout << "Derived的首地址" << (int *)&d << endl;
    int *pfun = ( (int *)*((int *)&d) );
    cout << "虚函数表的第一个函数的地址" << pfun << endl;

    Fun fun1 = (Fun)*pfun;
    fun1();
    pfun+=2; // 此时指针是8字节的,64位机器嘛
    Fun fun2 = (Fun)*pfun;
    fun2();
    pfun+=2;
    Fun fun3 = (Fun)*pfun;
    fun3();

    int *vt = (int *)&d;
    vt+=2;
    pfun = ( (int *)*(vt) );
    Fun fun4 = (Fun)*pfun;
    fun4();
    pfun+=2;
    Fun fun5 = (Fun)*pfun;
    fun5();
}

在这里插入图片描述
在这里插入图片描述

重写虚函数f1()
在这里插入图片描述
在这里插入图片描述

通过这两个小demo,我们得知,在多重继承的情况,虚函数的分布成了这样:

  • case1:没有重写继承的虚函数:有多少个基类,派生类就有多少张表。每一个基类都有一张单独的虚函数表。派生类自己的虚函数放在第一个基类的虚函数表中
  • case2:重写继承的虚函数:基类的虚函数被派生类的重写函数取代。位置顺序不发生改变
  • 5
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中,实现虚函数的动态绑定是通过虚函数表(Virtual Table)和虚函数表指针(Virtual Table Pointer)来实现的。在每个含有虚函数的类中都会有一张虚函数表,这个表中存储着虚函数的地址。而每个对象都会有一个指向其对应类的虚函数表的指针。当调用一个虚函数时,通过对象的虚函数表指针来选择执行对应的虚函数。这样就实现了在运行时根据对象的实际类型来调用相应的虚函数,即动态绑定。 举个例子来说明,如果有一个基类Base和派生类Derived,其中Base有两个虚函数f1和f2,Derived继承了Base,并重写了f1函数。当通过指向Base的指针调用虚函数f1时,实际上会根据对象的实际类型来选择执行哪个函数。如果派生类重写了继承的虚函数,那么调用该函数时将执行派生类中的实现。 总结起来,C++虚函数实现原理主要是通过虚函数表和虚函数表指针来实现动态绑定,使得在运行时可以根据对象的实际类型来调用对应的虚函数。这样可以实现多态性,提高代码的灵活性和可扩展性。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [一文搞懂C++虚函数实现原理](https://blog.csdn.net/qq_42518941/article/details/125086249)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值