learn C++ NO.17——继承

news2024/9/20 11:44:53

什么是继承?

用冒号 : 后跟基类名称来声明一个类是从某个基类继承而来的。继承方式可以是 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.coloradmin.cn/o/2148819.html

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

相关文章

浏览器恢复历史记录应该怎么操作?简单几步轻松搞定

浏览器的历史记录是用户上网过程中产生的所有浏览活动的记录。这些历史记录对于查找之前访问过的网站、恢复误关闭的页面&#xff0c;以及跟踪浏览活动有很大的帮助。当然有时候我们可能会不小心将浏览器历史记录给删除了&#xff0c;那浏览器清除的历史记录可以恢复吗&#xf…

Linux 信号的产生

1. 概念 在Linux系统中&#xff0c;信号是一种进程间通信的机制&#xff0c;它允许操作系统或其他进程向特定进程发送异步通知。我们可以通过命令 kill -l来查看信号的种类&#xff1a; Linux系统中的信号可以分为两大类&#xff1a;传统信号和实时信号。从上图可以看出它们分…

代码随想录算法训练营第40天 动态规划part07| 题目: 198.打家劫舍 、 213.打家劫舍II 、 337.打家劫舍III

代码随想录算法训练营第40天 动态规划part07| 题目&#xff1a; 198.打家劫舍 、 213.打家劫舍II 、37.打家劫舍III 文章来源&#xff1a;代码随想录 题目名称&#xff1a;198.打家劫舍 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff…

【随手笔记】485

1. 基础知识 2线&#xff0c;半双工&#xff0c;多点通信 电压差传递信号 逻辑 1&#xff1a; 两线间电压差为 2V ~ 6V 逻辑0 &#xff1a; 两线间电压差为-2V ~ -6V 10米最高速率达 35Mbps 1200米 速率达100Kbps 抗共模干扰能力强 一般支持32个节点 推荐使用点对点线型 总线…

IDEA开发HelloWorld程序

IDEA管理Java程序的结构 project&#xff08;项目、工程&#xff09;---project中可以创建多个modulemodule&#xff08;模块&#xff09;---module中可以创建多个packagepackage&#xff08;包&#xff09;---package中可以创建多个classclass&#xff08;类&#xff09;---c…

木牛科技PMO总监关沨受邀为第四届中国项目经理大会演讲嘉宾

全国项目经理专业人士年度盛会 北京木牛领航科技有限公司PMO总监关沨女士受邀为PMO评论主办的全国项目经理专业人士年度盛会——2024第四届中国项目经理大会演讲嘉宾&#xff0c;演讲议题为“如何培养能打胜仗的项目经理”。大会将于10月26-27日在北京举办&#xff0c;主题为&a…

图神经网络在推荐系统中的应用综述

1 研究计划 了解推荐系统的研究背景和发展历程了解为什么推荐系统需要GNN了解基于GNN的推荐的关键挑战了解基于GNN的推荐的现有方法 2 完成情况 2.1推荐系统的研究背景和发展历程 随着各种服务和平台(如电子商务、短视频等)上信息的快速爆炸&#xff0c;推荐系统在缓解信息…

UWA支持鸿蒙HarmonyOS NEXT

华为在开发者大会上&#xff0c;宣布了鸿蒙HarmonyOS NEXT将仅支持鸿蒙内核和鸿蒙系统的应用&#xff0c;不再兼容安卓应用&#xff0c;这意味着它将构建一个全新且完全独立的生态系统。 为此&#xff0c;UWA也将在最新版的UWA SDK v2.5.0中支持鸿蒙HarmonyOS NEXT&#xff0c…

NLP三天入门大模型,我领先你好几个版本了

大模型时代下&#xff0c;nlp初学者需要怎么入门? 入门姿势简单粗暴:打一些必要的基础就跑步进入Transformera 大模型时代&#xff0c;传统的算法&#xff0c;像分词、词性标注&#xff0c;被替代得非常厉害&#xff0c;在入门阶段没必要花费太多精力在传统算法上面。 数学和…

强弱电的基本知识和区别

什么是弱电&#xff1a; 弱电一般是指直流电路或音频、视频线路、网络线路、电话线路&#xff0c;直流电压一般在36V以内。家用电器中的电话、电脑、电视机的信号输入&#xff08;有线电视线路&#xff09;、音响设备&#xff08;输出端线路&#xff09;等用电器均为弱电电气设…

IDEA Cody 插件实现原理

近年来&#xff0c;智能编程助手 在开发者日常工作中变得越来越重要。IDEA Cody 插件是 JetBrains 生态中一个重要的插件&#xff0c;它可以帮助开发者 快速生成代码、自动补全、并提供智能提示&#xff0c;从而大大提升开发效率。今天我们将深入探讨 Cody 插件的实现原理&…

Facebook隐私设置指南:如何更好地保护个人信息

在数字化时代&#xff0c;隐私保护成为了每个互联网用户面临的重要课题。Facebook&#xff0c;作为全球最大的社交网络平台之一&#xff0c;拥有庞大的用户基础和丰富的个人数据。因此&#xff0c;了解和管理Facebook的隐私设置对保护个人信息至关重要。本文将为您提供一份详细…

RTX 4090/RTX 4090D停产,为RTX 5090扫平“障碍”

原文转载修改自&#xff08;更多互联网新闻/搞机小知识&#xff09;&#xff1a; RTX 4090/4090D或于10月停产&#xff0c;为RTX 5090“登基”铺路 作为网络人均一代旗舰的RTX 4090至今也已发售近两年&#xff0c;说实在的&#xff0c;按老黄一贯的手法&#xff0c;也到了该落…

金属材质检测系统源码分享

金属材质检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

Docker 进入容器并运行命令的方法

目录 理解 Docker 容器的基本概念 使用 docker exec 进入运行中的容器 基本用法 常用选项解析 选项详解 实际案例演示 1. 进入容器的交互式 Shell 2. 在容器中运行单个命令 3. 以指定用户运行命令 4. 设置环境变量并运行命令 5. 指定工作目录 使用 docker attach 附…

Lingo求解器基本语法

Lingo是一款用于线性规划和整数规划的数学建模和求解软件&#xff0c;被广泛应用于运筹学、生产优化、供应链管理等领域。今天与大家一起来熟悉一下它的基本语法 Lingo基本语法 1、定义目标函数为MIN&#xff0c;MAX. 2、以一个分号“&#xff1b;”结尾。除SETS,ENDSETS,D…

我的AI工具箱Tauri版-VideoClipMixingCut视频批量混剪

本教程基于自研的AI工具箱Tauri版进行VideoClipMixingCut视频批量混剪。 VideoClipMixingCut视频批量混剪 是自研AI工具箱Tauri版中的一款强大工具&#xff0c;专为自动化视频批量混剪设计。该模块通过将预设的解说文稿与视频素材进行自动拼接生成混剪视频&#xff0c;适合需要…

企业展厅数字化变革:多媒体创新方案打造全新体验

相较于动态、形象的内容表达方式&#xff0c;传统展馆展厅已经无法满足观众的需求&#xff0c;所以数字化已经成为展厅升级转型的必然趋势&#xff0c;通过数字多媒体将展厅的内涵呈现出来&#xff0c;这便是展厅的特色&#xff0c;本文就来了解一下多媒体创新解决方案在企业展…

【TPAMI 2024】如何让模型在任何环境下都能胜出?领域泛化学习从单一到多元!

Out-of-Domain Generalization From a Single Source: An Uncertainty Quantification Approach 题目&#xff1a;单一源域的域外泛化&#xff1a;一种不确定性量化方法 作者&#xff1a;Xi Peng; Fengchun Qiao; Long Zhao 关注公众号&#xff1a;AI前沿速递&#xff0c;获取…

深度学习自编码器 - 收缩自编码器(CAE)篇

序言 在深度学习的浪潮中&#xff0c;收缩自编码器&#xff08; Compressive Autoencoder, CAE \text{Compressive Autoencoder, CAE} Compressive Autoencoder, CAE&#xff09;作为自编码器的一种高级形式&#xff0c;正逐步崭露头角。收缩自编码器在保留自编码器核心功能—…