一文精通C++ -- 继承

news2024/11/14 23:05:59

       前言:继承是C++类和对象三大特性中关键的一环,上承封装,下接多态,C++中的继承是一种面向对象编程的概念,它允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法。子类可以使用父类的公共成员函数和变量,也可以重写父类的方法或添加新的方法和变量。继承可以帮助我们重用代码并提高代码的可维护性和可扩展性。相信大部分人都对继承不甚了解,那么,下面,我们就一起来进一步学习继承的相关知识~~

1.继承访问关系

       我们知道,派生类有三种继承关系,而基类中通常也具有三种方式的访问限定符,我们需要理清楚在不同的进程关系下派生类继承基类的成员访问,具体如下图,

总结

1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能访问它

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

3. 基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 = min(成员在基类的访问限定符,继承方式),public > protected > private。

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

5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

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

       派生类对象可以赋值给 基类的对象 / 基类的指针 / 基类的引用,这种赋值和一般的自定义对象的赋值是不同的,因为父类和子类之间是is-a的关系,我们认为在赋值时中间不会产生临时对象。这里有个形象的说法叫切片 或者切割。寓意把派生类中父类那部分切来赋值过去。基类对象不能赋值给派生类对象。基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(Run-Time Type Information)的dynamic_cast 来进行识别后进行安全转换。(这个地方我们在后续多态的还会继续深入,这里先了解一下)。

3.继承中的作用域

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

2. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏(子类同名成员默认情况下隐藏了父类的同名成员)

3. 注意在实际中在继承体系里面最好不要定义同名的成员。

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

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

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

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

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

4. 派生类的析构函数会在被调用完成后自动(这个是默认的,我们不用声明)先调用基类的析构函数清理基类成员,在按自己的析构执行。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。

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

5.继承与(友元&&静态成员)

       友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员,通俗的说,父亲的朋友并不能碰你的私房钱,你说是吧~ ,这个比较简单,就不再详述,我们来看一下静态成员,基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。这一点和继承普通的父类成员函数比较相似,我们子类只是继承父类成员函数的使用权。

6.菱形继承

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

菱形继承有数据冗余和二义性的问题,在上面的结构图中,可以看出,类Assistant中有两份person成员,虽然二义性问题可以利用类的作用域来区分,不会构成严重的错误,但是数据冗余问题是无法解决的。

菱形虚继承

         虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和 Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方使用。但是实际中一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题。

      那到这里有的人可能就会有疑问了?为什么子类对象d的两个类B和C的成员不直接在自己的第一个4字节处保存A成员存储的地址或者偏移量,而要不嫌麻烦的还要通过一层地址指出去,再到外面寻找偏移量呢?

      事实上,对于类B和C来说,当他们采用了虚继承的时候,在每个派生类中会有一个虚基类指针(占四个字节),和虚基类表(不占空间),虚基类指针指向派生类对象中的虚基类成员对象,当虚继承的派生类被当做基类再次虚继承,虚基类指针也会被继承。(因此每个派生类既可以通过虚基类指针,查找虚基类表找到其中的成员)。具体细节我们需要配合多态进行深入探究,后续也会在多态的章节重点讲解。

7.继承vs组合

        public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。

优先使用组合而不是继承

      继承和组合各有优缺点。类继承是在编译时刻静态定义的,且可直接使用,因为程序设计语言直接支持类继承。类继承可以较方便地改变被复用的实现。当一个子类重定义一些而不是全部操作时,它也能影响它所继承的操作,只要在这些操作中调用了被重定义的操作。

        但是类继承也有一些不足之处。首先,因为继承在编译时刻就定义了,所以无法在运行时刻改变从父类继承的实现。更糟的是,父类通常至少定义了部分子类的具体表示。因为继承对子类揭示了其父类的实现细节,所以继承常被认为“破坏了封装性” 。子类中的实现与它的父类有如此紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。当你需要复用子类时,实现上的依赖性就会产生一些问题。如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。一个可用的解决方法就是只继承抽象类,因为抽象类通常提供较少的实现。

        对象组合是通过获得对其他对象的引用而在运行时刻动态定义的。组合要求对象遵守彼此的接口约定,进而要求更仔细地定义接口,而这些接口并不妨碍你将一个对象和其他对象一起使用。这还会产生良好的结果:因为对象只能通过接口访问,所以我们并不破坏封装性(就像父类的保护成员在子类中不能使用);只要类型一致,运行时刻还可以用一个对象来替代另一个对象;更进一步,因为对象的实现是基于接口写的,所以实现上存在较少的依赖关系。

       继承允许根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。

       对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用,因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。 组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。

       实际尽量多去用组合。组合的耦合度低(耦合其实就可以简单的理解为两个对象之间依赖关系的大小,软件工程中,设计工程项目时,一般耦合程度越低越好),代码维护性好。不过继承也有用武之地的,有些关系就适合继承就用继承,另外要实现多态,也必须要继承

本来想和多态一块写的,但是那样篇幅就太大了,所以,多态就下一篇再出叭~

       村上春树说:挫折不会主动说话,却常在暗中帮助你成长,昨日的你承受的有多深,来日的你荣耀就有多高远,扛得住涅槃之痛,才能配得上重生之美。

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

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

相关文章

Java常见限流方式

Java常见限流方式 1、计数限流2、固定窗口限流3、滑动窗口限流4、漏桶算法5、令牌桶算法 1、计数限流 例如系统能同时处理 100 个请求,保存一个计数器,处理了一个请求,计数器就加一,一个请求处理完毕之后计数器减一。 每次请求来…

学校档案管理系统软件-学校数字档案室解决方案

学校档案管理系统软件是一个用于存储和管理学校重要文档和资料的软件系统,该软件通常包括档案录入、查询、统计、备份等模块,它通过电子化记录、分类和整理学校档案资料,实现了学校档案的高效管理和利用。 专久智能学校数字档案室解决方案需要…

N1中openwrt实现不插网线就能上网,通过wifi连接路由器

环境说明: 路由器:N1盒子 系统版本:openwrt 前言 既然想用这个功能,基本上就是没有网线连接盒子了,不仅限于N1盒子只要是openwrt系统就可以 创建接口 在openwrt界面上依次点击“网络”->“无线”,第一…

正点原子嵌入式linux驱动开发——RGB转HDMI

目前大多数的显示器都提供了HDMI接口,HDMI的应用范围也越来越广,但是STM32MP157这颗芯片原生并不支持HDMI显示。可以通过RGB转HDMI芯片将RGB信号转为HDMI信号,这样就可以连接HDMI显示器了。本章就来学习一下如何在正点原子的STM32MP1开发板上…

听说嵌入式门槛很高,值不值得学习?工资怎么样?

今日话题,嵌入式门槛很高,值不值得学习呢?关于嵌入式领域的门槛和薪酬,我有着五年的实际经验,我的看法是嵌入式岗位的门槛一般来说并不太高。只需掌握一些C语言编程和单片机知识,制作一个简单的电子产品就可…

会声会影2024 无损激活解锁完整旗舰版大师套装

会声会影2024 无损激活解锁完整旗舰版大师套装是功能非常强大的视频编辑软件,非常专业的使用效果,会声会影2024中文版可以针对剪辑电影进行使用,非常强大的色彩校正方式,无论什么光线下进行拍摄,都可以通过后期进行调整…

LinkedHashMap 源码解析

目录 一. 前言 二. 源码解析 2.1. 类结构 2.2. 成员变量 2.3. 构造方法 2.4. accessOrder 2.5. 添加元素 2.6. 获取元素 2.7. 删除元素 2.8. 迭代器 三. LRU简单实现 一. 前言 HashMap元素插入是无序的,为了让遍历顺序和插入顺序一致,我们可以…

海外网红评论:激发更高转化率的关键

随着社交媒体的兴起,海外网红营销已经成为企业推广产品和服务的一种重要方式。然而,在众多的海外网红营销策略中,网红评论的重要性往往被低估。事实上,海外网红评论可以在提高转化率方面发挥关键作用。本文Nox聚星将和大家深入探讨…

linux中好玩的数据流定向和管道命令一

知识点复习: 什么是数据流定向,个人理解就是将 一些结果信息不打印在屏幕上,而是定位在某一个文件里面 ll /wdf > file 会覆盖file的原内容 ll /wdf >> 会追加到原文件后面 比如在自己的目录新建1.TXT, 2.txt ll /…

制药企业计算机化系统验证(CSV)的重要性

上次我们介绍了>>《制药机械(设备)计算机化系统验证指南》,本期我们就来展开讲讲制药企业为什么需要以及如何进行计算机化系统验证。 计算机化系统验证(Computerized Systems Validation,简称CSV)是一种确保计算机化系统在药…

Java 新手如何使用Spring MVC 中的查询字符串和查询参数?

文章目录 什么是查询字符串和查询参数?步骤 1:步骤 2:步骤 3:步骤 4: 结论 🎉欢迎来到架构设计专栏~探索Java中的静态变量与实例变量 ☆* o(≧▽≦)o *☆嗨~我是IT陈寒🍹✨博客主页:IT陈寒的博客🎈该系列文章专栏&…

利用Jmeter对WebRTC应用进行压力测试

说明:WebRTC是一款开源的多人即时视频API,与一般的http请求不同,webrtc应用实际压力主要是码流 最近负责了一个WebRTC的视频会议性能测试,也蛮有意思的,因此将压测方案、思路记录下来 一、测试思路 1、WebRTC多人会…

安科瑞余压监控系统

安科瑞 崔丽洁 机械加压送风系统中为什么要设计旁通阀控制加压送风的正压值?火灾发生后,又能起到什么作用呢? 发生火灾时,绝大多数的人员伤亡不是因为火,而是烟气,随着可燃物的燃烧产生大量的高温烟气&…

【数据结构与算法】二叉树的知识讲解

目录 一,二叉树的结构深入认识 二,二叉树的遍历 三,二叉树的基本运算 3-1,计算二叉树的大小 3-2,统计二叉树叶子结点个数 3-3,计算第k层的节点个数 3-4,查找指定值的结点 一,二叉…

栈(Stack)的概念+MyStack的实现+栈的应用

文章目录 栈(Stack)一、 栈的概念1.栈的方法2.源码分析 二、MyStack的实现1.MyStack的成员变量2.push方法3.isEmpty方法和pop方法4.peek方法 三、栈的应用1.将递归转化为循环1.调用递归打印2.通过栈逆序打印链表 栈(Stack) 一、 栈…

【Javascript】数组的进阶操作

目录 splice 截取部分元素,保留剩下元素 清空数组 join 自定义分割符 concat 连接 a连接b b连接a a连接b,c 不会改变原数组 splice ⽤于删除或替换元素函数有返回值,返回的是被删除的元素这个⽅法会改变原来的数组 截取部分元素&#xff0…

黑金测评:电视盒子哪款好?双十一热销电视盒子排行榜

大家好,本期我们要分享的测评内容是关于电视盒子,双十一很多网友打算购入电视盒子,但并不了解电视盒子哪款好,本期我们自费测评了最热门的十款电视盒子,最终筛选出了五款最值得入手的电视盒子整理了这份电视盒子排行榜…

rust学习——智能指针

智能指针 在各个编程语言中,指针的概念几乎都是相同的:指针是一个包含了内存地址的变量,该内存地址引用或者指向了另外的数据。 在 Rust 中,最常见的指针类型是引用,引用通过 & 符号表示。不同于其它语言&#xf…

HCIA数据通信——基础设备配置

想了想,为了方便回顾复习,将理论和实践结合起来才是正确的,不然一边理论,又单独做实验这样不方便。 因此之前的文章都删了,还是以华为从头开始吧!实验与理论应用结合起来做。 一,查看设备信息 …

大数据Flink(一百零二):SQL 聚合函数(Aggregate Function)

文章目录 SQL 聚合函数(Aggregate Function) SQL 聚合函数(Aggregate Function) Python UDAF,即 Python AggregateFunction。Python UDAF 用来针对一组数据进行聚合运算,比如同一个 window 下的多条数据、或者同一个 key 下的多条数据等。针对同一组输入数据,Python A…