【C++】——继承(下)
【C++】——继承(下)
- 5 继承与友元
- 6 继承与静态成员
- 7 多继承
- 7.1 继承模型
- 7.2 菱形继承的问题
- 7.3 虚继承
- 7.4 多继承中的指针偏移问题
- 8 组合与继承
5 继承与友元
友元关系不能被继承。即一个函数是父类的友元函数,但不是子类的友元函数。也就是说父类的友元不能访问子类的私有和保护成员
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;
}int main()
{Person p;Student s;Display(p, s);return 0;
}
这里出现了许多报错,但没关系,我们先看框出来的这一条
这条报错说缺少","
,一般出现这种报错,而我们检查出并没有","
的相关问题时,通常都是 类型出问题了,一般是我们没有定义某个类型但我们直接去使用就会出现这种报错。
编译器遇到一个类型、变量、函数时都只会向上查找,这是为了提高编译速度
出现报错的原因是friend void Display(const Person& p, const Student& s);
中我们使用了 S t u d e n t Student Student 类型,它的定义在下面。但 S t u d e n t Student Student 是 P e r s o n Person Person 的继承,又不可能将其放在 P e r s o n Person Person 的前面,我们可以在 P e r s o n Person Person 前加上 S t u d e n t Student Student 的前置声明
class Student;
解决这个问题后报错就少很多啦,我们再看看剩下的报错
这里就是友元函数
的问题啦。 D i s p l a y ( ) Display() Display() 是 P e r s o n Person Person 的友元,但友元关系不能继承下来,因此 D i s p l a y ( ) Display() Display() 不是 S t u d e n t Student Student 的友元。解决方法也很简单,在 S t u d e n t Student Student 中加一个有元声明就好
//前置声明
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;
}
6 继承与静态成员
父类定义了 s t a t i c static static 静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个 s t a t i c static static 成员实例。
而普通成员,假设父类有一个_name
,子类继承下来也有另一个_name
,但是他们两个_name
不是同一个,各自是各自的。
我们来演示一下:
class Person
{
public :string _name;static int _count;
};int Person::_count = 0;class Student : public Person
{
protected :int _stuNum;
};int main()
{Person p;Student s;// 这⾥的运⾏结果可以看到⾮静态成员_name的地址是不⼀样的// 说明派⽣类继承下来了,⽗派⽣类对象各有⼀份cout << &p._name << endl;cout << &s._name << endl;// 这⾥的运⾏结果可以看到静态成员_count的地址是⼀样的// 说明派⽣类和基类共⽤同⼀份静态成员cout << &p._count << endl;cout << &s._count << endl;// 公有的情况下,⽗类子类指定类域都可以访问静态成员cout << ++Person::_count << endl;cout << ++Student::_count << endl;// 也可以通过对象进行访问cout << ++p._count << endl;cout << ++s._count << endl;return 0;
}
运行结果:
虽然静态变量可以通过对象访问,但一般不这么做,大多数都是直接指定类域去访问
7 多继承
7.1 继承模型
- 单继承:一个子类只有一个直接父类时,称为这个继承关系为单继承
单继承
- 多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承。多继承对象在内存中的模型是,
先继承的父类在前面,后继承的父类在后面,子类成员放在最后面
多继承
- 菱形继承:菱形继承是多继承的一种特殊情况。菱形继承的问题,从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题,在 A s s i s t a n t Assistant Assistant 的对象中 P e r s o n Person Person 成员会有两份。支持多继承就一定会有菱形继承,像 J a v a Java Java 就直接不支持多继承,规避掉了这里的问题,所以实践中我们是不建议设计出菱形继承这样的继承模型的
菱形继承
7.2 菱形继承的问题
菱形继承是很坑的,有数据冗余(浪费空间)和二义性(不知访问哪个)的问题,现实中不想被打就不要设计出菱形继承(多继承是没问题的,不要搞出菱形继承就行)。
我们通过代码来看一下菱形继承存在的问题
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; // 主修课程
};
上述就是菱形继承, P e r s o n Person Person 成员在 A s s i s t a n t Assistant Assistant 对象中有两份。我们试着访问 P e r s o n Person Person 的成员 _ n a m e name name
int main()
{Assistant a;a._name = "peter";return 0;
}
我们需要指定访问那个父类成员的成员可以解决二义性的问题,但是数据冗余问题无法解决
int main()
{Assistant a;a.Student::_name = "xxx";a.Teacher::_name = "yyy";return 0;
}
虽然解决了二义性,但数据冗余问题无法解决,Person 有两份
不仅如此,因为数据冗余,导致菱形继承对象的大小特别大
int main()
{Assistant a;cout << sizeof(Assistant) << endl;return 0;
}
那么现实中有没有人设计出菱形继承呢?还真有,我们简单看一下
虽然但是,别学他
7.3 虚继承
为了解决菱形继承的问题,C++ 引入了虚继承的概念,新增关键字: v i r t u a l virtual virtual
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; // 主修课程
};
注意:因为是Person
有数据冗余和二义性,所以是 Student 和 Teacher 继承 Person 时是虚继承,加 virtual 关键字
int main()
{Assistant a;a._name = "peter";a.Student::_name = "xxx";a.Teacher::_name = "yyy";return 0;
}
加了虚继承后,就只有一份 P e r s o n Person Person 成员了,共用了。既可以直接访问也可以指定类域访问。
这里虽然监视窗口显示的是 3 个 P e r s o n Person Person,到那实际上他们是共用的
。
其实库中的菱形继承也是用虚继承来解决的
template<class CharT, class Traits = std::char_traits<CharT>>
class basic_ostream : virtual public std::basic_ios<CharT, Traits>
{};
template<class CharT, class Traits = std::char_traits<CharT>>
class basic_istream : virtual public std::basic_ios<CharT, Traits>
{};
那虚继承对 S t u d e n t Student Student 和 T e a c h e r Teacher Teacher 有什么影响吗?
底层的角度有一些影响,用的角度没有影响。从用的角度来说 S t u d e n t Student Student 和 T e a c h e r Teacher Teacher 就是一个单继承。 v i r t u a l virtual virtual 真正影响的是下面的 A s s i s t a n t Assistant Assistant
注: S t u d e n t Student Student 和 T e a c h e r Teacher Teacher 都要给虚继承,不能只给其中一个虚继承
那这样算不算菱形继承呢?
算的,菱形继承并不是看是否构成菱形,而是看某个类是否被重复继承,是否产生数据冗余和二义性。
那如果我们要加虚继承,该加在哪里呢?
B B B 和 C C C。因为虚继承是:谁会产生数据冗余和二义性,谁继承它时就要虚继承。在 E E E 中是 A A A 有数据冗余二义性,所以 B B B、 C C C 继承 A A A 时使用虚继承
那能不能全部加上虚继承呢? D D D 和 E E E 都加上
不要,毕竟是药三分毒。
总结:单继承和多继承可以用,但使用多继承时不要设计出菱形继承
7.4 多继承中的指针偏移问题
- 下面说法中正确的是()
A:p1 == p2 == p2 B:p1 < p2 < p3 C:p1 == p3 != p2 D:p1 != p2 != p3 E:p2 == p3 != p1
class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base2, public Base1 { public: int _d; };
int main()
{Derive d;Base1* p1 = &d;Base2* p2 = &d;Derive* p3 = &d;return 0;
}
要做对这道题,我们要知道下面两个知识点:
- 子类给给父类的对象/指针/引用,会发生切片;子类对象给子类指针,
指向的是整个子类对象
,子类对象给父类指针,指向的是子类对象中父类的那一部分
- 多继承对象在内存中的声明是
先继承的基类在前⾯,后⾯继承的基类在后⾯,派⽣类成员在放到最后⾯
首先, p 3 p3 p3 指向开始肯定是没问题的。对 p 2 p2 p2:将子类对象给父类指针,其会指向子类中父类的那一部分, p 2 p2 p2 指向 B a s e 2 Base2 Base2,因为 B a s e 2 Base2 Base2先继承
,所以 p 2 p2 p2 也指向开始
。 p 1 p1 p1 则指向子类对象 B a s e 1 Base1 Base1 的部分, p 1 p1 p1 不可能再指向开始,它发生了偏移
。这题选 E
8 组合与继承
- p u b l i c public public 继承是一种 i s is is- a a a 的关系。也就是说每个子类对象都是一个父类对象
- 组合 是一种 h a s has has- a a a 的关系。假设 B B B 组合了 A A A,每个 B 对象都有一个 A 对象
什么意思呢?我举个例子大家就明白了
//组合
class Stack
{
public://成员函数private:vector<int> v;
};//继承
class Stack : public vector<int>
{};
我们再来看下 i s is is- a a a 与 h a s has has- a a a:
组合
是一种 h a s has has- a a a 的关系:栈有一个数组继承
是一种 i s is is- a a a 的关系:栈是一个数组
- 继承允许你根据父类的实现定义子类的实现。这种通过生成子类的复用通常称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,父类的内部细节对子类可见。继承一定程度破坏了父类的封装,父类的改变,对子类有很大的影响。子类和父类间的
依赖关系很强,耦合度很高
- 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象类获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合之间
没有很强的依赖关系,耦合度低
。优先使用对象组合有助于你保持每个类的封装
什么是黑什么是白呢?
测试中分为黑盒测试和白盒测试
- 黑盒测试:指看不见里面的实现,也不需要看见你的实现。比如现在有一个新的 APP,我只需要从用户用的角度去测试。黑盒测试有些地方也叫功能测试
- 白盒测试:白盒测试要了解其底层实现,从代码运行逻辑角度进行测试。
白盒测试明显比黑盒测试更难
那是耦合度高好还是耦合度低好呢?肯定是耦合度低好
比如现在有两个模块,模块一有100个接口函数,且全部对模块二透明,那模块二就能使用模块一的任意多个函数接口来实现自己的功能。但如果模块一今天把这个函数的参数类型改了,明天吧那个函数的参数个数给改了,因为模块二是依赖模块一的,模块二也只能跟着改。
但如果模块一虽然有100个函数接口,但只提供5个最关键函数接口给模块二。这时,两模块之间的耦合度就大大降低,只要模块一不改那5个函数,其他95个函数随便改都不影响模块二。
所以软件工程中提出了一个低耦合、高内聚的概念。高内聚可以认为是一个模块里面关系越紧密越好, 没关系的就拿出去
所以两个类的关系是继承好还是组合好呢?明显是组合更好
,因为继承关系下,父类的任何改动都可能会影响子类
- 优先使用组合,而不是继承。实际尽量多去用组合,组合的耦合度低,代码维护性好。不过也不是那么绝对,类之间的关系更适合继承( i s is is- a a a)那就用继承,另外要实现多态,也必须要继承。类之间的关系既适合用继承( i s is is- a a a)也适合组合( h a s has has- a a a),就用组合。
比如: T i r e Tire Tire(轮胎) 和 C a r Car Car(车) 更符合 h a s has has- a a a 的关系
class Tire
{
protected:string _brand = "Michelin"; // 品牌size_t _size = 17; // 尺⼨
};class Car {
protected:string _colour = "⽩⾊"; // 颜⾊string _num = "陕ABIT00"; // ⻋牌号Tire _t1; // 轮胎Tire _t2; // 轮胎Tire _t3; // 轮胎Tire _t4; // 轮胎
};
车 h a s has has- a a a 轮胎是正常的,但车 i s is is- a a a 轮胎就是错的
但 C a r Car Car 和 B M W BMW BMW / B e n z Benz Benz(宝马/奔驰)更符合 i s is is- a a a 的关系
class BMW : public Car
{
public:void Drive() { cout << "好开-操控" << endl; }
};
// Car和BMW/Benz更符合is-a的关系
class Benz : public Car {
public:void Drive() { cout << "好坐-舒适" << endl; }
};
只能说宝马/奔驰 i s is is- a a a 车,不能说宝马/奔驰 h a s has has- a a a 车
//组合
class Stack
{
public://成员函数private:vector<int> v;
};//继承
class Stack : public vector<int>
{};
但既可以说:栈有一个数组,也可以说:栈是一个数组,这种情况下优先使用组合。
判断两个类型适合组合还是继承,就用 i s is is- a a a 和 h a s has has- a a a 来判断
好啦,本期关于 priority_queue 与仿函数 的知识就介绍到这里啦,希望本期博客能对你有所帮助。同时,如果有错误的地方请多多指正,让我们在 C++ 的学习路上一起进步!
相关文章:

【C++】——继承(下)
【C】——继承(下) 5 继承与友元6 继承与静态成员7 多继承7.1 继承模型7.2 菱形继承的问题7.3 虚继承7.4 多继承中的指针偏移问题 8 组合与继承 5 继承与友元 友元关系不能被继承。即一个函数是父类的友元函数,但不是子类的友元函数。也就是说…...

Pandas处理时间序列之光谱分析与聚类
import matplotlib.pylab as plt %matplotlib inline import numpy as np from numpy import fft import pandas as pd 一、光谱分析 • 将时间序列分解为许多正弦或余弦函数的总和 • 这些函数的系数应该具有不相关的值 • 对正弦函数进行回归 光谱分析应用场景 基于光谱的…...
【WebGIS】Cesium:GeoJSON加载
GeoJSON 是一种常用的地理空间数据格式,它用于表示简单的地理要素及其属性,并且被广泛应用于 Web 地图和 GIS 系统中。在 Cesium 中,GeoJSON 文件可以很方便地加载到三维场景中展示,并且可以添加样式和事件处理。本文将为你提供详…...

PageHelper实现分页查询
前端发送的请求参数 后端返回的对象类型 Controller类实现 /*** 员工分页查询* param employeePageQueryDTO* return*/GetMapping("/page")ApiOperation("员工分页查询")public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO)…...

边缘检测评估方法:FOM、RMSE、PSNR和SSIM对比实验和理论研究
图像分割与边缘检测是密切相关的计算机视觉任务。以下图1展示了一个海岸线分割模型的输出示例: 图1: 分割掩码到边缘图的转换过程(数据集:LICS) 模型将每个像素分类为陆地或海洋(分割掩码)。随后,海岸线被定义为分类发生变化的像素位置(边缘图)。边缘检测可以通过提取图像分割…...
MySql 多表查询
多表查询:指从多张表中查询数据。 笛卡儿积:笛卡儿积是指在数学中,两个集合(A集合 和 B集合)的所有组合情况。 连接查询 内连接:相当于查询A、B交集部分数据外连接 左外连接:查询左表所有数据…...

数学建模算法与应用 第11章 偏最小二乘回归及其方法
目录 11.1 偏最小二乘回归概述 11.2 Matlab 偏最小二乘回归命令 Matlab代码示例:偏最小二乘回归 11.3 案例分析:化学反应中的偏最小二乘回归 Matlab代码示例:光谱数据的PLS回归 习题 11 总结 偏最小二乘回归(Partial Least …...

【MATLAB代码】TDOA定位,4个基站、3个时间差、三维定位(可直接复制粘贴到MATLAB上运行)
文章目录 程序结构源代码运行结果代码结构输入输出解析该MATLAB代码实现了基于时间差定位(TDOA, Time Difference of Arrival)的方法,使用最小二乘法在三维空间中估计一个未知点的位置。该算法利用一个主锚点和三个副锚点的已知位置,通过计算信号传播时间差来推算出目标位置…...

uniapp引入ThorUI的方法
1、下载文件 2、复制相应的文件除了pages 3、往项目中复制即可 4、引入即可实现 5、添加easycome自动引入...
面试官:手写一个New
在JavaScript中,new操作符主要用于创建一个对象示例。通过new操作符,可以创建一个新的对象,并将这个对象的原型链只想一个构造函数的原型对象,然后执行构造函数中的代码初始化这个新对象。 常见的new的使用为 new Array() new Set…...

merlion的dashboard打开方法
安装好merlion包后,在anaconda prompt中进行如下图操作: 先进入创建好的虚拟环境:conda activate merlion再执行命令:python -m merlion.dashboard在浏览器中手动打开图中的地址: http://127.0.0.1:8050 打开后的界面…...

自监督学习:引领机器学习的新革命
引言 自监督学习(Self-Supervised Learning)近年来在机器学习领域取得了显著进展,成为人工智能研究的热门话题。不同于传统的监督学习和无监督学习,自监督学习通过利用未标注数据生成标签,从而大幅降低对人工标注数据…...

Web安全常用工具 (持续更新)
前言 本文虽然是讲web相关工具,但在在安全领域,没有人是先精通工具,再上手做事的。鉴于web领域繁杂戎多的知识点(工具是学不完的,哭),如果你在本文的学习过程中遇到没有学过的知识点࿰…...

不踩坑,青龙面板小问题解决方案~
好久没写了,随手记录一下。 1. 新建目录 很多人跟我一样入坑的手机免root青龙面板,一般用的都是2.10.13版本。这个版本比较早,似乎没有新建目录的功能(也可能是我不会用哈哈),以下是对比图: 大家…...

2025秋招倒计时---招联金融
【投递方式】 直接扫下方二维码,或点击内推官网https://wecruit.hotjob.cn/SU61025e262f9d247b98e0a2c2/mc/position/campus,使用内推码 igcefb 投递) 【招聘岗位】 后台开发 前端开发 数据开发 数据运营 算法开发 技术运维 软件测试 产品策…...

基于yolov8、yolov5的果蔬检测系统(含UI界面、数据集、训练好的模型、Python代码)
项目介绍 项目中所用到的算法模型和数据集等信息如下: 算法模型: yolov8、yolov8 SE注意力机制 或 yolov5、yolov5 SE注意力机制 , 直接提供最少两个训练好的模型。模型十分重要,因为有些同学的电脑没有 GPU࿰…...

出海快报 | “三消+短剧”手游横空出世,黄油相机“出圈”日本市场,从Q1看日本手游市场趋势和机会
编者按:TopOn出海快报栏目为互联网出海从业者梳理出海热点,供大家了解行业最新发展态势。 1.“三消短剧”横空出世,融合创新手游表现亮眼 随着竞争的加剧,新产品想要突出重围,只能在游戏中加入额外的元素。第一次打开…...

Linux高效查日志命令介绍
说明:之前介绍Linux补充命令时,有介绍使用tail、grep命令查日志; Linux命令补充 今天发现仅凭这两条命令不够,本文扩展介绍一下。 命令一:查看日志开头 head -n 行数 日志路径如下,可以查看程序启动是否…...

非线性关卡设计
【GDC】如何设计完全非线性的单人关卡_DOOM (bilibili.com) 本文章算是此视频的简单笔记,更详细还请看视频 设计完全非线性关卡强调自由移动和沙盒式玩法,鼓励玩家进行不可预测的移动和空间探索。讲解者分享了设计此类关卡的具体步骤,包括明…...

Qt-链接数据库可视化操作
1. 概述 Qt 能够支持对常见数据库的操作,例如: MySQL、Oracle、SqlServer 等等。 Qt SQL模块中的API分为三层:驱动层、SQL接口层、用户接口层。 驱动层为数据库和SQL接口层之间提供了底层的桥梁。 SQL接口层提供了对数据库的访问࿰…...

UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...

MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台
🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
jmeter聚合报告中参数详解
sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample(样本数) 表示测试中发送的请求数量,即测试执行了多少次请求。 单位,以个或者次数表示。 示例:…...