C++中的多态(原理篇)

news2024/11/16 15:58:40

多态的原理

虚函数表

下面这一串代码

class A
{
public:
	virtual void func()
	{
		cout << "func1()" << endl;
	}
private:
	int _a;
};


我们看到了a对象的大小是8bit大小,但是a对象里面不应该只是一个_a吗?当我们打开监视窗口发现在这里插入图片描述
a对象中不仅存储了一个成员_a,还有一个_vfptr放在成员_a的前面,对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。这个指针指向虚函数表,因为虚函数的地址要放在虚函数表中,由此可以直接找到虚函数的位置。
我们将上面的代码改写一下,让B去继承A类

class A
{
public:
	virtual void func1()
	{
		cout << "func1()" << endl;
	}
	virtual void func2()
	{
		cout << "func2()" << endl;
	}
	void func3()
	{
		cout << "func3()" << endl;
	}
private:
	int _a;
};
class B :public A
{
public:
	virtual void func1()
	{
		cout << "func1()" << endl;
	}
private:
	int _b;
};
int main()
{
	A a;
	B b;
	return 0;
}

调试之后
在这里插入图片描述
经过调试之后我们发现,在 子类对象b中也有一个虚表指针,并且b对象将a对象中的虚表继承了下来,并且对第一个func1函数进行了重写,这里直接将func1函数进行了覆盖,因此重写也被称作覆盖。对于func2()是继承下来的虚函数,对于func3(),因为其不是虚函数,那么也自然没有放在虚表之中。
虚函数表的本质是一个存放函数指针的数组,在这个数组中一般情况下在最后一个位置为空(nullptr)(VS下)。

动态绑定与静态绑定

  1. 静态绑定称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载
  2. 动态绑定称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。

单继承的虚表函数

class A
{
public:
	virtual void func1()
	{
		cout << "func1()" << endl;
	}
	virtual void func2()
	{
		cout << "func2()" << endl;
	}
	virtual void func3()
	{
		cout << "func3()" << endl;
	}
private:
	int _a;
};
class B :public A
{
public:
	virtual void func1()
	{
		cout << "func1()" << endl;
	}
	virtual void func4()
	{
		cout << "func4()" << endl;
	}
	virtual void func5()
	{
		cout << "func5()" << endl;
	}
private:
	int _b;
};
int main()
{
	A a;
	B b;
	return 0;
}

上面的代码中,我们通过调试发现

在继承之后,重写了func1()函数,但是并没有看见func4()和func5()函数,这里是监视窗口的一个问题,将其隐藏了,我们使用代码打印,可以直接打印出来虚函数表里面的内容。

typedef void(*VF_PTR)();

void PrentfVfptr(VF_PTR* table)
{
	for (int i = 0; table[i] != nullptr; i++)
	{
		printf("第%d个虚函数的地址->%p\n", i, table[i]);
	}
	cout << endl << endl;
}

我们这个函数可以直接打印出虚函数表中的地址,因为我们虚函数表中存储的是函数指针,我们将函数指针typedef之后,虚函数表的本质是一个数组,因此我们可以使用数组的方式来进行打印。
在这里插入图片描述

多继承中的虚函数表

class A
{
public:
	virtual void func1() { cout << "func1()" << endl; }
	virtual void func2() { cout << "func2()" << endl; }
private:
	int _a;
};
class B
{
public:
	virtual void func1() { cout << "func1()" << endl; }
	virtual void func2() { cout << "func2()" << endl; }
private:
	int _b;
};
class C :public A, public B
{
public:
	virtual void func1() { cout << "func1()" << endl; }
	virtual void func2() { cout << "func2()" << endl; }
private:
	int _c;
};

typedef void(*VF_PTR)();

void PrentfVfptr(VF_PTR* table)
{
	for (int i = 0; table[i] != nullptr; i++)
	{
		printf("第%d个虚函数的地址->%p\n", i, table[i]);
	}
	cout << endl << endl;
}
int main()
{
	A a;
	B b;
	C c;
	PrentfVfptr((VF_PTR*)*((void**)&c));
	return 0;
}

上面一串代码,当我们打印C对象的虚函数表的时候,我们发现只打印出来了两个地址
在这里插入图片描述
明明有4个虚函数被继承了下来了在这里插入图片描述
这里我们看到了应该有两份虚函数表这里我们应该调用两次打印函数,那么第个虚函数表的位置在哪里呢,应该是c对象的开始往后偏移sizeof(a)的位置,那么此时就可以

int main()
{
	A a;
	B b;
	C c;
	PrentfVfptr((VF_PTR*)*((void**)&c));
	PrentfVfptr((VF_PTR*)*((void**)(((char*)&c+sizeof(a)))));
	return 0;
}

在这里插入图片描述
这样我们就能打印出来完整的虚函数表了
还有,我们在继承中学习了切片,那么这里我们也可以使用切片去实现

int main()
{
	A a;
	B b;
	C c;
	PrentfVfptr((VF_PTR*)*((void**)&c));

	B* p = &c;
	PrentfVfptr((VF_PTR*)*((void**)(p)));

	PrentfVfptr((VF_PTR*)*((void**)(((char*)&c+sizeof(a)))));
	return 0;
}

在这里插入图片描述

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

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

相关文章

C++STL-list的简易实现

文章目录1. list的介绍2. 迭代器的分类3. list的构造4. list的实现4.1 list的基本结构4.2 list的push_back函数4.2 list的迭代器4.2.1 operator- >4.2.2 const迭代器4.3 insert函数4.4 earse函数4.5 迭代器失效问题4.6 析构函数4.7 构造函数4.8 拷贝构造1. 传统写法2. 现代写…

【C++升级之路】第四篇:类和对象(下)

&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f; &#x1f36d;&#x1f36d;系列专栏&#xff1a;【C学习与应用】 ✒️✒️本篇内容&#xff1a;类与对象知识汇总&#xff0c;包括初始化列表的基本概念和使用的注意事项、explicit关键字、C/C中的static成…

Python【方法和返回值(Union)联合类型】注解

什么是类型注解&#xff1a;供调用者在使用函数&#xff08;方法&#xff09;时&#xff0c;如果没有完善的文档作为参考&#xff0c;开发者不知道要给定义的【变量、方法中的函数、】传入什么数据类型&#xff0c;以免减少编译错误。有了类型注解可以让 IDE 知道了数据类型后&…

C++语法2——for、while、do-while的语法及区别

C语言语法1详情请看这两篇博客&#xff1a;&#xff08;此号为本人小号&#xff09; 四则运算及基本语法 数据类型 接下来要讲得是循环语句 for循环 基本语法&#xff1a; for(表达式1&#xff1b;表达式2&#xff1b;表达式3) {内嵌语句&#xff1b; }执行顺序&#xff1a;…

js如何计算年龄?如何创建Javascript 年龄计算器?

如何创建年龄计算器? 要构建这个项目,我们需要HTML,CSS和Javascript。 让我们来看看这个项目是如何工作的。项目由输入日期组成。用户必须单击它并选择他们的出生日期或任何所需的日期。在此之后,用户必须单击计算按钮。用户单击计算按钮后,我们会根据他们输入的日期(…

深入理解ConcurrentLinkedQueue源码

1. 概述 在我们的日常开发中&#xff0c;经常会使用队列这种数据结构&#xff0c;需要它的队尾进、队头出的特点。于是&#xff0c;Doug Lea大师设计了一个线程安全的队列ConcurrentLinkedQueue&#xff0c;它是采用链表的形式构成的。我们接下来尝试通过代码去了解其中的设计…

S7-200SMART通过表格指令实现模拟量信号滑动平均值滤波的具体方法

S7-200SMART通过表格指令实现模拟量信号滑动平均值滤波的具体方法 当现场的模拟量信号波动太大,而通过硬件的方式尚无法实现平稳的信号输入时,可采用软件上的滤波进行信号处理, 本次和大家分享的即通过取多个信号值的平均值的方式实现模拟量滤波的具体方法示例,仅供大家参考…

DataNode节点下线速度优化

目录 一、节点掉线或退役 1.1区分节点掉线和节点退役的区别 1.2 如何处理节点掉线出现的各种风暴 1.2.1 Datanode的block复制 1.2.2 控制节点掉线RPC风暴的参数 二、如何快速节点下线 一、节点掉线或退役 背景&#xff1a;5台数据节点&#xff0c;存储40T数据 block数112…

高等数学(上) —— 一元积分学

文章目录Ch4.不定积分原函数F(x)F(x)F(x)原函数存在定理不定积分∫f(x)dx\int f(x)dx∫f(x)dx不定积分公式不定积分 ⇦⇨ 变上限积分&#xff1a;∫f(x)dx∫0xf(t)dt\int f(x){\rm d}x\int_0^xf(t){\rm d}t∫f(x)dx∫0x​f(t)dtCh5.定积分1.定积分定义定积分的几何意义2.定积分…

ESP32的python开发环境搭建:Thonny+MicroPython

1 Thonny安装 Thonny —— 一个面向初学者的 Python IDE。Thonny良好的支持Microbit、ESP32和树莓派等的开发. 安装下载地址&#xff1a; https://thonny.org/ 2 Micropython安装 MicroPython 是 Python 3 语言的精简实现 &#xff0c;包括Python标准库的一小部分&#xff0…

Vector - VT System - 板卡_VT8006/VT8012

由于最近不幸变为了小*人&#xff0c;因此断更了一周&#xff0c;今天稍有好转&#xff0c;就新加一块大家应该会比较感兴趣的VT板卡硬件介绍吧&#xff0c;也预示着新的开始&#xff0c;马上也要到了元旦&#xff0c;新的一年即将开始&#xff0c;提前在这里祝福大家在新的一年…

JavaPub面试宝典【第22版】

JavaPub面试宝典【第22版】 直接上干货&#xff0c;几百篇原创笔记都在这。 文章列表 &#x1f4da;最少必要面试题 Java基础Java并发入门Java容器JavaWebJVMMySQLMyBatisSpringSpringBootRedisElasticSearchKafkaZookeeperDocker缓存 &#x1f4d6;知识点总结 下面是原创…

linux 下命令

linux 下命令 Linux 是一套免费使用和自 由传播的类 Unix 操作系统&#xff0c; 是一个基于 POSIX 和 UNIX 的多用户、 多任务、 支持多线程和多 CPU 的操作系统。 它能运行主要的 UNIX 工具软件、 应用程序和网络协议。 它支持 32 位和 64 位硬件。 Linux 继承了 Unix 以网络为…

uniCloud云开发----4、uniCloud云开发进阶使用方法

uniCloud云开发进阶使用方法前言1、云对象的importObject的创建和使用(1&#xff09;创建云对象&#xff08;2&#xff09;编辑云对象&#xff08;3&#xff09;在.vue文件中调用云对象&#xff08;4&#xff09;在.vue文件中调用方法2、客户端直接连接数据库(1)直接在客户端引…

设计模式-牛刀小试02

前言 本文为datawhale2022年12月组队学习《大话设计模式》最后一次打卡任务&#xff0c;本次完成homework2。 【教程地址】https://github.com/datawhalechina/sweetalk-design-pattern 一、任务描述 1.1 背景 小李已经是一个工作一年的初级工程师了&#xff0c;他所在的公…

sqlite wal 分析

动手点关注干货不迷路sqlite 提供了一种 redo log 型事务实现&#xff0c;支持读写的并发&#xff0c;见 write-ahead log&#xff08;https://sqlite.org/wal.html&#xff09;。本文将介绍 wal 原理&#xff0c;并源码剖析 checkpoint 过程&#xff0c;同时讨论下 wal 使用中…

知行之桥EDI系统如何通过ZIP端口压缩文件?

在EDI项目当中&#xff0c;对于IT技术不够成熟或设备不够完善的用户来说&#xff0c;EXCEL方案是较为适中的选择。收到合作伙伴发来的850订单之后&#xff0c;将订单数据转换为EXCEL&#xff0c;再将EXCEL发送至用户指定的邮箱。若每条订单单独发送至邮箱&#xff0c;当订单量大…

#榜样的力量#《新冠战“疫”——2022中国数据智能产业最具社会责任感企业》榜正式发布...

‍数据猿出品数据猿此次推出的#榜样的力量#《新冠战“疫”——2022中国数据智能产业最具社会责任感企业》榜单/奖项为公益主题策划活动。旨在为正能量助威&#xff0c;为中国数据智能产业中具有社会责任感的典型性企业发声。数据智能产业创新服务媒体——聚焦数智 改变商业新冠…

66、【链表】leetcode——142. 环形链表 II(C++、Python版本)

题目描述 方法一&#xff1a;哈希表 C版本 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode(int x) : val(x), next(NULL) {}* };*/ class Solution { public:ListNode *detectCycle(ListNode *head) {unord…

年终小结:苟住

大家好&#xff0c;我是一哥&#xff0c;新的一年又要到来了&#xff0c;一个词总结下今年的状态 01 工作 2022真的比较难&#xff0c;作为互联网技能从业人员&#xff0c;相信大家一定经历了很多。这一年虽然我没有被毕业&#xff0c;但也经历了公司的业务调整&#xff0c;曾…