一文带你掌握多继承,菱形继承以及虚拟继承

news2024/11/27 20:32:16

在这里插入图片描述

🎈个人主页:🎈 :✨✨✨初阶牛✨✨✨
🐻强烈推荐优质专栏: 🍔🍟🌯C++的世界(持续更新中)
🐻推荐专栏1: 🍔🍟🌯C语言初阶
🐻推荐专栏2: 🍔🍟🌯C语言进阶
🔑个人信条: 🌵知行合一
🍉本篇简介:>:讲解C++中有关继承中多继承,菱形继承.
金句分享:
✨如果事事都如意,那就不叫生活了.✨

目录

  • 前言
  • 一、隐藏
  • 二、默认成员函数
  • 三、多继承
    • (1)菱形继承
    • 虚继承原理图:
  • 四、继承中的静态成员变量
  • 一、结语

前言

C++中多继承是指一个子类可以从多个父类中继承属性和行为.
其中涉及菱形继承和虚拟继承,显得复杂很多.
需要理解原理.

一、隐藏

继承体系中,子类和父亲类是两个不同的作用域,即子类和父类分别有自己的作用域.

<<同名成员变量>>

由于是两个不同的作用域,所以语法上是在子类和父类中可以定义同名的成员变量的.
但是,在访问子类时,子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问子类自己的成员,这种情况叫隐藏,也叫重定义。
如果不想访问子类的同名成员,可以在子类成员函数中显示调用父类的成员.
显示调用格式: 基类: 基类成员

出现相同的名称的变量终究是容易让人混乱的,还是不建议在子类和父类中定义同名成员变量.

<<同名成员函数>>

基类父类中定义同名的成员函数.

问题:下面的void Print(int a)函数和void Print(double b)函数构成函数重载吗?

class People{
public:
	void Print(){
		cout << "People()" << endl;
		cout << "num=" << _num << endl;
	}
protected:
	int _num=30;
};

class Student :public People{
public:
	void Print(int a){
		cout << "Student()" << endl;
		cout << "num=" << _num << endl;
		cout << "num=" << People::_num << endl;
	}
protected:
	int _num = 66;
};

答案:

不构成,因为函数重载是指在同一个作用域下的同名函数,这里是构成隐藏,并不能直接调用基类的成员函数.
子类和父类中的成员函数只要函数名相同,就会构成隐藏.

示例:

class People{
public:
	void Print(){
		cout << "People()" << endl;
		cout << "num=" << _num << endl;
	}

protected:
	int _num=30;
};

class Student :public People{
public:
	void Print(double b){
		cout << "Student()" << endl;
		cout << "num=" << _num << endl;
		cout << "num=" << People::_num << endl;
	}
protected:
	int _num = 66;
};
int main()
{
	People p1;				//父类对象
	Student s1;				//子类对象

	s1.Print(1);			//调用子类的函数
	cout << endl;

	s1.Print();				//会报错,因为基类和父类不构成函数重载,而子类的函数需要传参
	cout << endl;

	s1.People::Print();		//显示调用父类的函数
	cout << endl;

	p1.Print();				//父类对象调用父类的函数
	cout << endl;
	return 0;
}

二、默认成员函数

还记得C++的六大天选之子吗?

那在派生类中,这几个成员函数是如何生成的呢?

(1) 构造函数:
派生类的构造函数必须调用基类的构造函数,利用基类的构造函数去初始化基类的部分.并且是先调用基类的构造之后,再去构造派生类的成员.
如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用(即带参的构造).

(2)析构函数:
在进行派生类析构时,应当先析构派生类的成员,再析构基类的成员.
为了保证这个顺序,派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。

(3拷贝构造与赋值运算符重载:
派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
派生类的operator=必须要调用基类的operator=完成基类的复制。

重点:
派生类对象初始化先调用基类构造再调派生类构造。
派生类对象析构清理先调用派生类析构再调基类的析构。

测试:

class People
{
public:
	People(string name,int age=13)							
		:_name(name)
		,_age(age)
	{
		cout << "People(string name,int age=13)	" << endl;
	}

	People(const People& p1)		//拷贝构造
		:_name(p1._name)
		,_age(p1._age)
	{
		cout << "People(People& p1)" << endl;

	}

	People& operator =(const People& p1)		//赋值运算符重载
	{
		cout << "People& operator =(const People& p1)" << endl;
		_name = p1._name;
		_age = p1._age;
		return *this;
	}
	~People()
	{
		cout << "~People" << endl;
	}
protected:
	string _name;
	int _age;
};

class Student : public People
{
public:
	Student(const char* name="王也", int num=20214567)
		: People(name)
		, _num(num)
	{
		cout << "Student()" << endl;
	}

	Student(const Student& s)		//拷贝构造
		: People(s)
		, _num(s._num)
	{
		cout << "Student(const Student& s)" << endl;
	}

	Student& operator = (const Student& s)		//赋值运算符重载
	{
		cout << "Student& operator= (const Student& s)" << endl;
		if (this != &s)
		{
			People::operator =(s);
			_num = s._num;
		}
		return *this;
	}

	~Student()
	{
		cout << "~Student()" << endl;
	}
protected:
	int _num; 
};

int main()
{
	Student s1("初阶牛", 20);		//创建一个Student对象
	cout << endl;

	Student s2(s1);					//使用拷贝构造
	cout << endl;

	Student s3;
	s3 = s2;						//使用赋值运算符重载
	cout << endl;

	return 0;
}

运行结果:

在这里插入图片描述

三、多继承

单继承:
C++中的单继承是指一个子类只能继承一个父类的特性。单继承的好处在于它可以保证类之间的关系更加清晰和简单,并且可以减少代码的冗余和复杂度。
在这里插入图片描述
多继承:

在C++中,多继承是指当一个类继承自多个父类时的继承方式。多继承可以让一个类拥有多个不同父类的成员函数和成员变量,提高代码复用性。同时,多继承也会带来一些问题和挑战,例如菱形继承问题,需要合理使用。
在这里插入图片描述

(1)菱形继承

多继承中的特殊情况:菱形继承
在这里插入图片描述
由于StudentTeacher类都继承了People类,
MY类又同时继承了StudentTeacher,则MY类里面有两份People类.

菱形继承的问题:

一方面造成了数据冗余,另一方面也造成的访问数据的二义性,在MY类在访问People类的成员时,会产生二义性,不知道是访问哪一份.

示例:

class People
{
public:
	int _a;
};

class Student : public People
{
public:
	int _b;
};
class Teacher : public People
{
public:
	int _c;
};

class MY : public Student ,public Teacher
{
public:
	int _d;
};
int main()
{
	MY m;
	m.Student::_a = 7;			//不可直接访问,要指定显示访问
	m.Teacher::_a = 5;

	m.Student::_b=12;
	m.Teacher::_c=15;



	m._d = 10;

	return 0;
}

可以通过内存窗口进行观察,在内存窗口输入:&m
观察m的存储结构.
不难发现,m中,People类有两个,也就意味着m对象里面有两个_a,这也就导致了数据冗余,和数据访问的二义性.
在这里插入图片描述
菱形继承的这两个问题该如何解决呢?
猜测祖师爷在这里也没想到多继承可能会引发菱形继承,为了解决这个问题,祖师爷应该也很头痛.
解决方法:虚继承

虚继承的用法,在中间的类(这里可能描述的不清楚)继承前面增加 关键字virtual

class People
{
public:
	int _a;
};


class Student :virtual public People	//加上关键字virtual 成为虚继承
{
public:
	int _b;
};
class Teacher :virtual public People //加上关键字virtual 成为虚继承
{
public:
	int _c;
};

class MY : public Student, public Teacher
{
public:
	int _d;
};
int main()
{
	MY m;
	m.Student::_a = 7;
	m.Teacher::_a = 5;

	m.Student::_b = 12;
	m.Teacher::_c = 15;
	
	m._d = 10;
	return 0;
}

虚拟继承:解决数据冗余和二义性的原理.
在这里插入图片描述
其实在虚继承中,MY对象中将People放到的了对象组成的最下面,这个People同时属于StudentTeacher,那么StudentTeacher如何去找到公共的People呢?这里是通过了StudentTeacher的两个指针(虚基表指针),分别指向两张张表,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面公共People

虚基表:
在这里插入图片描述

偏移量的作用: 找到公共的People

在这里插入图片描述
在这里插入图片描述

虚继承原理图:

在这里插入图片描述

会不会小伙伴有疑问:为啥是非要搞一个虚基表出来,直接在对象中存放People的地址或者偏移量不好吗?,一定要搞一个夹在中间的虚基表干嘛?
在这里插入图片描述

如果细心的小伙伴就会发现,虚基表的第一个地址并没有存放东西,而是第二个位置存放了偏移量,至于第一个位置不存放东西,这里暂时不解释.
在这里插入图片描述

在这里插入图片描述

四、继承中的静态成员变量

在所有派生类和基类中,静态成员变量始终为一份,所有类公用.

我们可以通过静态成员变量实现求创建的派生类+基类对象的个数.

class People
{
public:
	People() { ++_count; }
	People(const People& p1){ ++_count;  }
	~People() { --_count; }

	static int _count;
};
int People::_count = 0;

class Student : public People
{
protected:
	int _num;
};
class Teacher : public People
{
protected:
	string_subject;
};

void test1(People p)
{
	cout << "cout2=" << People::_count << endl;		
	Student s1;
	cout << "cout3=" << People::_count << endl;		
}

void test2(People& p)
{
	cout << "cout4=" << People::_count << endl;		
	Student s1;
	cout << "cout5=" << People::_count << endl;		
}


int main()
{
	People p1;

	Student s1;
	Student s2;

	cout << "cout1=" << People::_count << endl;		

	test1(s1);
	test2(s1);

	Teacher t1;
	cout << "cout6=" << People::_count << endl;		

	return 0;
}

在这里插入图片描述

运行结果:

cout1=3
cout2=4
cout3=5
cout4=3
cout5=4
cout6=4

一、结语

对于多继承,菱形继承,可能还有些地方讲解的不是很清楚,牛牛已经很尽力的去讲解了.
C++语法十分复杂,多继承,菱形继承就是一种体现.
由于多继承和菱形继承的复杂性,后来的很多语言就跳过了这个大坑,比如隔壁Java.
C++开发中我们一般也不建议设计出多继承,特别是菱形继承,如果设计出奇奇怪怪的菱形继承,我觉得和你在一起工作的小伙伴可能会把你吃掉!
在这里插入图片描述

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

组和的耦合度相对就比较低了!
对象组合是类继承之外的另一种复用选择,他可以使用组合类的接口,但不暴露组合类的内部细节.
这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。

具体情况选择适合的复用方式,一般优先选择组合,因为组合的耦合度低,代码可维护性高.

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

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

相关文章

第二十三章 LaneAF框架结构以及接入MMDetection3D模型(车道线感知)

一 前言 近期参与到了手写AI的车道线检测的学习中去&#xff0c;以此系列笔记记录学习与思考的全过程。车道线检测系列会持续更新&#xff0c;力求完整精炼&#xff0c;引人启示。所需前期知识&#xff0c;可以结合手写AI进行系统的学习。 二 LaneAF接入openlane数据集 2.1 Lan…

Box2d 物理画线,Cocos Creator 3.8

一个简易的画线刚体Demo 效果 抱歉&#xff0c;放错图了&#xff0c;以上是 孙二喵 iwae https://forum.cocos.org/t/topic/142673[1] 的效果图。本Demo是根据文章的思路&#xff0c;合成的代码。首先&#xff0c;感谢孙二喵的技术分享。 以下是最终效果图 使用 版本 Cocos Cre…

Cube MX 开发高精度电流源跳坑过程/SPI连接ADS1255/1256系列问题总结/STM32 硬件SPI开发过程

文章目录 概要整体架构流程技术名词解释技术细节小结 概要 1.使用STM32F系列开发一款高精度恒流电源&#xff0c;用到了24位高精度采样芯片ADS1255/ADS1256系列。 2.使用时发现很多的坑&#xff0c;详细介绍了每个坑的具体情况和实际的解决办法。 坑1&#xff1a;波特率设置…

【C++初阶】第一站:C++入门基础(上) -- 良心详解

前言: 从这篇文章开始,将进入C阶段的学习&#xff0c;此篇文章是c的第一站的上半篇&#xff0c;讲述C初阶的知识 目录 什么是C C的发展史 C关键字(C98) 命名空间 命名空间定义 命名空间使用 1.加命名空间名称及作用域限定符 2.使用using将命名空间中某个成员引入 3.使…

PTA:前序序列创建二叉树

前序序列创建二叉树 题目输入格式输出格式输入样例&#xff08;及其对应的二叉树&#xff09;输出样例 代码 题目 编一个程序&#xff0c;读入用户输入的一串先序遍历字符串&#xff0c;根据此字符串建立一个二叉树&#xff08;以二叉链表存储&#xff09;。 例如如下的先序遍…

火狐浏览器导入burpsuite CA 证书无法正常上网

当我们给火狐浏览器设置burpsuite代理&#xff0c;并给火狐导入了burpsuite的CA证书后&#xff0c;仍然无法上网的解决方法。 当我们把浏览器的代理配置好之后&#xff0c;浏览器导入证书&#xff0c;burpsuite设置好代理后&#xff0c;如上图&#xff0c;仍然无法上网&…

精品Python手机数据收集软件-爬虫可视化大屏

《[含文档PPT源码等]精品基于Python的数据收集软件-爬虫》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 软件开发环境及开发工具&#xff1a; 开发语言&#xff1a;python 使用框架&#xff1a;Django 前端技术&#xff…

【教3妹学编程-算法题】重复的DNA序列

3妹&#xff1a;“太阳当空照&#xff0c;花儿对我笑&#xff0c;小鸟说早早早&#xff0c;你为什么背上炸药包” 2哥 :3妹&#xff0c;什么事呀这么开心呀。 3妹&#xff1a;2哥你看今天的天气多好啊&#xff0c;阳光明媚、万里无云、秋高气爽&#xff0c;适合秋游。 2哥&…

STM32WB55开发(6)----FUS更新

STM32WB55开发.6--FUS更新 概述视频教学硬件准备存储器映射FLASH安全区设置SRAM安全区设置通过USB进行下载注意事项 概述 在 STM32WB 微控制器中&#xff0c;FUS&#xff08;Firmware Upgrade Services&#xff09;是用于固件升级的一种服务。这项服务可以让你更新设备上的无…

回归预测 | Matlab实现SSA-CNN-SVM麻雀算法优化卷积神经网络-支持向量机的多输入单输出回归预测

回归预测 | Matlab实现SSA-CNN-SVM麻雀算法优化卷积神经网络-支持向量机的多输入单输出回归预测 目录 回归预测 | Matlab实现SSA-CNN-SVM麻雀算法优化卷积神经网络-支持向量机的多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.SSA-CNN-SVM麻雀算法…

Jupyter Notebook交互式开源笔记本工具

1、官网 http://jupyter.org/ 2、什么是Jupyter Notebook Jupyter Notebook一个交互式的开源笔记本工具&#xff0c;可以用于编写、运行、和共享代码、文本、图形等内容。 如下文本、代码、图形 支持多种编程语言&#xff0c;包括python、R和Julia等&#xff0c;可以走一个…

计算机服务器中了locked勒索病毒怎么办,勒索病毒解密,数据恢复

随着网络技术的不断成熟&#xff0c;网络中存在的病毒威胁也不断增多&#xff0c;近期&#xff0c;云天数据恢复中心陆续接到很多企业的求助&#xff0c;企业的计算机服务器数据库遭到了勒索病毒攻击&#xff0c;并且勒索病毒的攻击与加密形式也发生了许多变化。其中攻击次数较…

优化|求解非凸和无梯度lipschitz连续性的一阶算法在二次规划反问题中的应用(代码分享)

原文信息&#xff08;包括题目、发表期刊、原文链接等&#xff09;&#xff1a;First Order Methods Beyond Convexity and Lipschitz Gradient Continuity with Applications to Quadratic Inverse Problems 原文作者&#xff1a;Jrme Bolte, Shoham Sabach, Marc Teboulle, a…

Vue H5页面长按保存为图片

安装依赖&#xff1a;npm install html2canvas -d <template><div class"index"><div id"captureId" class"capture" v-show"firstFlag"><ul><li>1</li><li>2</li><li>3<…

数学概率 | 旋转矩阵、欧拉角、四元数

目录 一&#xff0c;旋转矩阵 二维旋转矩阵 三维旋转矩阵 二&#xff0c;欧拉角 三&#xff0c;四元数 四&#xff0c;矩阵、欧拉角、四元数相互转换 四元数转矩阵 矩阵转四元数 欧拉角转矩阵 矩阵转欧拉角 欧拉角转四元数 四元数转欧拉角 一&#xff0c;旋转矩阵 …

JavassmMYSQL宠物领养系统08465-计算机毕业设计项目选题推荐(附源码)

目 录 摘要 1 绪论 1.1课题背景及意义 1.2研究现状 1.3ssm框架介绍 1.3论文结构与章节安排 2 宠物领养系统系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1 数据流程 3.3.2 业务流程 2.3 系统功能分析 2.3.1 功能性分析 2.3.2 非功能性分析 2.4 系统用例分析 …

大数据Doris(十七):关于 Partition 和 Bucket 的数量和数据量的建议

文章目录 关于 Partition 和 Bucket 的数量和数据量的建议 关于 Partition 和 Bucket 的数量和数据量的建议 一个表的 Tablet 总数量等于 (Partition num * Bucket num)。一个表的 Tablet 数量,在不考虑扩容的情况下,推荐略多于整个集群的磁盘数量。单个 Tablet 的数据量理论…

MySQL第四讲·如何正确设置主键?

你好&#xff0c;我是安然无虞。 文章目录 主键&#xff1a;如何正确设置主键&#xff1f;业务字段做主键自增字段做主键手动赋值字段做主键 主键总结 主键&#xff1a;如何正确设置主键&#xff1f; 前面我们在讲解存储的时候&#xff0c;有提到过主键&#xff0c;它可以唯一…

CrossOver软件2024最新版本下载

我们都明白快速运行&#xff1a;无须再独立运行一个Win电脑操作系统&#xff0c;进而解决双启动的繁杂和vm虚拟机的卡屏。习惯上来说极速运行&#xff1a;CrossOver能够让Win软件全速全状态运行&#xff0c;不会有丝毫的性能影响&#xff0c;让你在MAC系统中使用熟悉的Win应用。…

【JMeter】后置处理器的分类以及场景介绍

1.常用后置处理器的分类 Json提取器 针对响应体的返回结果是json格式的会自动生成新的变量名为【提取器中变量名_MatchNr】,取到的个数由jsonpath expression取到的个数决定 可以当作普通变量调用,调用语法:${提取器中变量名_MatchNr}正则表达式提取器 返回结果是任何数据格…