[C++] 虚函数、纯虚函数和虚析构(virtual)

news2024/11/16 9:39:43
  • 📢博客主页:https://blog.csdn.net/weixin_43197380
  • 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
  • 📢本文由 Loewen丶原创,首发于 CSDN,转载注明出处🙉
  • 📢现在的付出,都会是一种沉淀,只为让你成为更好的人✨

文章预览:

      • 一. 虚函数(virtual)
      • 二. 虚函数中的关键字
      • 三. 纯虚函数
      • 四*. 基类的析构函数务必写成虚函数(虚析构函数)
      • 五. 总结


一. 虚函数(virtual)

定义:在某基类中的成员函数:

  • 成员函数声明基类中为 virtual开头;
  • 该成员函数在一个或多个子类(派生类)中被重新声明、定义;

格式virtual 函数返回类型 函数名 ( 参数表 ) { 函数体 }

目的通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数,实现多态性

  • 例如 Human *phumen = new Men(); //可通过基类Human的指针phumen调用子类中的同名函数,实现多态

多态性

  • 顾名思义就是“多个性态”。更具体一点的就是,用一个名字定义多个函数,这些函数执行不同但相似的工作。最简单的多态性的实现方式就是函数重载模板,这两种属于静态多态性。还有一种是动态多态性,其实现方式就是我们今天要说的虚函数

下面来看一段简单的代码:

class Human
{
public:
	void print() { cout << "This is 人类" << endl; }
};

class Men :public Human 
{
public:
	void print() { cout << "This is 男人" << endl; }
};

int main() 
{   
	Human human;
	Men men;
	human.print();
	men.print();
}

通过class Humanclass Menprint()这个接口,输出的结果也是我们预料中的,分别是This is 人类This is 男人

但这是否真正做到了多态性呢?

  • No多态还有个关键之处就是一切用指向派生类的基类指针或引用来操作对象。那现在就把main()处的代码改一改。
int main() 
{   
	//Human human;
	//Men men;
	//human.print();
	//men.print();
	Human * phuman = new Human;
	Human * phuman1 = new Men;
	phuman->print();
	phuman1->print();
}

在这里插入图片描述

可以看出,父类指针phuman1明明指向的是子类class Men对象但却是调用的父类class Humanprint()函数,这不是我们所期望的结果。

那么解决这个问题,即通过一个父类指针或对象调用所有子类中的成员函数或变量,就需要用到虚函数:

class Human
{
public:
	virtual void print() { cout << "This is 人类" << endl; }  //现在成了虚函数了
};

class Men :public Human
{
public:
	virtual void print() { cout << "This is 男人" << endl; } //这里需要在前面加上关键字virtual吗?
};

现在重新运行main的代码,这样输出的结果就是This is 人类This is 男人

在这里插入图片描述

毫无疑问,class A的成员函数print()已经成了虚函数,那么class Bprint()成了虚函数了吗?

回答是Yes,我们只需在把基类的成员函数声明前加virtual,其派生类的相应的同名同参成员函数也会自动变为虚函数。所以,class Bprint()也成了虚函数。对于在派生类的相应函数前是否需要用virtual关键字修饰, 看个人编程习惯。

总结:指向基类的指针在操作它的多态类对象时,会根据不同的派生类对象,调用其相应的函数,这个函数就是虚函数。‎


二. 虚函数中的关键字

override关键字

为了避免在子类中写错虚函数(没有和基类的成员函数同名同参),在C++11中,可以在子类虚函数声明后增加一个关键字 override

注意,override关键字用在子类中,而且是虚函数专用,用了这个关键字后,编译器会认为子类的虚函数覆盖了基类中的同名函数,那么编译器就会在父类中找同名同参的虚函数,如果没找到,编译器就会报错。这样,如果不小心在子类中把虚函数名称或参数写错了,编译器会帮助纠错。

final关键字

final关键字也是虚函数专用,但是是用在父类中的,作用是在父类的函数声明中加了final,那么任何尝试覆盖该函数的操作都将引发错误。


三. 纯虚函数

定义: 纯虚函数是在①基类中声明的虚函数,但它在基类中②没有定义,但③要求任何派生类都要定义自己的实现方法

格式: 在基类中实现纯虚函数的方法是在函数原型后加“=0” 

virtual void funtion1() = 0; //纯虚函数,在基类中定义,没有函数体,只有一个函数声明

抽象类由来:一旦基类中有纯虚函数,那么则不能生成这个类的对象,这个了就成为了“抽象类”。

抽象类目的:用来统一管理子类对象。

Human  human;                //不合法
Human *phuman = new Human;   //不合法

在这里插入图片描述

核心两点总结:

  • 含有纯虚函数的类叫抽象类,抽象类不能生成该类对象,主要用于当做基类来生成子类用的
  • 子类中必须要实现该基类中定义的纯虚函数;

问题:我们知道纯虚函数在基类中没有定义,那么虚函数在基类中一定要定义实现吗?

class Location
{
public:
	Location(){}
	~Location(){}

public:
	virtual bool Check();  // 这里一定要实现吗?
};

class LineLocation : public Location
{
public:
	LineLocation(){}
	~LineLocation(){}

public:
	virtual bool Check() {
		return 1;
	}
};

int _tmain(int argc, _TCHAR* argv[])
{
	Location* loc = NULL;
	loc = new LineLocation();
	bool b= loc->Check();
	return 0;
}

回答: 虚函数在基类中一定要实现,如果基类中的虚函数不想实现,只想通过派生类来实现,需要将基类中的虚函数换成纯虚函数(=0)。因为虚函数的地址在链接的时候需要放到类的虚函数表中,所以即使你的代码里面没有调用这个函数,编译器也需要取它的地址,已经有对它的引用了,就必须要实现才行。

注:因为纯虚函数就相当于接口,无法实例化,即Location loc;编译是不能通过的。即有纯虚函数的类,将其作为参数也好,另一个类的成员变量也好,只能将其定义为指针或引用,只要不给基类实例化对象就行。


四*. 基类的析构函数务必写成虚函数(虚析构函数)

基类中的虚拟成员希望其派生类定义自己的版本。特别是基类通常应该定义一个虚拟析构函数,即使它不起作用,析构函数必须是虚拟的,以允许动态分配和销毁继承层次结构中的对象。

那么为什么析构函数必须是虚拟的,而我们新建程序时,默认的析构函数却不是虚拟的呢?

1、为什么析构函数必须是虚拟的?

在这里插入图片描述
因为指针指向的是一个派生类实例,我们销毁这个实例时,肯定是希望先清理派生类自己的资源,同时又清理从基类继承过来的资源。而当基类的析构函数为非虚函数时,删除一个基类指针指向的派生类实例时只清理了派生类从基类继承过来的资源而派生类自己独有的资源却没有被清理

总结:如果一个类想要做基类(被其他类继承),那么我们必须定义这个类的析构函数并且还要将其写成虚函数(普通类可不定义析构函数为虚函数或直接不写析构函数)。这样,在delete释放指向的派生类实例的基类指针时,清理工作才能全面进行,才不会发生内存泄漏。

2、为什么默认的析构函数不是虚函数?

虚函数不同于普通成员函数,当类中有虚成员函数时,类会自动进行一些额外工作。这些额外的工作包括生成虚函数表虚表指针,虚表指针指向虚函数表。每个类都有自己的虚函数表,虚函数表的作用就是保存本类中虚函数的地址,我们可以把虚函数表形象地看成一个数组,这个数组的每个元素存放的就是各个虚函数的地址。
这样一来,就会占用额外的内存,当们定义的类不被其他类继承时,这种内存开销无疑是浪费的。

这样一说,问题就不言而喻了。当我们创建一个类时,系统默认我们不会将该类作为基类,所以就将默认的析构函数定义成非虚函数,这样就不会占用额外的内存空间。同时,系统也相信程序开发者在定义一个基类时,会显示地将基类的析构函数定义成虚函数,此时该类才会维护虚函数表和虚表指针。

参考博文:为什么析构函数必须是虚函数?为什么默认的析构函数不是虚函数?


五. 总结

1、定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。
2、虚函数必须实现,如果不实现,编译器将报错。
3、调用虚函数执行的是“动态绑定”。动态:表示的就是在我们程序运行的时候才能知道调用了哪个子类的虚函数。
4、实现了纯虚函数的子类,该纯虚函数在子类中就编程了虚函数,子类的子类即孙子类可以覆盖该虚函数,由多态方式调用的时候动态绑定。
5、虚函数是C++中用于实现多态的机制。核心理念就是通过基类访问派生类定义的函数。
6、在有动态分配堆上内存的时候,析构函数必须是虚函数,但没有必要是纯虚的。
7、友元不是成员函数,只有成员函数才可以是虚拟的,因此友元不能是虚拟函数。但可以通过让友元函数调用虚拟成员函数来解决友元的虚拟问题。
8、析构函数应当是虚函数,将调用相应对象类型的析构函数,因此,如果指针指向的是子类对象,将调用子类的析构函数,然后自动调用基类的析构函数。


下雨天,最惬意的事莫过于躺在床上静静听雨,雨中入眠,连梦里也长出青苔。

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

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

相关文章

http正向代理测试,nginx反向代理中转正向代理服务器

有3台服务器如下&#xff1a; 192.168.111.201&#xff08;反向代理到正向代理服务器&#xff09; 192.168.111.202&#xff08;正向代理服务器&#xff09; 192.168.111.203&#xff08;目标WEB系统&#xff09; 防火墙网络策略如图所示: 1、192.168.111.200 只能访问 192.168…

【送书活动】智能汽车、自动驾驶、车联网的发展趋势和关键技术

文章目录 前言01 《智能汽车》推荐语 02 《SoC底层软件低功耗系统设计与实现》推荐语 03 《SoC设计指南》推荐语 05 《智能汽车网络安全权威指南&#xff08;上册&#xff09;》推荐语 06 《智能汽车网络安全权威指南&#xff08;下册&#xff09;》推荐语 后记赠书活动 前言 …

普通二叉树和右倾斜二叉树--LeetCode 111题《Minimum Depth of Binary Tree》

本文将以解释计算二叉树的最小深度的思路为例&#xff0c;致力于用简洁易懂的语言详细描述普通二叉树和右倾斜二叉树在计算最小深度时的区别。通过跟随作者了解右倾斜二叉树的概念以及其最小深度计算过程&#xff0c;读者也将对左倾斜二叉树有更深入的了解。这将为解决LeetCode…

FreeRTOS学习——同步互斥

FreeRTOS学习——同步互斥 目录 FreeRTOS学习——同步互斥一、概念1.1 同步1.2 互斥 二、示例——有缺陷的同步三、示例——优化有缺陷的同步四、示例——有缺陷的互斥五、总结 一、概念 1.1 同步 在FreeRTOS中&#xff0c;同步是指任务之间按照某种规则进行协调和按序执行的…

HarmonyOS:使用MindSpore Lite引擎进行模型推理

场景介绍 MindSpore Lite 是一款 AI 引擎&#xff0c;它提供了面向不同硬件设备 AI 模型推理的功能&#xff0c;目前已经在图像分类、目标识别、人脸识别、文字识别等应用中广泛使用。 本文介绍使用 MindSpore Lite 推理引擎进行模型推理的通用开发流程。 基本概念 在进行开…

PDI/Kettle-9.2.0.0-R(对应jdk1.8)源码编译问题记录及源码结构简介

目录 &#x1f4da;第一章 前言&#x1f4d7;背景&#x1f4d7;目的&#x1f4d7;总体方向 &#x1f4da;第二章 代码结构初识基本结构&#x1f4d7;代码模块详情 ⁉️问题记录❓问题一&#xff1a;代码分支哪些是发布版本❗答&#xff1a;后缀-R的版本 ❓问题二&#xff1a;50…

AI智能配音助手微信小程序前后端源码支持多种声音场景选择

大家好今天给大家带来一款配音小程序 &#xff0c;这款小程序支持多种不同声音和场景的选择更人性化&#xff0c; 比如说支持各地区的方言,英文,童声呀等等、 另外也支持男声女声的选择,反正就是模板那些非常的多 当然啦音量,语调,语速那些都是可以DIY跳转的哟,所以说这一款小程…

使用PyTorch II的新特性加快LLM推理速度

Pytorch团队提出了一种纯粹通过PyTorch新特性在的自下而上的优化LLM方法&#xff0c;包括: Torch.compile: PyTorch模型的编译器 GPU量化:通过降低精度操作来加速模型 推测解码:使用一个小的“草稿”模型来加速llm来预测一个大的“目标”模型的输出 张量并行:通过在多个设备…

【专题】最小生成树(prim算法、kruscal算法)

目录 一、最小生成树二、Prim算法1. 算法思想2. 例题3. 性能分析 三、Kruscal算法1. 算法思想2. 例题3. 性能分析 一、最小生成树 生成树中边的权值&#xff08;代价&#xff09;之和最小的树。 二、Prim算法 1. 算法思想 设N(V,{E})是连通网&#xff0c;TE是N上最小生成树…

【IEEE】2区SCI,接收领域广,稳定检索47年!

重点 本期推荐 区块链是一种新兴技术&#xff0c;很多行业和领域都以创新方式采用了此技术&#xff0c;如能源、金融、媒体和娱乐以及零售等。此外&#xff0c;区块链作为一门新兴的交叉学科, 涉及密码学应用&#xff08;加密&#xff0c;隐私等&#xff09;&#xff0c; 分布式…

stm32与Freertos入门(二)移植FreeRTOS到STM32中

简介 注意&#xff1a;FreeRTOS并不是实时操作系统&#xff0c;而是分时复用的&#xff0c;只不过切换频率很快&#xff0c;感觉上是同时在工作。本次使用的单片机型号为STM32F103C8T6,通过CubeMX快速移植。 一、CubeMX快速移植 1、选择芯片 打开CubeMX软件&#xff0c;进行…

Diva配置——Communication Tests

关联文章:CANoe.Diva生成测试用例 Diva目录 一、CANoe.Diva简介二、Communication Tests配置一、CANoe.Diva简介 CANoe.DiVa 是一种 CANoe 选项,用于对 ECU 中的诊断软件实施进行自动化测试。 可以通过CANdelaStudio制作的CDD或ODX文件,经过Diva配置自动生成测试用例和测试脚…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《基于时空注意力卷积模型的超短期风电功率预测》

这个标题描述了一种用于超短期风电功率预测的模型&#xff0c;该模型基于时空注意力卷积模型。下面我会逐步解读这个标题的关键词和背景&#xff1a; 超短期风电功率预测&#xff1a;风电功率预测是指根据历史风速和其他相关数据&#xff0c;通过建立数学模型来预测未来特定时间…

Vue学习计划-Vue2--VueCLi(四)组件传值和自定义事件

1. 组件传值 组件化编码流程&#xff1a; 拆分静态组件&#xff1a;组件要按照功能点拆分&#xff0c;命名不要与html元素冲突实现动态组件&#xff1a;考虑好数据的存放位置&#xff0c;数据是一个组件在用&#xff0c;还是一些组件在用&#xff1a; 一个组件在用&#xff0c…

云演ctf 你能看到我吗?

1、看题目稍微尝试不好用&#xff0c;看到提示的文件访问也不好用。读取文件又好像拦截了啥。 读取hint.php文件payload ctfphp://filter/convert.base64-encode/resourcehihintnt.php2、解密后又看到一个文件 有需要绕过 ctfphp://filter/convert.base64-encode/resource..…

轻量封装WebGPU渲染系统示例<45>- 材质组装流水线(MaterialPipeline)灯光、阴影、雾(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/material/src/voxgpu/sample/MaterialPipelineFog.ts 当前示例运行效果: 此示例基于此渲染系统实现&#xff0c;当前示例TypeScript源码如下&#xff1a; export class MaterialPipelineFog {pr…

C语言数组刷题-----数组填充

输入一个整数 V &#xff0c;输出一个长度为 10 的数组 N &#xff0c;数组中的第一个元素为 V &#xff0c;每个后续元素的值都为上一个元素的值的 2 倍。 例如&#xff0c;如果输入整数为 1 &#xff0c;则数组为&#xff1a;1,2,4,8… 输入格式 输入一个整数 V 。输出格…

cmake多模块架构, DLL依赖编译

整体结构&#xff1a; 最外层的cmakelist CMakeLists.txt project(cmakeMulPackage) cmake_minimum_required(VERSION 3.17) set(CMAKE_CXX_STANDARD 11)#设置环境相关 message("set env.cmake") message("CMAKE_BUILD_TYPE is ${CMAKE_BUILD_TYPE}") …

TG-5510cb: txo高稳定性+105℃高温

TG-5510CB是一款高稳定性TCXO&#xff0c;可提供CMOS或限幅正弦输出&#xff0c;5G基站和边缘计算的额定温度为85C&#xff0c;需要室外安装、小型化和无风扇运行。与其他TCXO相比&#xff0c;实验室提供了许多改进&#xff0c;如低温度斜率和相位噪声。符合GR-1244-CORE地层3和…

详细教程 - 从零开发 Vue 鸿蒙harmonyOS应用 第一节

关于使用Vue开发鸿蒙应用的教程,我这篇之前的博客还不够完整和详细。那么这次我会尝试写一个更加完整和逐步的指南,从环境准备,到目录结构,再到关键代码讲解,以及调试和发布等,希望可以让大家详实地掌握这个过程。 一、准备工作 下载安装 DevEco Studio 下载地址&#xff1a;…