【C++进阶九】继承和虚继承
【C++进阶九】继承和虚继承
- 1.什么是继承
- 2.继承关系
- 2.1protected和private的区别
- 2.2通过父类的函数去访问父类的private成员
- 2.3默认继承
- 3.基类和派生类对象的赋值转换
- 4.继承中的作用域
- 5.子类中的默认成员函数
- 6.继承与静态成员
- 7. 菱形继承
- 8.虚继承
- 9.继承和组合
1.什么是继承
继承是代码复用的一种手段,子类继承父类就能使用父类中的变量
比如我们同时定义student类和teacher类

二者有大量的相同信息,因此可以把共同信息提取出来单独形成一个类
struct Person
{string name;string sex;int age;int height;
}
只用在实现student类和teacher类时继承person类即可
class Student : public Person//继承
{
protected:int _stuid;//学号
};
class Teacher : public Person//继承
{
protected:int _jobid;//工号
};

子类通过继承可以获得父类中的成员
Student st;
st._stuid = 123456;
st.name = "张三";
st.age = 20;
2.继承关系
类中的三种访问限定符和继承的三种方式组成了下表:

基类中的public成员通过protected继承方式到派生类中,变成派生类的protected成员,以此类推
基类中的private成员,通过任何继承方式都是不可见的,继承下来了但是不能用,子类没办法调用父类中的private成员变量,不想要被子类继承,就可以设置为private
2.1protected和private的区别
protected和private访问限定符被认为都是一样的,都是在类里面可以访问,类外不能访问
只有在继承的时候二者才有区别,基类设置为private后,子类继承不可见
而基类设置为protected后,子类继承可以使用
2.2通过父类的函数去访问父类的private成员
虽然基类的成员变量是由private修饰的,只是派生类中不可以用,但是子类student 可以调用父类的函数去访问

2.3默认继承
继承方式可以不写
使用关键字class时,默认的继承方式是private
使用关键字struct时,默认的继承方式是public
class Student : Person//继承
{
protected:int _stuid;//学号
};
3.基类和派生类对象的赋值转换
在共有继承的前提下,将子类对象赋值给父类对象天然支持,不存在类型转换发生,认为子类对象就是特殊的父类对象
由于d是double类型,而i是int类型,将d赋值给i会发生隐式类型转换,产生一个int类型的临时变量,再将临时变量传给i,但是由于临时变量具有常性,所以i需要使用const修饰
double d = 1.1;
const int& i = d;
而将子类对象赋值给父类对象不会产生临时变量,不需要const修饰
student st;
person p = st;

在子类中,把和父类相同的那部分找到,调用父类的拷贝构造依次将其拷贝到父类
这个过程被叫做切割或者切片
例:
student st;
person& p = st;

student st;
person* p_ptr = &st;

4.继承中的作用域
- 在继承体系中基类和派生类都有独立的作用域
- 子类和父类有同名成员,子类成员将屏蔽父类并对同名成员的直接访问,这种情况也叫隐藏,或者重定义
(在子类成员函数中,可以使用 基类::基类成员 显示访问) - 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏
- 注意在实际中的继承体系里面最好不要定义同名的成员
class Person
{
protected :int _num = 0010; //身份证号
};class Student : public Person
{
protected:int _num = 1119; //学号
};
在main函数中定义student对象后再打印_num默认为子类中的_num,若想打印父类中的_num,需要指定类域
Student st;
cout << st._num;
cout << st.Person::_num;
5.子类中的默认成员函数

- 子类中的父类的成员必须调用父类的构造去初始化(拷贝构造也是)
如果不显示调用,会在初始化列表去调用父类的默认构造函数:自己实现的全缺省的构造函数
class Person
{
public:Person(const char* name = "peter")//全缺省构造函数: _name(name){cout << "Person()" << endl;}
protected:string _name; // 姓名
};class Student : public Person
{
public:student(const char* name,int num)//构造函数:person(name)//显示调用父类的构造,_num(num){}
protected :int _num;
};
- 子类的拷贝构造需要调用父类的拷贝构造
父类的拷贝构造的const person& p = s,p作为子类Student中父类那部分的别名,被称之为切片或者切割
class Person
{
public:Person(const person& p)//拷贝构造: _name(p.name){cout << "Person()" << endl;}
protected:string _name; // 姓名
};class Student : public Person
{
public:student(const student& s)//拷贝构造:person(s)//显示调用父类的拷贝构造,_num(num){}
protected :int _num;
};
- 子类的operator=中必须调用父类的operator=完成父类成员赋值
在子类中operator=前面需要指定作用域从而调用父类的operator=(因为子类的operator= 与父类的operator= 形成隐藏,会优先使用子类的operator=)
class Person
{
public:person& operator=(const person& p){if(this != &p){_name = p._name;}return *this;}
protected:string _name; // 姓名
};class Student : public Person
{
public:student& operator=(const student& s){if(this != &s){person::operator=(s);_num = s._num;}return *this;}
protected :int _num;
};
- 子类的析构函数不用显示调用父类的析构,编译器会自动去调用
(构造时,先构造父类后构造子类,所以析构时,要先析构子类再析构父类,为了减少错误,编译器在子类的析构函数完成时,会自动调用父类的析构函数,保证先析构子,再析构父) - 子类初始化对象时,先初始化父类的成员变量,再初始化子类的成员变量
- 友元关系不能被继承
一个函数是父类的友元,但不是子类的友元
6.继承与静态成员
继承并不会把static修饰的静态成员变量继承下来,但是可以访问它
静态成员变量属于整个类,即属于父类,也属于子类

在父类和子类中,都可以调用_count,并且地址相同,说明是同一个_count
7. 菱形继承
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

但多继承可能会导致菱形继承

会导致在对象中存在两份person的信息,就存在了数据冗余和二义性(二义性:直接访问不知道访问谁,因为有两个父类带来的两个名字)

指定访问可以解决二义性:

但是实际上依旧是不合理的,大量相同的信息造成数据冗余,本质为空间浪费
8.虚继承
为了解决菱形继承的二义性和数据冗余的问题,提出了虚继承
非虚继承版本:
#include<iostream>
using namespace std;class A
{
public:int _a;
};class B : public A
{
public:int _b;
};class C : 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;
}
调试并调用内存窗口

由于A是B和C的父类,所以A类的_a在B类和C类中都存在
B类本身有一个_b的成员变量
C类本身有一个_c的成员变量

虚继承版本:
#include<iostream>
using namespace std;
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;
}
可以看到在调试模式下存在三份_a的数据,但内存中只有一份地址储存_a

相比于不是虚继承的版本,03 和04上面多个一份地址存在

换成8列观察:

使用地址00007FF73F4B9C20加上偏移量40正好为00 00 00 02即A的地址
使用地址00007FF73F4B9C20加上偏移量24正好为00 00 00 02即A的地址

因为B类和C类中都存在A,存在数据冗余和二义性,所以把A的数据放入公共区域,既不放入B中,也不放入C中
通过偏移量来寻找这个公共的位置
这两个偏移量没有存到各自的第一个位置上,因为第一个位置是为以后的多态做准备的、
解决数据冗余和二义性,会增加了两个指针,只节省了一个A,即4个字节,相当于多消耗了4个字节
但如果A变大,指向的空间忽略不计,会创建很多对象,每个对象都指向该空间,由大家共同分担,所以消耗可以忽略不计,节约大量空间
9.继承和组合
public继承是一种is-a的关系,每个派生类对象都是一个基类对象,如:学生和人,学生是一个人
组合是一种has-a的关系,假设B组合了A,每个B对象中都有一个A对象,如:车和轮胎的关系 ,车有轮胎

耦合度指的是关联关系,继承关系更紧密一些 ,说明继承的耦合度高
B可以直接用A的3个成员
D可以直接用C的一个成员,间接用另外的两个成员
若把A类中的_a1改了,会影响B类,A改动保护可能影响B
若把C类中的_c1改了,不会影响D类,C改动保护和私有成员基本不影响D
低耦合,高内聚
所以单个模块之间关联越低越好,这样一个模块出现问题,最大值程度上减少对另一个模块的影响
若两个类完成的功能是一样的,就把他们两个放在一起,若这两个类毫不相关,就不要合在一起
尽量使用组合去降低耦合度,但要用多态时就需要使用继承,多态是建立在继承之上的
相关文章:
【C++进阶九】继承和虚继承
【C进阶九】继承和虚继承 1.什么是继承2.继承关系2.1protected和private的区别2.2通过父类的函数去访问父类的private成员2.3默认继承 3.基类和派生类对象的赋值转换4.继承中的作用域5.子类中的默认成员函数6.继承与静态成员7. 菱形继承8.虚继承9.继承和组合 1.什么是继承 继承…...
近日八股——计算机网络
一.c. TCP握手为什么三次、不能是二次、或四次? i.不能是两次: 防止已经失效的连接报文突然又传到了服务端,产生错误 如果不采用三次握手,服务端直接建立连接,会白白浪费资源 三次握手告诉服务端,客户端有没有收这个数据&#…...
HOW - Axios 拦截器特性
目录 Axios 介绍拦截器特性1. 统一添加 Token(请求拦截器)2. 处理 401 未授权(响应拦截器)3. 统一处理错误信息(响应拦截器)4. 请求 Loading 状态管理5. 自动重试请求(如 429 过载)6…...
自适应信号处理任务(过滤,预测,重建,分类)
自适应滤波 # signals creation: u, v, d N = 5000 n = 10 u = np.sin(np.arange(0, N/10., N/50000...
电子电气架构 --- 面向服务的体系架构
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 周末洗了一个澡,换了一身衣服,出了门却不知道去哪儿,不知道去找谁&am…...
TypeScript 装饰器类型详解
TypeScript 装饰器类型详解 一、类装饰器 // 参数:类的构造函数 function ClassDecorator(constructor: Function) {Object.defineProperty(constructor.prototype, timestamp, {value: Date.now()}); }ClassDecorator class DataService {// 装饰后自动添加times…...
Nyquist内置函数-杂项函数
1 Nyquist内置函数-杂项函数 1.1 杂项函数 这些函数对于日常使用来说都是安全且推荐的。 1.1.1 to-mono(sound) [SAL] (to-mono sound) [LISP] 如果 sound 是多声道声音,返回其所有声道的总和;如果 sound 本身就是单声道声音,则直接返回&…...
基姆拉尔森计算公式
基姆拉尔森计算公式(Zellers Congruence 的变体)是一种快速根据公历日期计算星期几的数学公式。其核心思想是通过对年月日的数值进行特定变换和取模运算,直接得到星期几的结果。 公式定义 对于日期 年-月-日,公式如下:…...
5 分钟用满血 DeepSeek R1 搭建个人 AI 知识库(含本地部署)
最近很多朋友都在问:怎么本地部署 DeepSeek 搭建个人知识库。 老实说,如果你不是为了研究技术,或者确实需要保护涉密数据,我真不建议去折腾本地部署。 为什么呢? 目前 Ollama 从 1.5B 到 70B 都只是把 R1 的推理能力…...
Python数据可视化-第6章-坐标轴的定制
环境 开发工具 VSCode库的版本 numpy1.26.4 matplotlib3.10.1 ipympl0.9.7教材 本书为《Python数据可视化》一书的配套内容,本章为第6章 坐标轴的定制 本章主要介绍了坐标轴的定制,包括向任意位置添加坐标轴、定制刻度、隐藏轴脊和移动轴脊。 参考 第…...
18认识Qt坐标系
平面直角坐标系(笛卡尔坐标系) 数学上的坐标系 右手坐标系 计算机中的坐标系 左手坐标系 坐标系的原点(0,0) 就是屏幕的左上角 /窗口的左上角 给 Qt 的某个控件,设置位置,就需要指定坐标.对于这个控件来说, 坐标系原点就是相对于父窗口/控件的. QPushButton 的父元素/父控件/父…...
动态循环表单+动态判断表单类型+动态判断表单是否必填方法
页面效果: 接口请求到的数据格式: list: [{demandType: "设备辅助功能要求",demandSettingList: [{id: "1907384788664963074",name: "测试表单",fieldType: 0,contentValue: "",vaildStatus: 0, // 0 非必填&a…...
keep-alive缓存
#keep-alive缓存动态路由的使用指南# 代码如下图 : <router-view v-slot"{ Component }"> <keep-alive :include"[Hot, Notifications, User, Setting, Test]"> <component :is"Component" …...
25.4.3学习总结【Java】
又是一道错题: 1. 班级活动https://www.lanqiao.cn/problems/17153/learning/?page1&first_category_id1&sortdifficulty&asc1&second_category_id3 问题描述 小明的老师准备组织一次班级活动。班上一共有 n 名 (n 为偶数) 同学,老师…...
Python入门(3):语句
目录 1 基本语句 1.1 表达式语句 1.2 赋值语句 2 控制流语句 2.1 条件语句 2.2 循环语句 while循环: for循环: 2.3 流程控制语句 1. break语句:退出整个循环体 2. continue语句:只跳过本次循环,还会进…...
运维之 Centos7 防火墙(CentOS 7 Firewall for Operations and Maintenance)
运维之 Centos7 防火墙 1.介绍 Linux CentOS 7 防火墙/端口设置: 基础概念: 防火墙是一种网络安全设备,用于监控和控制网络流量,以保护计算机系统免受未经授权的访问和恶意攻击。Linux CentOS 7操作系统自带了一个名为iptables的…...
开发一个小程序需要多久时间?小程序软件开发周期
开发一个小程序所需时间受多种因素影响,以下为你详细列举: 一、需求复杂度。若只是简单展示类小程序,如企业宣传、产品介绍,功能单一,大概 1 - 2 周可完成。若涉及复杂交互,像电商小程序,涵盖商…...
【数据结构篇】算法征途:穿越时间复杂度与空间复杂度的迷雾森林
文章目录 【数据结构篇】算法征途:穿越时间复杂度与空间复杂度的迷雾森林 一、 什么是算法1. 算法的定义1.1 算法的五个特征1.2 好算法的特质 2. 时间复杂度3. 空间复杂度 【数据结构篇】算法征途:穿越时间复杂度与空间复杂度的迷雾森林 💬欢…...
新增帧能耗指标|UWA Gears V1.0.9
UWA Gears 是UWA最新发布的无SDK性能分析工具。针对移动平台,提供了实时监测和截帧分析功能,帮助您精准定位性能热点,提升应用的整体表现。 本次版本更新主要新增帧能耗指标,帮助大家对每一帧的能耗进行精准监控,快速…...
蓝桥杯嵌入式16届———LCD模块
LCD有官方给我们提供的库,我们使用其非常简单,唯一要注意的就是LCD和LED的引脚冲突。 引脚状况 STM32CubeMX 端口配置 使能 比赛给的选手 资源数据包中有以下三个文件,(除去led相关的),将他们复制到自己…...
Ubuntu服务器挂载之前的数据硬盘
这里假设在挂载硬盘之前,您的硬盘从之前的服务器上正确卸载下来。请注意,以下任何操作不当都有可能导致硬盘数据丢失或损坏,如果您的数据非常重要,请及时备份。 1. 确认硬盘分区信息 使用以下命令查看磁盘信息,找到要…...
CMake在Windows环境下Visual Studio Code的使用
1,安装下载 地址:Visual Studio Code - Code Editing. Redefined 双击安装 选择安装路径 可勾选微软的AI工具 2,环境介绍 2.1 ,界面介绍 2.2中文包的安装 下载中文简体 汉化后的界面 2.3 配置C/C环境 VSCode安装好之后…...
注意力机制在大语言模型中的原理与实现总结
注意力机制在大语言模型中的原理与实现总结 1. 章节介绍 在大语言模型的学习中,理解注意力机制至关重要。本章节旨在深入剖析注意力机制的原理及其在大语言模型中的应用,为构建和优化大语言模型提供理论与实践基础。通过回顾神经网络基础及传统架构的局…...
大学生机器人比赛实战(二)软件篇
大学生机器人比赛深度开发指南:核心技术详解与实战代码解析 作为参加过多次机器人竞赛的选手,我将详细剖析比赛中的核心技术实现,包括软件架构设计、实时系统应用、各大赛事代码特点以及性能优化技巧。本指南将帮助你从代码层面深入理解如何…...
AI大模型从0到1记录学习 day13
第 13 章 Python高级语法 13.1 浅拷贝与深拷贝 直接赋值:对象的引用(别名),不产生拷贝。 浅拷贝:拷贝父对象,不会拷贝对象的内部的子对象。拷贝后只有第一层是独立的。 深拷贝:完全拷贝…...
大模型学习三:DeepSeek R1蒸馏模型组ollama调用流程
一、说明 目前DeepSeek R1及其蒸馏模型均支持使用ollama进行调用,可以在模型主页查看调用情况 deepseek-r1https://ollama.com/library/deepseek-r1 显存需求 ,我们显存是16G,可以玩好几个 二、以14B模型演示 1、安装ollama curl -fsSL htt…...
RAGFlow部署与使用介绍-深度文档理解和检索增强生成
ragflow部署与使用教程-智能文档处理与知识管理的创新引擎 1. ragflow简介 RAGFlow作为新一代智能文档处理平台,深度融合检索增强生成(RAG)技术与自动化工作流引擎,为企业级知识管理提供全栈解决方案。通过结合多模态解析、语…...
一文读懂 UML:基础概念与体系框架
UML 图是一种标准化的建模语言,在软件开发和系统设计等领域有着广泛的应用。以下是对 UML 图各类图的详细介绍: 1.用例图 定义:用例图是从用户角度描述系统功能的模型图,展现了系统的参与者与用例之间的关系。作用:帮…...
【AI赋能:58招聘数据的深度剖析之旅】
影刀出鞘,抓取数据 在当今数字化时代,数据分析已成为企业决策和发展的关键驱动力。而获取高质量的数据则是数据分析的首要任务。在这个信息爆炸的时代,网络上蕴含着海量的数据,其中招聘网站的数据对于人力资源分析、市场趋势研究…...
几何法证明卡特兰数_栈混洗
模型: 考虑从坐标原点 (0, 0) 到点 (n, n) 的路径,要求路径只能向右(x 方向)或向上(y 方向)移动,并且路径不能越过直线 y x(即始终满足 y< x )。这样的路径数量就是…...
