当前位置: 首页 > news >正文

C/C++:04. 多态和虚函数

不加virtual,只能访问成员变量,不能访问成员函数

文章目录

  • 前言
  • 1 虚函数与多态
    • 不用虚函数的多态
    • 使用虚函数的多态
  • 2 C++虚函数注意事项 构成多态的条件
    • C++虚函数注意事项
    • 构成多态的条件
    • 什么时候声明虚函数
  • 3 C++`虚析构函数`的必要性
  • 总结


前言

本节阐述C++中的多态(polymorphism)虚函数(virtual function),首先来阐明一下什么是多态。

  • C++中的多态指通过一个基类的指针或引用来调用其派生类对象的函数,从而实现对不同子类对象的不同处理。
  • 基类指针指指向最原始那个类对象的指针。
  • 虚函数是C++中一种函数的声明方式,虚函数就是为了解决当一个基类指针调用派生子类对象函数的时候,执行的是派生子类对象的函数和参数,而不是基类的。

1 虚函数与多态

不用虚函数的多态

#include <iostream>
using namespace std;// 基类A
class A{
protected:int a_A;
public:// A(int a):a_A(a){}void display();
};
void A::display(){cout<< "this is class A\n"<<"and value = \n"<< a_A<<endl;
}
// 派生类 B
class B : public A{
public:B(int a): A(a){}void display();
};
void B::display()
{cout<< "this is class B\n"<<"and value = \n"<< a_A<<endl;
}int main()
{A *p = new A(1);p->display();p = new B(2);p->display();return 0;
}

运行结果:

this is class A
and value = 1
this is class A
and value = 2

我们常态思维:如果指针指向了派生类对象,就应该使用派生类的成员变量和成员函数。但是该实例告诉我们,当基类指针p指向派生类B的对象的时候,虽然使用了派生类B的成员变量,但是却没有使用它的成员函数,导致输出结果啥也不是。

也就是说,通过基类指针只能访问派生类的成员变量,但是不能访问派生类的成员函数。

为了消除上述尴尬,让基类指针能够访问派生类的成员函数,C++增加了虚函数(Virtual Function)。使用虚函数非常简单,只需要在函数声明前面增加virtual关键字。

使用虚函数的多态

#include <iostream>using namespace std;class A{
protected:int a_A;
public:A(int a):a_A(a){}virtual void display();
};void A::display(){cout<< "this is class A\n"<<"and value = "<< a_A<<endl;
}class B : public A{public:B(int a): A(a){}void display();
};void B::display()
{cout<< "this is class B\n"<<"and value = "<< a_A<<endl;
}int main()
{A *p = new A(1);p->display();p = new B(2);p->display();return 0;
}

运行结果:

this is class A
and value = 1
this is class B
and value = 2

只需要在函数申明的前面加上virtual便可以解决上述所说的尴尬。更全面地实现了多态。
C++提供多态的目的是:可以通过基类指针对所有派生类(包括直接派生和间接派生)的成员变量和成员函数进行“全方位”的访问,尤其是成员函数。如果没有多态,我们只能访问成员变量

2 C++虚函数注意事项 构成多态的条件

C++虚函数注意事项

  • 只需要在虚函数的声明处加上virtual关键字,函数定义处可以加也可以不加
  • 只需要将基类中的函数声明为虚函数,这样所有派生类中具有遮蔽关系的同名函数都将自动成为虚函数。
  • 当在基类中定义了虚函数,但是在派生类没有定义新的函数来遮蔽次函数,那么将使用基类的虚函数。
  • 只有派生类的虚函数覆盖基类的虚函数(函数原型相同)才能构成多态(通过基类指针访问派生类函数)。
  • 构造函数不能是虚函数。对于基类的构造函数,他仅仅是在派生类构造函数中被调用,这种机制不同于继承。也就是说,派生类不继承基类的构造函数,将构造函数声明为虚函数没有任何意义;另外还有一种原因:C++中的构造函数用于创建对象时进行初始化工作,在执行构造函数之前对象尚未创建完成,虚函数表尚不存在,也没有指向虚函数表的指针,所以此时无法查询虚函数表,也就不知道要调用哪个构造函数。
  • 析构函数可以声明为虚函数,而且有时候必须要声明为虚函数

构成多态的条件

  • 必须存在继承的关系;
  • 继承关系中必须有同名的虚函数,并且他们是覆盖关系(函数原型相同);
  • 存在基类的指针,通过该指针调用虚函数;
  • 通过基类指针只能访问从基类继承过去的成员,不能访问派生类新增加的成员

什么时候声明虚函数

  • 首先看成员函数所在的类是否会作为基类。
  • 然后看成员函数在类的继承后有无可能被更改功能。

3 C++虚析构函数的必要性

析构函数用于在销毁对象时进行清理工作,可以声明为虚函数,且有时候必要声明为虚函数。

#include <iostream>using namespace  std;
// 基类 A
class A{
protected:char* str;public:A(){str = new char[100];cout<<"A contructor"<<endl;}~A(){delete str;cout<<"A destructor"<<endl;}
};
// 派生类 B
class B: public A{
private:char* name;
public:B(){name = new char[100];cout<<"B contructor"<<endl;}~B(){delete name;cout<<"B destructor"<<endl;}
};int main()
{A *pa = new B();delete pa;cout<<"================="<<endl;B *pb = new B();delete pb;return 0;
}

在上述代码中,首先定义了基类A,定义了A的成员变量(protected)str,在构造函数中为str申请一块内存,在析构函数中销毁str的这块内存;定义了A的派生类B,B中定义成员变量name,在B的构造函数中为name申请一块内存,并在B的析构函数中销毁这块内存。 在main函数中,首先定义一个基类A的指针指向派生类B的对象,然后释放这个指针。又定义一个指向派生类的指针指向派生类的对象,再释放该指针。

运行结果:

A contructor
B contructor
A destructor
=================
A contructor
B contructor
B destructor
A destructor

很显然出现了问题:内存泄露,基类指针指向派生类对象,在释放基类指针的时候,并没有执行派生类的析构函数(没有释放掉派生类中为成员变量name申请的内存空间),只是执行基类的析构函数。

为什么delete pa; 不会调用派生类的析构函数?
这里的析构函数不是虚函数,通过指针访问非虚函数的时候,编译器会根据指针的类型来确定要调用的函数;也就是说,指针指向哪个类就调用哪个类的函数,。因此pa是基类的指针,不管它指向基类对象还是派生类的对象,始终都只调用基类的析构函数。

为什么delete pb; 会同时调用派生类和基类的析构函数?
pb是派生类的指针,编译器会根据它的类型匹配到派生类的析构函数,在执行派生类的析构函数的过程中,又会调用基类的析构函数。派生类析构函数始终会调用基类的析构函数,这个过程是隐式完成的

解决方法:将基类的析构函数声明为虚函数。

class A{
public:A();virtual ~A();
protected:char* str;
};

运行结果:

A contructor
B contructor
B destructor
A destructor
=================
A contructor
B contructor
B destructor

基类虚析构函数 联动:当基类的析构函数声明为虚函数之后,派生类的析构函数也会自动称为虚函数。此时编译器会忽略指针的类型,而根据指针的指向来选择函数;也就是说,指针指向哪个类就调用哪个类的函数。pa和pb都指向派生类的对象,所以会调用派生类的析构函数,继而再调用基类的析构函数。因此也就解决了内存泄漏问题。

因此多半情况下,基类的析构函数是虚函数


总结

使用了虚函数之后,我们就可以通过定义一个基类指针,遍历所有的派生类成员变量和成员函数,很大程度简化了代码的复杂度。

相关文章:

C/C++:04. 多态和虚函数

不加virtual&#xff0c;只能访问成员变量&#xff0c;不能访问成员函数 文章目录 前言1 虚函数与多态不用虚函数的多态使用虚函数的多态 2 C虚函数注意事项 构成多态的条件C虚函数注意事项构成多态的条件什么时候声明虚函数 3 C虚析构函数的必要性总结 前言 本节阐述C中的多态…...

拿下多个定点/比亚迪要上!这项技术要「降维打击」单目感知

2008年&#xff0c;斯巴鲁在全球首次推出了搭载有立体视觉摄像头的主动安全解决方案&#xff0c;并具有紧急制动功能的“EyeSight”。2010年&#xff0c;斯巴鲁推出升级版的“EyeSight”&#xff0c;成为世界上首个可以在全车速范围内避免碰撞的量产技术系统。 2014年&#xff…...

Go 的时间操作

Go 的时间操作 1. time 包 1.1. 时间类型 Go 语言中时间类型有两种&#xff1a; time.Time&#xff1a;表示时间的类型&#xff0c;常见的有两种方式&#xff1a; time.Now()&#xff1a;获取当前的时间time.Date()&#xff1a;根据指定的年、月、日、时、分、秒、纳秒等创…...

ThreeJS进阶之矩阵变换

ThreeJS的矩阵变换 Three.js使用matrix编码3D变换 —— 平移(位置),旋转和缩放。Object3D的每个实例都有一个matrix,用于存储该对象的位置,旋转和比例。这里介绍如何更新对象的变换。 三维物体(Object3D) 这是Three.js中大部分对象的基类,提供了一系列的属性和方法来对…...

分布式锁概念

什么是分布式锁 方案一&#xff1a;SETNX EXPIRE 方案二&#xff1a;SETNX value值是&#xff08;系统时间过期时间&#xff09; 方案三&#xff1a;使用Lua脚本(包含SETNX EXPIRE两条指令) 方案四&#xff1a;SET的扩展命令&#xff08;SET EX PX NX&#xff09; 方案五…...

PMP课堂模拟题目及解析(第10期)

91. 在项目执行阶段&#xff0c;一名项目干系人要求项目经理加入一个新过程的优化。项目经理应该怎么做&#xff1f; A. 执行实施整体变更控制过程。 B. 与过程专家一起审查项目。 C. 将优化项目分配给团队。 D. 拒绝范围蔓延企图。 92. 项目经理要求团队提供对项目应急计…...

Arm微架构分析系列3——Arm的X计划

1. 引言 前文介绍了Arm公司近几年在移动处理器市场推出的Cortex-A系列处理器。Cortex-A系列处理器每年迭代&#xff0c;性能和能效不断提升&#xff0c;是一款非常成功的产品。但是&#xff0c;Arm并不满足于Cortex-A系列每年的架构小幅度升级&#xff0c;又推出了X计划&#x…...

Kubernetes(K8S)的基础概念

文章目录 一、Kubernetes介绍1、什么是Kubernetes&#xff1f;2、为什么要用K8S?3、k8s的特性 二、k8s集群架构与组件1、Master组件2、配置存储中心——etcd3、Worker Node 组件 三、k8s核心概念●Pod●Pod 控制器(五大控制器)●Label●Label选择器(Label selector )●Service…...

【Linux进阶命令 04】lsof (看看是谁动了我的文件?)

文章目录 一、简介二、lsof语法2.1 基本格式2.2 选项2.3 输出字段解释 三、常用 lsof 操作3.1 查看某文件的相关进程3.2 网络相关&#xff1a;-i3.3 指定进程号打开的文件&#xff1a;-p3.4 指定用户打开的文件&#xff1a;-u3.5 某进程打开的文件&#xff1a;-c3.6 复合查询 四…...

华为OD机试真题 Java 实现【数字加减游戏】【2023Q1 200分】

一、题目描述 小明在玩一个数字加减游戏&#xff0c;只使用加法或者减法&#xff0c;将一个数字s变成数字t。 每个回合&#xff0c;小明可以用当前的数字加上或减去一个数字。 现在有两种数字可以用来加减&#xff0c;分别为a&#xff0c;其中b没有使用次数限制。 请问小明…...

Python: 结合多进程和 Asyncio 以提高性能

动动发财的小手&#xff0c;点个赞吧&#xff01; 简介 多亏了 GIL&#xff0c;使用多个线程来执行 CPU 密集型任务从来都不是一种选择。随着多核 CPU 的普及&#xff0c;Python 提供了一种多处理解决方案来执行 CPU 密集型任务。但是直到现在&#xff0c;直接使用多进程相关的…...

只需要两步就能快速接入GPT

缘起 最近一个朋友提出&#xff0c;让我出个关于如何快速接入GPT的教程&#xff0c;今天就给大家安排上。 需要的工具 经过实测&#xff0c;这是迄今为止最便捷的接入方式&#xff0c;而且亲测有效。 首先&#xff0c;第一步你需要下载最新版的微软Edge浏览器&#xff0c;去…...

使用Git-lfs上传超过100m的大文件到GitHub

文章目录 1. 安装 git-lfs2. 在Git中安装git-ifs3. 找到工程中的所有大文件4.执行完这行命令&#xff0c;项目目录下会生成文件 .gitattributes&#xff0c;此时Git push将 .gitattributes 提交到远程仓库。 5. 需要注意的事 1. 安装 git-lfs Git Large File Storage | Git La…...

【网络】计算机中的网络

目录 &#x1f341;计算机网络 &#x1f341;计算机网络模型 &#x1f341;布线工程 &#x1f341;布线系统 &#x1f990;博客主页&#xff1a;大虾好吃吗的博客 &#x1f990;专栏地址&#xff1a;网络专栏 计算机网络 计算机网络的功能 数据通信、资源共享、增加可靠性、提…...

什么是语音识别的语音助手?

前言 语音助手已经成为现代生活中不可或缺的一部分。人们可以通过语音助手进行各种操作&#xff0c;如查询天气、播放音乐、发送短信等。语音助手的核心技术是语音识别。本文将详细介绍语音识别的语音助手。 语音识别的基本原理 语音识别是将语音信号转换为文本的技术。语音识…...

自己动手写一个加载器

前言 当在 linux 命令行中 ./ 运行一个程序时&#xff0c;实际上操作系统会调用加载器将这个程序加载到内存中去执行。为了探究加载器的行为&#xff0c;今天我们就自己动手写一个简单的加载器。 工作原理 加载器的工作原理&#xff1a; 从磁盘读取 bin 文件到内存&#xf…...

C# 性能优化和Unity性能优化

C# 性能优化 C# 性能优化是一个非常广泛的话题&#xff0c;需要从各个方面来考虑&#xff0c;包括算法和数据结构、编译器优化、代码优化等等。下面是一些常见的 C# 性能优化技巧&#xff1a; 选择正确的数据结构&#xff1a;C# 提供了各种不同的数据结构&#xff0c;例如数组、…...

面试题背麻了,花3个月面过华为测开岗,拿个26K不过分吧?

计算机专业&#xff0c;代码能力一般&#xff0c;之前有过两段实习以及一个学校项目经历。第一份实习是大二暑期在深圳的一家互联网公司做前端开发&#xff0c;第二份实习由于大三暑假回国的时间比较短&#xff08;小于两个月&#xff09;&#xff0c;于是找的实习是在一家初创…...

跟着我学 AI丨教育 + AI = 一对一教学

随着人工智能&#xff08;AI&#xff09;技术的迅速发展&#xff0c;它已经开始了改变教育的方式。本文将介绍AI在教育行业中的应用场景&#xff0c;当前从事AI 教育的公司有哪些以及这些公司所提供的教育产品的特点&#xff0c;和未来AI 教育的潜在实现方式。 AI在教育行业的…...

1-动态规划算法理论基础

目录 1.什么是动态规划? PS&#xff1a;动态规划 VS 贪心 2.动态规划的解题步骤 ①确定dp数组&#xff08;dp table&#xff09;以及下标的含义。 ②确定递推公式/状态转移公式。 ③dp数组如何初始化。 ④确定遍历顺序。 ⑤举例推导dp数组。 3.动态规划应该如何debug…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具

作者&#xff1a;来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗&#xff1f;了解下一期 Elasticsearch Engineer 培训的时间吧&#xff01; Elasticsearch 拥有众多新功能&#xff0c;助你为自己…...

大语言模型如何处理长文本?常用文本分割技术详解

为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

CMake 从 GitHub 下载第三方库并使用

有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

IT供电系统绝缘监测及故障定位解决方案

随着新能源的快速发展&#xff0c;光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域&#xff0c;IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选&#xff0c;但在长期运行中&#xff0c;例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)

Aspose.PDF 限制绕过方案&#xff1a;Java 字节码技术实战分享&#xff08;仅供学习&#xff09; 一、Aspose.PDF 简介二、说明&#xff08;⚠️仅供学习与研究使用&#xff09;三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

基于Java+MySQL实现(GUI)客户管理系统

客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息&#xff0c;对客户进行统一管理&#xff0c;可以把所有客户信息录入系统&#xff0c;进行维护和统计功能。可通过文件的方式保存相关录入数据&#xff0c;对…...

搭建DNS域名解析服务器(正向解析资源文件)

正向解析资源文件 1&#xff09;准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2&#xff09;服务端安装软件&#xff1a;bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...

CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝

目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为&#xff1a;一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...

CSS3相关知识点

CSS3相关知识点 CSS3私有前缀私有前缀私有前缀存在的意义常见浏览器的私有前缀 CSS3基本语法CSS3 新增长度单位CSS3 新增颜色设置方式CSS3 新增选择器CSS3 新增盒模型相关属性box-sizing 怪异盒模型resize调整盒子大小box-shadow 盒子阴影opacity 不透明度 CSS3 新增背景属性ba…...

门静脉高压——表现

一、门静脉高压表现 00:01 1. 门静脉构成 00:13 组成结构&#xff1a;由肠系膜上静脉和脾静脉汇合构成&#xff0c;是肝脏血液供应的主要来源。淤血后果&#xff1a;门静脉淤血会同时导致脾静脉和肠系膜上静脉淤血&#xff0c;引发后续系列症状。 2. 脾大和脾功能亢进 00:46 …...