【C++】多态的底层原理(虚函数表)

news2024/10/6 10:25:59

文章目录

  • 前言
  • 一、虚函数表
  • 二、派生类中虚函数表
    • 1.原理
    • 2.例子:
  • 三、虚函数的存放位置
  • 四 、单继承中的虚函数表
  • 五、多继承中的虚函数表
  • 六、问答题


前言

一、虚函数表

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

通过观察测试我们发现b对象是8bytes,除了_b成员,还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数
的地址要被放到虚函数表中
,虚函数表也简称虚表

二、派生类中虚函数表

1.原理

class Base
{
public:
	virtual void Func1()
	{
		cout << "Base::Func1()" << endl;
	}
	virtual void Func2()
	{
		cout << "Base::Func2()" << endl;
	}
	void Func3()
	{
		cout << "Base::Func3()" << endl;
	}
private:
	int _b = 1;
};
class Derive : public Base
{
public:
	virtual void Func1()
	{
		cout << "Derive::Func1()" << endl;
	}
private:
	int _d = 2;
};
int main()
{
	Base b;
	Derive d;
	return 0;
}

在这里插入图片描述

1.基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
2.另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会放进虚表。
3.虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。

总结一下派生类的虚表生成a. 先将基类中的虚表内容拷贝一份到派生类虚表中 b. 如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 c. 派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。

2.例子:

在这里插入图片描述

  1. 观察下图的红色箭头我们看到,p是指向mike对象时,p->BuyTicket在mike的虚表中找到虚函数是Person::BuyTicket。
  2. 观察下图的蓝色箭头我们看到,p是指向johnson对象时,p->BuyTicket在johson的虚表中找到虚函数是Student::BuyTicket。
  3. 这样就实现出了不同对象去完成同一行为时,展现出不同的形态。
    在这里插入图片描述

满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象的中取找的。不满足多态的函数调用时编译时确认好的。

三、虚函数的存放位置

虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针

同类型共用虚函数表:
由于同一类的不同对象的虚函数表是相同的,它们所需的虚函数的地址是一样的,因此可以共用同一份虚函数表,这样就节省了内存空间。这种设计可以使得每个对象只需要一个指针来指向虚函数表,而不需要为每个对象都分配一份独立的虚函数表。

四 、单继承中的虚函数表

class Person {
	public:
		virtual void Func1() {cout << "Person::Func1()" << endl;}
		virtual void Func2() {cout << "Person::Func2()" << endl;}

		int _a = 0;
	};
class Student : public Person {
  
private:
	virtual void Func3(){cout << "Student::Func3()" << endl;}
	protected:
		int _b = 1;
};

	int main() {
		Person p;
		Student s;
		return 0;
	}

我们会发现虚函数表中少了一个func3的函数指针,我们下面会验证func3确实存在与派生类的虚基表中。
在这里插入图片描述
每个对象都有一个自己的虚函数指针,指向相应的虚函数表。这里基类和派生类的虚函数指针指向同一个地址,是因为基类的虚函数在派生类中没有被重写(Func1与Func2)当派生类继承基类时,派生类会继承基类的虚函数表,并在其虚函数表中添加或覆盖自己的虚函数。如果派生类没有重写基类的虚函数,那么派生类的虚函数表中对应的虚函数地址仍然指向基类中的虚函数。
在这里插入图片描述
虚函数表本质是一个存虚函数指针的指针数组,这个数组最后面放了一个nullptr

思路:取出ps、st对象的头4bytes,就是虚表的指针,然后利用虚表的指针对虚函数数组进行遍历,若能打印出Func3的函数指针,就说明Func3在虚函数表中

typedef void(*FUNC_PTR) ();//FUNC_PTR为函数指针
void PrintVFT(FUNC_PTR* table)//table为函数指针数组
{
	//虚函数表本质是一个存虚函数
	//指针的指针数组,这个数组最后面放了一个nullpt
	for (size_t i = 0; table[i] != nullptr; i++)
	{   printf("[%d]:%p->", i, table[i]);
		FUNC_PTR f = table[i];
		f();//函数指针指向函数的内存地址,
		//通过调用函数指针,实际上是调用了指向的函数
	}
	printf("\n");
}
	int main() {
	Person ps;
	Student st;
//1.先取ps的地址,强转成一个int * 的指针
// 2.再解引用取值,就取到了b对象头4bytes的值,这个值就是指向虚表的指针(二重函数指针)
	int vft1 = *((int*)&ps);
	PrintVFT((FUNC_PTR*)vft1);
//3.再强转成VFPTR * ,因为虚表就是一个存FUNC_PTR类型(虚函数指针类型)的数组。
// 4.虚表指针传递给PrintVTable进行打印虚表
	int vft2 = *((int*)&st);
	PrintVFT((FUNC_PTR*)vft2);
//5.需要说明的是这个打印虚表的代码经常会崩溃,因为编译器有时对虚表的处理不干净,虚表最
//后面没有放nullptr,导致越界,这是编译器的问题。我们只需要点目录栏的 - 生成 - 清理解决方案,再编译就好了。
		return 0;
	}

在这里插入图片描述

五、多继承中的虚函数表

多继承的对象虚函数表

在这里插入图片描述

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

多继承中的虚函数表有多个

多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中
在这里插入图片描述

六、问答题

  1. inline函数可以是虚函数吗?
    答:可以,不过编译器就忽略inline属性,这个函数就不再是inline,因为虚函数要放到虚表中去
    2.静态成员可以是虚函数吗?
    答:不能,因为静态成员函数没有this指针,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。
    3.构造函数可以是虚函数吗?
    答:不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的
    4.虚函数表是在什么阶段生成的,存在哪的?
    答:虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。
  2. 对象访问普通函数快还是虚函数更快?
    答:首先如果是普通对象,是一样快的。如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/850740.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

湖南科技学院图书馆藏八一新书《乡村振兴战略下传统村落文化旅游设计》

湖南科技学院图书馆藏八一新书《乡村振兴战略下传统村落文化旅游设计》

JDBC(常用类与接口、实现数据库的增删查改)

目录 1.Connection接口常用方法 2.DriverMange类 3.Statement接口 4.实现表的数据更新&#xff08;增、改、删&#xff09; 5.实现数据查找&#xff08;ResultSet接口&#xff09; 6.PreparedStatement 数据更新 1.Connection接口常用方法 用来与数据库连接的对象&#xff…

66 # form 数据格式化

实现一个 http 服务器 客户端会发送请求 GET POST 要处理不同的请求体的类型 表单格式&#xff08;formData a1&b2&#xff09;&#xff0c;可以直接通信不会出现跨域问题JSON &#xff08;"{"kaimo":"313"}"&#xff09;文件格式 &#x…

刚刚更新win11,记事本消失怎么处理?你需要注意些什么?

记录window11的bug hello&#xff0c;我是小索奇 昨天索奇从window10更新到了window11&#xff0c;由于版本不兼容卸载了虚拟机&#xff0c;这是第一个令脑壳大的&#xff0c;算了&#xff0c;还是更新吧&#xff0c;了解了解win11的生态&#xff0c;后期重新装虚拟机 第一个可…

uniapp scroll-view 隐藏滚动条

/*清除滚动条 - 适配安卓*/::-webkit-scrollbar {width: 0;height: 0;color: transparent;}/*清除滚动条 - 适配IOS*/::-webkit-scrollbar {display: none;}

前端项目时因chunk-vendors过大导致首屏加载太慢,Vue Build时chunk-vendors的优化方案...

1、compression-webpack-plugin插件打包.gz文件 安装插件 也可以指定版本 我这里下载的是1.1.2版本的&#xff0c;试过更高的版本会有ES6语法的报错&#xff0c;因为我node使用的是v12&#xff0c;如果node版本更高可以尝试更高版本 npm install --save-dev compression-webpa…

day14 引用数组类型

向上造型 计算机 编译过程 和运行过程 Pet pet new Cat(); 图中 pet.eat()为父类构造方法 、 pet.catchMouse&#xff08;&#xff09;为子类方法 向上造型构成对象不能调用子类独有方法 出现方法重写会调用子类方法 向下造型 Cat cat1 (Cat)new Pet 这种形式错误…

FortiGate防火墙日志审计运维

环境介绍 CPU&#xff1a;8核&#xff0c;内存&#xff1a;16GB&#xff0c;硬盘&#xff1a;100GB 操作系统版本&#xff1a;CentOS-7-x86_64-DVD-2003 平台版本&#xff1a;鸿鹄2.7.0 安装组件 安装环境支持确认 鸿鹄计算引擎使用了 AVX2 高级指令集做向量计算加速&#xf…

Python SQLAlchemy ( ORM )

From Python中强大的通用ORM框架&#xff1a;SQLAlchemy&#xff1a;https://zhuanlan.zhihu.com/p/444930067Python ORM之SQLAlchemy全面指南&#xff1a;https://zhuanlan.zhihu.com/p/387078089 SQLAlchemy 文档&#xff1a;https://www.sqlalchemy.org/ SQLAlchemy入门和…

STM32F103——GPIO八种工作模式

目录 1、GPIO 基本结构分析 2、GPIO 八种工作模式 2.1 输入浮空 2.2 输入上拉 2.3 输入下拉 2.4 模拟功能 2.5 开漏输出 2.6 开漏式复用功能 2.7 推挽输出 2.8 推挽式复用功能 3、GPIO 八种工作模式特点及应用 1、GPIO 基本结构分析 STM32F103的 GPIO 工作有八种…

Vue.js2+Cesium1.103.0 七、Primitive 绘制航线元素

Vue.js2Cesium1.103.0 七、Primitive 绘制航线元素 用 Primitive 绘制航线元素&#xff0c;包括航点图标&#xff0c;航线线段&#xff0c;线段距离标注&#xff0c;航点序号&#xff0c;海拔标注&#xff0c;总航程等信息。 可同时绘制多条航线&#xff1b;可根据 id 清除指…

【LeetCode】数据结构题解(11)[用队列实现栈]

用队列实现栈 &#x1f609; 1.题目来源&#x1f440;2.题目描述&#x1f914;3.解题思路&#x1f973;4.代码展示 所属专栏&#xff1a;玩转数据结构题型❤️ &#x1f680; >博主首页&#xff1a;初阳785❤️ &#x1f680; >代码托管&#xff1a;chuyang785❤️ &…

PHP最简单自定义自己的框架定义常量自动生成目录(三)

1、框架入口增加模块定义&#xff0c;实现多模块功能 index.php 定义模块 <?php //定义当前请求模块 define("MODULE",index); require "./core/KJ.php"; 创建后台模块admin.php <?php define("MODULE",admin); require "./cor…

在家构建您的迷你聊天Chat gpt

推荐&#xff1a;使用 NSDT场景编辑器 助你快速搭建可编辑的3D应用场景 什么是指令遵循模型&#xff1f; 语言模型是机器学习模型&#xff0c;可以根据句子的前一个单词预测单词概率。如果我们向模型请求下一个单词&#xff0c;并将其递减地反馈给模型以请求更多单词&#xff…

《Linux运维实战:Docker基础总结》

一、简介 1、docker的基本结构是什么&#xff0c;包含哪些组件&#xff1f; docker的基本机构是c/s模式&#xff0c;即客户端/服务端模式。 由docker客户端和docker守护进程组成。docker客户端通过命令行或其它工具使用docker sdk与docker守护进程通信&#xff0c;发送容器管理…

SaaS系统相比传统软件,为何数据更安全?

随着云计算、5G等技术的不断进步&#xff0c;SaaS行业步入了快速发展的阶段&#xff0c;应用场景也日趋多元化。预计2023年底&#xff0c;中国SaaS行业市场规模将达到555.1亿元。 中研网对于SaaS发展态势预测这样评价&#xff1a; 当前&#xff0c;我国在多个维度上具备发展 S…

Mysql主从搭建 基于DOCKER

创建目录 #主节点目录 mkdir -p /home/data/master/mysql/#从节点目录 mkdir -p /home/data/slave/mysql/创建配置文件 # 主节点配置 touch /home/data/master/mysql/my.cnf# 从节点配置 touch /home/data/slave/mysql/my.cnf编辑配置文件 主节点配置文件 vim /home/data/m…

【ChatGPT】自我救赎

ChatGPT辅助学习C之【在C中如果大数据类型转小数据类型会发生什么呢?】&#xff0c;今天问ChatGPT一个问题&#xff0c;让它解析下面这个C程序&#xff1a; #include <iostream> #include <cstdio> using namespace std; int main() {int a;long long b532165478…

haproxy基本编译环境部署

前提&#xff1a;haproxy支持基于lua实现功能扩展&#xff08;需要安装比较新的lua语言&#xff0c;方便进行haproxy编译&#xff09;。 wget http://www.lua.org/ftp/lua-5.3.5.tar.gz lua -v # 检查环境 yum list lua # 查看可以安装环境 同时还需要gcc&#xff0c;gcc-c&…

rocketMq消息队列详细使用与实践整合spring

文章目录 一、RocketMQ原生API使用1、测试环境搭建2、RocketMQ的编程模型3、RocketMQ的消息样例3.1 基本样例3.2 顺序消息3.3 广播消息3.4 延迟消息3.5 批量消息3.6 过滤消息3.7 事务消息3.8 ACL权限控制 二、SpringBoot整合RocketMQ1、快速实战2、其他更多消息类型&#xff1a…