【C++】C++面向对象编程三大特性之一——继承

❤️前言

        本篇博客主要是关于C++面向对象编程中的三大特性之一的继承,希望大家能和我一起共同学习进步!

正文

        我们刚刚学习一块全新的知识,首先简单关注一下它的概念和简单的使用方法。

继承的概念及定义

继承的概念

        继承的概念:继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用

        继承是类设计层次的复用,让我们在一个类的基础上拓展出许多不同的新类,它们之间的关系大概是新类即包含原类的特性,也有自己独特的特性,例如狗类可以拓展出卷毛狗类、直毛狗类等派生类。这样的拓展就是继承,对于派生类来说,我们就是复用了它的基础类的代码。

继承的定义

        了解了继承的概念之后,我们继续来看继承的定义方式,下面以人类和学生类做演示:

//     派生类   继承方式  基类
class Student : public  Person
{
public:int _stuid; // 学号int _major; // 专业
};

        上面的代码就是继承的定义方式,其中的人类Person被称作基类或者父类,学生类被称作派生类或者子类。

        继承方式和我们的访问限定符共用关键字,public 是公有继承,protected 是保护继承,private 是私有继承。

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

        当子类使用不同的继承方式继承了基类的成员之后,子类访问父类成员的情况(也就是父类成员在子类这里的访问权限相当于子类的哪种成员)如下表:

        我们在学校学习继承相关知识的时候,老师一定会给我们一个类似的表让我们去记,但是其实我们一般只需要记住 public 继承即可,这是因为 private 继承和 protected 继承在现实中几乎不太会使用。而且由于 protetced/private 继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强,也不太推荐使用它们。

        这里有一个偏门的小知识点:其实在定义继承时,使用关键字class定义类默认的继承方式private,使用struct时默认的继承方式是public,但是我们可以看到平时我们都是显式写出继承方式的,默认的方式并不被推荐。

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

        通过上面的一些知识的学习,我们可以发现:当我们定义一个继承关系之后,如果我们有一个子类对象,其实可以将父类的内容看做是子类对象的一部分,那么我们就可以看看父子类之间是否能进行赋值转换。

        在尝试之后我们可以发现,派生类对象可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。大概情景如下:

        子类可以赋值给父类,但是父类却不能赋值给子类,想想也十分的合理,父类中并没有子类特有的某些信息,那么凭什么赋值给子类呢?

        测试代码如下:

class Person
{
protected:string _name; // 姓名string _sex; // 性别int _age; // 年龄
};class Student : public Person
{
public:int _No; // 学号
};int main()
{Student sobj;// 1.子类对象可以赋值给父类对象 / 指针 / 引用Person pobj = sobj;Person* pp = &sobj;Person& rp = sobj;//2.基类对象不能赋值给派生类对象// 错误 sobj = pobj;return 0;
}

继承中的作用域

        现在我们继续来看继承中的作用域相关知识:

  1. 在继承体系中基类和派生类都有独立的作用域。
  2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用基类::基类成员显式访问)
  3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
  4. 注意在实际中在继承体系里面最好不要定义同名的成员。

测试代码:

class Person
{
public:void func(int x){cout << "P::func" << endl;}
protected:string _name; // 姓名string _sex; // 性别int _age; // 年龄
};class Student : public Person
{
public:void func(){cout << "S::func" << endl;}int _No; // 学号
};int main()
{Student stu;stu.func();return 0;
}

派生类的默认成员函数

        派生类继承了基类的特征,那么在派生类中的默认成员函数与普通的类会有什么不同呢?

        派生类的默认成员函数需要我们记住的点大概有这些:

  1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显式调用。
  2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
  3. 派生类的operator=()必须要调用基类的operator=()完成基类的复制。
  4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
  5. 派生类对象初始化先调用基类构造再调派生类构造。
  6. 派生类对象析构清理先调用派生类析构再调基类的析构。
  7. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同(这个我们后面会学到)。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。

        关于派生类和基类的构造和析构函数,它们的调用情况大概如下图:

        这里构造和析构的顺序对应着栈空间的先进后出,我们可以根据这个进行记忆,除此之外,还有一些原因就是派生类的一些构造析构行为可能会用到基类的数据。

继承与友元

        友元在继承中的特点就是:友元关系不能被继承,形象的比喻就是:父亲的朋友一开始不一定是我的朋友,他必须自己和我们确认朋友关系才能成为朋友。

继承与静态成员

        基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。

        这也可以做比喻,人数是人类的静态成员,小孩是人类的子类,小孩增加了难道人数会为这个小孩重新计数吗?小孩也是人类中的一种,具有人类共同的一些特征,这些特征可以对应静态成员。

        也可以说,派生类只继承了静态成员的使用权,但是并不会增加静态成员的实例。

测试代码:

class Person
{
public:Person() { ++_count; }
protected:string _name; // 姓名
public:static int _count; // 统计人的个数。
};
int Person::_count = 0;class Student : public Person
{
protected:int _stuNum; // 学号
};class Graduate : public Student
{
protected:string _seminarCourse; // 研究科目
};void TestPerson()
{Student s1;Student s2;Student s3;Graduate s4;cout << " 人数 :" << Person::_count << endl;Student::_count = 0;cout << " 人数 :" << Person::_count << endl;
}int main()
{TestPerson();return 0;
}

 复杂的菱形继承和菱形虚拟继承

        继承的种类可以由派生类继承基类的数量分为单继承和多继承。

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

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

        多继承的定义只需要在原有的单继承关系之后加上逗号和新的继承关系即可。

        而当我们使用多继承的时候,就会引出一个比较复杂的问题,也就是菱形继承问题:

        菱形继承似乎在现实中确实会有应用场景,但是它会引发一些问题,当我们使用如上的菱形继承关系,那么在Assistant类中似乎就会存在两个Person类,也就是说他可能具有两份人的特征,这会造成数据冗余和二义性的问题,更详细的我们可以通过如下的对象成员模型进行分析:

        那我们是否有方法去解决这样的问题呢?这时候我们就可以使用虚拟继承,虚拟继承可以很好地解决菱形继承所带来的数据冗余和二义性的问题,但需要注意的是,虚拟继承也只在菱形继承中有用,其他地方不建议使用。

        上述关系的菱形虚拟继承的代码如下:

class Person
{
public:string _name; // 姓名
};// 要使用菱形虚拟继承需要在中间位置的继承方式前加上 virtual 关键字class Student : virtual public Person
{
protected:int _num; //学号
};class Teacher : virtual public Person
{
protected:int _id; // 职工编号
};class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修课程
};void Test()
{Assistant a;Teacher& t = a;Student& s = a;a._name = "Joker";t._name = "Peter";s._name = "张三";}int main()
{Test();return 0;
}

        调试这段代码,我们可以发现通过菱形虚拟继承,Assistant对象中最终只含有一个Person类的数据,成功的解决了数据冗余和二义性的问题。

        那么虚拟继承是如何解决这个问题的呢?我们现在来了解菱形虚拟继承的底层原理。

菱形虚拟继承的底层原理

        下面是菱形虚拟继承的测试代码,我们可以通过调试下面的代码来研究底层原理。

class A
{
public:int _a;
};// class B : public A
class B : virtual public A
{
public:int _b;
};// class C : public A
class C : virtual public A
{
public:int _c;
};class D : public B, public C
{
public:int _d;
};int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}

        我们调试上面的代码时需要结合d对象的内存来看,这样可以更清晰的得到对象的存储模型,具体的内存分布如下图:

        我们可以看见d对象的b部分和c部分除了他们本身的数据成员以外还多出了一份数据,那么这个数据代表着什么意思呢?我们可以看到这个数据得大小是四个字节,而且有点不规整,那么很有可能就是一个指针,也确实猜对了,这个数据就是一个指针。它的名字叫做虚基表指针,指向一个叫做虚基表的东西,那么现在让我们看看这两个指针指向的虚基表:

        这两个虚基表中分别存了两个有效数据从b部分到c部分为20和12,它们的意思分别为这两个部分到a部分的偏移量,单位为字节。通过这样的模型,我们就可以通过保存的偏移量的值和地址来控制公有数据,也就是a部分的数据。

        将上面的内存结构整理成如下的结构示意图:

继承的总结和反思

  1. 很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题。
  2. 多继承可以认为是C++的缺陷之一,很多后来的OO语言都没有多继承,如Java。

继承和组合

        继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。
        因此我们尽量会选择另一种类设计层次复用的方式,也就是组合——对象组合是类继承之的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。

        简单来说,继承关系就是is-a,而组合关系则是has-a,继承中子类是父类的一种,而组合则是一个类是另一个类的成员。实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。

🍀结语

        今天的博客就到此为止啦,希望能对大家有用。

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

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

相关文章

WordPress Page Builder KingComposer 2.9.6 Open Redirection

WordPress Page Builder KingComposer 2.9.6 Open Redirection WordPress 插件 KingComposer 版本2.9.6 以及以前版本受到开放重定向漏洞的影响。该漏洞在packetstorm网站披露于2023年7月24日&#xff0c;除了该漏洞&#xff0c;该版本的插件还存在XSS攻击的漏洞风险 图1.来自…

AI「反腐」,德国马普所结合 NLP 和 DNN 开发抗蚀合金

内容一览&#xff1a;在被不锈钢包围的世界中&#xff0c;我们可能都快忘记了腐蚀的存在。然而&#xff0c;腐蚀存在于生活中的方方面面。无论是锈迹斑斑的钢钉&#xff0c;老化漏液的电线&#xff0c;还是失去光泽的汽车&#xff0c;这一切的发生都与腐蚀有关。据统计&#xf…

通信笔记:RSRP、RSRQ、RSNNR

0 基础概念&#xff1a;RE、RS和RB RE (Resource Element)&#xff1a;资源元素是 LTE 和 5G 网络中的最小物理资源单位。一个资源元素对应于一个子载波的一个符号周期。 RS (Reference Signal)&#xff1a;参考信号是在 LTE 和 5G 网络中用于多种目的的特定类型的信号。它们可…

深入剖析云计算与云服务器ECS:从基础到实践

云计算已经在不断改变着我们的计算方式和业务模式&#xff0c;而云服务器ECS&#xff08;Elastic Compute Service&#xff09;作为云计算的核心组件之一&#xff0c;为我们提供了灵活、可扩展的计算资源。在本篇长文中&#xff0c;我们将从基础开始&#xff0c;深入探讨云计算…

数学建模--三维图像绘制的Python实现

目录 1.绘制三维坐标轴的方法 2.绘制三维函数的样例1 3.绘制三维函数的样例2 4.绘制三维函数的样例3 5.绘制三维函数的样例4 6.绘制三维函数的样例5 1.绘制三维坐标轴的方法 #%% #1.绘制三维坐标轴的方法 from matplotlib import pyplot as plt from mpl_toolkits.mplot3…

【算法系列篇】分冶-快排

文章目录 前言什么是分冶1.颜色分类1.1 题目要求1.2 做题思路1.3 Java代码实现 2. 排序数组2.1 题目要求2.2 做题思路2.3 Java代码实现 3.数组中的第k个最大元素3.1 题目要求3.2 做题思路3.3 Java代码实现 4. 最小的k个数4.1 题目要求4.2 做题思路4.3 Java代码实现 总结 前言 …

Python Tkinter Multiple Windows 教程

一、说明 在这个Python Tkinter教程中&#xff0c;我们将学习如何在Python Tkinter中创建多个窗口&#xff0c;我们还将介绍与多个窗口相关的不同示例。而且&#xff0c;我们将介绍这些主题。 Python Tkinter multiple windows使用多个窗口的 Python Tkinter 用户注册Python Tk…

7 Series FPGAs GTX/GTH Transceivers

目录 1. Overview2. Block Diagram3. Transmitter4. Receiver5. Physical Coding Sublayer&#xff08;PCS&#xff09;6. Physical Medium Attachment&#xff08;PMA&#xff09; 本博客为Xilinx 7系列FPGA的千兆比特高速收发器&#xff08;Gigabit Transceiver, GT&#xff…

【聚类】DBCAN聚类

OPTICS是基于DBSCAN改进的一种密度聚类算法&#xff0c;对参数不敏感。当需要用到基于密度的聚类算法时&#xff0c;可以作为DBSCAN的一种替代的优化方案&#xff0c;以实现更优的效果。 原理 基于密度的聚类算法&#xff08;1&#xff09;——DBSCAN详解_dbscan聚类_root-ca…

跨源资源共享(CORS)Access-Control-Allow-Origin

1、浏览器的同源安全策略 没错&#xff0c;就是这家伙干的&#xff0c;浏览器只允许请求当前域的资源&#xff0c;而对其他域的资源表示不信任。那怎么才算跨域呢&#xff1f; 请求协议http,https的不同域domain的不同端口port的不同 好好好&#xff0c;大概就是这么回事啦&…

【权限提升-Windows提权】-UAC提权之MSF模块和UACME项目-DLL劫持-不带引号服务路径-不安全的服务权限

权限提升基础信息 1、具体有哪些权限需要我们了解掌握的&#xff1f; 后台权限&#xff0c;网站权限&#xff0c;数据库权限&#xff0c;接口权限&#xff0c;系统权限&#xff0c;域控权限等 2、以上常见权限获取方法简要归类说明&#xff1f; 后台权限&#xff1a;SQL注入,数…

伪类与伪元素

伪类与伪元素 伪类和伪元素的引入是因为在文档树里有些信息无法被充分描述&#xff0c;引用标准中的话就是&#xff1a;CSS引入伪类和伪元素的概念是为了实现基于文档树之外的信息的格式化。 文档树又称标准流&#xff0c;是浏览器在渲染显示网页内容时默认采用的一套排版规则…

1780_添加鼠标右键空白打开命令窗功能

全部学习汇总&#xff1a; GitHub - GreyZhang/windows_skills: some skills when using windows system. 经常执行各种脚本&#xff0c;常常需要切换到命令窗口中输入相关的命令。从开始位置打开cmd然后切换目录是个很糟糕的选择&#xff0c;费时费力。其实Windows 7以及Windo…

经管博士科研基础【19】齐次线性方程组

1. 线性方程组 2. 非线性方程组 非线性方程,就是因变量与自变量之间的关系不是线性的关系,这类方程很多,例如平方关系、对数关系、指数关系、三角函数关系等等。求解此类方程往往很难得到精确解,经常需要求近似解问题。相应的求近似解的方法也逐渐得到大家的重视。 3. 线…

vue3 封装千分位分隔符自定义指令

toLocaleString作用&#xff1a;在没有指定区域的基本使用时&#xff0c;返回使用默认的语言环境和默认选项格式化的字符串。可点击进入MDN查看 // 千分位分隔符指令 import { Directive, DirectiveBinding } from vueconst thousandSeparator: Directive {mounted(el: any, …

Win10搭建VisualSvn Server

Win10搭建VisualSvn Server 目录 Win10搭建VisualSvn Server一、下载VisualSvn Server安装包二、安装VisualSvn Server三、配置和使用VisualSVN Server四、添加用户及权限设定方法五、创建目录及配置权限 1、服务端&#xff1a;有集成了Subversion和Apache、安装使用非常简单且…

Redis从基础到进阶篇(三)----架构原理与集群演变

目录 一、缓存淘汰策略 1.1 LRU原理 1.2 案例分析 1.3 Redis缓存淘汰策略 1.3.1 设置最⼤缓存 1.3.2 淘汰策略 二、Redis事务 2.1 Redis事务典型应⽤—Redis乐观锁 2.2 Redis事务介绍 2.3 事务命令 2.3.1 MULTI 2.3.2 EXEC 2.3.3 DISCARD 2.3.4 WATCH 2.3.5 UNW…

Flutter之hydrated_bloc源码分析

Flutter_Bloc是状态管理组件,hydrated_bloc是 Flutter_Bloc的扩展,它可以在APP重启的情况下,自动记录上次APP的状态。android中可以使用SharePreference来实现状态记录,在Flutter之hydrate_bloc组件入门指南一文中已经讲解了其基本用法,本篇博文就不对其原理进行简单分析,…

【GitLab私有仓库】在Linux上用Gitlab搭建自己的私有库并配置cpolar内网穿透

文章目录 前言1. 下载Gitlab2. 安装Gitlab3. 启动Gitlab4. 安装cpolar5. 创建隧道配置访问地址6. 固定GitLab访问地址6.1 保留二级子域名6.2 配置二级子域名 7. 测试访问二级子域名 前言 GitLab 是一个用于仓库管理系统的开源项目&#xff0c;使用Git作为代码管理工具&#xf…

视频汇聚/视频云存储/视频监控管理平台EasyCVR部署后无法正常启用是什么问题?该如何解决?

安防监控/视频监控/视频汇聚平台EasyCVR能在复杂的网络环境中&#xff0c;将分散的各类视频资源进行统一汇聚、整合、集中管理&#xff0c;在视频监控播放上&#xff0c;视频云存储/安防监控汇聚平台EasyCVR支持多种播放协议&#xff0c;包括&#xff1a;HLS、HTTP-FLV、WebSoc…