多态

多态的概念

通俗来说,就是多种形态,具体点就是完成某个行为,当不同的对象去完成时会产生出不同的状态

多态的定义及实现

多态构成的条件

1、必须通过基类的指针或者引用调用虚函数
2、子类必须对基类的虚函数进行重写

虚函数

被关键字virtual修饰的类成员函数称为虚函数

虚函数的重写

子类中有一个和基类完全相同的虚函数(子类的虚函数和基类的虚函数返回类型、函数名、参数列表完全相同)称为子类的虚函数重写(覆盖)基类的虚函数

#include<iostream>
using namespace std;class Person 
{
public:virtual void BuyTicket() {cout << "买票-全价" << endl;}
};class Student : public Person 
{
public:// 在重写基类虚函数时,子类的虚函数也可以不叫virtual关键字// 因为子类会将基类的虚函数继承下来,在子类中依然会保留有虚函数性质// 但是这种写法不规范  void ButTicket ()virtual void BuyTicket() {cout << "买票-半价" << endl; }
};void Func(Person& p)
{p.BuyTicket();
}int main()
{Person ps;Student st;Func(ps);Func(st);return 0;
}

虚函数重写的两个例外

协变

当子类重写基类的虚函数时,与基类的虚函数返回值类型不同。(这里的不同意思是:基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用)

析构函数的重写

当基类的析构函数是虚函数时,子类想去重写基类的虚函数的析构函数,只需要定义就行,不需要加关键字virtual,也不需要同名。因为编译器对析构函数名做了特殊处理,统一将析构函数名处理为destructor。

C++11 关键字override 和 final

final

修饰虚函数,表示该虚函数不能再被重写

class Car

{

public: virtual void Drive() final

        {}

};

class Benz :public Car

public: virtual void Drive()

        {

                cout "Benz-舒适" endl;

        }

};

override

检查子类虚函数是否重写了基类的某个虚函数,如果没写重写就报错。

class Car

{

public:

        virtual void Drive()

                {}

};

class Benz :public Car

{

public:

        virtual void Drive() override

        {

                cout "Benz-舒适" endl;

        }

};

重载、重写(覆盖)、重定义(隐藏)的对比

概念对比
重载

1、两个函数在同一作用域

2、函数名相同;参数有三不同(顺序不同or类型不同or数量不同)

重写(覆盖)

1、两个函数分别在基类和子列的作用域

2、函数名/参数/返回值都必须相同(协变例外)

3、两个函数必须是虚函数

重定义(隐藏)

1、两个函数分别在基类和子列的作用域

2、函数名相同

3、基类和子类的两个同名函数不是构成重写就是重定义

抽象类

纯虚函数:在虚函数后面加上 =0。一个类中有纯虚函数就称为抽象类(也称接口类);抽象类不能实例化出对象。子类继承后也不能实例化出对象,只有重写纯虚函数,子类才能实例化出对象。

纯虚函数的作用:(纯虚函数不需要实现函数体)
1、强制子类去完成重写
2、表示抽象的类型
3、更能体现出接口继承

接口继承和实现继承

普通函数的继承是一种实现继承,子类继承了基类函数,可以使用该函数,继承的是函数的实现。虚函数的继承是一种接口继承,子类继承的是基类虚函数的接口,目的是为了重写,达成多态。所以如果不实现多态,就不要把函数定义成虚函数

多态的原理

虚函数表

如果一个类中有虚函数,那么当这个类实例化出对象时,该对象中不但有成员变量,还会有一个虚函数表指针(_vfptr)(这个_vfptr可能在对象的内存模型最前面,也可能在对象的内存模型最后面;和编译器有关)虚函数表指针指向的是虚函数表(简称虚表),这个虚表内存的是虚函数的地址。
 

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、可以看到子类是重写了基类的第一个虚函数(Func1),所以子类的虚表中覆盖类基类的第一个虚函数。
2、Func2在子类中没有重写,子类继承下来了,所以也放在了虚表中,Func3不是虚函数,所以不会放入虚表中
3、虚函数表本质是一个存虚函数指针的指针数组,一般情况会在这个数组最后放一个nullptr
4、虚函数存在代码段中,每一个虚函数都有一个虚函数指针,这些虚函数指针存在虚表中,虚表存在代码段中,这个虚表有一个首地址,这个首地址也叫虚表指针就在存在实例化出的对象中。
总结子类虚表的生成:

  1. 先将基类中的虚表内容拷贝一份到子类虚表中
  2. 如果子类重写了基类的某个虚函数,那么就用子类自己的虚函数覆盖虚表中基类的被重写的虚函数
  3. 子类自己新增的虚函数按照它们在子类中声明次序添加到虚表后面

多态的原理

有虚函数表知识的铺垫,多态的原理就很容易理解了。

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;
}

这段代码,基类和子类对象都调用了Func函数,然后再Func函数中都调用了BuyTicket函数,但是分别调用自己类中的BuyTicket函数。这样就是多态。

展示一下流程,当一个对象去调用BuyTicket函数时,会去自己的虚表中寻找这个虚函数。

这样可以看出满足多态的函数调用,不是在编译时确定的,是运行起来以后到对象中去寻找。不满足多态的函数调用,是在编译时确定好的。

动态绑定和静态绑定

静态绑定:静态的多态 (静态:编译时确定函数地址)

动态绑定:动态的多态(动态:运行时到虚表中找虚函数地址)

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

#include<iostream>
using namespace std;class Base
{
public:virtual void Func1(){cout << "Base::Func1()" << endl;}virtual void Func2(){cout << "Base::Func2()" << endl;}private:int _b = 1;
};
class Derive : public Base
{
public:virtual void Func1(){cout << "Derive::Func1()" << endl;}virtual void Func3(){cout << "Derive::Func3()" << endl;}virtual void Func4(){cout << "Derive::Func4()" << endl;}
private:int _d = 2;
};
int main()
{Base b;Derive d;return 0;
}

根据上面的代码和对象内存模型可以知道编译器隐藏了子类对象的Func3和Func4这两个虚函数。
我们也可以通过它们在虚表中去将它们打印出来。

class Base
{
public:virtual void Func1(){cout << "Base::Func1()" << endl;}virtual void Func2(){cout << "Base::Func2()" << endl;}private:int _b = 1;
};
class Derive : public Base
{
public:virtual void Func1(){cout << "Derive::Func1()" << endl;}virtual void Func3(){cout << "Derive::Func3()" << endl;}virtual void Func4(){cout << "Derive::Func4()" << endl;}
private:int _d = 2;
};
// void(*p)() // 定义一个函数指针
typedef void(*VFPTR)(); // 函数指针类型重定义void PrintVTable(VFPTR* VTable)
{cout << "虚表地址:" << VTable << endl;for (int i = 0; VTable[i] != nullptr; ++i){printf("第%d个虚函数地址:%p", i, VTable[i]);cout << endl;}
}int main()
{Base b;Derive d;// 取出d对象的前4个字节,就是虚表指针,虚表的本质就是一个存虚函数地址的指针数组,这个数组的最后有一个nullptr//强转int*,就是取出了前4个字节//解引用就是拿到了对象的前4个字节的值,这个值就是指向虚表的指针//再强转VFPTR*,因为虚表就是存VFPTR类型的数组PrintVTable( (VFPTR*)(*(int*)&d) );return 0;
}

多继承 

#include<iostream>
#include<stdio.h>
using namespace std;class Base1
{
public:virtual void Func1(){cout << "Base1::Func1()" << endl;}virtual void Func2(){cout << "Base1::Func2()" << endl;}private:int _b1 = 1;
};
class Base2
{
public:virtual void Func1(){cout << "Base2::Func1()" << endl;}virtual void Func2(){cout << "Base2::Func2()" << endl;}private:int _b2 = 1;
};
class Derive : public Base1, public Base2
{
public:virtual void Func1(){cout << "Derive::Func1()" << endl;}virtual void Func3(){cout << "Derive::Func3()" << endl;}virtual void Func4(){cout << "Derive::Func4()" << endl;}
private:int _d = 2;
};
// void(*p)() // 定义一个函数指针
typedef void(*VFPTR)(); // 函数指针类型重定义void PrintVTable(VFPTR* VTable)
{cout << "虚表地址:" << VTable << endl;for (int i = 0; VTable[i] != nullptr; ++i){printf("第%d个虚函数地址:%p -> ", i, VTable[i]);VFPTR f = VTable[i];f();cout << endl;}
}int main()
{Derive d;// 取出d对象的前4个字节,就是虚表指针,虚表的本质就是一个存虚函数地址的指针数组,这个数组的最后有一个nullptr//强转int*,就是取出了前4个字节//解引用就是拿到了对象的前4个字节的值,这个值就是指向虚表的指针//再强转VFPTR*,因为虚表就是存VFPTR类型的数组PrintVTable( (VFPTR*)(*(int*)&d) );// 想获取Base2中的虚表地址PrintVTable( (VFPTR*) ( *(int*) ((char*)&d + sizeof(Base1)))); return 0;
}

多继承的基类中未重写的虚函数放在第一个继承的基类的虚函数表中。 

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

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

相关文章

内网横向移动小补充 --->PTK

大家别急&#xff0c;我的基于资源的约束性委派攻击还在写&#xff0c;这个东西一时半会讲不清楚&#xff0c;所以我在这里先来补充一点横向移动以前没说好的东西&#xff01;&#xff01;&#xff01; 在更啦&#xff0c;别催啦~~~~ 还记得我之前在内网渗透里面讲过这个PTK&a…

2024.5.22 关于 SpringCloud —— Nacos 配置管理

目录 Nacos 配置统一管理 Nacos 配置热部署 Nacos 多环境配置共享 配置优先级 Nacos 配置统一管理 实例理解 我们想要利用 Nacos 在 user-service 的 application.yml 配置文件中新增配置项此处我们将新增配置日期格式为 yyyy-MM-dd HH:mm:ss下图为新增 Nacos 配置统一管理…

基于STM32实现智能园艺系统

目录 引言环境准备智能园艺系统基础代码示例&#xff1a;实现智能园艺系统 土壤湿度传感器数据读取水泵控制温湿度传感器数据读取显示系统用户输入和设置应用场景&#xff1a;智能农业与家庭园艺问题解决方案与优化收尾与总结 1. 引言 本教程将详细介绍如何在STM32嵌入式系统…

基于Zynq 7000 SoC的迁移设计

基于Zynq 7000 SoC的迁移设计 Vivado IDE工具使用IP集成器进行嵌入式开发。各种IP Vivado IDE IP目录中提供&#xff0c;以适应复杂的设计。您也可以添加 自定义IP到IP目录。 您可以将基于Zynq 7000平台处理器的设计迁移到Vivado design Suite中 使用以下步骤。 1.生成系统基础…

【搜索方法推荐】高效信息检索方法和实用网站推荐

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

亚马逊云主管马特·加尔曼面临压力,致力于在人工智能领域赶超竞争对手

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

四川景源畅信:抖音小店新手如何做?

随着短视频平台的兴起&#xff0c;抖音小店成为了许多创业者的新选择。但是&#xff0c;对于新手来说&#xff0c;如何在抖音上开设并经营好自己的小店呢?本文将围绕这一问题展开讨论。 一、明确目标和定位作为抖音小店的新手&#xff0c;首先要明确自己的经营目标和定位。是想…

【CTF Web】CTFShow web4 Writeup(SQL注入+PHP+字符型注入)

web4 1 管理员阿呆又失败了&#xff0c;这次一定要堵住漏洞 解法 注意到&#xff1a; <!-- flag in id 1000 -->拦截很多种字符&#xff0c;连 select 也不给用了。 if(preg_match("/or|\-|\\\|\/|\\*|\<|\>|\!|x|hex|\(|\)|\|select/i",$id)){die(&q…

Pycharm最新安装教程(最新更新时间2024年5月27日)

ps&#xff1a;本教程Pycharm安装&#xff0c;最新更新时间&#xff1a;2024年5月27日&#xff0c;公众号持续更新关注公众号防失联哦 Pycharm 再次更新了一个小版本。又回到老话题&#xff0c;2023.3.2这个版本是否还能安装&#xff0c;笔者也亲测了一下。还是沿用本站之前的…

刷题之从前序遍历与中序遍历序列构造二叉树(leetcode)

从前序遍历与中序遍历序列构造二叉树 前序遍历&#xff1a;中左右 中序遍历&#xff1a;左中右 前序遍历的第一个数必定为根节点&#xff0c;再到中序遍历中找到该数&#xff0c;数的左边是左子树&#xff0c;右边是右子树&#xff0c;进行递归即可。 #include<vector>…

零基础PHP入门(一)选择IDE和配置环境

配置环境 官网下载安装包&#xff0c;windows https://windows.php.net/download#php-8.3 我是下载的最新版&#xff0c;也可以切换其他版本 https://windows.php.net/downloads/releases/archives/ 下载好压缩文件后&#xff0c;双击解压到一个目录 D:\soft\php 复制ph…

技术贴 | Query 物理计划构建指南

在往期博客《执行器 - Query 执行详解》中&#xff0c;我们介绍到到一条 Query 的 SQL 语句需要经过&#xff1a;词法分析 —— 生成 AST 语法树 —— 生成物理计划。本期博客我们接续上篇讲解一条 Query 语句物理计划的具体结构&#xff0c;以及如何构建物理计划。 物理计划是…

无人机技术:倾转旋翼飞行器的关键技术详解

一、总体设计 倾转旋翼飞行器作为一种独特的垂直起降与水平巡航的航空器&#xff0c;其总体设计是关键技术之一。总体设计涵盖了飞行器的整体布局、重量分配、气动性能、机械结构设计等多个方面。在总体设计中&#xff0c;需要充分考虑飞行器的垂直起降、悬停、过渡飞行和水平…

Android14之Binder调试(二百一十一)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

气泡水位计的安装方法详解(二)

气泡水位计的安装方法详解&#xff08;二&#xff09; 产品简介 气泡式水位计ZL-BWL-013是一款适用于水文、水利信息化建设领域的新一代水位测量类设备&#xff0c;产品执行GB/T 11828.2-2022标准。ZL-BWL-013气泡水位计&#xff0c;具有安装方便、易于操作&#xff0c;高精度…

【leetcode2028. 找出缺失的观测数据(自己写出来了)】

给你一个长度为 m 的整数数组 rolls &#xff0c;其中 rolls[i] 是第 i 次观测的值。同时给你两个整数 mean 和 n 。返回一个长度为 n 的数组&#xff0c;包含所有缺失的观测数据&#xff0c;且满足这 n m 次投掷的 平均值 是 mean 。如果存在多组符合要求的答案&#xff0c;只…

Springboot阶段项目---《书城项目》

一 项目介绍 本项目采用集成开发平台IntelliJ IDEA开发了在线作业成绩统计系统的设计与实现&#xff0c;实现了图书商城系统的综合功能和图形界面的显示&#xff0c;可以根据每个用户登录系统后&#xff0c;动态展示书城首页图书&#xff0c;实现了分类还有分页查询&#xff0c…

【加密与解密(第四版)】第十六章笔记

第十六章 脱壳技术 16.1 基础知识 壳的加载过程&#xff1a;保存入口参数、获取壳本身需要使用的API地址、解密原程序各个区块的数据、IAT的初始化、重定位项的处理、HOOK API、跳转到程序原入口点 手动脱壳步骤&#xff1a;查找真正的入口点、抓取内存映像文件、重建PE文件&…

图论(二)-图的建立

引言&#xff1a; 建图&#xff0c;将图放进内存的方法 常用的建图方式&#xff1a;邻接矩阵&#xff0c;邻接链表&#xff0c;链式前向星 一、邻接矩阵 通过一个二维数组即可将图建立&#xff0c;邻接矩阵&#xff0c;考虑节点集合 &#xff0c;用一个二维数组定义邻接矩…

LINUX环境基础练习题(附带答案)

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…