learn C++ NO.17——继承

什么是继承?

用冒号 : 后跟基类名称来声明一个类是从某个基类继承而来的。继承方式可以是 public、protected 或 private,这决定了基类成员在子类中的访问权限。
下面通过代码简单进行一下演示.
在这里插入图片描述

派生类Student即子类,而基类Person是它的父类。语法格式为 class/struct + 派生类名 : 继承方式(如public) + 基类名。
在这里插入图片描述

继承的方式

类的访问限定符有三个分别是public、protected以及private。而继承方式有三种分别是公有继承(public)、保护继承(protected)以及私有继承(private)。而C++的祖师爷把继承方式用类的访问限定符和继承方式一组合,组合出了9种继承方式。

Public 继承:当使用 public 继承时,基类的 public 成员在子类中仍然是 public 的,基类的 protected 成员在子类中仍然是 protected 的,但基类的 private 成员在子类中无法直接访问。

Protected 继承:当使用 protected 继承时,基类的 public 和 protected 成员在子类中变成 protected 的,基类的 private 成员在子类中无法直接访问。

Private 继承:当使用 private 继承时,基类的 public 和 protected 成员在子类中变成 private 的,基类的 private 成员在子类中无法直接访问。

简单总结一句话就能记住这九种方式。基类的私有都不可见,而基类的公有成员和保护成员都是取和继承方式这两个比权限小的。 权限的级别从小到大 public < protected < private。

对于基类的私有成员私有不可见这个概念进行一些解释,这里的不可见是语言层面上的对其限制访问(类内类外都不可以使用),底层内存中实际还是会存储对应的数据。

基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的

使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
在这里插入图片描述
在这里插入图片描述
在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

基类和派生类对象赋值转换

首先,谈一谈基类和派生类的关系。在继承关系中,派生类对象是基类对象的扩展。这意味着派生类对象包含基类对象的所有成员(数据和函数),并且还可能有额外的成员。

可以将一个派生类对象赋值给一个基类对象,这种赋值是**切片(slicing)操作 **,即只将基类部分的数据成员复制到基类对象中。派生类特有的成员会被忽略。派生类对象不仅可以用来赋值给基类对象,还可以赋值给基类对象的引用和指针。

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

在语法层面,不允许用基类对象赋值给派生类对象。但是指针和引用还是可以做到向下转移,但是要涉及多态以及RTTI(RunTime Type Information)。这里不多赘述,后面会详细聊。也可以通过提供一个构造函数或赋值运算符来实现这种赋值。

继承中的作用域

作用域的概念可以理解成是编译器去查找对应的变量或函数的优先去哪个{}区域找。通常编译器是取最近的域的内容来进行匹配。对应到继承中,编译器会优先去找子类类域中的成员,若子类没有对应成员,则会去父类的作用域找。

在派生类中,如果定义了一个与基类同名的成员(无论类型是否相同),基类的那个成员会被隐藏。这意味着,在派生类的作用域中,访问该名称时只会看到派生类定义的成员。

下面通过样例看一看。
在这里插入图片描述
Person类中的_num和Student类中的_num构成隐藏(重定义)关系。不仅是成员变量可以构成隐藏,成员函数也可以构成隐藏。

在这里插入图片描述

当然,下面以一个非常迷惑人的题目为例。加深大家对继承中作用域的理解。
在这里插入图片描述
这里我先声明答案为A。而不是B。因为函数构成重载需要在同一作用域下。当然,你如果在主函数内定义一个Student对象去调用func()的话,会出现报错。因为,编译器优先去在Student的类域查找,发现找到了func(int i)。所以,编译器会认为是你的在使用函数语法上出错了。

派生类的默认成员函数

默认成员函数就是程序员不写,编译器自动生成的成员函数。通常有六个默认成员函数,如默认构造函数、析构函数、拷贝构造函数、赋值运算符重载,移动构造函数(C++11)以及移动赋值重载函数(C++11)。
在这里插入图片描述

下面我通过样例依次介绍派生类的默认成员函数的一些语法细节。下面是一份基类代码,这份代码中实现了默认构造函数、析构函数、拷贝构造和拷贝赋值运算符重载。

在这里插入图片描述

假设定义一个Student派生类来公有继承基类。然后,按照原来的写法写一份错误的样例。
在这里插入图片描述
如果在派生类的默认构造函数中没有初始化基类成员的话,编译器会去调用基类的默认构造函数来初始化基类的成员
在这里插入图片描述

若基类没有提供默认构造,派生类没有在构造函数中初始化基类成员则编译器报错。
在这里插入图片描述

派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
在这里插入图片描述

派生类的拷贝构造中,需要显示调用基类的拷贝构造函数来对基类成员进行正确的拷贝。
在这里插入图片描述

如果派生类不在构造函数中去拷贝初始化基类成员部分,此时若基类提供了默认构造,那么编译器将会自动去调用基类的默认构造,进而导致错误。
在这里插入图片描述

派生类的拷贝复制重载必须调用基类的拷贝复制重载,完成基类部分的拷贝赋值。 否则,由于这里的operator=与基类的operator=构成隐藏,不指定基类类域调用的话,默认调用派生类自己的operator=,进而导致栈溢出问 题。
在这里插入图片描述

下面介绍一下关于派生类继承基类后的析构函数问题。派生类不能在析构函数中,显示调用基类的析构函数。会导致析构多次的问题。因为,编译器自动会去调用析构函数。
在这里插入图片描述

上面我们提到,在实例化派生类对象中,会先根据声明的顺序,先去调用基类的构造函数初始化基类的成员。而在对象的生命周期结束后,编译器会根据对象的实例化顺序,优先去析构派生类的成员,再去析构基类成员。派生类在析构函数中显示调用基类析构函数,就无法保证先析构派生类后析构基类的顺序要求。先析构基类成员,还会引发派生类对象访问基类成员已经释放的空间导致程序崩溃。所以,不能在派生类的析构函数中显示调用基类的析构函数。
在这里插入图片描述

由于多态特性,编译器将析构函数的函数名进行了特殊的处理,在派生类中调用基类的析构函数需要指定基类类域。这里算是挖一个坑,下一篇文章来填这个坑。

友元与继承

**基类的友元关系派生类不能继承。**基类的友元无法访问派生类的保护成员以及私有成员。
在这里插入图片描述
如果Display函数也要访问Student的保护成员,那就在Student类内也得声明友元。
在这里插入图片描述

继承与静态成员的关系

基类定义了一份静态成员,无论多少个派生类继承了这个静态成员,整个继承体系中也只有一份静态成员的实例。派生类的对象模型中是没有这个静态成员的,但是,派生类可以使用这个静态成员。
在这里插入图片描述

多继承

多继承指的是一个派生类继承两个及以上的基类,语法如下。

class A
{};class B
{};//多继承
class C : class B , class A 
{};

上面都是一单继承为样例进行介绍。如何区分单继承还是多继承,主要是看 :右边有几个类。

C++的祖师爷们整出了多继承时,可能觉得这个东西非常妙,毕竟面向对象模型中,难免会有一些情况下,一个对象身兼数职。比如你可能既是一名程序员,又在下班后兼职当外卖骑手。不可否认的是,多继承在一些场景下是有用的,但是,它也会引发一些列的问题,如菱形继承。

菱形继承

菱形继承是一种特殊情况的多继承。下面看一看什么是菱形继承。
在这里插入图片描述
在这种结构中,如果 B 和 C 都各自有一个从 A 继承来的成员变量或函数,那么 D 将会拥有两份 A 中成员的拷贝,这可能会导致数据冗余和二义性问题。
在这里插入图片描述

在这里插入图片描述

虚继承

既然菱形继承有数据冗余和二义性的问题,那要如何解决呢?在腰部位置引入虚继承,即在class B 和 class C 前引入virtual 修饰继承,可以保证class D中只有一份class A成员。

在这里插入图片描述

虚继承如何解决的数据冗余和二义性

下面通过调试的内存窗口看一看,究竟C++底层是如何实现虚继承来解决数据冗余和二义性的问题。

首先,看一看菱形继承在内存窗口的模型是怎么样的。
在这里插入图片描述
在这里插入图片描述

通过上面的内存窗口可以看到,d对象中既有B对象的_a,又有C对象的_a。所以产生了数据冗余和二义性问题。

下面再看看引入虚继承后的内存窗口。
在这里插入图片描述

不难看到对比与上面没有引入虚继承的对象模型,这里引入虚继承的菱形继承的对象模型多了两个指针成员(虚基表指针),分别指向两块独立的空间(虚基表)。独立的空间内存的就是与对象a部分的相对位置(偏移量)

在这里插入图片描述
为什么需要这样处理呢?因为这样处理方便编译器统一对虚继承的派生类的公共基类成员进行查找处理。只要引入虚继承,派生类的对象模型都会产生变化。
在这里插入图片描述
而编译器并不会要针对切片进行特殊处理,因为对象模型的实现思路是一样的,编译器对虚继承的处理方式是统一的。下面通过反汇编简单对比一下。
在这里插入图片描述

总结一下菱形虚拟继承相关的问题

菱形虚拟继承在C++中主要是用来解决菱形继承(多重继承的一种特殊情况)所带来的数据冗余和二义性问题的。然而,它本身也引入了一些复杂性和潜在的问题,主要包括以下几点:

1. 复杂性开销

  • 实现复杂性:虚拟继承增加了编译器实现的复杂性,因为编译器需要管理虚拟基类表、偏移量等额外信息,以确保基类实例的共享和正确访问。
  • 代码复杂性:对于开发者而言,理解和维护使用虚拟继承的代码可能更加复杂。特别是当类继承层次较深或较复杂时,理解对象内存布局和成员访问方式可能会变得更加困难。

2. 性能影响

  • 内存开销:虚拟继承引入了额外的内存开销,因为需要存储指向虚拟基类实例的指针(通常通过虚拟基类表实现)。这可能会增加对象的大小,从而影响内存使用效率。
  • 访问开销:访问虚拟基类的成员可能需要通过额外的指针间接访问,这可能会增加访问成本,影响程序的性能。

3. 构造和析构顺序

  • 构造顺序:在菱形虚拟继承中,虚拟基类的构造函数会在任何派生类构造函数之前被调用,且只调用一次。然而,这可能会使得构造顺序的理解变得更加复杂,尤其是在涉及多个虚拟基类和深层继承层次时。
  • 析构顺序:与构造顺序相反,虚拟基类的析构函数会在任何派生类析构函数之后被调用。这同样需要开发者特别注意,以避免在析构过程中访问已销毁的对象成员。

4. 初始化问题

  • 显式初始化:在使用虚拟继承时,最终派生类必须显式地调用虚拟基类的构造函数,否则编译器将报错。这意味着在编写继承体系时,需要特别注意构造函数的调用方式和顺序。
  • 初始化列表:在派生类的初始化列表中,必须按照正确的顺序列出所有基类(包括间接基类)的构造函数调用,以确保基类被正确初始化。

5. 访问控制

  • 访问权限:虽然虚拟继承解决了基类成员的二义性问题,但它并不影响成员的访问权限。如果基类成员在派生类中被隐藏或重定义,那么访问这些成员时仍然需要遵循C++的访问控制规则。

6. 设计和维护难度

  • 设计考虑:在设计继承体系时,需要仔细考虑是否真的需要使用虚拟继承。因为虚拟继承虽然解决了菱形继承的问题,但也可能引入其他复杂性和性能开销。
  • 维护难度:使用虚拟继承的代码可能在后期维护中变得更加困难,特别是当继承层次变得复杂时。因此,在设计阶段就需要权衡其利弊。

总之,菱形虚拟继承在C++中是一种有用的特性,用于解决特定的多重继承问题。然而,它本身也引入了一些复杂性和潜在的问题,需要开发者在设计和维护过程中特别注意。在可能的情况下,优先使用对象组合而不是类继承,以降低复杂性和提高代码维护性。

继承和组和

class A
{};//继承
class B : public A
{};//组合
class C
{
private:A a;
};

继承是一种白箱复用,耦合度相对较高。组合是一种黑箱复用,耦合度较低。在软件工程学科中,程序要讲究高内聚低耦合。实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系既可以用继承,又可以用组合,那就用组合。

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

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

相关文章

Ubuntu22.04安装paddle

查看系统版本信息 使用命令lsb_release -a查看系统版本 rootLAIS01:~# lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 22.04.5 LTS Release: 22.04 Codename: jammy查看系统支持的cuda版本&#xff0c;使用命令nvidia-smi&#…

基于paddleocr的批量图片缩放识别

说明 在进行ocr文字识别的时候&#xff0c;有时候我们需要使用批量测试的功能&#xff0c;但是有些图片会识别失败或者个别根本识别不出来&#xff0c;这时候我们可以通过对原图片进行缩放&#xff0c;提高图像的分辨率&#xff0c;然后再次识别&#xff0c;这样可以大大提高图…

Canal+RabbitMQ数据同步环境配置

Canal 是阿里巴巴开发的开源工具&#xff0c;主要用于解析 MySQL 的 binlog 日志&#xff0c;从而实现数据同步。Canal 会模拟 MySQL 从库的协议&#xff0c;订阅主库的 binlog&#xff0c;从而获取数据库的变更信息。 将 Canal 解析到的 MySQL 数据库变更消息通过 RabbitMQ 分…

青柠视频云——视频丢包(卡顿、花屏、绿屏)排查

一、问题说明 近期有客户反馈&#xff0c;接入平台的设备经常出来卡顿、花屏、录屏的情况&#xff0c;出现这样的场景很是尴尬。 客户是私有化部署在公网环境&#xff0c;于是我们联系客户&#xff0c;对问题进行追踪排查。 二、场景复现 我们现场情况确认的过程中&#xff0c;…

蓝桥杯嵌入式客观题合集

十四届模拟赛二客观题 解析&#xff1a;STM32微控制器的I/O端口寄存器必须按32位字被访问 解析&#xff1a;微分电路能将三角波转换为方波&#xff1b;积分电路能将方波转换为三角波 解析&#xff1a;放大电路的本质是能量的控制与转换 解析&#xff1a;具有n个节点&#xff0c…

修改Docker默认存储路径,解决系统盘占用90%+问题(修改docker root dir)

随着Docker技术的广泛应用&#xff0c;它极大地简化了复杂项目的部署与维护流程&#xff0c;仅凭单一镜像即可轻松运行。然而&#xff0c;随着数据量不断增长&#xff0c;Docker的默认数据存储方式可能逐渐成为挑战&#xff0c;尤其是当默认安装于根目录&#xff08;“/”&…

【雪球-注册安全分析报告-无验证方式导致安全隐患】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 1. 暴力破解密码&#xff0c;造成用户信息泄露 2. 短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉 3. 带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造…

Python酷库之旅-第三方库Pandas(104)

目录 一、用法精讲 451、pandas.DataFrame.pow方法 451-1、语法 451-2、参数 451-3、功能 451-4、返回值 451-5、说明 451-6、用法 451-6-1、数据准备 451-6-2、代码示例 451-6-3、结果输出 452、pandas.DataFrame.dot方法 452-1、语法 452-2、参数 452-3、功能…

【C++】STL简介

&#x1f525;个人主页&#xff1a; Forcible Bug Maker &#x1f525;专栏&#xff1a;STL || C 目录 前言什么是STL&#xff1f;STL的历史STL的版本STL六大组件STL的优缺点STL的优点&#xff1a;STL的缺点&#xff1a; 如何学习STL结语 前言 本篇博客主要内容&#xff1a;ST…

灾备技术演进之路 | 虚拟化无代理备份只能挂载验证和容灾吗?只能无代理恢复吗?且看科力锐升级方案

灾备技术演进之路系列 虚拟化备份技术演进 摆脱束缚&#xff0c;加速前行 无代理备份仅能挂载/恢复验证吗&#xff1f; ——科力锐极简验证演练无代理备份来了 无代理备份无法应对平台级故障吗&#xff1f; ——科力锐应急接管无代理备份来了 无代理备份仅能同平台挂载吗&a…

Llama 3.1 Omni:颠覆性的文本与语音双输出模型

你可能听说过不少关于语言模型的进展,但如果告诉你,有一种模型不仅能生成文本,还能同时生成语音,你会不会觉得特别酷?今天咱们就来聊聊一个相当前沿的项目——Llama 3.1 Omni模型。这个模型打破了传统的文字生成边界,直接让文本和语音同时输出,实现了真正的"多模态…

无人机之AI跟踪篇

无人机的AI识别技术依托于计算机视觉和深度学习技术&#xff0c;实现了对目标的快速精准识别&#xff0c;在多个领域展现出了巨大的应用潜力和价值。以下是对无人机AI识别技术的详细解析&#xff1a; 一、无人机AI识别算法的基础原理 无人机AI识别算法主要基于先进的计算机视觉…

轻松解决Jetpack Compose中的一些痛点问题

公众号「稀有猿诉」 原文链接 轻松解决Jetpack Compose中的一些痛点问题 暑去秋来&#xff0c;金桂飘香&#xff0c;不知不觉中我们已经练完了『降Compose十八掌』&#xff0c;相信通过这一系列文章能够对Jetpack Compose有足够的理解&#xff0c;并能在实际项目中进行运…

Linux memcg lru lock提升锁性能

目录 内核关于per memcg lru lock的重要提交&#xff1a; 计算虚拟地址转换基本机制 问题背景 swap换入流程 时奎亮的per memcg lru lock分享视频 内核关于per memcg lru lock的重要提交&#xff1a; f9b1038ebccad354256cf84749cbc321b5347497 6168d0da2b479ce25a4647d…

感知笔记:ROS 视觉- 跟随红球

- 目录 - 如何在 ROS 中可视化 RGB 相机。如何作为机器人切换主题。如何创建 blob 检测器。如何获取要跟踪的颜色的颜色编码。如何使用 blob 检测数据并移动 RGB 相机以跟踪 blob。 机器人技术中最常见的传感器是不起眼的 RGB 摄像头。它用于从基本颜色跟踪&#xff08;blob 跟…

ssm自助购药小程序 LW PPT源码调试讲解

第二章开发技术介绍 此系统的关键技术和架构&#xff0c;Java技术、B/S结构、Ssm框架和Mysql数据库&#xff0c;是本系统的关键开发技术&#xff0c;对系统的整体、数据库、功能模块、系统页面以及系统程序等设计进行了详细的研究与规划。 2.1 系统开发平台 在线自助购药小程…

PMP--二模--解题--1-10

文章目录 4.整合管理--商业文件--商业论证&#xff08;是否值得所需投资、高管们决策的依据&#xff09;反映了&#xff1a;1、 [单选] 收到新项目的客户请求之后&#xff0c;项目经理首先应该做什么&#xff1f; 14.敏捷--角色--产品负责人PO–职责–1.创建待办列表并排序;2.确…

大数据概念与价值

文章目录 引言大数据的概念高德纳咨询公司的定义麦肯锡全球研究所的定义什么是大数据&#xff1f; 大数据的特征Volume&#xff08;体积&#xff09;Variety&#xff08;种类&#xff09;Velocity&#xff08;速度&#xff09;Value&#xff08;价值&#xff09;Veracity&#…

计算机毕业设计Python+Flask微博情感分析 微博舆情预测 微博爬虫 微博大数据 舆情分析系统 大数据毕业设计 NLP文本分类 机器学习 深度学习 AI

首先安装需要的python库&#xff0c; 安装完之后利用navicat导入数据库文件bili100.sql到mysql中&#xff0c; 再在pycharm编译器中连接mysql数据库&#xff0c;并在设置文件中将密码修改成你的数据库密码。最后运行app.py&#xff0c;打开链接&#xff0c;即可运行。 B站爬虫数…

恢复已删除文件的可行方法,如何恢复已删除的文件

在清理 PC 或优化存储设备时无意中删除重要文件是一种常见的人为错误。不可否认&#xff0c;在批量删除文件时&#xff0c;您通常会一起删除垃圾文件和重要文件。您后来意识到一堆文件或文件中缺少一个重要的文档或文件。在这种情况下&#xff0c;您唯一的选择是寻找恢复已删除…