多态以及多态底层的实现原理

本章目标

1.多态的概念
2.多态的定义实现
3.虚函数
4.多态的原理

1.多态的概念

多态作为面对三大特性之一,它所指代的和它的名字一样,多种形态.但是这个多种形态更多的指代是函数的多种形态.
多态分为静态多态和动态多态.
静态多态在前面已经学习过了,就是函数重载以及模板,它们是在编译时就已经确定下来了,也被成为编译时多态.它们通过传不同的参数实现函数不同的形态.
我们在这里主要将动态多态,也就是运行时多态.当我们运行某个函数的时候,它会根据传过来的对象的不同,来实现不同的行为,简单来说就是统一继承体系下的不同类对象去调用同一个函数产生了不同的行为

2.多态的定义实现

2.1实现多态的条件

1.必须是基类的指针或者引用去调用虚函数
2.虚函数必须完成了重写或者覆盖
因为我们前面所将的切片的类型兼容转换,只有基类的指针或者引用才能即指向基类对象又指向派生类对象.
虚函数的重写或者覆盖所指的是它的实现重写,这样基类和派生类才能有不同的函数.
才能实现多态.

#include<iostream>
using namespace std;
class A
{
public:virtual void  a(){cout << "A" << endl;}
};
class b :public A
{
public:virtual void a(){cout << "b" << endl;}};
int main()
{A* ptr1 = new A;A* ptr2 = new b;ptr1->a();ptr2->a();return 0;
}

在这里插入图片描述

以上就是多态的实现.

3.虚函数

类成员函数,在函数的前面加上virtual修饰,我们就称之为虚函数.非类成员函数是不能用virtual修饰的.

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

3.1虚函数的重写覆盖

虚函数的重写覆盖所指的是在派生类之中有一个和基类完全的一样的虚函数(返回值,函数名,参数列表),那么就叫做虚函数的重写覆盖.
在有的地方只在基类的虚函数的地方加上virtual,而在派生类中,并没有加入virtual来进行修饰,这样也是构成重写或者覆盖的.因为从基类继承下来的虚函数,在派生类也继承下来了它的虚函数属性

3.2协变

在派生类重写基类虚函数的时候,我们可以让派生的返回类型与基类不同,去返回基类或者派生类的指针或者引用,这个指针或者引用可以是其他类的.

class A {};class B : public A {};class Person {public:virtual A* BuyTicket() 
{ 
cout << "
买票
全价
" << endl;return nullptr;}};class Student : public Person {public:virtual B* BuyTicket() 
{ 
cout << "
买票
打折
" << endl;return nullptr;}};void Func(Person* ptr){ptr->BuyTicket();}int main(){Person ps;Student st;Func(&ps);Func(&st);return 0;}

3.3析构函数的重写

只要基类的析构函数为虚函数,它的派生类的析构一定会与基类的析构函数构成重写,在前面我们说继承的时候讲到析构函数会在编译时统一将名称处理成destructor,这样它们就构成了隐藏,而在这里则是构成了重写.

#include<iostream>
using namespace std;
class A
{
public:virtual void  a(){cout << "A" << endl;}virtual ~A(){cout << "~A" << endl;}
};
class b :public A
{
public:virtual void a(){cout << "b" << endl;}~b(){delete[] arr;cout << "~b" << endl;}
private:int* arr = new int[10];
};
int main()
{A* ptr1 = new A;A* ptr2 = new b;ptr1->a();ptr2->a();delete ptr1;delete ptr2;return 0;
}

在这里插入图片描述

3.4override和final关键字

从上面我们可以看出c++对虚函数的要求比较严格,可能有的时候参数类型写错了导致无法构成重写.我们就可以override来帮助我们进行检查.
在这里插入图片描述

class D
{
public:virtual void  d(){cout << "dadad" << endl;}
};
class E:public D
{
public:virtual void d(int a) override{cout << "dada" << endl;}
};

final关键字我们已经见过了,我们在实现一个不能被继承的类的时候,我们用final修饰或者构造私有.
而在这里我们不想让虚函数被继承也可用final来进行修饰.
在这里插入图片描述

3.5重载/重写/隐藏对比

重载
1.在统一作用域
2.函数名相同,参数不同,返回值可相同,可不同
重写
1.在统一继承体系下的不同的基类和派生类的作用域之中.
2.函数名,参数,返回值都必须相同,协变例外
3.两个函数都必须时虚函数
隐藏
1.在统一继承体系下的不同的基类和派生类的作用域之中.
2.函数名相同
3.两个函数只要不是重写就是隐藏.
4.变量名相同也可以构成隐藏

隐藏和重写的二者上是有所重叠但是并不完全相同

3.6纯虚函数与抽象类

在虚函数的后面加上=0,这个虚函数就是纯虚函数,纯虚函数所在的类被称为抽象类,抽象类是不能够实例化对象的,并且抽象类被继承之后的派生类的虚函数一定要被重写.
否则这个类也是抽象类.

class F
{
public:virtual void ff() = 0;};
class G :public F
{virtual void ff(){cout << "dada" << endl;}
};

在这里插入图片描述

4.多态的原理

class Base{public:virtual void Func1(){cout << "Func1()" << endl;}protected:int _b = 1;char _ch = 'x';};

当我们去算上面的类的时候,我们正常的结果是8bytes.
实际上则不同
在这里插入图片描述
它的大小是12bytes.
在这里插入图片描述
当我们创建一个Base类的对象来看的时候,我们发现除了上面的我们类中创建两个成员变量还有一个vfptr的函数指针.在x86的环境下它的大小就是12bytes.
这个指针就是虚函数表指针,每一个含有虚函数的类中,至少含有一个虚函数表,这个表里面放在虚函数的地址
在这里插入图片描述
从底层的角度我们该如何看到a是如何被调用的呢,当父类指针ptr1指向A的时候调用A的a函数,ptr2指向b的时候调用b中的a函数呢.
实际上当调用虚函数的时候,去调用函数的地址的时候,不是编译时通过对象来确定虚函数的地址.而是通过对象中的虚表来去call这个虚函数的地址

class Person {public:virtual void BuyTicket() { cout << "买票全价" << endl; }private:   
string _name;};class Student : public Person {public:virtual void BuyTicket() { cout << "买票打折" << endl; }private:   
string _id;};class Soldier: public Person {public:virtual void BuyTicket() { cout << "买票优先" << endl; }private:   
string _codename;};void Func(Person* ptr){// 这⾥可以看到虽然都是Person指针Ptr在调⽤BuyTicket 
// 但是跟ptr没关系,⽽是由ptr指向的对象决定的。ptr->BuyTicket();}int main(){// 其次多态不仅仅发⽣在派⽣类对象之间,多个派⽣类继承基类,重写虚函数后// 多态也会发⽣在多个派⽣类之间。Person ps;Student st;Soldier sr;Func(&ps);Func(&st);Func(&sr);return 0;}

4.1动态绑定与静态绑定

对于通过动态多态(父类的指针或者引用)去调用的函数,也就是运行时到指定对象的虚函数表中去调用函数的,我们叫做动态绑定.
对不满足动态多态条件的在编译时确定函数地址或者通过对象去确定函数的地址的,我们叫做静态绑定

4.2虚函数表

1.基类的虚函数表中存放着所以基类虚函数的地址,同一类型的对象公用同一张虚表,不同类的虚表之间时独立的.基类和派生类的虚表时相互独立的
2.派⽣类由两部分构成,继承下来的基类和⾃⼰的成员,⼀般情况下,继承下来的基类中有虚函数表指针,⾃⼰就不会再⽣成虚函数表指针。但是要注意的这⾥继承下来的基类部分虚函数表指针和基类对象的虚函数表指针不是同⼀个,就像基类对象的成员和派⽣类对象中的基类对象成员也独⽴的。
3.派⽣类中重写的基类的虚函数,派⽣类的虚函数表中对应的虚函数就会被覆盖成派⽣类重写的虚函数地址。
4.派⽣类的虚函数表中包含,(1)基类的虚函数地址,(2)派⽣类重写的虚函数地址完成覆盖,派⽣类⾃⼰的虚函数地址三个部分。
5.虚函数表本质是⼀个存虚函数指针的指针数组,⼀般情况这个数组最后⾯放了⼀个0x00000000标记。(这个C++并没有进⾏规定,各个编译器⾃⾏定义的,vs系列编译器会再后⾯放个0x00000000标记,g++系列编译不会放)
6.虚函数存在哪的?虚函数和普通函数⼀样的,编译好后是⼀段指令,都是存在代码段的,只是虚函数的地址⼜存到了虚表中。
7.虚函数表存在哪的?这个问题严格说并没有标准答案C++标准并没有规定,我们写下⾯的代码可以对⽐验证⼀下。vs下是存在代码段(常量区)

class Base {public:virtual void func1() { cout << "Base::func1" << endl; }virtual void func2() { cout << "Base::func2" << endl; }void func5() { cout << "Base::func5" << endl; }protected:int a = 1;};class Derive : public Base{public:// 重写基类的func1 
virtual void func1() { cout << "Derive::func1" << endl; }virtual void func3() { cout << "Derive::func1" << endl; }
int main(){int i = 0;static int j = 1;int* p1 = new int;const char* p2 = "xxxxxxxx";printf("栈:%p\n", &i);printf("静态区:%p\n", &j);printf("堆:%p\n", p1);printf("常量区:%p\n", p2);Base b;Derive d;Base* p3 = &b;Derive* p4 = &d;printf("Person虚表地址:%p\n", *(int*)p3);printf("Student虚表地址:%p\n", *(int*)p4);printf("虚函数地址:%p\n", &Base::func1);printf("普通函数地址:%p\n", &Base::func5);return 0;}

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

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

相关文章

linux下开发NFC读写器

linux下使用NFC读卡器&#xff0c;基于QT5开发 创建工程&#xff0c;引入lib开始编写代码 创建工程&#xff0c;引入lib 创建一个QT工程&#xff0c;如果是控制台程序&#xff0c;则去掉gui QT - gui引入lib库 LIBS -L$$PWD/lib -lyw60x这里需要将libyw60x.so库文件放在工程…

Linux基础使用-笔记

1. 文件和目录操作 查看当前目录&#xff1a;pwd 命令用于显示当前工作目录的完整路径。 pwd切换目录&#xff1a;cd 命令用于切换工作目录。 # 切换到指定目录 cd /home/user/Documents # 切换到上一级目录 cd .. # 切换到用户主目录 cd ~列出目录内容&#xff1a;ls 命令用…

DAG(有向无环图)计算模型面试内容整理-拓扑排序(Topological Sort)和节点依赖与并行度

拓扑排序(Topological Sort) 拓扑排序(Topological Sort): 拓扑排序是针对有向无环图(DAG)的一种线性排序方法。这种排序方法的特点是,对于DAG中的每一条有向边 (A → B),在拓扑排序中节点A总是排在节点B之前。

23种设计模式-结构型模式之享元模式(Java版本)

Java 享元模式&#xff08;Flyweight Pattern&#xff09;详解 &#x1f98b; 什么是享元模式&#xff1f; 享元模式是一种结构型模式&#xff0c;它通过共享相同的对象来减少内存消耗&#xff0c;适用于大量细粒度对象的场景。关键思想是缓存重复出现的对象&#xff0c;避免…

浏览器访问背后的秘密:从加载到关闭,数据是否会丢失?

⏩ 一次浏览器访问 www.xxx.com 背后发生了什么&#xff1f; —— 以及“我点了 &#xff0c;数据会不会丢&#xff1f;”的深度剖析 适读人群&#xff1a;Web 开发者、运维工程师、性能调优/安全从业者 1️⃣ 打开浏览器敲下网址&#xff1a;链路是如何启动的&#xff1f; 阶…

【HDFS入门】深入解析DistCp:Hadoop分布式拷贝工具的原理与实践

目录 1 DistCp概述与应用场景 2 DistCp架构设计解析 2.1 系统架构图 2.2 执行流程图 3 DistCp核心技术原理 3.1 并行拷贝机制 3.2 断点续传实现原理 4 DistCp实战指南 4.1 常用命令示例 4.2 性能优化策略 5 异常处理与监控 5.1 常见错误处理流程 5.2 监控指标建议…

hbuilderx云打包生成的ipa文件如何上架

使用hbuilderx打包&#xff0c;会遇到一个问题。开发的ios应用&#xff0c;需要上架到app store&#xff0c;因此&#xff0c;就需要APP store的签名证书&#xff0c;并且还需要一个像xcode那样的工具来上架app store。 我们这篇文章说明下&#xff0c;如何在windows电脑&…

第十五届蓝桥杯 2024 C/C++组 拼正方形

目录 题目&#xff1a; 题目描述&#xff1a; 题目链接&#xff1a; 思路&#xff1a; 思路详解&#xff1a; 易错点&#xff1a; 代码&#xff1a; 代码详解&#xff1a; 题目&#xff1a; 题目描述&#xff1a; 题目链接&#xff1a; P10898 [蓝桥杯 2024 省 C] 拼正…

华为云获取IAM用户Token的方式及适用分析

&#x1f9e0; 一、为什么要获取 IAM 用户 Token&#xff1f; 我们用一个生活中的比喻来解释&#x1f447;&#xff1a; &#x1f3e2; 比喻场景&#xff1a; 你要去一个 高级写字楼&#xff08;华为云物联网平台&#xff09; 办事&#xff08;调用接口管理设备&#xff09;&…

乐聚机器人与地瓜机器人达成战略合作,联合发布Aelos Embodied具身智能

要闻 4月19日&#xff0c;在CCF人形机器人与人工智能技术巡回研讨会&#xff08;武汉站&#xff09;上&#xff0c;乐聚机器人与地瓜机器人达成战略合作&#xff0c;双方将基于RDK X5、RDK S100以及更高性能的国产大算力平台&#xff0c;就夸父&#xff08;KUAVO&#xff09;、…

Web3架构下的数据隐私与保护

在这个信息爆炸的时代&#xff0c;Web3的概念如同一股清流&#xff0c;以其去中心化的特性&#xff0c;为数据隐私与保护带来了新的希望。Web3&#xff0c;也被称作下一代互联网&#xff0c;它通过区块链技术实现数据的去中心化存储和处理&#xff0c;旨在提高数据的安全性和隐…

【OceanBase相关】02-OceanBase数据库NFS备份实践

文章目录 一、前言1、概述2、备份方式3、备份流程4、恢复流程二、NFS备份1、注意事项2、服务端配置3、客户端配置4、备份策略配置三、常用操作四、Q&A1、数据备份任务执行失败,提示`start log archive backup when not STOP is not supported`1.1、问题说明1.2、解决措施2…

一行命令打开iOS模拟器

要在 Mac 命令行打开 iPhone 15 Pro 模拟器&#xff0c;需满足已安装 Xcode 这一前提条件&#xff0c;以下是具体操作步骤&#xff1a; 步骤一&#xff1a;列出所有可用模拟器设备 打开终端&#xff08;Terminal&#xff09;&#xff0c;输入并执行以下命令&#xff0c;用于列…

Java虚拟机(JVM)家族发展史及版本对比

Java虚拟机&#xff08;JVM&#xff09;家族发展史及版本对比 一、JVM家族发展史 1. 早期阶段&#xff08;1996-2000&#xff09; Classic VM&#xff08;Java 1.0-1.1&#xff09;&#xff1a; 厂商&#xff1a;Sun Microsystems&#xff08;Oracle前身&#xff09;。特点&…

嘻游电玩三端客户端部署实战:PC + Android + iOS 环境全覆盖教程

本篇文章将针对“网狐系列嘻游电玩组件”的三端客户端&#xff08;PC端、安卓端、iOS端&#xff09;进行详细部署实操讲解。文章将以实测部署为核心&#xff0c;提供资源结构说明、平台适配调整、打包配置、常见问题修复&#xff0c;并辅以必要的关键配置代码。 一、客户端资源…

LabVIEW实现Voronoi图绘制功能

该 LabVIEW 虚拟仪器&#xff08;VI&#xff09;借助 MathScript 节点&#xff0c;实现基于手机信号塔位置计算 Voronoi 图的功能。通过操作演示&#xff0c;能直观展示 Voronoi 图在空间划分上的应用。 各部分功能详细说明 随机地形创建部分 功能&#xff1a;根据 “Maximum a…

web刷题笔记

2024isctf ezrce 禁用了一些关键字符&#xff0c;查询函数&#xff0c;系统执行函数&#xff0c;执行函数都有&#xff0c;空格也和斜杆也禁用了&#xff0c;但是其他一些很大一部分字符都没有禁用&#xff0c;属于关键词禁用的类型&#xff0c;正常的步骤是去查一下列表&#…

集结号海螺捕鱼游戏源码解析(第二篇):水浒传捕鱼模块逻辑与服务器帧同步详解

本篇将全面解构“水浒传”子游戏的服务端核心逻辑、帧同步机制、鱼群刷新规则、客户端命中表现与服务器计算之间的协同方式&#xff0c;聚焦于 C 与 Unity3D 跨端同步的真实实现过程。 一、水浒传捕鱼模块资源结构 该模块包含三部分核心目录&#xff1a; 子游戏/game_shuihuz…

【产品经理从0到1】原型及Axure介绍

原型分类 原型的三种分类&#xff1a; 草图原型&#xff1a;⼿绘稿&#xff0c;制作⽅便&#xff0c;修改不⽅便&#xff1b;低保真原型&#xff1a;简单交互&#xff0c;⽆设计图&#xff1b; 最好的原型是⿊⽩灰的&#xff1b;⾼保真原型&#xff1a;复杂交互&#xff0c;有…

CVE-2024-23897-Jenkins 2.441之前版本存在任意文件读取漏洞

1.漏洞介绍 Jenkins 2.441及更早版本&#xff0c;以及LTS 2.426.2及更早版本没有禁用其CLI命令解析器的一个功能&#xff0c;该功能会将参数中字符后跟的文件路径替换为该文件的内容&#xff0c;允许未经身份验证的攻击者读取Jenkins控制器文件系统上的任意文件。 2.poc利用 下…