C++的三大特性之继承

news2024/11/24 1:08:37

目录

一 继承的概念

代码:

总结:

二 继承中的关系

 三 继承中的作用域问题

什么是域?

隐藏:

隐藏的场景:

总结

四 赋值兼容原则

什么是赋值兼容原则?

与平时强制类型转换的区别

 这一个赋值兼容原则的底层实现是怎么样的?

五 关于继承关系下的六个默认成员函数

什么是默认成员函数?

 对于初始化

1 构造函数

2 拷贝构造函数 

3 赋值运算符的重载

对于析构

对于取地址

六 继承和友元

七 继承和静态成员

八 多继承

多继承导致的问题:

题外话:如何定义一个不能被继承的类?

如何解决菱形继承带来的问题?

虚继承的原理

九 继承和组合


一 继承的概念

C++有三大特性,分别是封装,继承和多态。

继承主要体现了类设计层次的一个复用关系。打个比方,比如:

如图所示,Person派生生成了Student和Teacher类。Student类和Teacher继承了Person类。那么对于Person类中的成员变量和成员函数都是可以复用的,也就是说_agem_name,_sex在子类中各自都有一份。不管访问限定符是什么,都被复用到对应的类中了

如上图,这样去复用了别人的成员的是子类(基类),提供成员给别人复用的是父类(基类)

代码:

class Person
{
public:
	int _age;
	string _name;
	string _sex;
};
class Stduent:public Person
{
public:
	string _sid;
};
class Teacher :public Person
{
public:
	string _tid;
};

总结:

①复用:将共有的成员函数或者成员变量提取出来,作为父类,子类通过继承父类,就可以使用了。

②关系(一对对象,两种关系):父类和子类,基类和派生类。派生或者继承。

二 继承中的关系

根据基类的访问限定符和子类的继承方式,一共有如下的几种关系:

 

 

 注意:这里最后的关系都是在派生类中的体现

我们最后通过排列组合可以发现,一共有九种关系。非常的复杂和繁琐。我这里归纳了一下

1 如果父类是private的成员,不管是什么方式继承,都是不可见的。

所谓不可见就是无论是在类内还是在类外都是不能访问的。区别于派生类中的protect关系,在子类中可见,在类外不可见。
2  除了第一条的,继承方式和访问限定符中取较小权限的

我们发现,基类中的私有成员,无论如何继承下去,在派生类中都是不可见的。那么如果我们只想在父类中访问的话,不想被外界继承和使用,可以定义成私有的。但是说实话,如果这样定义的话,继承就没啥意义了,因为继承就是想使用父类的东西。

另外,如果类class不写继承方式的话,默认的是私有的,struct也是可以定义成类的,但是是私有的。不算语法错误

class Teacher: Person

但是,我们日常生活中,最常用的也就只有public相关的继承

 

 

 三 继承中的作用域问题

什么是域?

由{}括起来的,属于各自特定的域。可能会影响生命周期和访问。

比如:C语言中的域有全局域和局部域这样的概念,有些是会对生命周期造成影响的:局部域

但是c++涉及到的类域,只影响访问

基于此,在同一个域中,我们不能定义同名的函数,如果我们非要定义同名函数就会引发对应的问题。c++中也对此做了特定的处理。

隐藏:

隐藏的场景:

从c++的继承关系出发,如果父类有_name了的基础上,子类中也定义了_name的成员变量,这样子的话,由于子类继承父类的时候,会去复用父类的成员,在子类中,就会存在两个_name.

那么我们在子类中访问_name的时候,访问的是父类的还是子类的_name呢?

不妨自己验证一下

#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
	int _age;
	string _name;
	string _sex;
};
class Student:public Person
{
public:
	string _sid;
	string _name;
};
class Teacher: public Person
{
public:
	string _tid;
};
int main()
{
	Student s;
	s._name = "张三";
 	Person p;

	return 0;
}

 发现确实,如果父类子类都有_name的话,访问的时候优先访问的是子类中的,因为局部优先原则。如果我们想通过子类对象去访问父类中的_name的话,我们需要限定域

举一个用子类访问父类同名成员的例子

总结

什么是隐藏:

在继承关系中,如果子类中有父类同名的成员(函数或者变量),访问子类的对应成员,优先去匹配子类域中的,除非特别指定父类的,否则就访问不到父类的。

对于函数:不在乎参数,只要函数名相同,就构成隐藏。

也就是说,如果构成隐藏,除非特定指定访问父类的,否则就默认访问子类的,访问不到父类的

隐藏是由于子类和父类都有独立的域导致的

区别隐藏(重定义),重载,不可见(隐身)

隐藏(重定义):在继承关系中,由于父类和子类中有相同名字的成员变量,访问的时候默认访问子类中的

重载:在同一个域中,两个函数同名但是参数列表不同(包括个数 顺序),并且不要求返回值。这样子编译器编译的时候,优先匹配最合适的

不可见(隐身):是因为基类的private成员导致的派生类中继承关系是不可见的,那么在类内或者类外都无法访问

四 赋值兼容原则

什么是赋值兼容原则?

子类的对象指针或者引用可以直接赋值给父类

Student s;
	Person p = s;
	Person* p = &s;
	Person& p = s;

与平时强制类型转换的区别

我们平时写代码的时候,如果两个数据的数据类型不统一的话,要么编译器自动转换,要么就是手动写上强制类型转换才可以规避语法错误

但是,对于子类和父类的关系。为什么可以直接赋值?

这一种行为是完全不同于上述的行为的,因为他支持引用的直接赋值

正常情况下编译器转换的话,引用是不可以的。因为两个变量进行赋值的时候,中间会产生一个临时变量,是把这个临时变量赋值给另外的变量的但是临时变量具有常性。因此是属于权限的放大, 是会产生语法错误的

 这一个赋值兼容原则的底层实现是怎么样的?

发生了一个切片行为。可以这样理解:子类中除了与父类共有的那一部分,还有自己特有的。所以可以用子类给父类赋值,只用取出父类中的就可以了。

 

 同理,指针也是取出父类特有的。将子类的指针给父类,那么改变了指针+1的位置。

 引用也是同理

五 关于继承关系下的六个默认成员函数

按照默认成员函数的功能分类,可以分成以上这三大类。

什么是默认成员函数?

默认成员函数就是即使当自己什么都不写的时候,编译器默认生成的成员函数。一旦自己针对自己的需求写了对应的成员函数,编译器就不会生成了

那么在继承体系下,这几类默认成员函数函数是怎么使用的呢?

总体的大规则就是:继承体系下,派生类中的默认成员函数如果自己去定义,需要遵循“合成”的规则。把父类和子类分别当做一个整体,来进行操作 

 对于初始化

1 构造函数

在一般的情况下(这里的一般情况是指没有继承),默认构造函数对内置类型不做处理,对自定义类型会去调用类中自定义类型的构造函数完成初始化。

继承体系下,在子类中自己特有的也遵循这样的规则。但是对于父类的需要去调用父类的构造函数完成初始化。

这时候有两种情况1.父类没有显示写出,那么编译器生成一个默认的,子类直接去调用。2 父类如果定义了 子类如果通过初始化列表来写,需要创建一个匿名对象写出

class Person
{
public:
	int _age;
	string _name="李老师";
	string _sex;
	/*void show()
	{
		cout << _name << endl;
	}*/
	Person(int age,const char*name,const char*sex)
		:_age(age)
		,_name(name)
		,_sex(sex)
	{

	}
};
class Student:public Person
{
public:
	string _sid;
	Student(int age, const char* name, const char* sex, const char* sid)
		:Person(age,name,sex)
		, _sid(sid)
	{

	}
};

2 拷贝构造函数 

一般情况下,对于内置类型进行值拷贝。对于自定义类型,调用自定义类型的拷贝构造函数(内置类型进行值拷贝是没有问题的。但是对于在栈上等地方开辟了空间的,如果进行值拷贝的话,不行,会两个内容指向同一空间,造成析构两次或者改变的时候错误改变的问题。所以自定义类型是要去调用自定义类型的拷贝构造函数的:比如用两个栈实现一个队列,此时初始化这个队列调用的就是栈的拷贝构造函数)

继承体系下,子类自己特有的遵循上述的一般规则。但是对于父类的,需要调用父类的拷贝构造函数完成初始化

 对于父类的,如何在子类中取出父类的一部分呢?

赋值兼容原则把子类传给父类,那么父类中接收到的其实也是子类切片之后和父类吻合的那一部分。

3 赋值运算符的重载

一般情况下,是和拷贝构造函数类似的

在继承体系中,子类中如何写?

对于父类的调用去赋值,而对于子类自己的,需要单独写出

代码

由于子类和父类都有operator=,构成了隐藏。但是这里需要使用父类的,因此需要单独写出。

           

对于析构

一般情况下,对于内置类型不做处理,但是对于自定义类型会去调用自定义类型的析构函数

在继承体系下,不需要写出

为什么子类不用自己显示写出?

这里涉及到两个知识点:隐藏和多态

由于后续多态的需要,析构函数会被统一处理成函数名是destructor的函数。因此父子类函数中构成了隐藏。如果要写的话,对于父类的那一部分需要指定域。但是自己写的时候,可能会有顺序的不一致:构造函数是先初始化父类的再初始化子类的,但是析构函数反之。如果写出反而可能会搞错顺序。因此不用写。

对于取地址

取地址就是取出自己的对应的地址,不需要分成子类和父类两个部分去考虑了。也不需要自己显示的写出。

六 继承和友元

友元关系不能继承。

七 继承和静态成员

被static修饰的成员存储在栈上,父类和子类访问到的是同一个,只有一份的。可以在父类中的构造函数中定义一个static count,就可以统计一共构造了几个父类和子类。

 

八 多继承

一个子类有两个及以上的直接父类就是多继承关系

 

多继承导致的问题:

代码冗余(D中由两份A,分别来自B和C)

二义性,由于是这么存储的,因此在D的对象中,调用A中成员,不指定域的话,就无法分别到底是B的还是C的。(但是即使指定了也无法解决代码冗余这个问题)

题外话:如何定义一个不能被继承的类?

1 父类构造函数私有化:因为后续子类都需要用到父类的 因此私有化之后 就不能被继承了

2 使用c++新增的关键字final最终类,标识该类不能被继承

如何解决菱形继承带来的问题?

使用虚继承:virtual关键字(注意与多态中virtual的区分,属于一个关键词两用)

如何使用?

在腰部的位置虚拟继承。从A继承的地方都要使用虚继承。将共同基类设置为虚基类。

代码:

class B : virtual public A

 

虚继承的原理

如果是普通继承是这样存储的

 

如果是虚拟继承

(由于我的编译器版本有点高 观察结果不太一样 去网络上找了一个比较标准的)

 

 

原博客链接:C++之继承相关问题——菱形继承&继承 - 腾讯云开发者社区-腾讯云 

将共有的提取到最下面的位置。

虚拟继承的话,B和C中有一个虚表指针,指向虚表(本质上是一个函数指针数组),虚表再通过索引指向对应的函数。这里面记录了偏移量和距离,因此就可以通过这个方式找到对应的成员变量。

更复杂的场景:如果对于一个函数在A中,BC继承了,D再继承BC继承了该函数。D中的函数参数传参的时候传指针或者地址,如何找到对应的数据?

首先这个行为是被支持的,因为赋值兼容原理。那么如何找到呢?在汇编时期,汇编代码需要被确定下来,BC中都有一个虚基表,但是他们是不相同的。

因为需要支持多态行为,并且因此重写了函数,并把对应的函数地址放在虚函数表中。(虚函数仍然是存储在公共的代码段的)

那么我们就可以通过虚表指针找到对应的虚函数表,进而找到对应的成员变量和成员函数

注意,这种情况也是属于菱形继承。要解决相关的问题,需要在BC位置带上虚函数virtual标识

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   

 

九 继承和组合

继承:是一种is a的关系,可以通过“是”来判断。比如学生和人:“学生是人”

组合:has a “有没有” 耦合度比较低。b类有a ,b中的成员除了a,还有其他的组成。

有一些既是组合又是继承。比如stack和queue/vector/deque

如果在这样的一个情况下的话,优先使用组合-》高内聚,低耦合,减少模块与模块之间的耦合度,便于后期的维护。否则一个模块修改了,后期如果复用了这个模块,需要修改的地方就很多

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

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

相关文章

编译原理学习笔记18——语义分析和中间代码生成3

编译原理学习笔记18——语义分析和中间代码生成318.1 布尔表达式及其计算18.2 按数值表示法翻译布尔表达式18.3 带优化翻译布尔表达式18.1 布尔表达式及其计算 布尔表达式及其用途 计算布尔表达式的两种方法 计算布尔表达式的两种方法 18.2 按数值表示法翻译布尔表达式 数…

toString()、String.valueOf、(String)强转,有啥区别?

TOC 首先我们看这三头货&#xff0c;什么toString()、String.valueOf、(String)强转&#xff1f;有啥区别&#xff1f;就问你是不是很像&#xff0c;是不是都有一个String&#xff1f; 说白了&#xff0c;这三个都是将XX给变成String&#xff0c;简称变身String的方法 接下来一…

IDEA maven使用详解

前提 确保本地安装了maven 以一个模块工程为例&#xff0c;内部包含了许多maven项目 生命周期 这是全部的生命周期 其中&#xff0c;常用的生命周期的各个功能为&#xff1a; (clean生命周期)mvn clean清理target (default生命周期①)mvn validate验证项目为maven项目&a…

自然语言处理的进阶之路

1、隐马尔可夫链路一般较短原因 连乘链路太长&#xff0c;会导致数据稀疏&#xff0c;零频词太多 2、零频问题的一般解决方案 平滑/回退/差值 当n设置较小时&#xff0c;仍然会存在oov问题&#xff08;语料中未出现的词&#xff09; 2.1、平滑 2.1.1、加1平滑/拉普拉斯平…

Maven初级(一)

目录 一. Maven概述 1.1 Maven是什么 1.2 Maven的作用 1.2.1 项目构建 1.2.2 依赖管理 1.2.3 统一项目结构 1.3 Maven模型 1.3.1 插件 1.3.2 项目对象模型 1.3.3 依赖管理模型 二. Maven基础概念 2.1 仓库&#xff1a; 2.2 仓库分类 2.2.2 远程仓库(私服) 中央仓…

Python继承的优缺点

推出继承的初衷是让新手顺利使用只有专家才能设计出来的框架&#xff01;子类化内置类型的问题在Python2.2之前&#xff0c;内置类型不能子类化&#xff0c;如list、dict等。在Python2.2之后&#xff0c;内置类型可以子类化了&#xff0c;但是要注意的是&#xff1a;内置类型&a…

Individual tree segmentation and tree-counting using supervised clustering

ABSTRACT 个体树木分割 (ITS) 或树木计数是精准林业和农业过程中的一项基础工作。与费时费力的人工检查不同&#xff0c;计算机视觉在基于无人机 (UAV) 的应用中显示出巨大的前景&#xff1b;此类应用之一包括森林资源清单中的自动树木计数问题。然而&#xff0c;由于树冠冠层…

深度学习性能评估指标介绍

首先是相关数据描述。假设原始样本中有两类数据&#xff0c;其中&#xff1a;总共有P个类别为1的样本&#xff0c;假设类别1为正例总共有N个类别为0的样本&#xff0c;假设类别0为负例经过分类后&#xff1a;有TP个类别为1的样本被系统正确判定为类别1&#xff0c;FN个类别为1的…

【哈希表】leetcode1. 两数之和(C/C++/Java/Python/Js)

leetcode1. 两数之和1 题目2 思路3 代码3.1 C版本3.2 C版本3.3 Java版本3.4 Python版本3.5 JavaScript版本4 总结1 题目 题源链接 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数…

iPad 屏幕镜像到 macbook

将iPad 到屏幕投屏到 macbook&#xff0c;只需要三步就可以实现用数据线连接ipad和macbook在macbook的应用中找到QuickTime Player&#xff0c;打开QuickTime Player&#xff0c;在【文件】中选择【新建影片】在弹出窗口的小箭头中&#xff0c;选择需要的iPad名称通过数据线连接…

目标跟踪心得篇七:解决目标跟踪评价指标输出为0或异常(Trackeval、MMtracking)

如果在做跟踪任务测评时,发现输出的评价指标全为0或者异常值时该怎么办(如下图)?博主调试了很久发现其实这是MMtracking的一个Bug,因此如果不是用MMtracking框架的话本节可能对你帮助不大。 大致有以下两个内容: TrackEval目前还不能做到对多类别的MOT任务计算评价指标,…

FDD与TDD

TDD&#xff0c;时分双工(Time Division Duplexing) FDD&#xff0c;频分双工(Frequency Division Duplexing) 帮助理解&#xff1a; 1.FDD&#xff1a;双车道&#xff0c;一个车道只能走一个方向&#xff0c;双向互不干扰。 2.TDD&#xff1a;单车道&#xff0c;不同时间允…

RabbitMQ看这一篇文章就够了

第一章 RabbitMQ介绍 第1节 MQ是什么 1 2 3 41. 消息队列(Message Queue),又叫做消息中间件 2. 用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成 3. 通过提供消息传递和消息队列模型,可以在分布式环境下扩展进程的通信 4. MQ 是用来解…

北斗短报文遥测终端机在水雨情监测系统中的应用

一、方案概述 我国水利监管手段比较单一&#xff0c;水雨情监测移动公网覆盖不足等诸多问题&#xff0c;利用北斗短报文通信技术数字化信息采集技术&#xff0c;实现水文自动测报&#xff0c;大幅度提升湿地生态和水域的监测、查询、预警和应急处理能力。在恶劣天气情况或特殊灾…

360(drizzleDumper)脱壳教程“某药数据”

一、drizzleDumper的下载使用 1.上GitHub下载开源的脱壳工具mirrors / DrizzleRisk / drizzleDumper GitCodedrizzleDumper是一款基于内存搜索的Android脱壳工具。 &#x1f680; Github 镜像仓库 &#x1f680; 源项目地址 ⬇ ⬇...https://gitcode.net/mirrors/DrizzleRisk…

ZedGraph如何显示鼠标附近的曲线的点?介绍三种方法

使用ZedGraph绘制曲线图的时候&#xff0c;不仅仅是看曲线的走向&#xff0c;也需要查看曲线上某位位置处采集到的数据是多少。下面介绍三种方法&#xff0c;从简单到复杂。 文章目录1、使用自带的功能显示点的坐标2、 多条曲线的坐标点同时显示3、 多条曲线的坐标点同时显示&a…

100%国产C2000,P2P替代TMS320F280049C,独立32位双核CPU,主频高达400MHz

一、特性参数 1、独立双核&#xff0c;32位CPU&#xff0c;单核主频400MHz 2、IEEE 754 单精度浮点单元 &#xff08;FPU&#xff09; 3、三角函数单元 &#xff08;TMU&#xff09; 4、1MB 的 FLASH &#xff08;ECC保护&#xff09; 5、1MB 的 SRAM &#xff08;ECC保护&…

docker基础简介

一、docker架构 二、Docker 基本命令 1、查看 Docker 版本 查看 Docker 版本包括 Docker 版本号、API 版本号、对应的 Git Commit、Containerd 和 runC的版本信息等。 # docker version Client: Docker Engine - Community Version: 20.10.4 API version: 1.40 Go version: …

新手小白做跨境电商应该从哪里入手?

想从事跨境电商先从哪里入手?米贸搜整理如下&#xff0c;希望可以帮助到你跨境电商平台&#xff0c;如&#xff1a;Amazon、eBay、aliexpress、Cdiscount、wish等。想从事跨境电商&#xff0c;你得搭建电商团队&#xff0c;先从跨境电商平台的入驻入手&#xff0c;弄清楚入驻条…

【设计模式】行为型模式·模板方法模式

学习汇总入口【23种设计模式】学习汇总(数万字讲解体系思维导图) 一.概述 定义一个操作中的算法骨架&#xff0c;而将算法的一些步骤延迟到子类中&#xff0c;使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。 在面向对象程序设计过程中&#xff0c;程序员常…