【C++】虚函数表 多态的原理 动态绑定和静态绑定

news2024/9/26 3:30:51

梳理虚函数表、多态原理、动静态绑定的知识

目录

一、虚函数表

二、多态的原理

三、动态绑定和静态绑定


一、虚函数表

在学习多态原理之前,我们需要了解一下虚函数表的概念 

我们先一起来看下下面这段代码

// 这里常考一道笔试题:sizeof(Base)是多少?
class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}
private:
	int _b = 1;
};

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

虚函数表指针和虚表的关系我们在后文中会详细讲解。

 

我们先来学习一下虚表的知识,这个派生类中的表(虚表)究竟是什么呢?它里面又放了些什么呢?我们接着往下分析

// 针对上面的代码我们做出以下改造
// 1.我们增加一个派生类Derive去继承Base
// 2.Derive中重写Func1
// 3.Base再增加一个虚函数Func2和一个普通函数Func3
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. 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,其中包括虚表指针和父类的成员变量,另一部分是自己的成员。

2. 基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。

3. 另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会放进虚表。

4. 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。

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

6. 这里还有几个很容易混淆的问题:虚函数存在哪的?虚表存在哪的?虚函数是不是存在虚表中?虚表是不是存在对象里? 答:注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数是一样的,都是存在代码段的,只是他的指针又存到了虚表中另外对象中存的不是虚表,存的是虚表指针那么虚表存在哪的呢?实际我们去验证一下会发现,虚表在vs下是存在于代码段的。

【总结】对象->虚表指针->虚表(虚函数指针的指针数组)->虚函数指针->虚函数。

 
 


二、多态的原理

通过上面的学习,我们对虚表有了大概的了解。那么多态的原理到底是什么?和虚表又有什么样的关系呢?接下来让我们一起来看下面的图例,这里Func函数传Person调用的Person::BuyTicket,传Student调用的是Student::BuyTicket

代码如下

class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};

class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
};

void Func(Person& p)
{
	p.BuyTicket();
}

int main()
{
	Person Mike;
	Func(Mike);
	Student Johnson;
	Func(Johnson);
	return 0;
}

运行上面的代码并打开监视窗口,我们可以得到

1. 观察下图的红色箭头我们看到,p是指向mike对象时,p->BuyTicket在mike的虚表中找到虚函数是Person::BuyTicket。

2. 观察下图的蓝色箭头我们看到,p是指向johnson对象时,p->BuyTicket在johson的虚表中找到虚函数是Student::BuyTicket。

3. 这样就实现出了不同对象去完成同一行为时,展现出不同的形态。

4. 反过来思考我们要达到多态,有两个条件,一个是虚函数覆盖,一个是要对象(基类)的指针或引用调用虚函数。有兴趣的小伙伴可以思考一下为什么?答案就是虚函数覆盖才能输出不同的结果,通过基类的指针或引用调用虚函数,才能准确完成下面的流程:对象->虚表指针->虚表->虚函数指针->虚函数。

5. 再通过下面的汇编代码分析,我们可以得出,满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象的中取找的。不满足多态的函数调用时编译时确认好的。

//多态调用
void Func(Person* p)
{
	p->BuyTicket();
}
int main()
{
	Person mike;
	Func(&mike);
	mike.BuyTicket();

	return 0;
}

下面展示的是多态调用的反汇编 

// 以下汇编代码中与问题不相关的都被去掉了
void Func(Person* p)
{
	...
	p->BuyTicket();
	// p中存的是mike对象的指针,将p移动到eax中

	001940DE  mov         eax, dword ptr[p]
	// [eax]就是取eax值指向的内容,这里相当于把mike对象头4个字节(虚表指针)移动到了edx

	001940E1  mov         edx, dword ptr[eax]
	// [edx]就是取edx值指向的内容,这里相当于把虚表中的头4字节存的虚函数指针移动到了eax

	00B823EE  mov         eax, dword ptr[edx]

    001940EA  call        eax
    // call eax中存虚函数的指针。这里可以看出满足多态的调用,不是在编译时确定的,是运行起来
	//以后到对象的中取找的。
	
    001940EC  cmp         esi, esp
}

下面展示的是普通函数调用的反汇编 

//普通函数调用
int main()
{
	...
	// 首先BuyTicket虽然是虚函数,但是mike是对象,不满足多态的条件,所以这里是普通函数
	// 普通函数的调用转换成地址时,是在编译时已经从符号表确认了函数的地址,直接call 地址
	mike.BuyTicket();
	00195182  lea         ecx, [mike]
	00195185  call        Person::BuyTicket(01914F6h)
	...
}


三、动态绑定和静态绑定

1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载。

2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。【我们接触到的多态多为动态多态】。

3. 第二节中展示的反汇编代码汇编代码很好的解释了什么是静态(编译器)绑定和动态(运行时)绑定。

满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象的中取找的,因此它为动态绑定。而一般的函数调用,在程序编译期间确定了程序的行为,是静态绑定。


以上就是本篇文章的所有内容,如果对您有帮助,不妨点赞、收藏、关注,感谢您的阅读。

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

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

相关文章

Centos7设置yum源

为什么要修改yum源呢&#xff1f; 因为默认的yum源下载速度很慢&#xff0c;所以我们需要修改yum源。我是用的阿里云的yum源 环境 虚拟机&#xff1a;VirtualBox 7.0.4 操作系统&#xff1a;CentOS 7 x86_64 查看默认的yum源 cd /etc/yum.repos.d将列出的所有默认yum源复…

unity愤怒的小鸟学习制作(一)

基础知识已经差不多了&#xff0c;现在开始模仿敲代码然后在模仿中熟悉软件和语法 视频链接和素材如下&#xff1a;视频 目录 第一部分&#xff1a;游戏逻辑1、新建2D工程2、创建三个场景3、导入游戏需要的资源4、开始编辑02-game4.1 裁切图片4.2 初步编辑4.3 实现小鸟的拖拽4…

【源码分析】从源码层面深度剖析Redisson实现分布式锁的原理

快速入门 引入redisson依赖 <dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.16.0</version> </dependency>编写测试代码 public class RedissonTest {private static RedissonC…

SubMain GhostDoc Enterprise v2022 Crack

GhostDoc 是一个 Visual Studio 扩展&#xff0c;适用于需要使用可自定义模板从源代码生成 XML 注释、维护干净和最新文档、生成多种格式的帮助文档、在 Visual Studio 中使用智能源代码拼写检查器等的开发人员. GhostDoc 还有助于自动生成干净、有用的干净代码文档&#xff0c…

scratch统计距离学校远近 中国电子学会图形化编程 少儿编程 scratch编程等级考试四级真题和答案解析2023年3月

目录 scratch统计距离学校远近 一、题目要求 1、准备工作 2、功能实现 二、案例分析

Elasticsearch:结合两全其美:Elasticsearch 与 BM25 和 HNSW 的混合搜索

就搜索算法而言&#xff0c;没有万能的解决方案。 不同的算法在不同的场景下效果更好&#xff0c;有时需要算法的组合才能达到最好的效果。 在 Elasticsearch 中&#xff0c;一种流行的组合搜索算法的方法是使用混合搜索&#xff0c;将用于文本搜索的 BM25 算法与用于最近邻搜索…

数据结构与算法十 并查集

一 并查集 并查集是一种树型的数据结构 &#xff0c;并查集可以高效地进行如下操作&#xff1a; 查询元素p和元素q是否属于同一组合并元素p和元素q所在的组 1.1 并查集结构 并查集也是一种树型结构&#xff0c;但这棵树跟我们之前讲的二叉树、红黑树、B树等都不一样&#xf…

【Stable Diffusion】基本概念之hypernetwork

1.基本概念 hypernetwork&#xff0c;中文名为超网络&#xff0c;是一种神经网络架构,它允许动态生成神经网络的参数(权重)。简而言之,hypernetwork可以生成其他神经网络。 在Stable Diffusion中,hypernetwork被用于动态生成分类器的参数&#xff0c;为Stable Diffusion模型添加…

牛客刷SQL题Day5

SQL69 返回产品并且按照价格排序 select prod_name , prod_price from Products where prod_price between 3 and 6 select prod_name , prod_price from Products where 6>prod_price and prod_price >3 踩坑1&#xff1a; between......and.......包括边界。 踩坑2&am…

ES6之箭头函数

文章目录 前言一、定义二、简化1.当函数参数只有一个时2.当函数体只有一条return语句时 三、注意1.箭头函数的this2.不能作为构造函数实例化对象3.不能使用argument变量 总结 前言 简单的讲&#xff0c;箭头函数是将原function关键字和函数名删掉的一种简写函数形式。 一、定义…

二维体光子晶体的平面波展开法代码

%书上的代码&#xff0c;和FEM符合的更好 %在这个代码里试着把单位原胞的相对介电常数分布画出来 %这个代码的单位原胞的中心就是(0,0)点&#xff0c;也就是坐标原点 %The program for the computation of the PhC photonic %band structure for 2D PhC. %Parameters of the st…

Jvm --java虚拟机(上)

为什么学习jvm 如果你这辈子只甘心做一个平庸的Java码农&#xff0c;那么你可以利用阅读本文的时间去学习其他新的技术知识&#xff0c;但是如果你想成为一个更更更更优秀的中高级程序员&#xff01;那么请继续阅读本文&#xff0c;希望这篇文章会对你有所帮助&#xff0c;那么…

国考省考结构化面试:综合分析题,名言哲理(警句观点启示)、漫画反驳题等

国考省考结构化面试&#xff1a;综合分析题&#xff0c;名言哲理&#xff08;警句观点启示&#xff09;、漫画反驳题等 2022找工作是学历、能力和运气的超强结合体! 公务员特招重点就是专业技能&#xff0c;附带行测和申论&#xff0c;而常规国考省考最重要的还是申论和行测&a…

从面向过程到面向对象

目录 1、抽象 2、UML类图 3、类定义 4、类成员函数 &#xff08;1&#xff09;构造函数&#xff08;constructor&#xff09; &#xff08;2&#xff09;析构函数&#xff08;destructor&#xff09; 5、对象实现 6、封装 7、getter、setter方法 为什么要从面向过程转…

C++实现ini配置文件解析——API设计

什么是配置文件 INI文件&#xff08;Initialization File&#xff09;是一种文本文件格式&#xff0c;通常用于存储配置数据。INI文件最初由Microsoft在Windows系统中引入&#xff0c;用于存储应用程序的配置信息。 INI文件的结构相对简单&#xff0c;由一系列的节&#xff0…

国外15家值得关注的AI创业公司

文 | 小戏、iven 星星之火&#xff0c;可以燎原。 在大模型横空出世的这个疯狂的春天&#xff0c;一场关于 AI 产品的革命也正在席卷全球。这边是大公司一个接一个模型搞军备竞赛&#xff0c;那边是各路豪强纷纷下场创业招兵买马。那么&#xff0c;除了咱们耳熟能详的 OpenAI 以…

数字化转型导师坚鹏:企业数字化营销

企业数字化营销 ————助力零售业务向批量化开发转变&#xff0c;对公业务向智慧化转变 课程背景&#xff1a; 很多企业存在以下问题&#xff1a; 不清楚数字化营销对企业发展有什么影响&#xff1f; 不知道如何提升企业数字化营销能力&#xff1f; 不知道企业如何开…

面试官:一千万的数据,你是怎么查询的

面试官&#xff1a;一千万的数据&#xff0c;你是怎么查询的&#xff1f; 前言 面试官&#xff1a;来说说&#xff0c;一千万的数据&#xff0c;你是怎么查询的&#xff1f;B哥&#xff1a;直接分页查询&#xff0c;使用limit分页。面试官&#xff1a;有实操过吗&#xff1f;B…

word@通配符@高级搜索查找@替换@中英文标点符号替换

文章目录 高级搜索通配符批量选中引用序号上标调整搜索替换作用范围设置&#x1f388;通过样式选择作用区域通过鼠标选择作用区域 高级替换操作顺序 标点符号替换&#x1f388;将英文逗号替换为中文逗号使用普通查找和替换&#xff1a;使用通配符替换 将英文句点替换为中文句号…

【Stable Diffusion】ControlNet基本教程(二)

接上篇【Stable Diffusion】ControlNet基本教程&#xff08;一&#xff09;&#xff0c;本篇介绍两个ControlNet常见的基本用法&#xff0c;更多用法欢迎关注博主&#xff0c;博主还会更新更多有趣的内容。 3.ControlNet基本用法 3.1漫画线稿上色 &#xff08;1&#xff09;上传…