C++中的继承(二)
文章目录
- 前言
- 多继承
- 虚继承
- 虚继承的底层
- 组合
前言
上一篇文章我们C++的正常继承其实已经讲完了,但是后面还有一个大坑。
实际当中继承有单继承和多继承。
单继承就是直接继承一个类。
只有一个直接父类的就叫做单继承。
如果是单继承那就比较简单。
现实世界除了有单继承还有多继承。
多继承
多继承就是我一个类我具备另外两个类的特征。
单继承就是一个类只具备另外一个类的特征。
现实世界当中有什么东西需要具备两个特征都继承一下呢?
比如:有没有一种物种既具有水果的特征,也具有蔬菜的特征?
番茄。
多继承很重要,它能够更好的描绘这个世界。
所以多继承看起是很合理的,但是它有一个大坑。
多继承就可能会导致这种菱形继承。
菱形继承会有什么样的问题呢?
他会导致对象里面有两份人的信息。
这样你就不知道要访问从学生那里继承来的呢,还是从老师那里继承来的呢?
指定访问是能解决二义性的
但是这样很不合理的。
首先名字肯定有一个正式的名字比较合理,另外如果有其他的一些信息,
你肯定会觉得很冗余,比如
数据冗余的本质是空间浪费
所以不要搞出菱形继承,非常非常坑。
虚继承
菱形继承怎样解决数据冗余和二义性呢?
这里引入了一个新的东西,虚继承。
它们都变成了同一个。所以也得出一个结论,监视窗口看到的不一定是真实的,
它们都是被处理过的。
从实际的角度,可以用多继承,但是不要用菱形继承。
但是从学习的角度,我们还得学一下这个菱形继承。
这样是不是菱形继承?
是的,它都有数据冗余和二义性。
虚继承的底层
虚继承是如何解决数据冗余和二义性的?
虚继承虽然解决了数据冗余和二义性,但是这个过程是很难看的。
监视窗口已经看不出它最真实的面目。它的底层不是这样的。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Person
{
public:string _name; // 姓名
};
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;a._name = "peter";// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决a.Student::_name = "xxx";a.Teacher::_name = "yyy";
}int main()
{Test();return 0;
}
这里虽然有三个_name,但是每个_name都是一样的。
它的底层到底是怎么样呢?
物理上它的底层到底是怎么样呢?这里要换一个角度去看,
监视窗口已经看不到真实的东西,我们这里看一下内存窗口,内存窗口是真实的,不加修饰的。
我们这里用一个简化的类模型,方便我们看。
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;
};
ABCD依次构成继承。
看它的底层是什么样的,我们先看不看虚继承,就看菱形继承。
接下来我们看虚继承。
对象模型相比刚才已经发生了本质的变化。
注意看这里面还有我们不认识的两个东西,这些东西是什么?
00 aa cd 8c
00 aa cb ac
这两个有点像指针,而且距离也不远
它们具体是什么呢,我们再用一个内存窗口观察一下。
注意这是小端,然后把它调成4,因为编译器是32位的,指针也是32位的,并且存的是整型,正好对齐方便观察。
这是怎么回事,这个地址指向的空间是个0, 并且下面有一个值。
这个值到底是什么?
所以这两个值是偏移量,也是相对距离。
之前是B里有一个a,C里也有一个a,那这就造成数据冗余和二义性。
现在把一个a放到公共的空间去,然后通过偏移量去找。
那现在问题来了,为什么不直接存a的地址呢?
直接存a的地址,这样不是更好吗?
大家注意,这个偏移量它没有存到第一个位置,这个位置是空出来的,为以后的
多态做准备。
如果这里存a地址,只解决了一个问题。而如果这个地方存放一个指针,指向一个表,这张表
可以存很多其他信息。
什么情况会涉及刚才这样一块问题呢?
以前的赋值兼容转换直接切割就可以了,现在直接切父类没毛病,但是不完整,还有一个a.
这个a在哪呢,在切的时候就涉及一个问题,找到对应的a。
怎么找呢?拿到对应的偏移量,然后计算才能找到a.
第二种就更复杂了。(这里还是比较难的)
ptrc指向哪里?
不是指向最开始的地方,多继承指针会发生偏移。
虚继承还有更复杂的问题。b对象的对象模型是什么样的?
按照我们以前的理解,b里面有一个_a,有个一个_b,现在实际并不是这样。
虚继承影响了这块。它要保持一致。
也就意味着这里面有两种情况,这两个代码看起来一样,实际上跑起来天差地别。
一个指向d对象,一个指向b对象。它们的偏移量也是不一样的。
但是它们的汇编指令是一样的。
它们去访问a是一样的,都是找第一个位置的地址。拿这个地址找到指向的表,
找到偏移量,然后计算找到a.
验证一下上面说的
一个是普通赋值,一个是切片
这样它的模型就对上了,它不需要区分是子类对象还是父类对象,它的动作是一样的。
汇编指令是一样的。
虚继承不是要解决数据冗余的问题吗?怎么还变大了?
这块变大了,是因为a太小了。它要解决数据冗余是有成本的,增加两个指针。
但是这个成本是固定的,而这个数据的大小是不确定的。
为什么不需要考虑指向的空间?
一个类可能定义很多很多对象,每个对象空间都要多8个字节,
但是这个空间不是每个对象独立的,是共同分担的,因为它们的偏移量是不变的。
真正的消耗并不在这里。
自己可以单独去验证一下。
小问题
1.如果A有多个成员,需不需要增加指针?
不需要,首先偏移量不会变,并且它是按照声明顺序去访问的。
内存对齐并不会影响这个。
举个例子。ptr是如何访问这些成员的?
这里也有内存对齐。编译器也是根据内存对齐的规则去算的。
写编译器的人真的是高手中的高手
虚继承是有一定的效率损失的。
在实际当中我们不要去玩菱形继承,效率上有损失而且出问题了很难分析。
看一下下面这道题,结果是什么,看一下你还想不想玩菱形继承。
这道题也没什么,就是在虚继承的基础上加入了构造函数。
这里打印顺序是什么?
这里面调用三次A的构造函数,难道打印了三次A吗?
打印三次A就意味着A被初始化三次。看起来好像这样。
编译器肯定做了很多特殊处理。
A的构造函数实际应该是调用一次,因为只有一份A.
现在还有一个问题,调的这个A,是B里的A,还是C里的A,还是单独的A?
肯定是D里面单独调用这个A最好。外面单独去搞更好。
这里面的运行顺序是怎样的呢?
为什么先调A?
因为初始化列表初始化的顺序跟出现的顺序无关,跟声明的顺序有关,
谁先声明谁先初始化。
注意,谁先被继承谁就先声明
这样出题难度更大。
实际结果没有变。
组合
什么是组合?举个例子。
D想复用C,可以像上面这样复用。
单从关系来说AB的继承关系更紧密一些,还是CD的组合关系更紧密一些?
也就是耦合度,继承的耦合度更高一些,为什么?
实际当中组合更好
我们之前的适配器就用了组合。
为什么还要用继承?
有些关系适合继承那就用继承。另外多态的基础必须是继承。
两个都可以那就用组合,适合用继承就用继承,
适合用组合就用组合。
容器里面有没有迭代器?
容器里面是没有迭代器的,除了vector.
容器只是通过begin()去获取那个位置的迭代器,并不是里面有。
相关文章:

C++中的继承(二)
文章目录 前言多继承虚继承虚继承的底层组合 前言 上一篇文章我们C的正常继承其实已经讲完了,但是后面还有一个大坑。 实际当中继承有单继承和多继承。 单继承就是直接继承一个类。 只有一个直接父类的就叫做单继承。 如果是单继承那就比较简单。 现实世界除了有…...
sklearn多项式回归和线性回归
什么是线性回归? 回归分析是一种统计学方法,用于研究自变量和因变量之间的关系。它是一种建立关系模型的方法,可以帮助我们预测和解释变量之间的相互作用。 回归分析通常用于预测一个或多个因变量的值,这些因变量的值是由一个或多…...

Postman报:400 Bad Request
● 使用Postman发送Post请求报400,入参为JSON; 二、分析 1、Postman请求并没有请求到后台Api(由于语法错误,服务器无法理解请求); 2、入参出错范围:cookie、header、body、form-data、x-www-f…...
apache poi_5.2.5 实现表格内某一段单元格的复制
apache poi_5.2.5 实现表格内,某一段单元格的复制。 实现思路 1.定位开始位置 2.从开始位置之后,在行索引集合中添加行索引下标 3.截至到结束位置。 4.对行索引集合去重,并循环行索引集合 5.利用XWPFTableRow对像的getCtRow().copy()方法&a…...
Oracle重建索引详解
更新:2023-05-17 18:08 一、Oracle重建索引命令 Oracle重建索引可以通过ALTER INDEX命令来完成。下面是示例代码: ALTER INDEX index_name REBUILD [PARAMETERS];其中,index_name是需要重建的索引名称,PARAMETERS是可选的重建参…...

众和策略证券开户首选:股票增持是好还是坏?大股东增持规定?
股票增持是好仍是坏? 股东增持在一定程度上反映股东对个股比较看好,大量的买单,增加了市场上的多方力气,会推动股价上涨,是一种利好消息。 一般大股东会增持可能是上市公司运营成绩较好,具有较大的发展前…...

UE4移动端最小包优化实践
移动端对于包大小有着严苛的要求,然而UE哪怕是一个空工程打出来也有90+M,本文以一个复杂的工程为例,探索怎么把包大小降低到最小。 一、工程简介 工程包含代码、插件、资源、iOS原生库工程。 二、按官方文档进行基础优化 官方文档 1、勾选Use Pak File和Create comp…...

用户管理第2节课--idea 2023.2 后端--实现基本数据库操作(操作user表) -- 自动生成
一、插件 Settings... 1.1 File -- Settings 1.2 Settings -- Plugins 1.2.1 搜索框,也可以直接搜索 1.3 Plugins -- 【输入 & 搜索】mybatis 1.3.1 插件不同功能介绍 1.3.2 翻译如下 1.4 选中 Update,更新下 1.4.1 更新中 1.4.2 Restart IDE 1…...

java开发面试:常见业务场景之单点登录SSO(JWT)、权限认证、上传数据的安全性的控制、项目中遇到的问题、日志采集(ELK)、快速定位系统的瓶颈
单点登录(SSO) 单点登录,Single Sign On(简称SSO),只需要登录一次,就可以访问所有信任的应用系统。 如果是单个tomcat服务,session可以共享,如果是多个tomcat,那么服务s…...

Java网络编程原理与实践--从Socket到BIO再到NIO
文章目录 Java网络编程原理与实践--从Socket到BIO再到NIOSocket基本架构Socket 基本使用简单一次发送接收客户端服务端 字节流方式简单发送接收客户端服务端 双向通信客户端服务端 多次接收消息客户端服务端 Socket写法的问题BIO简单流程BIO写法客户端服务端 BIO的问题 NIO简述…...

ARM GIC(三) gicv2架构
ARM的cpu,特别是cortex-A系列的CPU,目前都是多core的cpu,因此对于多core的cpu的中断管理,就不能像单core那样简单去管理,由此arm定义了GICv2架构,来支持多核cpu的中断管理 一、gicv2架构 GICv2,支持最大8个core。其框图如下图所示: 在gicv2中,gic由两个大模块组成: …...

第4章Netty第二节入门案例+channel,future,promise介绍
需求 开发一个简单的服务器端和客户端 客户端向服务器端发送 hello, world服务器仅接收,不返回 <dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.39.Final</version> </d…...

【论文笔记】3D Gaussian Splatting for Real-Time Radiance Field Rendering
原文链接:https://arxiv.org/abs/2308.04079 1. 引言 网孔和点是最常见的3D场景表达,因其是显式的且适合基于GPU/CUDA的快速栅格化。神经辐射场(NeRF)则建立连续的场景表达便于优化,但渲染时的随机采样耗时且引入噪声…...
【生物信息学】层次聚类过程
文章目录 一、理论二、实践过程1过程2 一、理论 层次聚类是一种基于树状结构的聚类方法,它试图通过在不同层次上逐步合并或分裂数据集来构建聚类结构。这个树状结构通常被称为“树状图”(dendrogram),其中每个节点代表一个数据点或…...

变分自动编码器【03/3】:使用 Docker 和 Bash 脚本进行超参数调整
一、说明 在深入研究第 1 部分中的介绍和实现,并在第 2 部分中探索训练过程之后,我们现在将重点转向在第 3 部分中通过超参数调整来优化模型的性能。要访问本系列的完整代码,请访问我们的 GitHub 存储库在GitHub - asokraju/ImageAutoEncoder…...

KnowLM知识抽取大模型
文章目录 KnowLM项目介绍KnowLM项目的动机ChatGPT存在的问题 基于LLama的知识抽取的智析大模型数据集构建及训练过程预训练数据集构建预训练训练过程指令微调数据集构建 指令微调训练过程开源的数据集及模型局限性信息抽取Prompt 部署环境配置模型下载预训练模型使用LoRA模型使…...

MySQL数据库 索引
目录 索引概述 索引结构 二叉树 B-Tree BTree Hash 索引分类 索引语法 慢查询日志 索引概述 索引 (index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种…...
ES 错误码
2xx状态码(如200)表示请求成功处理,并且不需要重试。 400状态码表示客户端发送了无效的请求,例如请求的语法有误或缺少必需的参数。在这种情况下,重试相同的请求很可能会导致相同的错误。因此,应该先检查并…...

听GPT 讲Rust源代码--src/tools(18)
File: rust/src/tools/rust-analyzer/crates/ide-ssr/src/from_comment.rs 在Rust源代码中的from_comment.rs文件位于Rust分析器(rust-analyzer)工具的ide-ssr库中,它的作用是将注释转换为Rust代码。 具体来说,该文件实现了从注…...

如何实现设备远程控制?
在工业自动化领域,设备远程控制是一项非常重要的技术。它使得设备可以在远离现场的情况下进行远程操作和维护,大大提高了设备的可用性和效率。 设备远程控制的应用场景有哪些? 远程故障排除:当设备出现故障时,工程师…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...

基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...

DingDing机器人群消息推送
文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人,点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置,详见说明文档 成功后,记录Webhook 2 API文档说明 点击设置说明 查看自…...

uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...
Vite中定义@软链接
在webpack中可以直接通过符号表示src路径,但是vite中默认不可以。 如何实现: vite中提供了resolve.alias:通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...
API网关Kong的鉴权与限流:高并发场景下的核心实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中,API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关,Kong凭借其插件化架构…...
Linux安全加固:从攻防视角构建系统免疫
Linux安全加固:从攻防视角构建系统免疫 构建坚不可摧的数字堡垒 引言:攻防对抗的新纪元 在日益复杂的网络威胁环境中,Linux系统安全已从被动防御转向主动免疫。2023年全球网络安全报告显示,高级持续性威胁(APT)攻击同比增长65%,平均入侵停留时间缩短至48小时。本章将从…...

【51单片机】4. 模块化编程与LCD1602Debug
1. 什么是模块化编程 传统编程会将所有函数放在main.c中,如果使用的模块多,一个文件内会有很多代码,不利于组织和管理 模块化编程则是将各个模块的代码放在不同的.c文件里,在.h文件里提供外部可调用函数声明,其他.c文…...