【C++】多态(下)

在这里插入图片描述

个人主页~

多态(上)~


多态

  • 四、多态的原理
    • 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.xdnf.cn/news/1553450.html

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

相关文章

【ubuntu】【VirtualBox】VirtualBox无法加载USB移动设备的解决方法(支持U盘启动盘)

TOC 提示&#xff1a;测试可用 一、安装VirtualBox VirtualBox-7.1.2-164945-Win。 下载路径。 Download_Old_Builds_7_0 – Oracle VirtualBox 二、安装Oracle_VirtualBox_Extension_Pack-7.1.2 下载路径见上文。 三、安装增强功能 四、挂载USB 4.1 设置USB协议 4.2 挂…

Android Context是什么?有很多的context他们之间有什么区别?什么时候该使用哪个?

目录 一、Context是什么&#xff1f; 在Android中&#xff0c;Context是一个抽象类 &#xff0c;它代表了应用程序的当前状态&#xff0c;包括资源和类加载器等&#xff0c;它提供了一个应用运行所需的信息&#xff0c;比如我们要获取资源 &#xff0c;那么需要她&#xff0c;…

Java 每日一刊(第19期):泛型

文章目录 前言1. 泛型概述1.1 不使用泛型 vs 使用泛型1.2 泛型的作用 2. 泛型的基本语法2.1 定义带类型参数的泛型类2.2 使用泛型类2.3 泛型方法 3. 泛型类型推断与钻石操作符3.1 类型推断3.2 钻石操作符 4. 通配符的使用4.1 无界通配符 <?>4.2 上界通配符 <? exten…

毕业设计选题:基于ssm+vue+uniapp的教学辅助小程序

开发语言&#xff1a;Java框架&#xff1a;ssmuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;M…

Golang | Leetcode Golang题解之第452题用最少数量的箭引爆气球

题目&#xff1a; 题解&#xff1a; func findMinArrowShots(points [][]int) int {if len(points) 0 {return 0}sort.Slice(points, func(i, j int) bool { return points[i][1] < points[j][1] })maxRight : points[0][1]ans : 1for _, p : range points {if p[0] > …

【微服务】初识(day1)

基础概念 集群 集群是将一个系统完整的部署到多个服务器&#xff0c;每个服务器提供系统的所有服务&#xff0c;多个服务器可以通过负载均衡完成任务&#xff0c;每个服务器都可以称为集群的节点。 分布式 分布式是将一个系统拆分为多个子系统&#xff0c;多个子系统部署在…

C++11 异步操作 std::future类

阅读导航 引言一、异步的概念二、应用场景1. 异步任务处理2. 并发控制3. 结果获取 三、使用示例1. 使用std::async关联异步任务&#x1f4bb;示例代码说明 2. 使用std::packaged_task和std::future配合&#xff08;1&#xff09;定义std::packaged_task&#xff08;2&#xff0…

Pikachu-Cross-Site Scripting-DOM型xss

DOM型xss DOM型XSS漏洞是一种特殊类型的XSS,是基于文档对象模型 Document Object Model (DOM)的一种漏洞。是一个与平台、编程语言无关的接口&#xff0c;它允许程序或脚本动态地访问和更新文档内容、结构和样式&#xff0c;处理后的结果能够成为显示页面的一部分。 dom就是一…

【Qt】控件概述 (1)

控件概述 1. QWidget核心属性1.1核心属性概述1.2 enable1.3 geometry——窗口坐标1.4 window frame的影响1.4 windowTitle——窗口标题1.5 windowIcon——窗口图标1.6 windowOpacity——透明度设置1.7 cursor——光标设置1.8 font——字体设置1.9 toolTip——鼠标悬停提示设置1…

后台管理系统脚手架

后台管理系统脚手架 介绍 在快速迭代的软件开发世界里&#xff0c;时间就是生产力&#xff0c;效率决定成败。对于构建复杂而庞大的后台系统而言&#xff0c;一个高效、可定制的后台脚手架&#xff08;Backend Scaffold&#xff09;无疑是开发者的得力助手。 脚手架 后台脚…

GO网络编程(一):基础知识

1. 网络编程的基础概念 TCP/IP 协议栈 TCP/IP 是互联网通信的核心协议栈&#xff0c;分为以下四个层次&#xff1a; 应用层&#xff08;Application Layer&#xff09;&#xff1a;为应用程序提供网络服务的协议&#xff0c;比如 HTTP、FTP、SMTP 等。传输层&#xff08;Tra…

C++中stack和queue的模拟实现

目录 1.容器适配器 1.1什么是适配器 1.2STL标准库中stack和queue的底层结构 1.3deque的简单介绍 1.3.1deque的原理介绍 1.3.2deque的优点和缺陷 1.3.3deque和vector进行排序的性能对比 1.4为什么选择deque作为stack和queue的底层默认容器 2.stack的介绍和模拟…

数据库第8章编程题2

10-1 查询选修某两门课程的学生&#xff08;MSSQL) 本题目要求编写SQL语句&#xff0c; 检索出 sc表中至少选修了’C001’与’C002’课程的学生学号。 提示&#xff1a;MSSQLServer 评测SQL语句。 表结构: 请在这里写定义表结构的SQL语句。例如&#xff1a; -- 学生选课成…

前端组件化开发

假设这个页面是vue开发的&#xff0c;如果一整个页面都是编写在一个vue文件里面&#xff0c;后期不好维护&#xff0c;会特别的庞大&#xff0c;那么如何这个时候需要进行组件化开发。组件化开发后必然会带来一个问题需要进行组件之间的通信。组要是父子组件之间通信&#xff0…

SuperMap iClient for MapLibreGL 根据SQL条件过滤显示动态图层

查阅发现iClient 有子图层控制类 LayerStatus 可实现&#xff1a;子图层显示参数类。此类存储了各个子图层的名称、是否可见的状态、SQL 过滤条件等参数。 API详情&#xff1a;http://support.supermap.com.cn:8090/iserver/iClient/forJavaScript/docs/maplibregl/LayerStatus…

Kafka和RabbitMQ区别

RabbitMQ的消息延迟是微秒级&#xff0c;Kafka是毫秒级&#xff08;1毫秒1000微秒&#xff09; 延迟消息是指生产者发送消息发送消息后&#xff0c;不能立刻被消费者消费&#xff0c;需要等待指定的时间后才可以被消费。 Kafka的单机呑吐量是十万级&#xff0c;RabbitMQ是万级…

Webpack模式-Resolve-本地服务器

目录 ResolveMode配置搭本地服务器区分环境配置 Resolve 前面学习时使用了各种各样的模块依赖&#xff0c;这些模块可能来自于自己编写的代码&#xff0c;也可能来自第三方库&#xff0c;在 Webpack 中&#xff0c;resolve 是用于解析模块依赖的配置项&#xff0c;它决定了 We…

特权访问管理阻力最小的途径

特权访问管理 (PAM) 已经存在 20 多年&#xff0c;它将关键帐户放入保险库中&#xff0c;以确保只有特定人员才能安全地访问它们。 从那时起&#xff0c;PAM 不断发展&#xff0c;现在专注于控制访问本身&#xff0c;这意味着防止广泛访问特定数据&#xff0c;并提供有关谁有访…

JS基础练习|点击按钮更改背景色

效果图 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><link rel"…

《易泊车牌识别相机:精准识别的智能之选》

在如今的智能交通领域&#xff0c;车牌识别技术起着至关重要的作用。而易泊车牌识别相机以其卓越的性能脱颖而出。 易泊车牌识别相机的识别率极高。无论是在白天还是夜晚&#xff0c;无论是清晰的车牌还是稍有磨损的车牌&#xff0c;它都能迅速准确地识别出车牌号码。这得益于其…