【C++初阶】多态

news2024/9/22 23:31:54

image.png
重写子类时不要求必须有 virtual 关键字

虚函数允许派生类重写这个函数,并确保即使是通过基类指针调用该函数,也能调用到派生类的版本

虚函数关键字 virtual 只在声明时加上,在类外实现时不用加
虚函数只需在类声明中加上 virtual 关键字,在类外实现时无需重复使用。这样做是为了避免不必要的冗余,同时也因为编译器已经从类声明中得知该函数是虚函数。

普通调用:看指针或者引用或者对象的类型
多态调用:看指针或者引用指向的对象(父类指针调用虚函数+虚函数完成重写)

image.png|300

static和virtual是不能同时使用的
静态成员函数与具体对象无关,属于整个类,核心关键是没有隐藏的this指针,可以通过类名::成员函数名直接调用,此时没有this无法拿到虚表,就无法实现多态,因此不能设置为虚函数

下面这道题,结合解析(以区分继承和多态)

class A
{
public:
	A() :m_iVal(0) { test(); }
	virtual void func() { std::cout << m_iVal << ‘ ’; }
	void test() { func(); }
public:
	int m_iVal;
};

class B : public A
{
public:
	B() { test(); }
	virtual void func()
	{
		++m_iVal;
		std::cout << m_iVal << ‘ ’;
	}
};

int main(int argc, char* argv[])
{
	A* p = new B;
	p->test();
	return 0;
}

答案为 0 1 2
分析: new B时先调用父类A的构造函数,执行test()函数,在调用func()函数,由于此时还处于对象构造阶段,多态机制还没有生效,所以,此时执行的func函数为父类的func函数,打印0,构造完父类后执行子类构造函数,又调用test函数,然后又执行func(),由于父类已经构造完毕,虚表已经生成,func满足多态的条件,所以调用子类的func函数,对成员m_iVal加1,进行打印,所以打印1,最终通过父类指针p->test(),也是执行子类的func,所以会增加m_iVal的值,最终打印2。

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

image.png
重写和重定义都发生在继承体系(即在不同的类),但重载只能在一个范围内(不能在不同类)

  • 重载允许同名函数有不同的参数列表。
  • 重写要求派生类中的函数与基类中的虚函数具有相同的原型。
  • 重定义允许派生类中的函数隐藏基类中的非虚函数,即使参数列表不同。
    在C++中,函数的原型(或签名)通常指的是函数名、参数列表(参数的类型和顺序),不包括返回类型和参数名称。

抽象类

纯虚函数的声明以“=0;”结束

有纯虚函数的类叫抽象类,不能实例化对象,所以它不能用来定义对象,作为对象返回。但是它可以定义指针,(经常这么做),其目的是用父类指针指向子类从而实现多态。

抽象类可以有函数体,但意义不大。
如果一个派生类没有实现其基类的纯虚函数,该派生类仍然是一个抽象类

多态的原理

多态调用,运行时,在指向对象的虚表中找到虚函数地址。
有了虚函数后就有指针。虚函数表指针
image.png

多态调用:运行时去虚函数表中找函数的地址,进行调用。所以指向父类调的是父类虚函数,指向子类调用的是子类虚函数
普通调用:编译时,通过调用者类型确定函数地址

虚函数表和虚函数表指针

派生类的虚表生成:
a.先将基类中的虚表内容拷贝一份到派生类虚表中
b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数
c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。

存放位置关系

虚函数和普通函数一样存在于代码段,虚表存的是虚函数指针,不是虚函数。对象存的是虚表指针而不是虚表。虚函数表存在哪个区域?常量区
虚表是编译期间生成的,虚表指针是初始化的时候(初始化列表的最开始的位置)构造的。

一个类在多继承的时候有可能有多张虚表
一个类的不同对象共享该类的虚表

动态绑定与静态绑定

多态分为编译时多态和运行时多态,也叫早期绑定(静态绑定)和晚期绑定(动态绑定)
编译时多态主要通过重载实现,(模板也属于)

多继承

怎么存?
多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中

子类有几个父类,如果父类有虚函数,则就会有几张虚表,自身子类不会产生多余的虚表
子类自己的虚函数只会放到第一个父类的虚表后面,其他父类的虚表不需要存储,因为存储了也不能调用

常见面试题

问答题

什么是多态?
多态:不同数据类型的实体提供统一的接口,或使用一个单一的符号来表示不同的类型。在继承关系中,也就是不同类的对象,调用同一个函数(参数列表和返回值),产生不同的行为。

什么是重载、重写 (覆盖)、重定义 (隐藏)?
重载:在同一作用域下函数名相同,参数不同;
重写(覆盖):子类继承了父类的虚函数的声明,参数返回值完全相同(除了协变),子类重新实现它;
重定义(隐藏):子类和父类中的函数名相同。父类和子类的同名函数如果不构成重写,就是重定义。

多态的实现原理?
如果父类有虚函数,子类(公有地)继承了它,子类和父类实例化对象时,子类也会继承父类的虚表,各自在对象中用一个地址保存它们各自的虚表;如果子类重写了父类的某个虚函数,那么子类就会修改虚表中对应虚函数的地址;其他未被重写的虚函数的地址依旧是不变的,是和父类的虚表中对应的虚函数的地址是一样的。
当父类指针或引用调用子类对象的虚函数,父类指针或引用会通过子类对象中的虚表指针找到虚表,然后调用虚表中的虚函数。如果调用的虚函数被子类覆盖(重写了),那么就是调用子类重写的那个虚函数。如果父类指针或引用指向父类,自然只会调用父类自己的函数。
通过父类指针或引用指向不同的对象,调用同一个函数,实现多态。

inline 函数可以是虚函数吗?
个人感觉这个问题怪怪的,可以理解成:inline 函数可以被 virtual 修饰吗?
可以。因为 inline 是一个建议性的关键字,对于编译器而言,inline 不 inline 取决于编译器的决策。事实是编译器不会把虚函数作为内联函数。
原因:内联函数会在调用的地方直接展开,所以内联函数也就没有地址可言,但是虚函数是一定要把地址存到虚表中的,所以编译器会忽略虚函数的内联属性。

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

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

析构函数可以是虚函数吗?什么场景下析构函数是虚函数?
可以,并且最好把基类的析构函数定义成虚函数。
原因:有这样的场景:用父类指针分别指向两个被 new 出来的父类对象和子类对象。如果使用 delete 释放对象资源,只有当父类的析构函数是虚函数时才能调用父类和子类的析构函数分别对父类和子类对象进行清理;否则使用 delete 清理父类指针指向的对象,只能调用到父类自己的析构函数。

对象访问普通函数快还是虚函数更快?
如果是普通对象,一样快。
如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。

虚函数表是在什么阶段生成的,存在哪的?
虚函数表是在构造函数初始化阶段初始化的,在编译阶段生成的,一般情况下存在代码段 (常量区) 的。

C++菱形继承的问题?虚继承的原理?
菱形虚拟继承因为子类对象当中会有两份父类的成员,导致数据冗余和二义性的问题。
虚继承对于相同的虚基类在对象当中只会存储一份,若要访问虚基类的成员需要通过虚基表找到到偏移量,进而找到对应的虚基类成员,解决了数据冗余和二义性的问题。

什么是抽象类?抽象类的作用?
抽象类体现了虚函数的继承是一种接口继承(也就是声明的部分),强制子类去抽象纯虚函数。如果子类也不实现继承下来的纯虚函数,那么子类也是一个抽象类,也不能实例化出对象。抽象类也可以表示现实世界中没有实例对象对应的抽象类型,比如:植物、人、动物等。

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

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

相关文章

COD论文学习 ZoomNext

现有方法的不足之处 高内在相似性&#xff1a;伪装物体与背景之间的高内在相似性使得检测变得困难&#xff0c;现有方法难以准确区分二者。多样化的规模和模糊的外观&#xff1a;伪装物体在规模和外观上多样化&#xff0c;且可能严重遮挡&#xff0c;导致现有方法难以处理。不…

景联文科技构建高质量心理学系知识图谱,助力大模型成为心理学科专家

心理大模型正处于快速发展阶段&#xff0c;在临床应用、教育、研究等多个领域展现出巨大潜力。 心理学系知识图谱能够丰富心理大模型的认知能力&#xff0c;使其在处理心理学相关问题时更加精确、可靠和有洞察力。这对于提高心理健康服务的质量和效率、促进科学研究以及优化教育…

vue项目build以后整合到springboot项目里面---------gxl

很多时候我们需要用到vue的组件&#xff0c;但是全栈的背景下懒得去搞前后端分离&#xff0c;很多权限校验后台都写好了&#xff0c;没必要再去做接口或者前端写一遍了&#xff0c;因此我们需要把打包后的项目整合到项目里面。 整合也很简单&#xff0c;照常vue项目开发&#…

Seaborn库学习之heatmap()函数

Seaborn库学习之heatmap(函数) 一、简介 seaborn.heatmap是Seaborn库中用于绘制热图&#xff08;Heatmap&#xff09;的函数。热图是一种数据可视化技术&#xff0c;通过颜色的变化来展示数据矩阵中的数值大小。这种图表非常适合展示数值数据的分布和关系&#xff0c;尤其是在…

什么是MIMO?(通俗易懂)

MIMO&#xff0c;一句话解释&#xff1a;多输入多输出&#xff08;Multi Input Multi output&#xff09; 用图说话&#xff0c;图①-图④&#xff0c;分别代表&#xff1a; ① SISO&#xff1a;单输入单输出 ( Single Input Single Output ) ② SIMO&#xff1a;单输入多输出…

YOLOV5学习记录

前言&#xff1a; 计算机视觉 什么是目标检测&#xff1f; 物体分类和目标检测的区别 目标检测&#xff0c;物体的类别和位置 学习选题&#xff0c;口罩检查&#xff0c;人脸识别 算法原理&#xff1a;知乎&#xff0c;csdn&#xff0c;目前还没到这种程度 大大滴崩溃&am…

木舟0基础学习Java的第二十天(线程,实现,匿名有名,休眠,守护,加入,设计,计时器,通信)

多线程 并发执行的技术 并发和并行 并发&#xff1a;同一时间 有多个指令 在单个CPU上 交替执行 并行&#xff1a;同一时间 有多个指令 在多个CPU上 执行 进程和线程 进程&#xff1a;独立运行 任何进程 都可以同其他进程一起 并发执行 线程&#xff1a;是进程中的单个顺…

鸿蒙语言基础类库:【@system.mediaquery (媒体查询)】

媒体查询 说明&#xff1a; 从API Version 7 开始&#xff0c;该接口不再维护&#xff0c;推荐使用新接口[ohos.mediaquery]。本模块首批接口从API version 3开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import mediaquery from sy…

【进阶篇-Day10:JAVA中泛型、平衡二叉树、红黑树、TreeSet集合的介绍】

目录 1、泛型1.1 泛型类1.2 泛型方法1.3 泛型接口1.4 泛型通配符1.5 总结 2、数据结构&#xff08;树&#xff09;2.1 树的基本介绍2.2 二叉树的介绍2.2.1 概念&#xff1a;2.2.2 二叉查找树的介绍&#xff1a;2.2.3 二叉查找树添加节点&#xff1a;2.2.4 二叉查找树查找节点&a…

window11 部署llama.cpp并运行Qwen2-0.5B-Instruct-GGUF

吾名爱妃&#xff0c;性好静亦好动。好编程&#xff0c;常沉浸于代码之世界&#xff0c;思维纵横&#xff0c;力求逻辑之严密&#xff0c;算法之精妙。亦爱篮球&#xff0c;驰骋球场&#xff0c;尽享挥洒汗水之乐。且喜跑步&#xff0c;尤钟马拉松&#xff0c;长途奔袭&#xf…

从零开始读RocketMq源码(五)Consumer消费Message流程解析

目录 前言 准备 拉取服务和重平衡服务启动 初识PullRequest 重平衡服务 对重平衡资源进行排序 MessageQueue消息队列集合来源 Consumer消费者集合数据来源 确实分配资源策略 执行分配策略 初始化ProcessQueue 初始化PullRequest 内存队列填充PullRequest 消息拉取…

hackmyvm--Decode

环境 靶机&#xff1a;ip未知 攻击机kali&#xff1a;192.168.233.128 192.168.56.101 主机探测 锁定靶机ip为108 端口扫描 nmap -p- -T4 -A 192.168.56.108 常规套路80和22 web打点 dirsearch -u http://192.168.56.108/ 访问robots,txt文件 访问/decode 发现其自动添加了/,怀…

Chromium源码阅读(9):了解Log模块

Chromium许多日志被TraceEvent代替了&#xff0c;因此TraceEvent出现的频率要比Log高很多。 但是也有不少场景使用Log。 在blink&#xff0c;Log的实现由base提供&#xff0c;而blink/render/core/base/logging.h进行了二次封装。 日志系统的设计细节 错误对话框处理 错误消…

Qt第十二章 样式表

样式表 文章目录 样式表1.样式表盒子模型 2.选择器选择器类型伪状态选择器Pseudo-State 3.控件示例4继承自QWidget的类&#xff0c;设置qss样式表没有效果&#xff0c;需要重写paintEvent 1.样式表 盒子模型 2.选择器 样式表语法&#xff0c;选择器{属性1:值;属性2:值;}如果只…

韦东山嵌入式linux系列-驱动进化之路:设备树的引入及简明教程

1 设备树的引入与作用 以 LED 驱动为例&#xff0c;如果你要更换LED所用的GPIO引脚&#xff0c;需要修改驱动程序源码、重新编译驱动、重新加载驱动。 在内核中&#xff0c;使用同一个芯片的板子&#xff0c;它们所用的外设资源不一样&#xff0c;比如A板用 GPIO A&#xff0c…

鸿蒙仓颉语言【类型class】

类与结构&#xff08;class & struct&#xff09; 面向对象的编程语言&#xff0c;必不可少的基础元素&#xff0c;类或者叫类型&#xff0c;在仓颉中类可以抽象(abstract)、继承&#xff08;<:&#xff09;&#xff0c;公开&#xff08;Public&#xff09;或者私有&am…

在jsPsych中使用Vue

jspsych 介绍 jsPsych是一个非常好用的心理学实验插件&#xff0c;可以用来构建心理学实验。具体的就不多介绍了&#xff0c;大家可以去看官网&#xff1a;https://www.jspsych.org/latest/ 但是大家在使用时就会发现&#xff0c;这个插件只能使用js绘制界面&#xff0c;或者…

【算法专题】归并排序

目录 1. 排序数组 2. 交易逆序对的总数 3. 计算右侧小于当前元素的个数 4. 翻转对 总结 1. 排序数组 912. 排序数组 - 力扣&#xff08;LeetCode&#xff09; 今天我们使用归并排序来对数组进行排序&#xff0c;实际上&#xff0c;归并排序和快速排序是有一定相似之处的&a…

什么是蓝牙芯片?蓝牙芯片和蓝牙模块的区别

蓝牙芯片&#xff0c;是一种集成了蓝牙无线通信技术的微型电子元件。它如同一个微小的通信枢纽&#xff0c;能够在各种电子设备之间建立无线连接&#xff0c;实现数据的传输与共享。蓝牙芯片的设计精妙而复杂&#xff0c;内部集成了射频前端、数字基带、协议栈等多个功能模块&a…

Linux中nohup(no hang up)不挂起,用于在系统后台不挂断地运行命令,即使退出终端也不会影响程序的运行。

nohup的英文全称是 no hang up&#xff0c;即“不挂起”。这个命令在Linux或Unix系统中非常有用&#xff0c;主要用于在系统后台不挂断地运行命令&#xff0c;即使退出终端也不会影响程序的运行。默认情况下&#xff08;非重定向时&#xff09;&#xff0c;nohup会将输出写入一…