深入篇【C++】C++<继承>特性详细总结附代码案例(单继承/多继承)

news2024/11/24 11:01:28

深入篇【C++】C++<继承>特性详细总结(单继承/多继承)

  • Ⅰ.继承理解
  • Ⅱ.继承方式
  • Ⅲ.基类派生类对象赋值转化
  • Ⅳ.继承中同名成员
  • Ⅴ.派生类的默认成员函数
  • Ⅵ.继承中友元与静态
  • Ⅶ.多继承<菱形继承问题>
  • Ⅷ.继承与组合

Ⅰ.继承理解

继承的本质就是复用,而在C++中这种继承是类层次的复用。
基类和派生类就是父类和子类之间的关系。子类继承父类,子类在保持原有父类的基础上进行扩展,增加功能。

class person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
//protected:
	string _name = "xiaotao";
	int _age = 19;
};

class student :public person//子类student,继承父类person的成员变量和成员函数
{
protected:
	int _stuid;
};
class teacher :public person//子类teacher,继承父类person的成员变量和成员函数
{
protected:
	int _joined;
};
int main()
{
	person p;//父类
	student s;//子类
	teacher t;//子类
}

在这里插入图片描述

Ⅱ.继承方式

继承的方式很简单,只要在子类的后面加上冒号和要继承的方式,还有要继承的父类即可。

在这里插入图片描述
要注意基类的私有成员在派生类中都是不可见的,不是说没有继承下来,而是不允许访问。这与我们所理解私有限定符不同,私有限定符是在类里可以访问,而在类外无法访问,这里一旦父类成员私有,则派生类类里类外都无法访问。即父类的私有成员,子类无法"继承"。(并不是没有继承,是不能访问)。

基类中访问限定符有三种类型,而派生类中继承方式又有三种类型,这组合起来就有9种方式,不过这九种组合后的规则是有规律可循的。
在这里插入图片描述
在这里插入图片描述

1.继承方式和访问限定符两两组合后,最终的决定权在于这两个种权限比较小的那种。
权限:private<protected<public.
比如protected与public组合,最终的权限是protected。private与public组合,最终的权限是private。
private与任何权限组合最终仍然是private。
不过在C++继承中种很少使用private权限,都已经要继承了,还能就给一部分?在继承中大多数都是使用public权限的。
2.protected成员就和正常类中的私有成员一样,在类里可以访问,而在类外无法访问。即如果想让基类的成员被派生类继承后,派生类类里可以访问,而在类外无法访问,就可以用protected。

3.注意class定义的类成员默认是私有,而struct定义的类成员默认是公有。不过最好在写时,显式的标注成员的访问方式。

Ⅲ.基类派生类对象赋值转化

1.父类对象是不可以赋值给子类对象,子类对象可以赋值给父类对象/父类引用/父类指针。
2.子类对象赋值给父类对象之间发生赋值兼容转化(切片,切割)。
3.正常来说,不同类型之间赋值会产生临时变量,而子类对象赋值给父类对象,中间没有产生临时变量。而是发生切片,直接将子类中父类的那一部分之间拷贝一份切割过去赋给父类对象。子类可以看成一个特殊的父类,赋值时将相同的部分切割拷贝过去。
4.子类对象赋值给父类对象,中间不会产生临时变量,就没有常性,这就允许子类可以成为父类对象的引用/指针。
5.父类对象就可以根据引用或指针,来改变子类中父类的数据。

int main()
{
	person p;
	student s;
	teacher t;
 
	//发生赋值兼容转化:切片,切割
	//不同类型之间赋值正常来说是会发生类型转化
	p = s;
    //而子类赋给父类直接将子类中跟父类对象相同的部分切割拷贝过去
	person p1 = s;

	person& p2 = s;
	//中间不会产生临时变量,所以可以将子类赋给父类变名
	//将子类中跟父类对应那一部分切割拷贝过来
	p2._name = "xiaoyao";
	//可以通过别名,指针来找到子类中的跟父类相对应的数据
	//子类是一种特殊的父类
	person* p3 = &s;
	p3->_name = "hehehe";

}

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

Ⅳ.继承中同名成员

1.当派生类中出现与基类相同的成员函数时,要注意这时这两个函数不是构成函数重载。因为函数重载要求在同一作用域中。而这明显是两个作用域了。这里这两个函数构成隐藏(重定义)。

2.隐藏(重定义):子类和父类中有同名成员变量/函数时,子类的成员会隐藏父类的成员。也就是当调用子类对象中该成员函数时,会先在子类中找,如果在子类找不到再去父类中找。
3.父类和子类中只要有相同名字的函数就会构成隐藏,不管函数参数是否相同。
4.在子类成员函数中如果想要访问父类的同名成员,可以使用基类::基类成员 ,显式的访问。

class person
{
public:
	void fun()
	{
		cout << "person::fun()" << endl;
	}
protected:
	string _name = "heeheh";
	int _num = 111;
};
class student :public person
{
public:
	void fun()
	{
		cout << "student::fun()" << endl;

	}
	void Print()
	{
		cout << "姓名:" << _name << endl;
		cout << "学号" << _num << endl;
		cout << person::_num << endl;
	}
protected:
	int _num = 999;
};
int main()
{
	student s;
	s.Print();
	s.fun();
	//默认会先到子类找,子类找不到就会报错
	s.person::fun();//想调用父类中的同名函数,需要显式的调用。
}

Ⅴ.派生类的默认成员函数

1.默认成员函数就那几个:构造函数,拷贝构造函数,析构函数………
子类对象要初始化肯定要在初始化列表初始化,而父类对象初始化可以用它的默认构造初始化,但如果父类没有默认构造函数,则需要在子类的初始化列表,显式的初始化父类对象。
(类似于匿名对象初始化)。而且父类对象需要先初始化,因为父类对象先声明(从父类中继承下来,就说明先声明父类)。
2.子类的拷贝构造函数,需要显式的初始化父类对象。因为如果不显式的初始化父类,那么父类就会自动调用父类默认构造函数,这时对象初始化的结果可能不是你期待的,所以需要手动显式初始化父类,即调用父类的拷贝构造函数。
3.析构函数,就不需要改动,就正常析构子类即可。因为父类先构造,子类后构造,这就要求子类先析构,父类后析构。为了满足这个条件,析构函数被改成了隐藏(重定义)。那么这样当子类的析构函数结束,会自动去父类找父类的析构函数去析构父类对象。(隐藏:子类中找不到后再去父类中找) 所以如果自己写析构函数,就无法保证析构的顺序。

①派生类的构造函数必须调用基类的构造函数来初始化基类的那一部分。父类有默认成员函数时,派生类初始化会自动调用。当父类没有默认成员函数时,需要在派生类的初始化列表显式初始化基类。
②派生类的拷贝构造必须调用基类的拷贝构造函数来完成父类的拷贝。
③派生类的赋值必须调用基类的赋值完成父类部分赋值。
④析构函数析构子类即可,当结束时,会自动调用父类析构。
⑤要注意父类先声明先构造,子类后声明后构造。

继承中的默认成员函数:
class person
{
public:
	
	person(const char* name)
		:_name(name)
	{

	}
	person(const person& p)
		:_name(p._name)
	{
		cout << "person(const person& p)" << endl;
	}
	person& operator=(const person& p)
	{
		cout << "person& operator=(const person& p)" << endl;
		if (this != &p)
		{
			_name = p._name;
		}
		
		return *this;
	}
	~person()
	{
		cout << "~person" << endl;
	}
		
protected:
	string _name;
};

class student :public person
{
	public:
	//父类先声明的,所以初始化需要先初始化父类
	student(const char* name="SDSAD", int id=123)
		:person(name)//需要显式调用的初始化父类,父类的构造
		, _id(id)
	{

	}
	student(const student& s)
		:person(s)//显式的调用父类中的拷贝构造
		, _id(s._id)
	{
	}
	student& operator=(const student& s)
	{
		if (this != &s)
		{
			//父类中的赋值运算符与子类中的赋值运算符重载构成的隐藏,必须显式的调用父类中的赋值重载来赋值父类的数据
			person::operator=(s);
			_id = s._id;
		}
		return *this;
	}
	~student()
	{
		//多态中将父类和子类的析构弄成隐藏,先找子类再找父类
		//应该先析构子类再析构父类
		
	}
protected:
	int _id;
};

Ⅵ.继承中友元与静态

1.父类中的友元,子类是无法继承下来的。如果子类想用父类的友元类,需要手动显式的将这个友元变成自己的友元。
2.继承中的静态变量,是共同属于父类和子类的。父类中的静态成员,在子类中不会单独的拷贝一份,而是继承它的使用权。

//继承中的静态变量
class person
{

public:
	person()
	{
		++_cout;
	}
//protected:
	string _name;
public:
	static int _cout;
};
int person::_cout = 1;

class student :public person
{

protected:
	int _num;
};
//静态成员是共同属于父类和子类的,子类继承静态成员不是将父类的静态成员拷贝过来,而是
//继承父类静态成员的使用权
int main()
{
	person p;
	student s1;
	student s2;

	cout << &s1._name << endl;
	cout << &s2._name << endl;

	cout << &p._cout << endl;
	cout << &s1._cout << endl;
	cout << &s2._cout << endl;

	cout << &person::_cout << endl;
	cout << &student::_cout << endl;
}

在这里插入图片描述

Ⅶ.多继承<菱形继承问题>

多继承常见的就是双继承了,双继承就是一个派生类有两个基类。

在这里插入图片描述
正常情况下是没有问题的。不过多继承还是引发了一个问题:菱形继承
菱形继承是什么意思呢?
就是派生类的两个基类,又是从同一个基类继承下来的。
在这里插入图片描述
菱形继承存在的问题:<数据冗余>和<二义性问题>。
数据冗余:因为基类1和基类2继承下来父类的数据是一样的。当派生类再从基类1和基类2中继承时,那么最初的基类的数据就继承了两份下来。这样就造成了空间浪费。
二义性问题:与上面同理,派生类继承了两份一样的数据,那么当对数据访问时,该访问的是基类1中的父类数据还是该访问基类2中的父类的数据呢?这就造成就访问不明确的问题。
在这里插入图片描述

解决方法: 虚继承
采用虚继承后就可以解决菱形继承带来的问题:数据冗余和二义性。
在继承方式前面加上关键字virtual即可变成虚继承。

//双继承
class person
{
public:
	string _name;
	int _age;
};
class student :virtual public person//虚继承
{
protected:
	int _num;
};
class teacher :virtual public person//虚继承
{
protected:
	int _id;
};
//双继承--->存在问题:菱形继承
//person ,student ,teacher 都没事,只有assistant有事,存在两个person中的数据
//解决方法:虚继承
class assistant :public student, public teacher
{
protected:
	string _marjor;
};
int main()
{
	assistant a;
	a.student::_age = 10;
	a.teacher::_age = 20;
	//使用虚继承后,person中的数据就只有一个了
	//改变一个另一个父类数据也会改动
	a._age = 31;
}

虚继承的原理比较复杂,是采用一个叫虚基表的东西来完成的。编译器会将公用数据放在内存的最下面,虚假表里存的是偏移量,这个偏移量的左右就是为了让两个基类找到相同的数据,这样一个基类访问该数据就可以通过相同的模型来完成。

菱形继承复杂的一批,大家最好没事不用搞这玩意,一旦出错,你会被坑的裤衩子都没有。

Ⅷ.继承与组合

《优先使用组合而不是继承》
1.复用有两种方式,一种是继承复用,一种是组合复用。

2.继承复用,基类的内部细节派生类是可以可见的。这样的复用,依赖关系更强,耦合度高。

3.对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象
来获得。组合后,基类的细节是不可见的,这样的复用,依赖关系不强,耦合度低。

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

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

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

相关文章

螺旋模型四象限指的是什么?

螺旋模型融合了瀑布模型、快速原型模型&#xff0c;最大的优点强调了风险分析&#xff0c;有助于将软件质量融入开发中;小分段构建大型软件&#xff0c;易于计算成本;客户参与&#xff0c;保证项目可控性。但构建过程太过繁琐&#xff0c;适合大型项目不适合小型项目。 上图是螺…

通信原理板块——基础知识(一)

微信公众号上线&#xff0c;搜索公众号小灰灰的FPGA,关注可获取相关源码&#xff0c;定期更新有关FPGA的项目以及开源项目源码&#xff0c;包括但不限于各类检测芯片驱动、低速接口驱动、高速接口驱动、数据信号处理、图像处理以及AXI总线等 1、通信的基本概念——消息、信息和…

【QT】 QTabWidgetQTabBar控件样式设计(QSS)

很高兴在雪易的CSDN遇见你 &#xff0c;给你糖糖 欢迎大家加入雪易社区-CSDN社区云 前言 本文分享QT控件QTabWidget&QTabBar的样式设计&#xff0c;介绍两者可以自定义的内容&#xff0c;以及如何定义&#xff0c;希望对各位小伙伴有所帮助&#xff01; 感谢各位小伙伴…

内生安全构建数据存储

一、数据安全成为防护核心&#xff0c;存储安全防护不容有失 1、数据作为企业的核心资产亟需重点保护&#xff0c;数据安全已成网络空间防护核心 2、国家高度重视关键信息基础设施的数据安全&#xff0c;存储安全已成为审核重点 二、存储安全是数据安全的关键一环&#xff0c;应…

Linux6.35 Kubernetes Pod详解

文章目录 计算机系统5G云计算第三章 LINUX Kubernetes Pod详解一、Pod基础概念1.在Kubrenetes集群中Pod有如下两种使用方式2.pause容器使得Pod中的所有容器可以共享两种资源&#xff1a;网络和存储3.kubernetes中的pause容器主要为每个容器提供以下功能4.Kubernetes设计这样的P…

VBA技术资料MF41:VBA_将常规数字转换为文本数字

【分享成果&#xff0c;随喜正能量】时有落花至&#xff0c;远随流水香。人生漫长&#xff0c;不攀缘&#xff0c;不强求&#xff0c;按照自己喜欢的方式生活&#xff0c;不必太过在意&#xff0c;顺其自然就好。路再长也有终点&#xff0c;夜再黑也有尽头。 我给VBA的定义&am…

纯鸿蒙!华为HarmonyOS NEXT不再兼容安卓应用,无法安装Apk文件

8月7日消息&#xff0c;近日&#xff0c;华为举行2023年华为开发者大会&#xff08;HDC.Together&#xff09;上&#xff0c;除了发布HarmonyOS 4、全新升级的鸿蒙开发套件外&#xff0c;华为还带来了HarmonyOS NEXT开发者预览版。 据了解&#xff0c;HarmonyOS NEXT开发者预览…

Android Framework解析——WMS原理

作者&#xff1a;bobby_developer 1. WMS原理&#xff1a;WMS角色与实例化过程 window:它是一个抽象类&#xff0c;具体实现类为 PhoneWindow &#xff0c;它对 View 进行管理。Window是View的容器&#xff0c;View是Window的具体表现内容&#xff1b; windowManager:是一个接…

个人玩具小程序商城制作攻略,不容错过

在如今的移动互联网时代&#xff0c;小程序成为了企业和个人开展业务的重要工具。如果你是一个小白用户&#xff0c;想要打造一个玩具小程序商城&#xff0c;但又没有编程基础&#xff0c;不用担心&#xff01;本文将通过乔拓云提供的简单操作步骤&#xff0c;教你如何轻松实现…

林【2019】

关键字&#xff1a; 哈夫曼树权值最小、哈夫曼编码、邻接矩阵时间复杂度、二叉树后序遍历、二叉排序树最差时间复杂度、非连通无向图顶点数&#xff08;完全图&#xff09;、带双亲的孩子链表、平衡二叉树调整、AOE网关键路径 一、判断 二、单选 三、填空 四、应用题 五、算…

VScode中同时打开两个脚本

使用快捷键&#xff1a; CtrlAltRightArrow 效果&#xff1a; 可以看到&#xff0c;上述两个脚本使用独立的窗口进行编辑和查看。

Ajax 笔记(二)

笔记目录 2. Ajax 综合案例2.1 案例一-图书管理2.1.1 渲染列表2.1.2 新增图书2.1.3 删除图书2.1.4 编辑图书 2.2 案例二-背景图的上传和更换2.2.1 上传2.2.2 更换 2.3 案例三-个人信息设置2.3.1 信息渲染2.3.2 头像修改2.2.3 信息修改2.3.4 提示框 Ajax 笔记接口文档&#xff1…

图片如何转化为pdf格式?几种好用方法推荐

图片如何转化为pdf格式&#xff1f;在当今社会&#xff0c;我们经常需要将多张图片转化为PDF格式&#xff0c;比如将多张照片合并成一份文档&#xff0c;或者将一系列的截图整合在一起。那么&#xff0c;我们该如何将图片转化为PDF格式呢&#xff1f;本文将介绍几种好用的方法&…

Qt做警告处理界面

解决的问题&#xff1a; 做上位机时&#xff0c;多有检测仪器状态&#xff0c;事实显示警告&#xff0c;错误等状态&#xff0c;笔者就是需要显示各种仪器状态&#xff0c;做显示&#xff0c;后做出处理逻辑 Axure设计图&#xff1a; 需求&#xff1a;更新状态&#xff0c;根…

7.2 手撕VGG11模型 使用Fashion_mnist数据训练VGG

VGG首先引入块的思想将模型通用模板化 VGG模型的特点 与AlexNet&#xff0c;LeNet一样&#xff0c;VGG网络可以分为两部分&#xff0c;第一部分主要由卷积层和汇聚层组成&#xff0c;第二部分由全连接层组成。 VGG有5个卷积块&#xff0c;前两个块包含一个卷积层&#xff0c…

qemu 虚拟化

一、介绍QEMU Qemu是种非常古老的虚拟化技术&#xff0c;用于虚拟化系统组件并在其上运行多种CPU架构的程序或操作系统。 借助KVM&#xff0c;Qemu可以通过使用基于硬件的虚拟化来获得超快的计算速度。QEMU充当硬件供应商&#xff0c;KVM是CPU。KVM驻留在Linux内核中&#xff0…

Zabbix介绍及部署

目录 一、zabbix的基本概述 二、zabbix的构成 三、zabbix的监控对象 四、zabbix 监控原理 zabbix 监控部署的常见5个程序 五、zabbix的监控框架 六、安装zabbix 5.0 服务端 6.1 zabbix服务端部署软件 6.2 关闭防火墙&#xff0c;修改主机名 6.3 获取zabbix 的下载…

Telink泰凌微TLSR8258蓝牙开发笔记(一)

一、开发环境搭建 1.1、软件开发环境&#xff1a; 1.1.1、开发的IDE&#xff1a; IDE下载链接 1.1.2、烧录工具 DBT下载地址 1.1.3、蓝牙SDK 蓝牙SDK下载地址 1.2、硬件开发环境 8258开发板烧录工具一套 二、运行例程&#xff0c;并使能打印调试信息功能 File-->Impo…

【MongoDB】数据库、集合、文档常用CRUD命令

目录 一、数据库操作 1、创建数据库操作 2、查看当前有哪些数据库 3、查看当前在使用哪个数据库 4、删除数据库 二、集合操作 1、查看有哪些集合 2、删除集合 3、创建集合 三、文档基本操作 1、插入数据 2、查询数据 3、删除数据 4、修改数据 四、文档分页查询 …

Java课题笔记~ Spring 的事务管理

一、Spring 的事务管理 事务原本是数据库中的概念&#xff0c;在 Dao 层。但一般情况下&#xff0c;需要将事务提升到业务层&#xff0c;即 Service 层。这样做是为了能够使用事务的特性来管理具体的业务。 在 Spring 中通常可以通过以下两种方式来实现对事务的管理&#xff…