C++之继承(通俗易懂版)

前言:我们都知道C++是一门支持过程化编程,面向对象的高级语言,既然是面向对象的语言,那么对于对象而言,对象会有很多中相同的属性,举个例子:你和你老师,你们都有着共同的属性和身份,例如学生和老师都有性别,年龄这个成员变量,但你们又有着差异,你们可能学生有学号,老师有工号,老师可能还有职称,所以我们可以想象一下如何我们定义一个关于学生和老师的类,对于老师和学生是不是都有着共同点(都有年龄和性别),也有着不同点,所以如果我们定义老师类之后,又定义一个学生类,学生类是不是和老师类有一些成员变量重复了,可能有老铁认为这几个成员相同没啥太大的影响,但你想想一个工程项目得有多少个文件和多少个类,每一个类都有几个重复的成员变量和成员函数,是不是会导致代码运行效率大大降低了,也会占用更多的内存。为了解决这个问题,C++提出了继承的概念

继承:

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

简单的类的继承

语法格式:class 子类名 : 继承方式(一般是公有继承) 父类名
//基类(父类)
class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "peter"; // 姓名int _age = 18; // 年龄
};
//派生类(子类)
//class 子类名 : 继承方式(一般是公有继承) 父类名
class Student : public Person//子类对父类的继承
{
protected:int _stuid; // 学号
};
//派生类(子类)
//class 子类名 : 继承方式(一般是公有继承) 父类名
class Teacher : public Person//子类对父类的继承
{
protected:int _jobid; // 工号
};
int main()
{Student s;Teacher t;s.Print();t.Print();return 0;
}

在这里插入图片描述
调试看看派生类是否含有基类的成员变量
在这里插入图片描述

类的继承方式:

(1)private(基类的成员是private):如果基类成员(成员函数和成员变量)都是私有继承,那么派生类虽然继承基类,但基类成员在派生类中和在派生类外都是不可见。
在这里插入图片描述
(2)protected(基类的成员是protected):派生类继承基类,虽然在派生类外不能访问基类成员,但在派生类中可以访问基类成员
在这里插入图片描述
(3)public(基类的成员是public):派生类继承基类,既可以在派生类中访问基类成员,也可以在派生类外访问基类成员。

1.通过以上的对于三种访问限定符的分析,我们知道访问范围:public>protected>private
2.派生类使用class关键字时默认是private继承,struct关键字默认是public继承。(但是建议还是写出继承方式比较好),在实际中还是推荐使用public继承方式,其他俩种继承方式实用性不强。

基类和派生类对象赋值的转换

(1)派生类可以赋值给基类对象/基类指针/基类引用(形象的叫法是切割:把派生类中和父类相似的一部分切割出来赋值给基类)
在这里插入图片描述

(2)基类不能赋值给派生类
在这里插入图片描述

继承的作用域

基类和派生类都是独立的类,都有着自己独立的作用域,但是如果基类和派生类有着同名的成员,那么会出现什么情况呢?


//基类(父类)
class Person
{
protected:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "peter"; // 姓名int _age = 18; // 年龄
};
class Student : public Person//子类对父类的继承
{
protected:int _age = 17; // 年龄
};int main()
{Student s;return 0;}

你们看看这个代码能不能运行
在这里插入图片描述
运行没有任何问题,但是这个代码却非常容易混淆
Student的_age和Person的_age构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆

我们再看看这段代码

class A
{
public:void fun(){cout << "func()" << endl;}
};
class B : public A
{
public:void fun(int i){//这里是想调用基类的成员函数fun()fun();cout << "func(int i)->" << i << endl;}
};
void Test()
{B b;b.fun(10);
};int main()
{Test();
}

在这里插入图片描述
编译器却是调用派生类的fun函数,这是为什么呢?有的老铁可能会认为这两个函数构成重载了,但请老铁们注意,这两个函数并不是在同一个作用域,所以它们并不构成重载,它们函数名相同,是构成隐藏了。

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

// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
class A
{
public:void fun(){cout << "func()" << endl;}
};
class B : public A
{
public:void fun(int i){//这里是想调用基类的成员函数fun()A::fun();cout << "func(int i)->" << i << endl;}
};
void Test()
{B b;b.fun(10);
};int main()
{Test();
}

在这里插入图片描述
所以在继承体系中我们尽量不要定义同名的成员。

派生类的默认成员函数:

无论是何种成员函数,派生类都会调用基类的默认成员函数去把从基类继承的成员进行相对应得操作,例如:派生类的构造函数会自动去调用基类的构造函数,从而完成对派生类中从基类继承下来的成员的构造,如果基类没有默认构造函数,则必须在派生类构造函数的初始化列表阶段显示调用基类的自定义的构造函数。
在这里插入图片描述
对于析构函数,会先调用派生类的析构函数,再调用基类的析构函数,因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。

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

继承与友元:

基类中如果有友元函数,派生类继承基类却不能继承基类的友元函数,也就是说基类友元不能访问子类的私有和保护成员。

class Person
{
public:friend void Display(const Person& p, const Student& s);
protected:string _name; // 姓名
};
class Student : public Person
{
protected:int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{cout << p._name << endl;cout << s._stuNum << endl;
}
void main()
{Person p;Student s;Display(p, s);
}

在这里插入图片描述

继承与静态成员:

如果基类中有静态成员,则整个体系中唯一只有这个静态成员,无论派生出多少个子类,都只有这一个static成员实例。

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

单继承:一个派生类只有一个直接基类
多继承:一个派生类有两个或两个以上的直接基类

菱形继承(多继承的特殊情况):
在这里插入图片描述
二义性:

class Person
{
public:string _name; // 姓名
};
class Student : public Person
{
protected:int _num; //学号
};
class Teacher : public Person
{
protected:int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修课程
};
void Test()
{// 这样会有二义性无法明确知道访问的是哪一个Assistant a;a._name = "peter";
}int main()
{Test();return 0;
}

在这里插入图片描述
在这里插入图片描述

解决二义性

class Person
{
public:string _name; // 姓名
};
class Student : public Person
{
protected:int _num; //学号
};
class Teacher : public Person
{
protected:int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修课程
};
void Test()
{// 这样会有二义性无法明确知道访问的是哪一个Assistant a;a.Person::_name = "peter";// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决a.Student::_name = "xxx";a.Teacher::_name = "yyy";
}int main()
{Test();return 0;
}
虚拟继承可以解决菱形继承的数据冗余和数据二义性
class A
{
public:int _a;
};
// class B : public A
class B : virtual public A//在继承方式前加上virtual关键字就构成虚拟继承
{
public:int _b;
};
// class C : public A
class C : virtual public A//在继承方式前加上virtual关键字就构成虚拟继承
{
public:int _c;
};
class D : public B, public C//在继承方式前加上virtual关键字就构成虚拟继承
{
public:int _d;
};

虚拟继承的原理(以上面代码为例子):
虚拟继承通过把D对象中的A放到最下面,然后B和C中的成员变量各自放好,在B和C中不只有成员变量,还有各存放一个指针,各指向一张表,用于寻找公共的A,这两个指针叫虚机表指针,这两个表叫虚机表,虚机表中存放着A和B,C的相对的偏移量,B和C都可以通过各自的偏移量去寻找到公共的A
在这里插入图片描述

继承与组合

在工程项目都是提倡高内聚,低耦合,那什么是高内聚,什么又是低耦合呢?举个例子,一个部门一般是都是有很多个团队,每个团队实现项目中的一部分功能,我们希望每个团队实现的功能能够相对独立性比较强,便于我们维护,这个就是低耦合,高内聚是多个团队充分发挥合作,把项目完整和高效的实现出来!

继承:是低内聚,高耦合,允许你根据基类的实现来定义派生类的实现,这种生成派生类的复用被称为白箱复用,基类的内部细节是对子类开放的,所以继承一定程度上破环了基类的封装,基类的改变,对派生类有

组合:是高内聚,低耦合,组合是黑箱复用,这种复用是对象内部细节不可见的。

Is-a 关系:也称为继承,如果一个类是另一个类的一种类型,那么就存在 is-a 关系。
例如:如果你有一个 Vehicle 类和一个 Car 类,而 Car 是 Vehicle 的一种,那么 Car 就可以继承自 Vehicle。这意味着 Car 类拥有 Vehicle 类的所有属性和方法,并且还可以添加自己的特定属性和方法。

Has-a 关系:也称为聚合或组合。当一个类包含另一个类的对象作为其成员时,就是 has-a 关系。
例如,假设你有一个 Car 类和一个 Engine 类。每个 Car 都有一个 Engine,所以你可以将 Engine 对象作为一个成员变量放在 Car 类中。
这种关系不是通过继承来表达的,而是通过在一个类中声明另一个类类型的实例变量来实现。

// Car和BMW Car和Benz构成is-a的关系
class Car {
protected:string _colour = "白色"; // 颜色string _num = "陕ABIT00"; // 车牌号
};
class BMW : public Car {
public:void Drive() { cout << "好开-操控" << endl; }
};
class Benz : public Car {
public:void Drive() { cout << "好坐-舒适" << endl; }
};
// Tire和Car构成has-a的关系
class Tire {
protected:string _brand = "Michelin"; // 品牌size_t _size = 17; // 尺寸
};
class Car {
protected:string _colour = "白色"; // 颜色string _num = "陕ABIT00"; // 车牌号Tire _t; // 轮胎
};

总结:这篇文章我们分享了继承的概念,继承的方式,继承的赋值,继承的作用域,派生类的默认成员函数,继承与友元函数和静态成员,还有复杂的菱形继承和菱形继承的解决方式虚拟继承,最后是继承和组合的对比。

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

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

相关文章

Linux--守护进程与会话

进程组 概念 进程组就是一个或多个进程的集合。 一个进程组可以包含多个进程。 下面我们通过一句简单的命令行来展示&#xff1a; 为什么会有进程组&#xff1f; 批量操作&#xff1a;进程组允许将多个进程组织在一起&#xff0c;形成一个逻辑上的整体。当需要对多个进程…

【关联规则】【Apriori算法】理解

关联规则学习是数据挖掘中的一种技术&#xff0c;用于发现大型数据库中变量间的有趣关系&#xff0c;特别是变量之间的有意义的关联、相关和依赖关系。这种类型的规则在零售业中特别有用&#xff0c;因为它可以帮助确定哪些商品经常一起购买。 关键概念 频繁项集&#xff08;F…

连锁会员管理系统应该有的高级功能

会员连锁管理系统是一种专门针对连锁企业设计的会员管理软件&#xff0c;它可以帮助连锁企业实现跨区域、跨店铺的会员信息、消费记录和积分等的统一管理。以下分析商淘云连锁会员管理系统的主要功能。 会员信息管理&#xff1a;全面收集和管理会员信息&#xff0c;如手机号码、…

2.4 卷积1

2.4 卷积1 2.4 卷积 在了解了系统及其脉冲响应之后&#xff0c;人们可能会想知道是否有一种方法可以通过任何给定的输入信号&#xff08;不仅仅是单位脉冲&#xff09;确定系统的输出信号。卷积就是这个问题的答案&#xff0c;前提是系统是线性且时不变的&#xff08;LTI&…

不用价位宠物空气净化器有什么区别?性价比高宠物空气净化器推荐

自新冠之后&#xff0c;越来越多人意识到优质空气对健康的重要性了&#xff0c;纷纷购置了空气净化器。不少铲屎官便关注到了“宠物空气净化器”这一专业品牌&#xff0c;但越后面入手宠物空气净化器的人&#xff0c;看到的品牌越多。整个市场那是个“蓬勃发展”。 随着消费者…

erlang学习:mnesia数据库与ets表1

Mnesia 和 ETS 都是 Erlang 提供的表管理工具&#xff0c;用于存储和检索数据&#xff0c;但它们之间有一些重要的区别和共同点。 共同点 都是Erlang提供的表存储机制&#xff1a;ETS 和 Mnesia 都允许你在内存中创建表&#xff0c;并且可以用来存储键值对或者更复杂的数据结…

高级大数据开发协会

知识星球——高级大数据开发协会 协会内容: 教你参与开源项目提供新技术学习指导提供工作遇到的疑难问题技术支持参与大数据开源软件源码提升优化以互利共赢为原则&#xff0c;推动大数据技术发展探讨大数据职业发展和规划共享企业实际工作经验 感兴趣的私聊我&#xff0c;…

我要走遍三山五岳之---嵩山

文章目录 嵩山通勤开爬总结 嵩山 2024.9.16登顶第一座五岳。 为啥第一座高山选择了嵩山呢&#xff1f;因为本来就是新手&#xff0c;想选择一个低难度的开始爬。看了小红书上的攻略&#xff0c;五岳的难度&#xff1a;华山>泰山>嵩山>衡山>恒山。 本来想选择的是…

Linux常见查看文件命令

目录 一、cat 1.1. 查看文件内容 1.2. 创建文件 1.3. 追加内容到文件 1.4. 连接文件 1.5. 显示多个文件的内容 1.6. 使用管道 1.7. 查看文件的最后几行 1.8. 使用 -n 选项显示行号 1.9. 使用 -b 选项仅显示非空行的行号 二、tac 三、less 四、more 五、head 六、…

GAMES101(10~11节,几何)

Geometry implicit隐式几何表示&#xff1a; 函数f(x,y,z)&#xff1a; 根据函数fn描述几何&#xff0c;遍历所有空间内 的点&#xff0c;如果带入xyz到函数f(x,y,z)结果0那就绘制这个点 如果xyz求值结果>0表示在几何外&#xff0c;0在表面,<0在几何内 构造几何csg(…

GIS OGC之WMTS地图服务,通过Capabilities XML描述文档,获取matrixIds,origin,计算resolutions

GIS OGC之WMTS地图服务&#xff0c;通过Capabilities XML描述文档&#xff0c;获取matrixIds&#xff0c;origin&#xff0c;计算resolutions 需求&#xff1a;如何根据WMTS服务的Capabilities描述文档得到&#xff0c;openlayers调用wmts服务时的matrixIds&#xff0c;origin…

【C++二叉树】JZ36 二叉搜索树与双向链表

二叉搜索树与双向链表_牛客题霸_牛客网 (nowcoder.com) 思路分析&#xff1a; 根据题目要求可以看出将二叉搜索树转换为排序的双向链表就是中序遍历二叉搜索树。在中序遍历的过程中将前后节点链起来即可。左指针指向前一个&#xff0c;右指针指向后一个。中序遍历是递归实现的&…

linux下的日志编写

1、日志初始化创建 2、日志写入 3、日志关闭 log.c #include "log.h"static log_t LOG;//初始化日志文件&#xff0c;在当前目录创建日志文件 int log_init(char *pdirname) {time_t t;struct tm *ptm NULL;char filepath[64] {0};int ret 0;time(&t);ptm …

资源创建方式

kubernetes支持两种创建资源的方式&#xff1a; 用kubectl命令直接创建&#xff0c;比如&#xff1a;kubectl run nginx-deployment --imagenginx1.7.9 --replicas2&#xff0c;在命令行中通过参数指定资源的属性 通过配置文件和kubectl apply创建&#xff0c;创建nginx.yml文…

Apache SeaTunnel Zeta引擎源码解析(三) Server端接收任务的执行流程

作者&#xff1a;刘乃杰 编辑整理&#xff1a;曾辉 引入 本系列文章是基于 Apache SeaTunnel 2.3.6版本&#xff0c;围绕Zeta引擎给大家介绍其任务是如何从提交到运行的全流程&#xff0c;希望通过这篇文档&#xff0c;对刚刚上手SeaTunnel的朋友提供一些帮助。 我们整体的文…

物联网行业中心跳机制的介绍以及如何实现

一 概述 心跳机制出现在TCP长连接中&#xff0c;客户端和服务端之间定时发送一种特殊的数据包通知对方还在线&#xff0c;以确保TCP连接地可靠性&#xff0c;有可能TCP连接由于某些原因&#xff08;例如网线被拔了&#xff0c;突然断电&#xff09;导致客户端断了&#xff0c;…

WINDOWS AGENTARENA:EVALUATING MULTI-MODAL OS AGENTS AT SCALE论文学习

文章开头说现有的agent都是局限于特定领域&#xff08;什么网络问答啊&#xff0c;仅限文字啊&#xff0c;仅限于某一个app啊&#xff09;这样的&#xff0c;本文的工作主打一个贴近用户使用场景&#xff0c;用户用什么软件&#xff0c;看什么网页&#xff0c;本文的模型就用什…

1×1卷积核【super star 卷积核】

一、11卷积的作用 我们先来给出11卷积的一般作用&#xff0c;如下所示&#xff1a; • 跨通道的特征整合 • 特征通道的升维与降维 • 减少权重参数&#xff08;卷积核参数&#xff09; 【 简化模型 】 1.1 特征通道的升维与降维/跨通道的特征整合/简化模型 输入数据&…

字幕编辑用什么软件好?盘点国内外7款视频加字幕软件,简单高效!

视频添加字幕被认为是让观众更好理解您在视频中讲述内容的最佳和最常见的方式。例如&#xff0c;您可以给视频中的某些文字添加不同的颜色&#xff0c;以帮助观众识别视频中的角色。然而&#xff0c;在制作视频过程中&#xff0c;添加字幕往往是个耗时耗力的任务。因此&#xf…

五种数据库特性对比(Redis/Mysql/SQLite/ES/MongoDB)

做后端开发的程序员基本都要学会数据库的相关知识。 1、关系型数据 今天就着这段时间了解大模型的事需要牵扯到是我们接触最多的、也是入门后端必学的关系型数据库。在关系型数据库中&#xff0c;数据以表的形式进行组织和存储&#xff0c;每个表就像一个 Excel 表格&#xf…