C++(进阶) 第1章 继承
C++(进阶) 第1章 继承
文章目录
- 前言
- 一、继承
- 1.什么是继承
- 2.继承的使用
- 二、继承方式
- 1.private成员变量的(3种继承方式)继承
- 2. private继承方式
- 3.继承基类成员访问⽅式的变化
- 三、基类和派生类间的转换
- 1.切片
- 四、 继承中的作⽤域
- 1.隐藏规则:
- 2.考察继承作⽤域相关选择题
- 五、派⽣类的默认成员函数
- 1.默认成员函数
- 2.派生类的默认成员函数
- 1.构造函数
- 2.析构函数
- 3.拷贝构造
- 4.赋值
- 六、继承与友元
- 1.友元函数不会被继承
- 七、继承与静态成员
- 八、多继承
- 1.什么叫多继承
- 2.多继承的坑
- 3、虚继承
- 总结
前言
在初级篇提过面向对象的三大特性:封装继承多态,在初阶篇可以非常直观的感受到封装是什么那么继承到底是什么呢?
一、继承
1.什么是继承
继承(inheritance)机制是⾯向对象程序设计使代码可以复⽤的最重要的⼿段,它允许我们在保持原有类特性的基础上进⾏扩展,增加⽅法(成员函数)和属性(成员变量),这样产⽣新的类,称派⽣类。继承呈现了⾯向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触函数层次的复⽤,继承是类设计层次的复⽤。
举个例子:
假如我现在有三个类分别是:骑手 商家 用户 ,这三个类都有下面这些基础信息,这些基础信息太过于冗余重复,这个时候就可以定义一个公共类然后让这三个类去继承
就像这样
2.继承的使用
class person
{
public:void print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "张三";int _age = 18;
};
class Student: public person
{
protected:int _stuid;
};
假如现在有上面这俩个类,可以看到student
类里面是没有print函数的
可以看到这里s也是可以去调用print函数的
假如现在我们成员变量修改成public
这里可以看到俩个_name并不是一个,但是这里的成员函数是一个,可见父类和子类并不是同一个成员
但是成员函数是同一个,因为函数并不是存在一个对象里面,他们都是公用的
总结:继承本质上其实也是一种复用
二、继承方式
继承方式有三种和类里面的访问限定符是一样的
他们之间可以这样9组组合
这里可以把这表格堪称俩个部分
下面这个蓝色的可以看见都是不可见的,那么不可见是什么意思呢?
1.private成员变量的(3种继承方式)继承
private三种继承方式都是不可见这里只演示一个
class person
{
public:void print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
//private:string _name = "张三";int _age = 18;
};
class Student: public person
{
public:void func(){cout << _age << endl;}
protected:int _stuid;
};
int main()
{Student s;s.func();return 0;
}
比如说上面的代码,是可见的,运行以后就是这样,可以去访问
现在我把private加上,这里就不让继承的类用了,这个就叫不可见
这里有一个容易混淆的概念,不可见不代表没有继承
可以看到上面还是给继承了的,但是就是无法直接调用,但是这里可以简直的去调用它,这里就可以直接去调用父类的成员函数实现间接的去调用
2. private继承方式
在1的代码的基础上,我把public的基础方式改成private
可以看到原本的public的成员函数也会变成private
3.继承基类成员访问⽅式的变化
通过上面的例子可以发现访问权限和继承权限他们是取权限小的那个
public < protected < private
现在在回头看原来的那个表格
可以看到最后的结果全部都是取到了最小的权限哪里
三、基类和派生类间的转换
1.切片
现在假如有一个父类和一个子类
class person
{
public:void print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "张三";int _age = 18;};
class Student: public person
{protected:int _stuid;
};
现在思考这样几个问题
- 能否把子类赋值给父类呢?
- 子类的指针能否赋值给父类?
- 子类的引用能否赋值给父类?
答案是可以的
这里引入一个新的概念赋值兼容转换
为了方便理解可以看下面这个图片
一般情况下父类的东西会比子类的少,因为父类一般都是放基本信息的,子类一般都是继承了父类信息的基础上在添加了一些东西就会像上面这样
#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
using namespace std;class person
{
public:void print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}string _name = "张三";
protected:int _age = 18;};
class Student: public person
{protected:int _stuid;
};
int main()
{person p;Student s;p = s;person* ptr = &s;person& ref = s;return 0;
}
现在我把保护去掉
可以发现上面的代码是没有报错的
值得注意的是这里并不是之前说的隐式类型转换这里是一个新的概念赋值兼容转换
如果子类赋值给父类那么编译器就会走特殊处理,他并不是传统类型的转换 ,但是这里父类不能给子类赋值
四、 继承中的作⽤域
1.隐藏规则:
- 在继承体系中基类和派⽣类都有独⽴的作⽤域。
- 派⽣类和基类中有同名成员,派⽣类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏。(在派⽣类成员函数中,可以使⽤ 基类::基类成员 显⽰访问)
- 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成藏。
- 注意在实际中在继承体系⾥⾯最好不要定义同名的成员。
假如现在我代码是这样的
class person
{
public:void print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:int _age = 18;string _name = "张三";};
class Student: public person
{protected:string _name = "李四";int _stuid;
};
我这里student继承了父类,那么子类其实这里的函数并不会直接拷贝过来,他们其实还是共用一个那么,这里再用子类去调用print函数,假如这个时候刚好子类和父类里面刚好有同名变量那么这个print函数会去调用那个呢?
首先这样写并不会报错这里会按照就近原则去查找
2.考察继承作⽤域相关选择题
class A
{
public:void fun(){cout << "func()" << endl;}
};
class B : public A
{
public:void fun(int i){cout << "func(int i)" <<i<<endl;}
};
int main()
{B b;b.fun(10);b.fun();return 0;
};
1. A和B类中的两个func构成什么关系(B)
A. 重载 B. 隐藏 C.没关系2.下⾯程序的编译运⾏结果是什么(A)
A. 编译报错 B. 运⾏报错 C. 正常运⾏
五、派⽣类的默认成员函数
首先第一个问题:父类的构造函数子类能不能用?答案是:能
1.默认成员函数
这里就只考虑前四个后面俩个没有那么重要
2.派生类的默认成员函数
1.构造函数
假如现在这么一个代码
父类:
class Person
{
public:Person(const char* name = "peter"): _name(name){cout << "Person()" << endl;}Person(const Person& p): _name(p._name){cout << "Person(const Person& p)" << endl;}Person& operator=(const Person& p){cout << "Person operator=(const Person& p)" << endl;if (this != &p)_name = p._name;return *this;}~Person(){cout << "~Person()" << endl;}
protected:string _name;
};
派生类:
class Studen :public Person
{
protected:int _x;string _addrss;
};
派生类在原来的基础上在加俩个成员变量一个内置类型一个自定义类型
派生类的默认成员函数可以这样看
- 父类成员(整体)
- 子类自己的自定义类型
- 子类自己的内置类型
只会调用默认构造函数
有变化的就只有1 下面的俩个都和普通的类一样
可以看到这里定义了一个子类,但是这里调用的是一个父类的构造和析构
从上面代码可以发现子类是会继承父类的构造函数和析构函数的
走完上面的1就直接去走23,内置初始化成随机值(不同编译器处理方式不一样)自定义就去找自定义的处理方式
值得注意的是这里只会调用默认构造带参数的构造是不会掉用的
假如说我现在要显示的写这个构造函数
它是不可以这么写的,这里报错是因为_name 是父类的成员函数
这里是编译器的规定记住就行了
假如说这里非要去显示调用构造函数这里就要这么写
2.析构函数
如果不写析构函数系统就会生成默认的析构函数,但是如果要显示的写
但是这里有这一个问题这里根本就编译不过
由于多态的原因析构函数会被编译器统一处理成destructorr()
,但是如果他们俩个都叫这个那么这里就会构成隐藏,所以这里连续调用了俩次就会报错
下面全部都是错误演示
所以这里要指定作用域
但是过了以后这里又开始扯淡了这里直接调用了这么多
这里可以发现多调用了,这里不得不说一个机制就是这里编译器做了处理子类析构函数里面会自动调用父类的析构,这里就是析构特殊的地方,其他几个都是显示切片调用就只有它是隐藏
3.拷贝构造
如果我们在子类里面不写拷贝构造那么编译器就会去调用父类的拷贝构造
所以一般情况下子类不需要写拷贝构造除非是设计到深拷贝
子类的就要这么写,子类调用拷贝构造就要去调用父类的拷贝构造就要这样去切片,因为这里要传一个父类过去但是我们没有所以我们这里就要用到上面说的复制兼容转换转一个自己(子类)编译器就会自己去切割
4.赋值
这里就是一个典型的隐藏错误
这里我是要子类切片去调用父类的拷贝,然后这里触发了上面说的隐藏,这里变成了自己调用自己,所以这里要指定作用域
Studen& operator=(const Studen& st)
{if (this != &st){operator=(st);_x = st._x;_addrss = st._addrss;}return *this;
}
Studen& operator=(const Studen& st)
{if (this != &st){Person:: operator=(st);_x = st._x;_addrss = st._addrss;}return *this;
}
六、继承与友元
1.友元函数不会被继承
class Student;
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;
}
假如现在有这个代码编译器报错
这里就很简单友元函数不会给继承
所以想要解决这个问题就要在子类也写一个友元函数
class Student;
class Person
{
public:friend void Display(const Person& p, const Student& s);
protected:string _name; // 姓名
};class Student : public Person
{
public:friend void Display(const Person& p, const Student& s);
protected:int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{cout << p._name << endl; cout << s._stuNum << endl;
}
七、继承与静态成员
class Person
{
public:string _name;static int _count;
};
int Person::_count = 0;
class Student : public Person
{
protected:int _stuNum;
};
这里可以思考一下这里的继承的_count是用一个吗?
这里的答案是他们是同一个静态成员函数是不会在继承一份的,这里可以打印地址看出来
八、多继承
1.什么叫多继承
如果一个类像上面这样连续继承这还不算多继承
像这样继承了俩个类就叫多继承,它的语法就是加个逗号继续继承
2.多继承的坑
assistant继承了student 和 teacher 但是这俩个类又继承了person也就是assistant有俩个person,这里就会出现
数据冗余和二义性
这里访问就会有二义性
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;
};int main()
{// 编译报错:error C2385: 对“_name”的访问不明确Assistant a;a._name = "peter";// 需要显⽰指定访问哪个基类的成员可以解决⼆义性问题,但是数据冗余问题⽆法解决a.Student::_name = "xxx";a.Teacher::_name = "yyy";return 0;
}
但是即使可以解决赋值的问题访问也还会有问题,为了解决这个问题就需要下面这个
3、虚继承
很多⼈说C++语法复杂,其实多继承就是⼀个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂,性能也会有⼀些损失,所以最好不要设计出菱形继承。多继承可以认为是C++的缺陷之⼀,后来的⼀些编程语⾔都没有多继承,如Java。
总结
构造的顺序是先父后子,析构的顺序是先子后父,这里编译器做了强制处理
相关文章:

C++(进阶) 第1章 继承
C(进阶) 第1章 继承 文章目录 前言一、继承1.什么是继承2.继承的使用 二、继承方式1.private成员变量的(3种继承方式)继承2. private继承方式3.继承基类成员访问⽅式的变化 三、基类和派生类间的转换1.切片 四、 继承中的作⽤域1.隐藏规则&am…...

获国家权威机构认可 亚信安全荣获CNVD技术组支撑单位认证
近日,国家信息安全漏洞共享平台(CNVD)依据《CNVD管理办法》及《CNVD支撑单位能力要求》,对申请加入考察期的单位进行了全面而严格的能力评估。经过层层筛选与审核,亚信安全凭借卓越的技术实力与专业的服务能力…...
2. Autogen官网教程 (Terminating Conversations Between Agents)
在这一章中,我们将探讨如何结束自动生成代理之间的对话。 导入必要的库 import osfrom autogen import ConversableAgent配置智能体 我们需要配置智能体使用的语言模型(LLM)。以下是一个配置示例: llm_config {"config_…...
java 排序 详解
Java 提供了多种方式对数据进行排序,包括数组和集合的排序。排序在日常开发中非常常见,以下将从排序算法的基本原理、Java 中的内置排序方法以及自定义排序三方面进行详解。 1. 排序的基本概念 排序是将一组数据按特定顺序排列的过程,常见顺…...
【数据集】城市通量塔站点观测数据
【数据集】城市通量塔站点观测数据 数据概述数据下载参考数据概述 数据集简介:Harmonized gap-filled dataset from 20 urban flux tower sites 数据集名称:Harmonized gap-filled dataset from 20 urban flux tower sites (用于 Urban-PLUMBER 项目的 20 个城市通量塔站点…...
scau编译原理综合性实验
一、题目要求 题目: 选择部分C语言的语法成分,设计其词法分析程序、语法语义分析程序。 要求: 设计并实现一个一遍扫描的词法语法语义分析程序,将部分C语言的语法成分(包含赋值语句、if语句、while循环语句…...

ETAS工具导入DBC生成Com协议栈
文章目录 前言DBC配置关键属性Cobra参数配置Cobra使用isolar工程配置总结前言 ETAS工具导入DBC主要也是生成arxml用的,ETAS推荐使用Cobra导入,本文介绍导入过程及注意事项 DBC配置关键属性 对于普通Com报文,配置为周期发送,及其周期,NmMessage配置为No,示例如下: 对…...

表单校验规则
这里简单记录下vue使用表单时候,给表单添加校验规则,直接上代码 <script setup>import { ref } from vue// 定义表单对象const form ref({account: ,password: ,agree: true})// 定义表单验证规则const rules {account: [{required: true, mess…...
接口的扩展
1. 接口中新增的方法 JDK7之前接口中只能定义抽象方法。 JDK8的新特性:接口中可以定义有方法体的方法。(默认、静态) JDK9的新特性:接口中可以定义有私有方法体的方法。 有方法体的方法:接口升级时,为了兼容…...
新能源电机轴承电腐蚀,如何破?
近年来,随着全球范围内对可再生能源的重视与推动,新能源电机作为新能源汽车、风力发电和太阳能发电等系统的重要组成部分,得到了迅猛的发展。然而,在实际应用中,新能源电机的维护与管理越来越受到关注,其中…...

Java中的File和IO流
File对象 File对象本质是一个文件或文件夹,用于写入和读取文件内容 注意:对于相对路径而言,在单元测试方法中的File是相对于Module,在main中的File是相对于Project 构造器 File(String pathname)File file1 new File("D:…...

ls命令实操笔记
ls命令:全称list,显示文件的文件名与相关属性。(目前工作目录所含之文件及子目录) 4567 45678 7891 a1b2 a2b3c abcd Abcd acde aD7E bcde 通过ls浏览上述文件所在的目录,实现以下需求: 浏览含…...

线段数--算法
线段树是常用来维护 区间信息 的数据结构 线段树可以在 O(logN) 的时间复杂度内实现 单点修改区间修改区间查询 区间求和求区间最大值求区间最小值 简单介绍一下线段树 线段树是一个将区间内的数不断细分的一种数据结构,也就是一个完全二叉树,用每一…...

JS的DOM操作和事件监听综合练习 (具备三种功能的轮播图案例)
下面是是对dom操作的一个综合练习 下面代码是html的基本骨架(没有任何的功能): <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" c…...

低温存储开关机问题
问题: 某消费电子产品在进行可靠性实验室,在低温-30C存储两个小时后,上电不开机。在常温25C时,开关机正常。 分析: 1、接串口抓log信息,从打印信息可以看出uboot可以起来,在跑kernel时&#x…...

mysql系列1—mysql架构和协议介绍
背景: 本文开始整理mysql相关的文章,用于收集数据库相关内容;包括mysql架构和存储方式、索引结构和查询优化、数据库锁等内容。思考如何根据具体的业务给出最优的分表规划和表设计、字段选择和索引设计、优化的SQL语句,以及数据库…...
设计模式——模板模式
定义与基本概念 模板模式(Template Pattern)是一种行为设计模式。它在一个抽象类中定义了一个操作的算法骨架,将一些步骤的实现延迟到具体子类中。这个抽象类就像是一个模板,定义了执行某个流程的基本框架,而具体的细…...

CV22_语义分割基础
1. 常见的分割类型 在计算机视觉领域,根据不同的应用场景和需求,分割任务可以分为几种主要类型。以下是几种常见的分割类型: 语义分割(Semantic Segmentation): 语义分割的目标是将图像中的每个像素分配到…...

Dubbo源码解析-Dubbo的线程模型(九)
一、Dubbo线程模型 首先明确一个基本概念:IO 线程和业务线程的区别 IO 线程:配置在netty 连接点的用于处理网络数据的线程,主要处理编解码等直接与网络数据 打交道的事件。 业务线程:用于处理具体业务逻辑的线程,可以…...

【Canvas与标志】圆角三角形生化危险警示标志
【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>圆角三角形生化危险警示标志 Draft1</title><style type&qu…...

智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...

2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...

Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表
##鸿蒙核心技术##运动开发##Sensor Service Kit(传感器服务)# 前言 在运动类应用中,运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据,如配速、距离、卡路里消耗等,用户可以更清晰…...

在 Spring Boot 中使用 JSP
jsp? 好多年没用了。重新整一下 还费了点时间,记录一下。 项目结构: pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...
k8s从入门到放弃之HPA控制器
k8s从入门到放弃之HPA控制器 Kubernetes中的Horizontal Pod Autoscaler (HPA)控制器是一种用于自动扩展部署、副本集或复制控制器中Pod数量的机制。它可以根据观察到的CPU利用率(或其他自定义指标)来调整这些对象的规模,从而帮助应用程序在负…...
【WebSocket】SpringBoot项目中使用WebSocket
1. 导入坐标 如果springboot父工程没有加入websocket的起步依赖,添加它的坐标的时候需要带上版本号。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dep…...

yaml读取写入常见错误 (‘cannot represent an object‘, 117)
错误一:yaml.representer.RepresenterError: (‘cannot represent an object’, 117) 出现这个问题一直没找到原因,后面把yaml.safe_dump直接替换成yaml.dump,确实能保存,但出现乱码: 放弃yaml.dump,又切…...