C++第三十一弹---C++继承机制深度剖析(下)

news2024/11/15 23:46:48

个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】

1.菱形继承及菱形虚拟继承

1.1 单继承

单继承:一个子类只有一个直接父类时称这个继承关系为单继承

Student的直接父类是PersonPostGraduate的直接父类是Student,且都只有一个直接父类,因此均为单继承。

注意:

PostGraduate类只是间接继承了Person,因此依旧为单继承关系。

1.2 多继承 

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承。

Assistant子类既继承了Student父类,又继承了Teacher父类,且均为直接继承,因此为多继承关系。 

1.3 菱形继承

菱形继承:菱形继承多继承的一种特殊情况,如下图:

菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。
Assistant的对象中Person成员会有两份。 

从上图我们可以看到,Assistant 类既继承了 Student 类又继承了 Teacher 类 ,而 Student 类和 Teacher 类又都继承了 Person 类, 因此 Assistant 类中有两份 Person 的成员,这也就是菱形继承所带来的数据冗余和二义性问题。

class Person
{
public:
	string _name; // 姓名
};
class Student : public Person
{
protected:
	int _num; //学号
};
class Teacher : public Person
{
protected:
	int _id; // 职工编号
};
// Assistant有两份Person类,存在数据冗余和二义性问题
class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};

 主函数

int main()
{
	Assistant a;
	// a._name = "peter"; // 这样会有二义性无法明确知道访问的是哪一个
	// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";
}

监视窗口 

从监视窗口我们可以看到,Assistant类确实有两个_name成员,且不相同,即存在数据冗余问题。

解决办法:

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和
Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用。

class Person
{
public:
	string _name; // 姓名
};
// 腰部加virtual关键字,构成虚拟继承
class Student : virtual public Person
{
protected:
	int _num; //学号
};
// 腰部加virtual关键字,构成虚拟继承
class Teacher : virtual public Person
{
protected:
	int _id; // 职工编号
};

class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};

主函数 

int main()
{
	Assistant a;
	a._name = "peter"; 
}

 测试结果

1.3.1 虚拟继承解决数据冗余和二义性的原理


为了研究虚拟继承原理,我们给出了一个简化的菱形继承继承体系,再借助内存窗口观察对象成
员的模型。

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;
}

下图是菱形继承的内存对象成员模型:这里可以看到数据冗余。

虚拟继承代码 

class A
{
public:
	int _a;
};
class B : virtual public A
{
public:
	int _b;
};
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;
}

下图是菱形虚拟继承的内存对象成员模型:这里可以分析出D对象中将A放到了对象组成的最下
,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指
向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表虚基表中存的偏移量,通过偏移量
可以找到下面的A(虚基类)。

 

B 和 C 对象也满足菱形虚拟继承的对象模型

int main()
{
    B* pb = &d;
    C* pc = &d;
    return 0;
}

画图演示

下面是上面的Person关系菱形虚拟继承的原理解释:

总结:

在实践中可以设计多继承,但是切记不要实现菱形继承,因为太复杂,容易出各种问题。

2.继承的总结和反思

  • 1. 很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题。
  • 2. 多继承可以认为是C++的缺陷之一,很多后来的OO语言都没有多继承,如Java。
  • 3. 继承和组合

        public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。

        组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。

// 组合 一个类中的包含另一个类 has-a 关系
class A
{
private:
	int _a;
};

class B
{
private:
	A _aa;
	int _b;
};

组合和继承很类似,都可以使用其他类的成员,但是组合只能使用public成员,而继承既可以使用public成员,也可以使用protected成员,除此之外还能间接使用private成员。


继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见(子类可以访问基类保护成员)。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高

低耦合:类和类之间,模块与模块之间关系不那么紧密,关联不高。

高耦合:类和类之间,模块与模块之间关系很紧密,关联很高。


对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。


实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。 

总结:

适合is-a关系(学生是人),就用继承;适合has-a关系(汽车有轮胎),就用组合;is-a 和 has-a 关系都可以(栈是特殊的链表,栈有链表),就用组合。

3.笔试面试题


1. 什么是菱形继承?菱形继承的问题是什么?

        存在四个类,分别是person,student,teacher,assistant,student和teacher分别继承自person,assistant同时继承student和teacher,这种现象就是菱形继承,更深层次的说,assistant内部维护了两份person的成员变量,一份来自student,一份来自person,菱形继承是一种特殊的多继承
  因为维护了两份person的成员变量,因此,在使用的时候,不知道是使用的来自teacher还是student,因此会存在数据冗余二义性的问题。


2. 什么是菱形虚拟继承?如何解决数据冗余和二义性的

        菱形虚拟继承是一种解决菱形继承问题的技术。在 C++ 中,通过虚继承(virtual inheritance)可以确保最基类在菱形继承结构中只被继承一次,从而避免数据冗余和二义性问题。

        菱形虚拟继承通过使用virtual关键字来修饰共同的基类,确保了子类在继承多个拥有共同基类的父类时,只会在内存中保留一份基类数据,并且使用唯一的基类指针来访问基类成员,从而解决了数据冗余和二义性的问题。


3. 继承和组合的区别?什么时候用继承?什么时候用组合?

        public继承是一种is-a的关系,也就是说每一个派生类对象都是一个基类对象,组合是一种has-a的关系,假设B组合了A,每一个B对象中都有一个A对象。继承一定程度上破坏了基类的封装性,基类的改变对于派生类有很大的影响,派生类和基类间的依赖关系很强,耦合度很高。并且这种通过派生类的方式的复用通常被称为白箱复用(white-box reuse)。对象通过组合来获得更多功能,要求被组合的对象有良好定义的接口,这种复用风格称为黑箱复用,组合类之间没有很强的依赖关系,耦合度低。

        适合is-a关系(学生是人),就用继承;适合has-a关系(汽车有轮胎),就用组合;is-a 和 has-a 关系都可以(栈是特殊的链表,栈有链表),就用组合。

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

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

相关文章

双向循环链表和内核链表

目录 双向循环链表 结构设计 初始化 插入 删除 遍历(顺序/逆序,打印输出) 查找 主函数 内核链表 内核链表初始化定义 内核链表的插入定义 内核链表的遍历定义 内核链表剔除节点定义 内核链表如何移动节点定义 内核链表的应用 临时补充…

身在职场,不得不提防的几个问题,能让少走许多弯路

职场路本就崎岖,如果再走了弯路,脚下的路将会更漫长且难走。 谁不想一帆风顺,可谁又能一帆风顺?不是人心险恶,而是立场本就不同,为了各自的利益考虑无可厚非。 你可以说凭借能力获取利益,为什…

CVE-2023-37569~文件上传【春秋云境靶场渗透】

# 今天我们拿下CVE-2023-37569这个文件上传漏洞# 经过简单账号密码猜测 账号:admin 密码:password# 找到了文件上传的地方# 我们直接给它上传一句话木马并发现上传成功# 上传好木马后,右键上传的木马打开发现上传木马页面# 直接使用蚁剑进行连…

Linux5:Shell编程——函数、重定向

目录 前言 一、函数 1.函数结构 2.函数实例 3.函数传参 二、重定向 1.输出重定向 2.输入重定向 3.同时使用 4.重定向深入了解 5.垃圾桶 总结 前言 Shell编程将会在本章完结 一、函数 1.函数结构 #!/bin/sh # 函数function fun1() {echo "this is a funtion&q…

【有手就行】:从无到有搭建后端SpringBoot项目

前言 想静下心来写点东西,但是确实想不到该写点啥,可能是少了点感觉吧 😢。前面刚整理了下前端VUE,就想了下把后端也一起整理下吧,免得换电脑了安装环境又要弄半天,那就开搞吧 首先 准备环境 1.安装IDEA…

云计算实训21——mysql-8.0.33-linux-glibc安装及使用

一、mysql-8.0.33-linux-glibc安装 安装步骤 1.解压 tar -xvf mysql-8.0.33-linux-glibc2.12-x86_64.tar.xz 2.清空其他环境 rm -rf /etc/my.cnf 3.安装依赖库 yum list installed | grep libaio 4.创建用户 useradd -r -s /sbin/nologin mysql 查看 id mysql 5.创建mysql-fi…

PXE批量网络装机(超详细实验教程)教会你自动化批量安装linux 系统 红帽7

1.创建自动化安装服务器 1.1. 搭建本地厂库 写入rpm.re文件内容 [rhel7]namerhel7baseurlfile:///rhel7gpgcheck0 Yum makecache 测试是否挂载成功 1.2.关闭虚拟机的本地DHCP 1.3下载必要软件 下载图形化脚本自动生成工具方便编写脚本 下载dhcp分配ip httpd 搭建网页 …

数据排序之旅

1、排序的概念 排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。 稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序…

vue3 + i18n 实现国际化并动态切换语言

安装 npm install vue-i18n// index.ts import { createI18n } from vue-i18n // 语言包 import ch from ./ch import en from ./enconst lang localStorage.getItem(localeLangD) || ch if (!localStorage.getItem(localeLangD)) {localStorage.setItem(localeLangD, lang) …

linux文本命令:文本处理工具awk详解

目录 一、概述 二、基本语法 1、awk 命令的基本语法 2、常用选项 3、获取帮助 三、工作原理 四、 功能特点 五、分割字段 六、 示例 1. 打印所有行 2. 计算总和 3. 过滤特定模式 4. 使用多个模式 5. 复杂的脚本 6. 自定义分隔符 7. 打印指定列 8. 使用 BEGIN …

微信小程序教程011-1:京西购物商城实战

文章目录 1、起步1.1 uni-app简介1.2 开发工具1.2.1 下载HBuilderX1.2.2 安装HBuilderX1.2.3 安装scss/sass编译1.2.4 快捷键方案切换1.3 创建uni-app项目1.4 目录结构1.5 把项目运行到微信开发者工具1.6 使用Git管理项目1.6.1 本地管理1.6.2 把项目托管到码云1、起步 1.1 uni…

【Unity】3D功能开发入门系列(五)

Unity3D功能开发入门系列(五) 一、预制体(一)预制体(二)预制体的创建(三)预制体实例(四)预制体的编辑 二、动态创建实例(一)动态创建实…

2024/8/4 汇川变频器低压产品分类选型

VF就是通过电压、频率控制 矢量就是通过开环(svc)和闭环(fvc) MD310、MD200 开环,不支持闭环,无法接编码器 290 、200s、280、都是VF控制

有哪些供应链管理方法?详解四种常用的供应链管理方法!

在当今复杂多变的商业环境中,供应链管理已成为企业获取竞争优势的关键。有效的供应链策略不仅能提升企业的响应速度和市场适应性,还能显著降低成本、提高效率。本文将深入探讨几种主流的供应链管理方法,包括快速反应、有效客户反应、基于活动…

LeetCode 0572.另一棵树的子树:深搜+广搜(n^2做法就能过,也有复杂度耕地的算法)

【LetMeFly】572.另一棵树的子树:深搜广搜(n^2做法就能过,也有复杂度耕地的算法) 力扣题目链接:https://leetcode.cn/problems/subtree-of-another-tree/ 给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 s…

DEBUG:sw模板不对

问题 sw自带模板不合适 解决 工具 选项 文件位置 自己新建一个文件夹 放入模板 (三维 二维各一个 一般就是统一标准 可以自己新建个模板)

深度学习笔记(神经网络+VGG+ResNet)

深度学习 主要参考博客常用英语单词 概念应用神经网络基础神经网络基本结构 超参数超参数是什么常用超参数超参数搜索过程常用超参数调优办法(通过问题复杂度和计算资源选择) 激活函数介绍为什么要使用激活函数推荐博客 sigmoid激活函数(使用…

【教程-时间序列预测】PyTorch 时间序列预测入门

文章目录 from博客: https://zhajiman.github.io/post/pytorch_time_series_tutorial/#%E9%AB%98%E7%BA%A7%E6%96%B9%E6%B3%95%E8%87%AA%E5%9B%9E%E5%BD%92%E6%A8%A1%E5%9E%8B 数据集产生 窗口 也是难点!

工作中,如何有效解决“冲突”?不回避,不退让才是最佳方式

职场里每个人都在争取自己的利益,由于立场的不同,“冲突”不可避免。区别在于有些隐藏在暗处,有些摆在了台面上。 隐藏在“暗处”的冲突,表面上一团和气,实则在暗自较劲,甚至会有下三滥的手段;…

常见API(二)

API 应用程序编程接口,提高编程效率。本次学习了Object类,Objects工具类,包装类,StringBuilder,StringBuffer,和StringJoiner。 目录 1.Object 常见方法: 2.Objects 常见方法: 3…