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.复杂性开销:
虚拟继承增加了编译器实现的复杂性,可能导致生成的代码更加复杂,这可能会间接影响程序的性能。
总之,虚拟继承虽然解决了萎形继承问题,但其机制带来的额外空间和时间开销,以及在复杂性和性能上的潜在影响,使得程序员在考虑
使用虚拟继承时需要权衡其利弊。在设计继承体系时,如果可以避免,通常推荐不使用虚拟继承
相关文章:

c++进阶学习-----继承
1.继承的概念及定义 1.1继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。 继承呈现了面向对象 程序设计的…...
C++学习笔记(37)
302、makefile 在实际开发中,项目的源代码文件比较多,按类型、功能、模块分别存放在不同的目录和文件中,哪 些文件需要先编译,那些文件后编译,那些文件需要重新编译,还有更多更复杂的操作。 make 是一个强大…...

Redis发布和订阅
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者(sub) 接收消息 可以实现进程间的消息传递。这种模式非常适用于实时消息传递、事件通知和消息分发等场景 Redis可以实现消息中间件MQ的功能,通过发布订阅实现消息…...

计算机毕设设计推荐-基于python+Djanog大数据的电影数据可视化分析
精彩专栏推荐订阅:在下方主页👇🏻👇🏻👇🏻👇🏻 💖🔥作者主页:计算机毕设木哥🔥 💖 文章目录 一、电影数据可视…...

dhtmlxGantt 甘特图 一行展示多条任务类型
效果如图: 后台拿到数据 处理之后如图: 含义: 如上图所示, 如果一行需要展示多个 需要给父数据的那条添加render:split属性, 子数据的parent为父数据的Id即可 切记 父数据的id 别为0 为0 时 会出现错乱 因为有些小伙伴提出分段展示的数据结构还是有点问题,下面展示一个完整…...

COLORmap
在这段MATLAB代码中,surf(peaks)、map的定义以及colormap(map)的调用共同完成了以下任务: 1. **绘制曲面图**: - surf(peaks):这个函数调用了MATLAB内置的peaks函数来生成数据,并使用surf函数将这些数据绘制成一个…...

手机在网状态查询接口如何用Java进行调用?
一、什么是手机在网状态查询接口? 手机在网状态查询接口,又叫运营商在网状态查询,手机号在网状态查询,传入手机号码,查询该手机号的在网状态,返回内容有正常使用、停机、在网但不可用、不在网(…...
mysql性能优化- 数据库配置优化
MySQL 性能优化 - 数据库配置优化 MySQL 是一个广泛使用的关系型数据库管理系统,但随着数据量的增长和访问频率的提高,其性能可能会成为瓶颈。为了保持高效的性能,除了应用层的查询优化和索引优化之外,数据库配置优化 也是非常重…...
(算法)大数的进制转换
题目描述 将一个长度最多为30位数字的十进制非负整数转换为二进制数输出输入描述: 多组数据,每行为一个长度不超过30位的十进制非负整数。 (注意是10进制数字的个数可能有30个,而非30bits的整数)解析 例子 :123&…...

演示jvm锁存在的问题
文章目录 1、AlbumInfoApiController --》testLock()2、redis添加键值对3、AlbumInfoServiceImpl --》testLock() 没有加锁4、使用ab工具测试4.1、安装 ab 工具4.2、查看 redis 中的值 5、添加本地锁 synchronized6、集群情况下问题演示 jvm锁:synchronized lock 只…...
Android SharedPreference详解
Android SharedPreference详解 SharedPreferences作为一种数据持久化的方式,是处理简单的key-value类型数据时的首选。 一般用法: //demo是该sharedpreference对应文件名,对应的是一个xml文件,里面存放key-value格式的数据. SharedPreferences sharedPreferences…...
论文阅读 | 可证安全隐写(网络空间安全科学学报 2023)
可证安全隐写:理论、应用与展望 一、什么是可证安全隐写? 对于经验安全的隐写算法,即使其算法设计得相当周密,隐写分析者(攻击者)在观察了足够数量的载密(含有隐写信息的数据)和载体…...

Arthas jvm(查看当前JVM的信息)
文章目录 二、命令列表2.1 jvm相关命令2.1.3 jvm(查看当前JVM的信息) 二、命令列表 2.1 jvm相关命令 2.1.3 jvm(查看当前JVM的信息) 基础语法: jvm [arthas18139]$ jvmRUNTIME …...
【c++】介绍
C是一种强大而灵活的编程语言,广泛用于开发各种应用程序和系统软件。它结合了C语言的高效性和面向对象编程的特性,为程序员提供了丰富的工具和功能,以满足各种编程需求。 C的历史可以追溯到上世纪80年代,最初由丹尼斯里奇和贝尔实…...
JavaScript typeof与instanceof的区别
typeof 和 instanceof 都是 JavaScript 中的运算符,用于检查数据类型或对象的类型。它们有不同的用途和适用场景: 1. typeof 作用:返回变量的数据类型,适用于原始数据类型(如 number、string、boolean 等)…...

C++11 可变的模板参数
前言 本期我们接着继续介绍C11的新特性,本期我们介绍的这个新特性是很多人都感觉抽象的语法!它就是可变的模板参数! 目录 前言 一、可变的模板参数 1.1可变的参数列表 1.2可变的参数包 1.3可变参数包的解析 • 递归展开解析 • 逗号…...

手机在网状态查询接口如何用PHP进行调用?
一、什么是手机在网状态查询接口? 手机在网状态查询接口,即输入手机号码查询手机号在网状态,返回有正常使用、停机、在网但不可用、不在网(销号/未启用/异常)、预销户等多种状态。 二、手机在网状态查询适用哪些场景…...

MATLAB中多张fig图合并为一个图
将下列两个图和为一个图 打开查看-----绘图浏览器 点击第一幅图中曲线右键复制,到第二幅图中粘贴即可完成...

Java启动Tomcat: Can‘t load IA 32-bit .dll on a AMD 64-bit platform报错问题解决
🎬 鸽芷咕:个人主页 🔥 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活! 专栏介绍 在软件开发和日常使用中,BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…...

基于微信小程序的家教信息管理系统的设计与实现(论文+源码)_kaic
摘 要 随着互联网时代的来临,使得传统的家教模式已不复存在,亟需一种方便、快捷的在线教学平台。因此,利用Java语言作为支撑和MySQL数据库存储数据,结合微信小程序的便利性,为用户开发出了一个更加人性化、方便的家庭…...

接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...

1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...

srs linux
下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935,SRS管理页面端口是8080,可…...

高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...

【网络安全】开源系统getshell漏洞挖掘
审计过程: 在入口文件admin/index.php中: 用户可以通过m,c,a等参数控制加载的文件和方法,在app/system/entrance.php中存在重点代码: 当M_TYPE system并且M_MODULE include时,会设置常量PATH_OWN_FILE为PATH_APP.M_T…...
Java多线程实现之Runnable接口深度解析
Java多线程实现之Runnable接口深度解析 一、Runnable接口概述1.1 接口定义1.2 与Thread类的关系1.3 使用Runnable接口的优势 二、Runnable接口的基本实现方式2.1 传统方式实现Runnable接口2.2 使用匿名内部类实现Runnable接口2.3 使用Lambda表达式实现Runnable接口 三、Runnabl…...
用js实现常见排序算法
以下是几种常见排序算法的 JS实现,包括选择排序、冒泡排序、插入排序、快速排序和归并排序,以及每种算法的特点和复杂度分析 1. 选择排序(Selection Sort) 核心思想:每次从未排序部分选择最小元素,与未排…...

aurora与pcie的数据高速传输
设备:zynq7100; 开发环境:window; vivado版本:2021.1; 引言 之前在前面两章已经介绍了aurora读写DDR,xdma读写ddr实验。这次我们做一个大工程,pc通过pcie传输给fpga,fpga再通过aur…...
DriveGPT4: Interpretable End-to-end Autonomous Driving via Large Language Model
一、研究背景与创新点 (一)现有方法的局限性 当前智驾系统面临两大核心挑战:一是长尾问题,即系统在遇到新场景时可能失效,例如突发交通状况或非常规道路环境;二是可解释性问题,传统方法无法解释智驾系统的决策过程,用户难以理解车辆行为的依据。传统语言模型(如 BERT…...