【C++】多态(下)

news2024/11/24 13:10:53

在这里插入图片描述

个人主页~

多态(上)~


多态

  • 四、多态的原理
    • 1、虚表的存储位置
    • 2、多态的原理
    • 3、动态绑定和静态绑定
  • 五、单继承和多继承关系的虚函数表
    • 1、单继承中的虚函数表
    • 2、多继承中的虚函数表
  • 六、多态中的一些小tips

四、多态的原理

1、虚表的存储位置

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

void func()
{
	cout << "void func()" << endl;
}

int main()
{
	A a1;
	A a2;

	static int a = 0;
	int b = 0;
	int* p1 = new int;
	const char* p2 = "hello world";
	printf("静态区:%p\n", &a);
	printf("栈:%p\n", &b);
	printf("堆:%p\n", p1);
	printf("代码段:%p\n", p2);
	printf("虚表:%p\n", *((int*)&a1));
	printf("虚函数地址:%p\n", &A::func1);
	printf("普通函数地址:%p\n", func);

	return 0;
}

在这里插入图片描述
被static修饰的变量a存放在静态区,局部变量b存储在栈区,指针p1指向在堆上开辟出的对象,常量字符串的指针存放在代码段

虚表这里,因为a1是一个类对象,它的地址存放了虚表指针和内置类型_a两部分,虚表指针是一个void类型的指针,占4个字节,把它强制转换成int*类型的指针,再解引用,得到的是虚表的指针

虚函数地址就是固定用法,要把是哪个类的虚函数标注出来,然后用取地址符号

从上图我们可以观察到,虚函数和普通函数存放位置接近,代码段和虚表存放位置接近,而虚表和虚函数相对于静态区栈区以及堆区来说还是离代码段更近近,也就是说,虚函数和普通函数以及虚表存放在代码段

2、多态的原理

class A
{
public:
	virtual void D()
	{
		cout << "A : virtual void D()" << endl;
	}
};

class B : public A
{
public:
	virtual void D()
	{
		cout << "B : virtual void D()" << endl;
	}
};

void func(A& ra)
{
	ra.D();
}

void test()
{
	A a;
	B b;
	func(a);
	func(b);
}

在这里插入图片描述
在这里插入图片描述
当ra为A对象时,函数调用时在A的虚表中找到func,当ra为B对象时,函数调用时在B的虚表中找到func,然后调用,这样就实现出了不同对象去完成同一行为时,展现出不同的形态

我们要达到多态,有两个条件,一个是虚函数覆盖,一个是对象的指针或引用调用虚函数

class A
{
public:
	virtual void D()
	{
		cout << "A : virtual void D()" << endl;
	}
};

class B : public A
{
public:
	virtual void D()
	{
		cout << "B : virtual void D()" << endl;
	}
};

void func(A* p)
{
	p->D();
}

void test()
{
	A a;
	func(&a);
	a.D();
}

在这里插入图片描述
在这里插入图片描述
对于多态调用:
p中存的是A对象的指针,将p移动到eax中:
00382571 mov eax,dword ptr [p]

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

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

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

对于普通调用:
因为不满足多态调用所以是普通函数调用,直接call地址

3、动态绑定和静态绑定

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

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

五、单继承和多继承关系的虚函数表

1、单继承中的虚函数表

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

int main()
{
	A a;
	B b;

	return 0;
}

在这里插入图片描述
图中的监视窗口中我们发现看不见func3和func4,这里是编译器的监视窗口故意隐藏了这两个函数

那我们如何查看整个b的虚表呢

typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{
	// 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数
	cout << "虚表地址>" << vTable << endl;
	for (int i = 0; vTable[i] != nullptr; ++i)
	{
		printf("第%d个虚函数地址 :0X%x,->", i, vTable[i]);
		VFPTR f = vTable[i];
		f();
	}
	cout << endl;
}
int main()
{
	A a;
	B b;

	VFPTR * vTablea = (VFPTR*)(*(int*)&a);
	PrintVTable(vTablea);
	VFPTR* vTableb = (VFPTR*)(*(int*)&b);
	PrintVTable(vTableb);
	return 0;
}

在这里插入图片描述

PrintVTable函数:
核心点就是这个函数指针VFPTR,这是一个函数指针,指向的类型是void*,参数为(),也就是无参,也就是说这个指针可以指向任意一个返回类型为void*并且无参的函数

PrintVTable函数的参数也可以写成VFPTR* vTable,虚表的地址就是指针vTable,后加[]就是对表中的指针进行访问,打印出它们的指针,并且将这些指针指向的函数调用表示出来,让我们可以看到这个地址对应的是哪个函数

main函数:
取出a、b对象的头4bytes,就是虚表的指针,虚函数表本质是一个存虚函数指针的指针数组,这个数组最后面放了一个nullptr
1.先取a的地址,强转成一个int*的指针

2.再解引用取值,就取到了a对象头4bytes的值,这个值就是指向虚表的指针

3.再强转成 VFPTR* ,因为虚表就是一个存VFPTR类型(虚函数指针类型)的数组

4.虚表指针传递给PrintVTable进行打印虚表

5.需要说明的是这个打印虚表的代码经常会崩溃,因为编译器有时对虚表的处理不干净,虚表最后面没有放nullptr,导致越界,这是编译器的问题,我们只需要点重新生成解决方案就行

2、多继承中的虚函数表

class A1 
{
public:
	virtual void func1() 
	{
		cout << "A1::func1" << endl; 
	}
	virtual void func2() 
	{
		cout << "A1::func2" << endl; 
	}
private:
	int a1;
};
class A2 
{
public:
	virtual void func1() 
	{
		cout << "A2::func1" << endl; 
	}
	virtual void func2() 
	{
		cout << "A2::func2" << endl; 
	}
private:
	int a2;
};
class B : public A1, public A2 
{
public:
	virtual void func1() 
	{
		cout << "B::func1" << endl; 
	}
	virtual void func3() 
	{
		cout << "B::func3" << endl; 
	}
private:
	int b;
};

typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{
	cout << "虚表地址>" << vTable << endl;
	for (int i = 0; vTable[i] != nullptr; ++i)
	{
		printf("第%d个虚函数地址 :0X%x,->", i, vTable[i]);
		VFPTR f = vTable[i];
		f();
	}
	cout << endl;
}

int main()
{
	B b;
	VFPTR* vTablea1 = (VFPTR*)(*(int*)&b);
	PrintVTable(vTablea1);
	VFPTR* vTablea2 = (VFPTR*)(*(int*)((char*)&b + sizeof(A1)));
	PrintVTable(vTablea2);
	return 0;
}

在这里插入图片描述

多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中,也就是func3函数,第一个继承基类就是最左边继承的这个基类

在这里插入图片描述

六、多态中的一些小tips

内联函数可以是虚函数,但是如果被inline修饰的函数是虚函数,那么inline特性将会消失,被修饰的函数相当于没被修饰

静态成员不可以是虚函数,因为静态成员没有this指针使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表

构造函数不能是虚函数,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的

最好把基类的析构函数定义为虚函数,因为如果基类的析构函数不是虚函数,那么只会调用基类的析构函数,而不会调用派生类的析构函数,这会导致派生类部分的对象没有被正确析构,可能会引发资源泄露

对象在访问虚函数与普通函数速度的对比,如果是普通对象访问,两者一样快,如果是多态对象访问(指针对象或者引用对象),则调用普通函数更快,因为虚函数构成多态,运行时需要到虚函数表中去查找

虚函数表在编译阶段就生成了


今日分享就到这里了~

在这里插入图片描述

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

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

相关文章

AIGC(AI网站分享)

博客C知道 人工智能(Artificial Intelligence,简称AI)是一门研究如何使机器能够像人一样思考和行动的科学。它涉及到模拟和复制人类智能的各个方面,包括学习、推理、问题解决、感知、原创性等。人工智能技术的发展使得计算机可以模拟人类的思维过程,并根据外界的输入做出相…

day02笔试练习

1.牛牛的快递 题目链接&#xff1a;牛牛的快递_牛客题霸_牛客网 public static void main(String[] args){Scanner in new Scanner(System.in);double a in.nextDouble();char c in.next().charAt(0);int ret 0;if(a < 1){ret 20;}if(a > 1){ret 20 (int) Math…

C++ | Leetcode C++题解之第447题回旋镖的数量

题目&#xff1a; 题解&#xff1a; class Solution { public:int numberOfBoomerangs(vector<vector<int>> &points) {int ans 0;for (auto &p : points) {unordered_map<int, int> cnt;for (auto &q : points) {int dis (p[0] - q[0]) * (p…

波数k(空间中角频率的变化速度,即走多少长度,变化多少角频率)

K不是电磁波的移动速度&#xff0c;那个是相速度。 K是空间中角频率的变化速度&#xff0c;即走多少长度&#xff0c;变化多少角频率

第25天:web攻防-通用漏洞sql读写注入MYSQLMSSQL

#知识点&#xff1a; 1、sql注入-mysql数据库 2、sql注入-mssql数据库 3、sql注入-postgreSQL数据库 #详细点&#xff1a; Access无高权限注入点-只能猜解&#xff0c;还是暴力猜解 MYSQL&#xff0c;PostgreSQL&#xff0c;MSSQL高权限注入&#xff0c;可升级为读写执行。…

[C#]C# winform部署yolov11目标检测的onnx模型

【测试环境】 vs2019 netframework4.7.2 opencvsharp4.8.0 onnxruntime1.16.2 【效果展示】 【实现部分代码】 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Drawing; usi…

自给自足:手搓了一个睡眠监测仪,用着怎么样?

很久不分享手搓党作品拉&#xff01; 今天分享一个“基于毫米波雷达的睡眠监测仪”作品&#xff01; 用Air700E开发板毫米波雷达&#xff0c;手搓一个开箱即用的睡眠监测仪&#xff0c;不花冤枉钱&#xff01; 来仔细瞧瞧&#xff01; 一、项目原理及硬件制作 毫米波是指频率…

1688商品API接口:电商数据自动化的新引擎

1688作为中国领先的B2B电子商务平台&#xff0c;为广大商家和制造商提供了一个展示和交易商品的广阔市场。随着1688商品API接口的推出&#xff0c;开发者和商家现在能够通过编程方式自动化获取和管理商品数据&#xff0c;极大地提高了工作效率和数据处理的灵活性。 一、1688商品…

(作业)第三期书生·浦语大模型实战营(十一卷王场)--书生入门岛通关第3关Git 基础知识

任务编号 任务名称 任务描述 1 破冰活动 提交一份自我介绍。 2 实践项目 创建并提交一个项目。 破冰活动 提交一份自我介绍。 每位参与者提交一份自我介绍。 提交地址&#xff1a;https://github.com/InternLM/Tutorial 的 camp3 分支&#xff5e; 安装并设置git 克隆仓库并…

解锁PDF阅读器的神奇功能与应用场景

PDF格式的文档因其稳定性、兼容性和安全性&#xff0c;成为了广泛传播和存储信息的重要载体。而PDF阅读器则是我们打开这个数字知识宝库的关键钥匙。接下来&#xff0c;让我们一同走进福昕PDF阅读器和它小伙伴们的世界&#xff0c;去探索它们的神奇之处。 1.福昕阅读器 链接一…

学校在线学习作业批改教学管理平台的设计与实现SpringBoot+VUE

目录 一、项目背景及目标 二、技术选型 三、系统功能模块设计 四、关键技术实现 五、总结 在当今社会上&#xff0c;随着社会的发展和进步&#xff0c;对于现代的学生来说网络课程已经广泛应用于学校的每个角落&#xff0c;而一个课程教学管理平台对于现如今的课堂是不可缺…

使用OneAPI+Ollama+Dify搭建一个兼容OpenAI的API发布及AI应用开发系统(二)客户端设置

这一编我们介绍Ollama客户端的设置&#xff0c;那么客户端在这里指的就是你放在家里的Ollama服务器&#xff0c;通过与VPS里安装的OneAPI配合&#xff0c;从而实现了为Ollama生成API访问的服务&#xff0c;并为后端服务器提供安全保障。 一&#xff1a;安装客户端软件 客户端…

ISA-95制造业中企业和控制系统的集成的国际标准-(5)

ISA-95 文章目录 ISA-95ISA-95与工业互联网一、工业互联网在哪里&#xff1f;二、维护自动化金字塔 ISA-95与工业互联网 ISA95作为指导性原则&#xff0c;自动化的阶段构建了以人和业务流程为中心的生产组织方式&#xff0c;极大的提高了生产的效率和灵活性&#xff0c;也满足…

Python 入门--基础语法

目录 1. 注释 2. 字面量 3. 变量 4. 数据类型 5. 字符串扩展 (1). 字符串的三种定义方式 (2). 字符串拼接 (3). 字符串格式化1 (4). 格式化精度控制 (5). 字符串格式化2 (6). 表达式的格式化 6. 数据类型转换 7. 标识符 8. 运算符 9. 数据输入(input语句) 1. …

眼镜识别数据集类别和数量已经在文档中说明,训练集和验证集共2200,g是眼镜,ng是没有眼镜。

眼镜识别数据集 类别和数量已经在文档中说明&#xff0c;训练集和验证集共2200&#xff0c;g是眼镜&#xff0c;ng是没有眼镜。 眼镜识别数据集 (Glasses Detection Dataset) 规模 图像数量&#xff1a;2200张图像&#xff08;训练集和验证集&#xff09;。类别&#xff1a;2…

鸢尾花书实践和知识记录[数学要素3-3几何]

书的作者 文章目录 思维导图使用到的函数几何的介绍&#xff08;略&#xff09;点线面和定义欧几里得几何原本的公理正多边形代码&#xff1a;如何绘制正多边形 三维的几何体柏拉图立体几何变换 角度和弧度角度弧度正负角&#xff08;相位&#xff09;三个角 勾股定理到三角函…

MySQL优化实战 解决CPU100%

问题表象 在24年初有一个日经问题困扰着我们&#xff0c;每到正点03分DB的CPU开始打满&#xff0c;持续1分钟又恢复正常水平。但由于日常业务交付压力较大且权限限制没有登录DB主机的权限&#xff0c;大家也就得过且过一直没有去认真排查。直到某天我来兴趣了也有时间了&#…

【Kubernetes】常见面试题汇总(五十一)

目录 114. K8S 集群服务访问失败&#xff08;情况一&#xff09;&#xff1f; 115. K8S 集群服务访问失败&#xff08;情况二&#xff09;&#xff1f; 特别说明&#xff1a; 题目 1-68 属于【Kubernetes】的常规概念题&#xff0c;即 “ 汇总&#xff08;一&#xff…

严重 Zimbra RCE 漏洞遭大规模利用(CVE-2024-45519)

攻击者正在积极利用 CVE-2024-45519&#xff0c;这是一个严重的 Zimbra 漏洞&#xff0c;该漏洞允许他们在易受攻击的安装上执行任意命令。 Proofpoint 的威胁研究人员表示&#xff0c;攻击始于 9 月 28 日&#xff0c;几周前&#xff0c;Zimbra 开发人员发布了针对 CVE-2024-…

TCP/UDP初识

TCP是面向连接的、可靠的、基于字节流的传输层协议。 面向连接&#xff1a;一定是一对一连接&#xff0c;不能像 UDP 协议可以一个主机同时向多个主机发送消息 可靠的&#xff1a;无论的网络链路中出现了怎样的链路变化&#xff0c;TCP 都可以保证一个报文一定能够到达接收端…