[C++从入门到精通] 14.虚函数、纯虚函数和虚析构(virtual)

news/2024/2/27 21:21:03
  • 📢博客主页: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.ppmy.cn/news/1274812.html

相关文章

高可用接入层技术演化及集群概述

集群概述 集群的介绍及优势 集群&#xff1a;将多台服务器通过硬件或软件的方式组合起来&#xff0c;完成特定的任务&#xff0c;而这些服务器对外表现为一个整体。集群的优势 高可靠性&#xff1a;利用集群管理软件&#xff0c;当主服务器故障时&#xff0c;备份服务器能够自…

Axure中继器的使用

目录 一. 中继器 概述 作用 运用场景 二. 中继器的使用 三. 三列表格增删改查案例展示 一. 中继器 概述 在Axure软件中&#xff0c;中继器&#xff08;Repeater&#xff09;是一种特殊的控件&#xff0c;它的作用是允许用户创建重复的数据项&#xff0c;并以列表或表格…

autosar引入了Secure SOME/IP-ACL 有什么目的?如何实现的?

Secure SOME/IP-ACL 是一种基于 AUTOSAR 的安全机制&#xff0c;用于保护车载以太网上的 SOME/IP 通信。它的目的是防止未经授权的客户端或服务端访问或提供敏感的服务。 Secure SOME/IP-ACL 的实现主要依赖于两个模块&#xff1a; Secure Onboard Communication (SecOC) Sec…

linux中数据库的概念、mysql的两种安装方法

数据库 数据库数据库DBsql语言关系型数据库&#xff08;SQL&#xff09;非关系型数据库&#xff08;NOSQL&#xff09;关系型数据库与非关系型数据库的区别 Mysqlmysql定义版本演化 Mysql安装Mysql源码安装准备环境清理环境安装依赖包添加MySQL用户添加mysql组创建数据目录和其…

React Hooks解决了什么问题?

前言 之前被面试官问到hooks解决了什么问题&#xff0c;当时就说了一部分&#xff0c;还是决定总结一下&#xff0c;毕竟临场发挥肯定会说不全。 React Hooks React Hooks 是 React 16.8 引入的一项重要功能&#xff0c;它解决了一些在使用类组件时存在的问题&#xff0c;并…

【操作系统导论】内存篇——页面置换

一. 机制 在硬盘上开辟一部分空间用于物理页的移入/移出&#xff0c;在 OS 中称为交换空间 (swap space)。 假设 OS 以页大小为单元读取/写入交换空间。为此&#xff0c;OS 需要记住给定页的硬盘地址。 交换空间的大小是非常重要的&#xff0c;它决定了系统在某一时刻能够使…

如何使用 TailwindCSS 画一条0.5px的线条

背景 在移动端项目&#xff0c;一般为了让线条看起来更细、更锐利&#xff0c;此时使用0.5px的线条是非常合适的。那么如何使用TailwindCSS画一条0.5px的线条呢&#xff1f; 在实现这个需求的时候&#xff0c;首先去TailwindCSS官网查了一下border有没有对应的内置工具类&…

Linux操作系统—文件和目录管理命令

从Linux目录结构我知道&#xff0c;Linux目录是一个树状结构&#xff0c;从/根目录开始&#xff0c;不同的目录挂载在/根目录之下&#xff0c;通过解除挂载移除相应的文件系统。文件与目录管理主要是通过命令行来进行操作&#xff0c;因此我必须了解文件系统的路径。 路径分为绝…

智能 GPT 图书馆又重生了

智能 GPT 图书馆又重生了 作者&#xff1a;程序员小白条 1&#xff09;概述 自从大二寒假准备开始筹备这个项目&#xff0c;到现在已经一年了&#xff0c;这个项目能维护一年&#xff0c;不愧是我.jpg。本来这个项目只是想练练手&#xff0c;因为那时候刚学完 Spring Boot2 V…

大语言模型--数据

数据 大语言模型 数据WebText和OpenWebText数据集Colossal Clean Crawled Corpus&#xff08;C4&#xff09;Benchmark的数据污染问题GPT-3的数据集The Pile数据集 WebText和OpenWebText数据集 WebText数据集被用于训练GPT-2模型。其目标是获取既多样化又高质量的数据集。以前…

3.3 内容管理模块 - 课程计划

文章目录 内容管理模块 - 课程计划一、介绍1.1 需求分析1.2 数据模型1.2.1 课程计划teachplan表1.2.2 teachplan_media 课程计划视频关联表 二、查询课程计划2.1 查询课程计划2.2 课程计划Dto2.3 TeachplanMapper2.4 TeachplanServiceImpl2.5 TeachPlanController 三、新增/修改…

算法通关第十九关-青铜挑战理解动态规划

大家好我是苏麟 , 今天聊聊动态规划 . 动态规划是最热门、最重要的算法思想之一&#xff0c;在面试中大量出现&#xff0c;而且题目整体都偏难一些对于大部人来说&#xff0c;最大的问题是不知道动态规划到底是怎么回事。很多人看教程等&#xff0c;都被里面的状态子问题、状态…

CentOS:Docker 创建及镜像删除

1、安装docker 远程连接服务器&#xff0c;可以直接下载netsarang比较好用 家庭/学校免费 - NetSarang Website 如果有残留docker未删除干净&#xff0c;请使用 sudo yum -y remove docker docker-common docker-selinux docker-engine Step1&#xff1a;安装必要的一些…

C# CSV文件(逗号分隔符)的读取和写入

1.CSV文件的写入原理&#xff1a; ①csv文件文件为逗号分隔符方式写入&#xff0c;起始写入的坐标位置为单元格的&#xff08;0,0&#xff09;假设用x,y分别表示单元格的横纵坐标&#xff0c;每次追加写入时&#xff0c;默认x坐标不变&#xff0c;y坐标都会1。 ②默认csv文件…

一文快速了解oCPX

01 什么是oCPX o是Optimized&#xff0c;优化一词的首字母。 CPX即Cost Per X&#xff0c;“X”即泛指传统的那些按不同方式进行结算的模式&#xff0c;如&#xff1a;CPC&#xff08;Cost Per Click&#xff0c;按点击付费&#xff09;、CPM&#xff08;Cost Per Mille&…

Spring学习?这一篇文章就够,史上最全!

文章目录 前言一、IOC概述及底层原理1.概述和原理 二、思路分析三、IOC操作bean管理&#xff08;基于xml&#xff0c;使用的是IDEA2022.3.3&#xff0c;maven仓库&#xff09;1.xml实现bean管理&#xff08;1&#xff09;创建对象&#xff08;2&#xff09;注入属性&#xff08…

Hugging Face实战-系列教程20:文本摘要建模实战2 之 Tokenizer处理

&#x1f6a9;&#x1f6a9;&#x1f6a9;Hugging Face 实战系列 总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在Jupyter Notebook中进行 本篇文章配套的代码资源已经上传 文本摘要建模实战1 之 数据清洗 文本摘要建模实战2 之 Tokenizer处理 3 Tokenizer处理 …

vue 项目配置反向代理导致项目白屏

问题&#xff1a;vue 项目配置反向代理导致项目白屏 一、现象描述 添加反向代理代码后&#xff0c;前端运行白屏 // 设置baseURL&#xff0c;8888是后端端口号&#xff0c;前端请求默认发送到baseURL的地址 var axios require(axios) axios.defaults.baseURL http://local…

【Linux】进程状态、进程优先级和进程切换

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大二&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;了解冯诺依曼体系结构与操作系统&#xff0c;掌握…

【C盘清理】Jetbrains全家桶(PyCharm、Clion……)更改 IDE 特定文件(配置、缓存、插件、日志等)存储位置

文章目录 一、官网说明二、更改 IDE 目录的位置1. 转到“帮助”|“编辑自定义属性”2. 各文件位置3. 以PyCharm系统目录为例4. 修改idea.properties 三、清理旧的 IDE 目录 一、官网说明 IDE 使用的目录官网说明 二、更改 IDE 目录的位置 默认情况下&#xff0c;PyCharm 将每…
最新文章