C++修炼之路之继承<二>

news2025/1/12 18:22:42

目录

一:子类的六大默认成员函数 

二:继承与友元

三:继承与静态成员

四:复杂的继承关系+菱形继承+菱形虚拟继承 

1.单继承

2.多继承 

3.菱形继承;一种特殊的多继承 

4.菱形虚拟继承

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

不使用虚拟继承前数据冗余的情况

使用虚拟继承数据的情况

经典例题

五:继承和组合

接下来的日子会顺顺利利,万事胜意,生活明朗-----------林辞忧

衔接上篇的内容继续介绍

一:子类的六大默认成员函数 

1.默认的意思就是我们不写,编译器会自动生成一个,在子类中的默认成员函数要把父类单独看成一个独立的对象来完成对应操作

父类的默认函数

class Person
{
public:
	Person(const char* name = "peter")
		: _name(name)
	{
		cout << "Person()" << endl;
	}

	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,int id)
		:Person(name)//作为一个单独对象构造
		,_id(id)
	{
		cout << "Student(const char* name, int id)" << endl;
	}
	Student(Student& s)
		:Person(s)//父子类的赋值规则
		, _id(s._id)
	{
		cout << "Student(const Student& s)" << endl;
	}
	Student& operator=(Student& s)
	{
		if (this != &s)
		{
			_id = s._id;
			Person::operator = (s);//父子类的赋值规则
		}
		cout << "Student& operator=(const Student& s)" << endl;

		return *this;
	}
	~Student()
	{
		//Person::~Person();
		cout << "~Student()" << endl;
	}
protected:
	int _id;
};

 特殊的对于子类此处的析构函数,不能显示调用父类的析构,原因为

1.由于多态的原因,析构函数统一会被处理成destructor

2.父子类的析构函数构成隐藏

3.为了保证析构安全,先子后父(如果先父后子的话,在父类析构后,在子类还可以访问父类的成员,此时就会导致访问已经释放的空间,导致越界访问),构造为先父后子

4.父类析构函数不需要显示调用,子类析构函数结束时会自动调用父类析构,保证先子后父

练习:如何实现一个不能被继承的类

方法为:可以将父类的成员函数或者整个成员访问方式设置为private,这样继承的时候无论继承方式为那种,都是不可见的

在c++11中新添加了final关键字,使用的话意味着该类不能被继承

二:继承与友元

友元关系不能继承,基类友元不能访问子类私有和保护成员

如要访问子类的私有和保护成员的话,在子类中也声明友元关系(就是基类的友元声明在派生类声明一下)

三:继承与静态成员

基类中定义了static静态成员,则整个继承体系里面只有一个这样的成员,无论派生出多少个子类,都只有一个static成员实例

练习:计算创建多少个类对象--子类初始化时不管我们写不写都会走初始化列表,也一定会走父类的构造

class Person
{
public:
	Person() { ++_count; }
protected:
	string _name; // 姓名
public:
	static int _count; // 统计人的个数。
};

int Person::_count = 0;

class Student : public Person
{
protected:
	int _stuNum; // 学号
};

Student func()
{
	Student st;
	return st;
}

int main()
{
	Person p;
	Student s;
	func();

	cout << Student::_count << endl;

	return 0;
}

四:复杂的继承关系+菱形继承+菱形虚拟继承 

1.单继承

一个子类只有一个直接父类

2.多继承 

一个子类有两个或以上直接父类

3.菱形继承;一种特殊的多继承 

从这里就可以看出,在Assistant的对象中就有两份Person成员,这就导致数据冗余和二义性的问题,这样在访问数据的时候就不知道具体访问哪一个

4.菱形虚拟继承

于是提出了使用虚继承即添加关键字virtual来解决,这是就只有一份Person成员,只初始化一次

如:

class Person
{
public:
	string _name; // 姓名
	int _age;
	int _tel;
	// ...
};
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.Student::_name = "小张";
	a.Teacher::_name = "老张";
	a._name = "张三";
}

int main()
{
	Test();

	return 0;
}

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

不使用虚拟继承前数据冗余的情况

发现在B子类 和C子类中都有一份父类A的数据,造成数据冗余

使用虚拟继承数据的情况

 这时父类的数据只有一份,子类访问的都是同一父类的数据

这时就会看到在 添加virtual的子类中会多存储一个地址,这个地址其实存储的是距离A的偏移量

此时A同属于B和C,这样B和C该如何找到公共的A呢,这里是通过了B和C的两个指针,指向的一张表,这两个指针叫虚基表指针,这两个表叫虚基表,虚基表中存的是偏移量,通过偏移量找到A

这个偏移量就是从该位置向后跳跃的字节数,然后找到并访问父类的成员数据 

经典例题

 

对于p1和p3指向的位置相同,但访问权限不同 

打印顺序为什么?

对于任何的派生类的构造都要 调用基类的构造

注意:初始化列表出现的顺序是按声明顺序来执行的,不是执行顺序

所以结果为 class A,class B ,class C,class D

一般不建议设计出菱形继承

五:继承和组合

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

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

对于这两个的大小都是一样大的, 

但对于继承父类中的protected 成员可以在子类中访问

对于组合的话,一个类中的protected成员,另一个类不能使用,不能直接调用另一个类的public成员函数

3.

4.实际尽量多去使用组合,组合的耦合度低,代码的可维护性好。对于有些关系就适合继承就使用继承,对于多态,也必须实现继承,对于既能用组合又能继承的,就使用组合 

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

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

相关文章

Spectre-v1 简介以及对应解决措施

文章目录 前言一、Variant 1: Exploiting Conditional Branches.二、 BACKGROUND2.1 Out-of-order Execution2.2 Speculative Execution2.3 Branch Prediction2.4 The Memory Hierarchy2.5 Microarchitectural Side-Channel Attacks2.6 Return-Oriented Programming 三、 ATTAC…

阿里云OSS 存储对象的注册与使用

目录 一、什么是阿里云OSS 二、 点击免费试用 2.1 选择第一个&#xff0c;点击免费试用 ​编辑 2.2 登录管理控制台 2.3 进入Bucket 2.4、在阿里云网站上的个人中心配置Accesskey,查询accessKeyId和accessKeySecret。 2.5、进入AccssKey管理页面应该会出现下图提示&…

前端从零到一搭建脚手架并发布到npm

这里写自定义目录标题 一、为什么需要脚手架&#xff1f;二、前置-第三方工具的使用1. 创建demo并运行-4步新建文件夹 zyfcli&#xff0c;并初始化npm init -y配置入口文件 2.commander-命令行指令3. chalk-命令行美化工具4. inquirer-命令行交互工具5. figlet-艺术字6. ora-lo…

QT跨平台读写Excel

QT跨平台读写Excel 背景Excel工具CMakeLists.txt工程目录 背景 开发框架QT&#xff0c;makefile构建工具CMake&#xff0c;编译器MinGW Excel工具 考虑跨平台则不能使用针对微软COM组件的QAxObject来读写Excel&#xff0c;因此使用开源QtXlsx。 这里是将QXlsx当做源码嵌入使…

门禁管理系统服务器如何内网映射让外网访问?

禁管理系统整体解决方案,可实现请假出入联动、门状态监控、电子地图、非法闯入报警、远程开门、红外防夹、智能统计等功能&#xff0c;应用非常广泛。 如果门禁管理系统部署在没有公网IP的本地服务器上&#xff0c;如何设置&#xff0c;能让外网互联网上也能登录访问内部的管理…

亚马逊云科技AWS CloudUp for Her送亚马逊认证考试50%优惠券活动

最近总有小伙伴问小李哥&#xff0c;有没有送AWS考试50%优惠券的活动&#xff1f;这次送云从业者(cloud practitioner)、助理级架构师(SAA)考试50%优惠券活动就来了&#xff01; 本次活动叫AWS CloudUp for Her&#xff0c;完成免费在线培训课程即可获得AWS证书考试50%折扣券&a…

ZooKeeper设置监听器

ZooKeeper设置监听器&#xff0c;通过getData()/getChildern()/xists()方法。 步骤&#xff1a; 1.创建监听器&#xff1a;创建一个实现Watcher接口的类&#xff0c;实现process()方法。这个方法会在ZooKeeper向客户端发送一个Watcher事件通知的时候被调用。 2.注册监听器&…

Servlet第四篇【request对象常用方法、应用】

什么是HttpServletRequest HttpServletRequest对象代表客户端的请求&#xff0c;当客户端通过HTTP协议访问服务器时&#xff0c;HTTP请求头中的所有信息都封装在这个对象中&#xff0c;开发人员通过这个对象的方法&#xff0c;可以获得客户这些信息。 简单来说&#xff0c;要得…

蓝桥杯竞赛类型:Web应用开发 全程详解

既然大家准备报名蓝桥杯&#xff0c;那么对蓝桥杯就应该有一定的了解了。没有了解也没关系&#xff0c;简单来说&#xff0c;蓝桥杯就是一个计算机竞赛&#xff0c;竞赛类型大多是使用各种语言写算法&#xff0c;当然还有本文的主体——Web应用开发。对蓝桥杯有了基本了解之后&…

C#使用PaddleOCR进行图片文字识别✨

PaddlePaddle介绍✨ PaddlePaddle&#xff08;飞桨&#xff09;是百度开发的深度学习平台&#xff0c;旨在为开发者提供全面、灵活的工具集&#xff0c;用于构建、训练和部署各种深度学习模型。它具有开放源代码、高度灵活性、可扩展性和分布式训练等特点。PaddlePaddle支持端…

IntelliJ IDEA2020下使用Maven构建Scala 项目

1.创建maven文件 2.进入pom.xml导入依赖 <!--添加spark的依赖--><dependency><groupId>org.apache.spark</groupId><artifactId>spark-core_2.12</artifactId><version>3.2.1</version></dependency><!--添加scala依…

【ONE·基础算法 || 栈 】

总言 主要内容&#xff1a;编程题举例&#xff0c;熟悉理解以栈此类数据结构为主的题型。       文章目录 总言1、栈2、删除字符中的所有相邻重复项&#xff08;easy&#xff09;2.1、题解 3、比较含退格的字符串&#xff08;easy&#xff09;3.1、题解 4、基本计算器 II&a…

Buildroot系统构建学习笔记(以百问网imx6ullPro开发板为例)

一、Builroot是什么&#xff1f; Buildroot是一组Makefile和补丁&#xff0c;可简化并自动化地为嵌入式系统构建完整的、可启动的Linux环境(包括bootloader、Linux内核、包含各种APP的文件系统)。Buildroot运行于Linux平台&#xff0c;可以使用交叉编译工具为多个目标板构建嵌…

c++|list使用及深度剖析模拟实现

目录 一、list介绍与使用 1.1 list介绍 1.2 list的使用 1.2.1list的构造 1.2.2iterator 1.2.3容量 1.2.4元素访问 1.2.5 元素修改 二、list的深度剖析及模拟实现 三、list与vector的对比 一、list介绍与使用 1.1 list介绍 ①list底层是带头双向循环链表&#xff0c;在…

Redis进阶——BitMap用户签到HyperLogLog实现UV统计

目录 用户签到实现签到功能 签到统计HyperLogLog实现UV统计UV和PV的概述测试百万数据的统计 用户签到 BitMap功能演示 我们针对签到功能完全可以通过MySQL来完成&#xff0c;例如下面这张表 用户签到一次&#xff0c;就是一条记录&#xff0c;假如有1000W用户&#xff0c;平…

RCE漏洞及其绕过——[SWPUCTF 2021 新生赛]easyrce、caidao、babyrce

目录 什么是Shell 1、Shell简介 2、印刷约定 一、什么是RCE 漏洞产生条件&#xff1a; 漏洞检测&#xff1a; 1.远程命令执行 system()函数&#xff1a; passthru()函数&#xff1a; exec()函数&#xff1a; 无回显 shell_exec()函数&#xff1a; 2.远程代码执行 e…

我们一起看看《看漫画学C++》中如何讲解对象的动态创建与销毁

《看漫画学C》这本书中会用图文的方式生动地解释对象的动态创建与销毁。在C中&#xff0c;动态创建对象是通过new运算符来实现的&#xff0c;而销毁对象则是通过delete运算符来完成的。这种方式可以让程序在需要时分配内存给对象&#xff0c;并在对象不再需要时释放内存&#x…

「Word 论文排版」插入分节符导致word转PDF后出现空白页

问题 word转PDF后出现空白页 解决 但是此方法会让页面页脚标记出错 TODO 如下图所示 在论文目录后有一个分节符&#xff0c;转成PDF之后就多了一个空白页 文件-打印-页面设置-选中封面那一页-版式-从偶数页开始 再导出空白页就没了

Nginx莫名奇妙返回了404

描述 nginx作为反向代理&#xff0c;代理python的服务&#xff0c;但是通过代理访问服务的时候&#xff0c;报了404的错误。 难受的是客户现场没有查看日志的权限&#xff0c;只有查看配置文件的权限&#xff0c;我们检测了几遍配置文件也没有找到问题&#xff0c;哎~ 问题引…

34. 【Android教程】菜单:Menu

作为 Android 用户&#xff0c;你一定见过类似这样的页面&#xff1a; 它就是我们今天的主角——菜单&#xff0c;它的使用场景和作用不用多说&#xff0c;几乎每个 App 都会用到它&#xff0c;今天我们就一起来看看 Android 提供的几种菜单类型及用法。 1. 菜单的几种类型 根…