多态——细致讲解

🔶多态基础概念
 🔶概念
  🔱多态性
  🔱多态——重新(覆盖)
 🔶示例
  🔶基本使用方法
  🔶特例
   🔱协变
   🔱析构函数重写
 🔱多态原理
  🔱1. 虚函数形成虚表
  🔱2. 虚函数存储位置(覆盖)
  🔱3. 多态中重写的虚函数存储位置
   🔱1. 重写原理——虚表
   🔱2. 单继承中,子类新增虚函数会存到父类的虚表中——普通继承
   🔱3. 单继承中,子类新增虚函数会存到父类的虚表中——虚继承
   🔱4. 同类公用一个虚表;父类和子类不共用一张虚表
 🔱多态例题
🔱经典问题

多态基础概念

概念

多态性

 1. 静态多态:函数重载和运算符重载
 2. 动态多态:继承和虚函数

多态——重写(覆盖)

 1. 父类的指针/引用调用虚函数
 2. 调用的虚函数必须是子类重写的虚函数
这样就能在指针调用相应的对象函数的时候使用相应的成员函数,具体看示例
这里条件很严格
重写的函数要是一摸一样——返回值,函数名,参数个数,参数位置,参数类型都要完全一样,虚函数之后的const也要一样

示例

基本使用方法
  1. 父类中需要使用virtual修饰函数,子类中virtual可以不写
class A
{
public:virtual void func(){puts("A-->func");}
};
class B:public A
{
public:virtual void func(){puts("B-->func");}
};
int main()
{// 父类指向子类A* a1 = new B;a1->func();// 父类指向父类a1 = new A;a1->func();// 父类引用子类B tb;A& a2 = tb;a2.func();// 父类引用父类A ta;A& a3 = ta; // 不能直接使用a2=ta,引用不能重新赋值,虽然他不会报错,但是他的结果是错的a3.func();return 0;
}

在这里插入图片描述


  1. final 修饰类——不能继承

在这里插入图片描述

修饰虚函数——不能背重写

在这里插入图片描述

  1. override ——这个函数一定要重新父类的某一个虚函数

在这里插入图片描述

一定要注意这两个关键字加载虚函数结尾

特例
协变

虚函数的返回值可以不一样,只能出现父类返回父类的指针/引用,子类返回子类的指针/引用

在这里插入图片描述

不可以一个返回指针,一个返回引用
只能同时返回指针/同时返回引用

析构函数重写
#include<iostream>
using namespace std;
typedef void (*T)();
class A
{
public:virtual void fun1(){cout << "A::fun1" << endl;}virtual void fun2(){cout << "A::fun2" << endl;}~A(){cout << "delete A" << endl;}int _a = 1;};
class B :public A
{
public:virtual void fun1(){cout << "B::fun1" << endl;}virtual void fun3(){cout << "B::fun3" << endl;}~B(){cout << "delete B" << endl;}int _b = 2;
};
int main()
{A* a = new B;delete a;return 0;
}

在这里插入图片描述

delete释放看的是类型,也就是说这里delete调用的是A的析构函数
根本上说,delete会被处理成—> destructor() + operator delete,所以他们能构成重写,在具体实现的时候需要写成virtual

	virtual ~A(){cout << "delete A" << endl;}

在这里插入图片描述


多态原理

1. 虚函数形成虚表
#include<iostream>
using namespace std;
typedef void (*T)();
class A
{
public:virtual void fun1(){cout << "A::fun1" << endl;}virtual void fun2(){cout << "A::fun2" << endl;}};
int main()
{A a;return 0;
}

在这里插入图片描述

2. 虚函数存储位置

虚函数和普通函数放在一起,虚表存储在代码段

3. 多态中重写的虚函数存储位置
🎭1. 重写原理——虚表
#include<iostream>
using namespace std;
typedef void (*T)();
class A
{
public:virtual void fun1(){cout << "A::fun1" << endl;}virtual void fun2(){cout << "A::fun2" << endl;}};
class B :public A
{
public:virtual void fun1(){cout << "B::fun1" << endl;}
};
int main()
{B b;return 0;
}

在这里插入图片描述

🎭2. 单继承中,子类新增虚函数会存到父类的虚表中——普通继承
#include<iostream>
using namespace std;
typedef void (*T)();
class A
{
public:virtual void fun1(){cout << "A::fun1" << endl;}virtual void fun2(){cout << "A::fun2" << endl;}};
class B :public A
{
public:virtual void fun1(){cout << "B::fun1" << endl;}virtual void fun3(){cout << "B::fun3" << endl;}
};
int main()
{B b;print((T*)(*(int*)(&b)));return 0;
}

在这里插入图片描述
vs中虚表通常在最后一个都是0,Linux不是

在这里插入图片描述

🎭3. 单继承中,子类新增虚函数会存到父类的虚表中——虚继承
#include<iostream>
using namespace std;
typedef void (*T)();
class A
{
public:virtual void fun1(){cout << "A::fun1" << endl;}virtual void fun2(){cout << "A::fun2" << endl;}int _a = 1;};
class B :virtual public A
{
public:virtual void fun1(){cout << "B::fun1" << endl;}virtual void fun3(){cout << "B::fun3" << endl;}int _b = 2;
};
int main()
{B b;A* a = &b; // 要注意这种写法,确保他能准确跳到下一个虚表处print((T*)(*(int*)(a)));print((T*)(*(int*)(&b)));return 0;
}

在这里插入图片描述
在这里插入图片描述

🎭4. 同类公用一个虚表;父类和子类不共用一张虚表
#include<iostream>
using namespace std;
typedef void (*T)();
class A
{
public:virtual void fun1(){cout << "A::fun1" << endl;}virtual void fun2(){cout << "A::fun2" << endl;}int _a = 1;};
class B :virtual public A
{
public:virtual void fun1(){cout << "B::fun1" << endl;}int _b = 2;
};
int main()
{A a;B b1;B b2;return 0;
}

在这里插入图片描述


🖼多态例题

class A
{
public:virtual void fun(int val = 1){cout << "val = " << val << endl;}virtual void test(){fun();}
};
class B :public A
{
public:virtual void fun(int val = 0){cout << "val = " << val << endl;}
};
int main()
{A* a = new B;a->test();return 0;
}
void print(T a[])
{for (int i = 0; a[i] != 0; i++){printf("[%d]--->%p\n", i, a[i]);}puts("");
}
int main()
{B b;print((T*)(*(int*)(&b)));return 0;
}

在这里插入图片描述

  1. 父类指向子类,调用的test函数,test函数是父类的虚函数,类内的函数有一个默认的this指针,test内部调用的fun函数实际上是this->fun(this类型是A*——父类的指针指向虚函数),fun是子类重写的虚函数(函数是子类重写的虚函数)——满足多态条件
  2. 虚函数中的this是根据是否重写确定的,这里的test没有被重写,是A*this指针,然后调用fun,fun是经过重写的函数,所以调用的是重写的函数
  3. 虚函数继承的是函数的接口,重写的是函数的实现

所以缺省值才是1


#include<iostream>
using namespace std;class A
{
public:virtual void fun(int val = 0){printf("A::fun()--> %d", val);}virtual void test(){fun();}
};
class B:public A
{
public:void fun(int val = 1){printf("B::fun()--> %d", val);}
};
int main()
{B b;b.test();return 0;
}

在这里插入图片描述


🔒经典问题

  1. 什么是多态?
  2. 什么是重载、重写(覆盖)、重定义(隐藏)?
  3. 多态的实现原理?
  4. inline函数可以是虚函数吗?

可以,不构成多态就是inline,构成多态就不是inline

  1. 静态成员可以是虚函数吗?

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

  1. 构造函数可以是虚函数吗?

不能,虚表在编译时生成
在调用构造函数之后,但是虚表指针在成员初始化之前

  1. 析构函数可以是虚函数吗?

本就应该是,在A* a = new B;这种场景下,在释放子类对象时,需要将析构函数变成虚函数

  1. 对象访问普通函数快还是虚函数更快?

首先如果是普通对象,是一样快的。如果构成多态,就是普通函数快,因为运行时调用虚函数需要到虚函数表中去查找。

  1. 虚函数表是在什么阶段生成的,存在哪的?

虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。

  1. C++菱形继承的问题?虚继承的原理?

菱形继承会造成祖宗类数据冗余的问题
在每一个继承自祖宗类的派生类中,使用一个指针指向一个偏移量,根据偏移量找到的地址就是祖宗类的数据,并且这个数据只有一份

  1. 什么是抽象类?抽象类的作用?

抽象类含有形如 virtual void fun() =0; 的基类/派生类
强制派生类重写父类的实现


原理的角度理解,重写之后将fun虚函数进行覆盖test是A*this调用经过重写的虚函数fun符合多态的条件,并且继承的是接口不是实现fun虚函数继承父类函数接口,并使用重写的虚函数实现,最终形成了这个样子


优秀多态文章
优秀多态文章
为什么要使用父类指针和引用实现多态,而不能使用对象?
虚析构函数
虚表位置
虚表位置

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

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

相关文章

`useState` 和 `useImmer` 都是 React 中用于管理状态的钩子

useImmer如何使用&#xff1a; 安装&#xff1a;yarn add use-immer使用&#xff1a; const [data, updateData] useImmer({fields: [], });updateData((draft) > {draft.fields.splice(index, 1, {id:1});});useState 和 useImmer 都是 React 中用于管理状态的钩子&…

redis实战笔记汇总

文章目录 1 NoSQL入门概述1.1 能干嘛&#xff1f;1.2 传统RDBMS VS NOSQL1.3 NoSQL数据库的四大分类1.4 分布式数据库CAP原理 BASE原则1.5 分布式集群简介1.6 淘宝商品信息的存储方案 2 Redis入门概述2.1 是什么&#xff1f;2.2 能干嘛&#xff1f;2.3 怎么玩&#xff1f;核心…

46、WEB攻防——通用漏洞PHP反序列化原生类漏洞绕过公私有属性

文章目录 几种常用的魔术方法1、__destruct()2、__tostring()3、__call()4、__get()5、__set()6、__sleep()7、__wakeup()8、__isset()9、__unset()9、__invoke() 三种变量属性极客2019 PHPphp原生类 几种常用的魔术方法 1、__destruct() 当删除一个对象或对象操作终止时被调…

关于 yarn 的中央仓库 registry.yarnpkg.com

"Yarn" 是一个开源的 JavaScript 包管理工具&#xff0c;用于管理项目中的依赖关系。Yarn 通过一个叫做 "registry" 的中央仓库来存储和检索各种 JavaScript 包。这个中央仓库可以通过 https://registry.yarnpkg.com/ 访问&#xff0c;它是 Yarn 包管理系统…

像用Excel一样用Python:pandasGUI

文章目录 启动数据导入绘图 启动 众所周知&#xff0c;pandas是Python中著名的数据挖掘模块&#xff0c;以处理表格数据著称&#xff0c;并且具备一定的可视化能力。而pandasGUI则为pandas打造了一个友好的交互窗口&#xff0c;有了这个&#xff0c;就可以像使用Excel一样使用…

数据库运维01

数据备份多重方案 核心sql语句 mysql复制架构 mysql 生产实践 mysql可用的集群和中间件 linux环境 linux的命令要掌握 dba数据库管理员 it部门负责数据库维护 一定规模的企业 健康良好的运行数据库 对数据库做策略&#xff0c;保证数据库的稳定 查数据要尽快的返回 复杂的数据需…

【Spring Boot 3】的安全防线:整合 【Spring Security 6】

简介 Spring Security 是 Spring 家族中的一个安全管理框架。相比与另外一个安全框架Shiro&#xff0c;它提供了更丰富的功能&#xff0c;社区资源也比Shiro丰富。 一般来说中大型的项目都是使用SpringSecurity 来做安全框架。小项目有Shiro的比较多&#xff0c;因为相比与Sp…

Linux线程【互斥与同步】

目录 1.资源共享问题 1.1多线程并发访问 1.2临界区和临界资源 1.3互斥锁 2.多线程抢票 2.1并发抢票 2.2 引发问题 3.线程互斥 3.1互斥锁相关操作 3.1.1互斥锁创建与销毁 3.1.2、加锁操作 3.1.3 解锁操作 3.2.解决抢票问题 3.2.1互斥锁细节 3.3互斥…

github用法详解

本文是一篇面向全体小白的文章,图文兼备。为了让小白们知道如何使用GitHub,我努力将本文写得通俗易懂,尽量让刚刚上网的小白也能明白。所以各位程序员们都可以滑走了~ 啥是GitHub? 百度百科会告诉你, GitHub是一个面向开源及私有软件项目的托管平台,因为只支持Git作为…

大模型训练——PEFT与LORA介绍

大模型训练中的PEFT&#xff08;Parameter-Efficient Fine-Tuning&#xff09;与LoRA&#xff08;Low-Rank Adaptation&#xff09;是两种重要的技术&#xff0c;它们在大型预训练模型的应用中发挥着重要作用。 首先&#xff0c;让我们来了解一下PEFT。PEFT是一种参数高效的微…

GO基本类型

Go语言同时提供了有符号和无符号的整数类型。 有符号整型&#xff1a;int、int8、int64、int32、int64无符号整型&#xff1a;uint、uint8、uint64、uint32、uint64、uintptr 有符号整型范围&#xff1a;-2^(n-1) 到 2^(n-1)-1 无符号整型范围: 0 到 2^n-1 实际开发中由于编…

英语中的提问方式(问法)(bug提问、bug描述)

文章目录 英语提问方式一、单词、短语、句子的意思1.1 提问单词的意思1.2 提问短语的意思1.3 提问句子的意思 二、在编程中提问2.1 提问bug2.2 请求代码帮助 如何提出反问句1. 构建反问句的基本结构2. 提问反问句的方法3. 理解反问句的意图 在口语中提问&#xff1a;确保清晰度…

Topaz Gigapixel AI:让每一张照片都焕发新生mac/win版

Topaz Gigapixel AI 是一款革命性的图像增强软件&#xff0c;它利用先进的人工智能技术&#xff0c;能够显著提升图像的分辨率和质量。无论是摄影爱好者还是专业摄影师&#xff0c;这款软件都能帮助他们将模糊的、低分辨率的照片转化为清晰、细腻的高分辨率图像。 Topaz Gigap…

JavaWeb——011 SpringBootWeb综合案例(删除/修改员工、文件上传、配置文件)

SpringBootWeb案例 目录 SpringBootWeb案例1. 新增员工1.1 需求1.2 接口文档1.3 思路分析1.4 功能开发1.5 功能测试1.6 前后端联调 2. 文件上传2.1 简介2.2 本地存储2.3 阿里云OSS2.3.1 准备2.3.2 入门2.3.3 集成 3. 修改员工3.1 查询回显3.1.1 接口文档3.1.2 实现思路3.1.3 代…

07 编译器

目录 编译过程编译器查看详解函数库自动化构建工具进度条程序 1. 编译过程 预处理: a. 去注释 b.宏替换 c.头文件展开 d.条件编译 编译: 汇编 汇编: 可重定向二进制目标文件 链接: 链接多个.o, .obj合并形成一个可执行exe gcc编译c程序, g编译c程序 2. 编译器查看 输入gcc …

mac苹果电脑c盘满了如何清理内存?2024最新操作教程分享

苹果电脑用户经常会遇到麻烦:内置存储器(即C盘)空间不断缩小&#xff0c;电脑运行缓慢。在这种情况下&#xff0c;苹果电脑c盘满了怎么清理&#xff1f;如何有效清理和优化存储空间&#xff0c;提高计算机性能&#xff1f;成了一个重要的问题。今天&#xff0c;我想给大家详细介…

备战蓝桥杯---线段树基础2

今天我们把线段树的另一个模板看一下&#xff1a; 在这里&#xff0c;我们注意到乘的操作&#xff0c;因此我们用两个懒标记来分别表示加和乘&#xff0c;这时我们面临了一个问题&#xff0c;就是当我们把标记往下传时&#xff0c;它的儿子怎么知道是先乘还是先加&#xff1f; …

2025张宇考研数学,百度网盘视频课+36讲PDF讲义+真题

张宇老师的课属于幽默生动&#xff0c;会让一个文科生爱上数学&#xff0c;但是有的同学不知道在哪看&#xff0c;可以看一下&#xff1a;2025张宇考研数学全程网盘 docs.qq.com/doc/DTmtOa0Fzc0V3WElI 可以粘贴在浏览器 张宇30讲作为一本基础讲义&#xff1a;和教材…

java的线程池介绍

什么是线程池&#xff1f; 线程池是一种用于管理和复用线程的机制&#xff0c;旨在减少线程的创建和销毁次数&#xff0c;提高线程的可重用性和执行效率。通过线程池&#xff0c;可以控制线程的数量、数量大小以及线程的执行方式&#xff0c;从而更加有效地处理并发任务。 线…

代码随想录刷题第48天

今天来看看股票市场。第一题是买卖股票的最佳时机https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/description/&#xff0c;首先想到了暴力解法&#xff0c;两层for循环&#xff0c;时间复杂度为n * n&#xff0c;代码超时了。 class Solution { public:int m…