C++多态底层刨析(虚函数指针,虚函数表)

2021年9月9日 10点热度 0条评论 来源: 四舍五入两米高的小晨

前言:相信小伙伴们在学习到C++面向对象特性之一的多态的时候,都或多或少有一些疑惑。搞不清楚多态在底层是如何实现的,今天我就带大家刨析一下多态的底层实现,了解一下虚函数指针和虚函数表到底是什么东西?(注意本文操作环境是VS2019 x86架构 32位机器)

多态底层刨析

1 、多态的定义和分类

1.1 多态的定义

1.1.1 定义:
多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。
C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
----------来自菜鸟教程

1.2 多态的分类

1.2.1 多态分为两类:
·静态多态:函数重载和运算符重载属于静态重载,复用函数名
·动态多态:派生类和虚函数实现运行时多态
1.2.2 静态多态和动态多态的区别:
·静态多态的函数地址早绑定–编译阶段确定函数地址
·动态多态的函数地址晚绑定–运行阶段确定函数地址

2、静态多态代码演示

2.1 静态多态代码:

#include<iostream>
using namespace std;
class Father
{ 
public:
    void speak() { 
        cout << "爸爸在说话!" << endl;
    }
};
class Son :public Father
{ 
public:
    void speak() { 
        cout << "儿子在说话!" << endl;
    }
};
//执行说话函数
//地址早被绑定 在编译阶段确定函数地址
void doSpeak(Father &father)//父类引用接收子类对象
{ 
    father.speak();
}
void test01()
{ 
    Son son;
    doSpeak(son);
}
int main()
{ 
    test01();
    return 0;
}

2.2 运行结果:

2.3 分析:
在此案例中,派生类和基类中都出现了speak函数,当用父类指针或者引用接收子类对象时,程序会执行基类中的同名函数,这是为什么呢?因为父类中的speak函数地址在编译期间就被绑定,所以在执行程序时无论传递的是哪种对象,执行的都是基类中的speak函数。这就是静态动态的弊端,那么如果想实现传入哪种对象就执行哪种类的函数,这就需要用到动态多态了。

3 、动态多态的代码刨析

动态多态满足条件:
1、有继承关系
2、子类重写父类的虚函数
动态多态的使用:
父类的指针或者引用指向子类对象

3.1 动态多态代码演示

3.1.1动态多态代码

#include<iostream>
using namespace std;
class Father
{ 
public:
    virtual void speak() { 
        cout << "爸爸在说话!" << endl;
    }
};
class Son :public Father
{ 
public:
    void speak() { 
        cout << "儿子在说话!" << endl;
    }
};
//执行说话函数
//地址早被绑定 在编译阶段确定函数地址
void doSpeak(Father &father)//父类引用接收子类对象
{ 
    father.speak();
}
void test01()
{ 
    Son son;
    doSpeak(son);
}
int main()
{ 
    test01();
    return 0;
}

3.1.2 运行结果:

3.1.3 分析:
静态多态变为动态多态只需要给父类的函数加上virtual关键字变为虚函数。

3.2 代码刨析

小知识:在C++中空类也占内存,占一个字节的空间

3.2.1
我们先来看一下父类的函数前加上virtual关键字,父类的内存占用有什么变化?

通过运行发现此时父类所占的空间变成了4个字节,那么这四个字节到底是存了什么????其实聪明的小伙伴们可能已经猜出来这四个字节是什么了,没错存的就是一个指针。这个指针就叫虚函数指针,简写为vfptr(virtual function pointer). 对于一个类来说,如果类中存在虚函数,那么编译器就会自动在类中加一条生成虚函数指针的语句(void * vfptr),并且在类的构造函数中为虚函数指针进行赋值(vfptr=&Father::vtal),此时这个虚函数指针就会指向虚函数表。所以,如果对象存在虚函数,那么编译器就会生成一个指向虚函数表的指针,所有的虚函数都存在于这个表中,虚函数表就可以理解为一个数组,每个单元用来存放虚函数的地址。
3.2.2
此时我们在父类中写两个虚函数
然后打开vs2019的调试功能查看一下对象father可以看到

此时可以发现父类对象确实有一个vfptr指针,这个指针对应的
表里就是储存着两个虚函数的地址,这两个函数都是属于父类的。
3.2.3
当子类继承了父类并且子类重写了父类的虚函数之后,我们可以看到:

此时子类中虚函数指针对应的虚函数表中存的是子类经过重写的函数了。所以当传入一个子类对象时通过查询子类vfptr找到对应的虚函数表,从而找到其中存的函数地址去执行,这也就是为什么动态多态可以根据传入对象的不同来执行不同的语句。

3.2.4 画图演示

4、使用VS自带的开发人员工具检验

4.1 找到VS2019开发人员工具

4.2 查看有虚函数的父类布局

4.3 查看子类重写虚函数之后子类的布局


由此证明,我们的结论是正确的!

    原文作者:四舍五入两米高的小晨
    原文地址: https://blog.csdn.net/weixin_53051813/article/details/120176028
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系管理员进行删除。