c++进阶学习-----继承

1.继承的概念及定义

1.1继承的概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。

继承呈现了面向对象 程序设计的层次结构,体现了由简单到复杂的认知过程。

以前我们接触的复用都是函数复用,继承是类设计层次的复用。

继承的本质就是复用代码
假设你现在需要写一个 学校教务系统,单从角色划分上来说,可以简单分为:教职工和学生 这两大类

但如果继续划分的话,还可以分出:

校领导、各级院长、辅导员、后勤人员、大一/二/大三/大四学生等

假设为每种不同的只角色都设计一个 struct,那么这个工程量也未免太大了

为了复用代码、提高开发效率,可以从各种角色中选出共同点,组成基类

比如每个 人 都有姓名、年龄、性别、联系方式等基本信息

而 教职工与学生的区别就在干管理与被管理,因此可以在基类的基础上加一些特殊信息

如教职工号表示教职工,加上学号表示学生,

其他细分角色设计也是如此

这样就可以通过继承的方式,复用基类的代码,划分出各种子类

1.2 继承定义

1.2.1定义格式

下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类。  

1.2.2关系访问限定符 

 

1.2.3继承基类成员访问方式的变化 

1.3总结

1. 基类private成员在派生类中无论以什么方式继承都是不可见的。

这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。

2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。

可以看出保护成员限定符是因继承才出现的。

3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。

基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。

4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。

5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承 

因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。 

在实际开发中,继承会经常用到(不然也不会作为 面向对象三大特性 之一了)

比较经典的例子:c++中的IO流玩的就继承,并且还是菱形继承

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

派生类对象可以赋值给 基类的对象 / 基类的指针 / 基类的引用。

这里有个形象的说法叫切片或者切割。

寓意把派生类中父类那部分切来赋值过去。

 

 

 

 

基类对象不能赋值给派生类对象。

 

派生类对象赋值给基层对象,是不会产生临时变量的

基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。

但是必须是基类的指针是指向派生类对象时才是安全的。

这里基类如果是多态类型,可以使用RTTI(RunTime Type Information)的dynamic_cast 来进行识别后进行安全转换。

3.继承中的作用域

1. 在继承体系中基类和派生类都有独立的作用域。

2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏, 也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)

3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。

这里有个常见的面试题

解决方法:可以直接制定在Person的作用域内,就能避免报错

 

4.派生类的默认成员函数

6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类 中,这几个成员函数是如何生成的呢?

1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。

2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
 

没有调用父类成员的时候,编译器还是会给父类成员进行默认构造

如果派生类的拷贝构造函数没有调用基类的拷贝构造完成基类的拷贝初始化。 

3. 派生类的operator=必须要调用基类的operator=完成基类的复制。

这样写会造成operator自己调用自己,导致栈溢出所以要指定Person类 

4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能 保证派生类对象先清理派生类成员再清理基类成员的顺序。

5. 派生类对象初始化先调用基类构造再调派生类构造。

6. 派生类对象析构清理先调用派生类析构再调基类的析构。

7. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加 virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。

 

 

主要原因就是析构顺序先父后子了 ,析构顺序应该先子后父

为什么调用析构函数的时候要先子后父
两个原因:
1.一般对象存储在内存中的栈上,存储方式和数据结构的栈差不多。所以编译器一般默认规定最先创建的对象最后销毁,最后创建的对象最先销毁

2.如果子类当中有可能会用到父类成员的。父类是不能调用子类的成员。如果先把父类析构了,子类调用父类成员的时候就会报错。

 总的来说,子类中的默认成员函数调用规则可以概况为以下几点:

1.子类的构造函数必须调用父类的构造函数,初始化属于父类的那一部分内容;如果没有默认构造函数,则需要显式调用

2.子类的拷贝构造、赋值重载函数必须要显式调用父类的,否则会造成重复析构问题

3.父类的析构函数在子类对象销毁后,会自动调用,然后销毁父类的那一部分
注意:

子类对象初始化前,必须先初始化父类那一部分

子类对象销毁后,必须销毁父类那一部分

不能显式的调用父类的析构函数(因为这不符合栈区的规则),父子类析构函数为同名函数destructor ,构成隐藏,如果想要满足我们的析构需求,就需要将其变为虚函数,构成重写

析构函数必须设为虚函数,这是一个高频面试题,同时也是多态中的相关知识

5.继承与友元

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员

除非子类和父类有同一个友元 

6. 继承与静态成员

静态成员是唯一存在的,无论是否被继承

静态变量为于静态区,不同于普通的堆栈区,静态变量的声明周期很长,通常是程序运行结束后才会被销毁,因此 假设父类中存在一个静态变量,那么子类在继承后,可以共享此变量

class Base
{friend void Print();
public:Base() { num++; }static int num;	//静态变量
};int Base::num = 0;	//初始化静态变量class Derived : public Base
{
public:Derived() { num++; }
};void Print()
{cout << Base::num << endl;
}int main()
{Derived d1;Derived d2;Derived d3;Print();return 0;
}

 

创建了三个子类对象,同时 因为在创建子类对象前,会自动调用父类的默认构造函数,因此最终结果为6

这也从侧面证明了静态成员是唯一存在的,并且被子类共享 

7.复杂的菱形继承及菱形虚拟继承

单继承:一个子类只有一个直接父类时称这个继承关系为单继承

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承 

菱形继承:菱形继承是多继承的一种特殊情况。 

注意:菱形继承这个术语描述的是一种特定的多重继承结构,它不一定局限于只有四个类。
菱形继承的核心特征是存在一个共同的基类,以及至少两个从这个基类继承的派生类,再由一个或多个最终派生类从这两个派生类继承。

 

这些也算菱形继承

菱形继承的问题:

从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。

用::符号指明即可,但是在Assistant的对象中Person成员会有两份。  

 

这只解决了二义性,并没有解决数据冗余问题 

真正的解决方法:

7.1虚继承

注:虚继承是专门用来解决萎形继承问题的,与多态中的虚函数没有直接关系

虚继承:在菱形继承的腰部继承父类时,加上virtual 关键字修饰被继承的父

 

 

7.2虚拟继承解决数据冗余和二义性的原理

为了研究虚拟继承原理,我们给出了一个简化的菱形继承继承体系,再借助内存窗口观察对象成 员的模型。  

下图是菱形继承的内存对象成员模型:这里可以看到数据冗余 

下图是菱形虚拟继承的内存对象成员模型:

这里可以分析出D对象中将A放到的了对象组成的最下面,这个A同时属于B和C

那么B和C如何去找到公共的A呢?

这里是通过了B和C的两个指针,指向的一张表。

这两个指针叫虚基表指针,这两个表叫虚基表

虚基表中存的偏移量。通过偏移量可以找到下面的A。

此时无论这个 冗余 的数据存储在何处,都能通过 基地址 + 偏移量 的方式进行访问

7.3总结 

虚继承底层是如何解决菱形继承问题的?

对于冗余的数据位,改存指针,该指针指向相对距离

对于冗余的成员,合并为一个,放置后面,假设想使用公共的成员(冗余成员),可以通过相对距离(偏移量)进行访问这样就解决了数据几余和二义性问题

为何在冗余处存指针?

指针指向空间有预留一个位置,可以用于多态

因此虚继承用的是第二个位置

虚函数是否会造成空间浪费?
不会,指针大小固定为 4/8 字节

指针所指向的空间(虚基表)是否浪费空间?

可以忽略不计,所有对象共享

新建对象进行兼容赋值时,对象指向指针处,该指针(偏移量)指向的目标位置不定,无论最终位置在何处,最终汇编指令都一样(得益于偏移量的设计模式)

假设存在多个共享成员,需要新增指针(偏移量),因为这些成员都是连续的,找到第一个,即可找到其他,即使涉及内存对齐问题,编译器也会根据规则做出调整

额外消耗:
1.空间开销!

1.虚基类指针:每个含有虚基类的对象都需要额外的空间来存储指向虚基类表的指针。
2.虚基类表:需要额外的存储空间来维护每个对象的虚基类偏移信息。

2. 时间开销:
1.访问调整:每次访问虚基类的成员时,都需要进行指针调整,这增加了访问时间

2.构造和析构:在构造和析构过程中,需要确保虛基类部分只被初始化和清理一次,这可能导致更复杂的构造函数和析构函数调用序列,从而增加时间开销。

3.复杂性开销:

虚拟继承增加了编译器实现的复杂性,可能导致生成的代码更加复杂,这可能会间接影响程序的性能。

总之,虚拟继承虽然解决了萎形继承问题,但其机制带来的额外空间和时间开销,以及在复杂性和性能上的潜在影响,使得程序员在考虑
使用虚拟继承时需要权衡其利弊。在设计继承体系时,如果可以避免,通常推荐不使用虚拟继承

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

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

相关文章

006——队列

队列&#xff1a; 一种受限的线性表&#xff08;线性逻辑结构&#xff09;&#xff0c;只允许在一段进行添加操作&#xff0c;在另一端只允许进行删除操作&#xff0c;中间位置不可操作&#xff0c;入队的一端被称为队尾&#xff0c;出队的一端被称为队头&#xff0c;在而我们…

iOS 中 KVC 与 KVO 底层原理

KVC 本质&#xff1a; [object setValue: forKey:];即使没有在.h 文件中有property 的属性声明&#xff0c;setValue:forKey依然会按照上图流程执行代码 KVC 如果成功改变了成员变量&#xff0c;是一定可以被 KVO 监听到成员变量的前后改变的 KVO runtime会生成中间类&…

Leetcode 378. 有序矩阵中第 K 小的元素

1.题目基本信息 1.1.题目描述 给你一个 n x n 矩阵 matrix &#xff0c;其中每行和每列元素均按升序排序&#xff0c;找到矩阵中第 k 小的元素。 请注意&#xff0c;它是 排序后 的第 k 小元素&#xff0c;而不是第 k 个 不同 的元素。 你必须找到一个内存复杂度优于 O(n^2…

GPT1-GPT3论文理解

GPT&#xff11;&#xff0d;GPT&#xff13;论文理解 视频参考&#xff1a;https://www.bilibili.com/video/BV1AF411b7xQ/?spm_id_from333.788&vd_sourcecdb0bc0dda1dccea0b8dc91485ef3e74 1 历史 2017.6 Transformer 2018.6 GPT 2018.10 BERT 2019.2 GPT-2 2020…

ER论文阅读-Decoupled Multimodal Distilling for Emotion Recognition

基本介绍&#xff1a;CVPR, 2023, CCF-A 原文链接&#xff1a;https://openaccess.thecvf.com/content/CVPR2023/papers/Li_Decoupled_Multimodal_Distilling_for_Emotion_Recognition_CVPR_2023_paper.pdf Abstract 多模态情感识别&#xff08;MER&#xff09;旨在通过语言、…

闯关leetcode——67. Add Binary

大纲 题目地址内容 解题代码地址 题目 地址 https://leetcode.com/problems/add-binary/description/ 内容 Given two binary strings a and b, return their sum as a binary string. Example 1: Input: a “11”, b “1” Output: “100” Example 2: Input: a “101…

【LeetCode:116. 填充每个节点的下一个右侧节点指针 + BFS(层次遍历)】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

MFC - 常用基础控件

前言 各位师傅大家好&#xff0c;我是qmx_07&#xff0c;今天给大家讲解MFC中的基础控件 基础控件 单选按钮 绘图准备: 调整窗口大小&#xff0c;设置 radio button 单选按钮button 按钮 设置单选按钮变量分别为 m_BN1、 m_BN2、m_BN3 void CMFCApplication3Dlg::OnBnC…

【笔记】机器学习算法在异常网络流量监测中的应用

先从一些相对简单的综述类看起&#xff0c;顺便学学怎么写摘要相关工作的&#xff0c;边译边学 机器学习算法在异常网络流量监测中的应用 原文&#xff1a;Detecting Network Anomalies in NetFlow Traffic with Machine Learning Algorithms Authors: Quc Vo, Philippe Ea, Os…

C++入门——类的默认成员函数(构造函数)

文章目录 前言一、构造函数二、栈的构造函数总结 前言 ⼀个类&#xff0c;我们不写的情况下编译器会默认⽣成以下6个默认成员函数 默认成员函数很重要&#xff0c;也⽐较复杂&#xff0c;我们要从两个⽅⾯去学习&#xff1a; 第⼀&#xff1a;我们不写时&#xff0c;编译器默认…

Spring后端直接用枚举类接收参数,自定义通用枚举类反序列化器

在使用枚举类做参数时&#xff0c;一般会让前端传数字&#xff0c;后端将数字转为枚举类&#xff0c;当枚举类很多时&#xff0c;很可能不知道这个code该对应哪个枚举类。能不能后端直接使用枚举类接收参数呢&#xff0c;可以&#xff0c;但是受限。 Spring反序列默认使用的是J…

如何用Shell命令结合 正则表达式 统计文本中的ip地址数量

文章目录 简介问题回答 简介 IP 地址&#xff08;Internet Protocol Address&#xff09;是互联网协议地址的简称&#xff0c;是互联网上为联网的设备&#xff08;如计算机、服务器、路由器、手机等&#xff09;分配的唯一标识符。IP 地址的主要功能是实现不同网络设备之间的通…

[Python]一、Python基础编程(2)

F:\BaiduNetdiskDownload\2023人工智能开发学习路线图\1、人工智能开发入门\1、零基础Python编程 1. 文件操作 把⼀些内容 ( 数据 )存储存放起来,可以让程序下⼀次执⾏的时候直接使⽤,⽽不必重新制作⼀份,省时省⼒ 。 1.1 文件的基本操作 1. 打开文件 2. 读写操作 3. 关闭…

hive-拉链表

目录 拉链表概述缓慢变化维拉链表定义 拉链表的实现常规拉链表历史数据每日新增数据历史数据与新增数据的合并 分区拉链表 拉链表概述 缓慢变化维 通常我们用一张维度表来维护维度信息&#xff0c;比如用户手机号码信息。然而随着时间的变化&#xff0c;某些用户信息会发生改…

【软件工程】需求分析概念

一、定义 二、为什么要进行需求分析&#xff1f; 三、需求分析任务 四、与用户沟通获取需求的方法 五、分析建模 六、软件需求规格说明 例题 选择题

【题解】【枚举,数学】——小 Y 拼木棒

【题解】【枚举&#xff0c;数学】——小 Y 拼木棒 小 Y 拼木棒题目背景题目描述输入格式输出格式输入输出样例输入 #1输出 #1 提示数据规模与约定 1.题意简述2.思路解析3.AC代码 前置知识&#xff1a;排列组合&#xff0c;暴力枚举基础知识。 小 Y 拼木棒 通往洛谷的传送门 …

基于SpringBoot+Vue+MySQL的医院信息管理系统

系统展示 用户前台界面 管理员后台界面 系统背景 在当今社会&#xff0c;随着医疗服务需求的不断增长和医疗信息化的快速发展&#xff0c;提升医院管理效率和服务质量成为了医疗行业的核心需求。传统的医院管理模式面临着效率低下、资源分配不均、患者就医体验差等问题。为了应…

图像处理基础知识点简记

简单记录一下图像处理的基础知识点 一、取样 1、释义 图像的取样就是图像在空间上的离散化处理,即使空间上连续变化的图像离散化, 决定了图像的空间分辨率。 2、过程 简单描述一下图象取样的基本过程,首先用一个网格把待处理的图像覆盖,然后把每一小格上模拟图像的各个…

一种求解无人机三维路径规划的高维多目标优化算法,MATLAB代码

在无人机三维路径规划的研究领域&#xff0c;高维多目标优化算法是一个重要的研究方向。这种算法能够同时考虑多个目标&#xff0c;如航迹距离、威胁代价、能耗代价以及多无人机协同性能等&#xff0c;以实现无人机路径的最优规划。 无人机路径规划算法的研究进展表明&#xf…

中国最厉害的改名大师,颜廷利教授的名字来自于国学易经元亨利贞

颜廷利教授&#xff0c;一位源自齐鲁大地山东济南的世界级文化名人&#xff0c;他的名字背后承载着深厚的家族易学传统。在颜廷利教授的童年记忆中&#xff0c;家族长辈常以《易经》中频繁出现的“元、亨、利、贞”四字&#xff0c;寓意四季之变换&#xff0c;将这四个字分别对…