复杂的C++继承

news2024/11/18 10:35:35

文章目录

    • 什么是继承
    • 继承方式
    • 赋值规则
    • 继承中的作用域(隐藏)
    • 子类中的默认成员函数
      • 需要自己写默认成员函数的情况
    • 继承与友元及静态成员
    • 多继承
      • 菱形继承
      • 菱形继承的问题
      • 菱形虚拟继承
    • 继承和组合

面向对象三大特性:封装继承和多态。封装在类和对象阶段我们已经学习过了,封装就是指不暴露底层实现细节在规范使用的前提下又方便了用户。本文将就继承这一特性展开讲解

什么是继承

继承这个词对我们来说应该不陌生,在现实生活中,你作为你父亲的继承者之一可以获得你父亲的财产,直接少奋斗N年。这里的继承也差不多是这个意思:在一个程序中有很多不同的类,但是这些类可能有共同的属性(成员变量或成员方法),为了避免多次对同样的成员方法和成员变量声明,C++就提出了继承。继承是类设计层次的代码复用。被继承的类就是父类或者基类,继承的类叫做子类或者派生类。
在这里插入图片描述

当两者之间的关系是is a的关系时就可以使用继承,在上述用例中Person就是父类/基类,Student就是子类/派生类,在叫的时候最好搭配起来,不要叫基类和子类。

public是公有继承方式,继承方式和访问限定符一样有三个:public,protected,private

继承方式

类成员/继承方式public继承protected继承private继承
基类的public成员派生类的public成员派生类的protected 成员派生类的private 成员
基类的protected 成员派生类的protected 成员派生类的protected 成员派生类的private 成员
基类的private成员在派生类中不可见在派生类中不可见在派生类中不可

父类的private成员在子类中是不可见的,不可见并不是因为没有被继承下来,只是不能被访问(除非使用共有函数)

private和protected的主要区别就是在与继承,从有了继承以后我们说要尽量少用private(除非该成员是该类所特有的),因为继承的目的就是在于代码复用。

在实际当中一般使用的都是公有继承,其他两种方式很少用。

class定义的类默认继承是private,struct默认是public继承。但写明继承方式是一个好习惯

赋值规则

子类对象可以赋值给父类对象,父类的指针,父类的引用,并且中间没有临时变量的产生,这种赋值方式被称为切片/切割。(只能子类赋值给父类,而不能将父类赋值给子类,因为子类中有父类的那一块,但是父类中没有子类特有的成员。)

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

切片仅限公有继承,因为如果是保活或者私有继承,父类中的成员权限在子类中改变了。

继承中的作用域(隐藏)

父类和子类都有各自独立的类域,如果它们有同名的成员函数(不是继承下来的),就会产生隐藏。所谓隐藏就是对在子类中只能看到子类自己定义的函数,在父类中也是一样。为什么不是函数重载?(函数重载要求在同一个作用域下)

class Parent
{
public:
	void test(double i)
	{
		cout << "you can not see me"<<i << endl;
	}
protected:
	int _a;
};

class Child:public Parent
{
public:
	void test(int a)
	{
		cout << "这是一个隐藏示例" <<_a<< endl;
	}
private:
	int _b;
};

int main()
{
	Child b;
	b.test(1.1);

	return 0;
}

在这里插入图片描述

可以看到即使我传一个浮点数,也只能调用到子类中的test函数,因为b是一个子类对象。

当然你需要通过子类对象调用父类的test函数也是可以的,因为子类继承了父类的test函数,只是被隐藏了,只要加上作用域限定符就可以调到父类的test函数:

在这里插入图片描述

不但同名的函数会隐藏,同名的成员变量也是会隐藏的

class Child:public Parent
{
public:
	void test()
	{
		cout << "这是一个隐藏示例" <<_a<< endl;
	}
private:
	int _a = 3;
	int _b;
};

]

如果子类中存在与父类同名的成员函数,会默认将父类的函数隐藏,除非使用域访问限定符指明类域访问。

父类和子类的析构函数也会构成隐藏,因为底层将析构函数统一处理成了destructor函数

父类和子类中不要定义同名的成员,因为这本身也容易让人混淆

子类中的默认成员函数

继承的子类与普通类不同的地方在于,子类中还有父类中的那一部分。

构造函数和析构函数:对于内置类型不做处理,对于自定义类型调用它的构造和析构

拷贝构造和赋值重载:对于内置类型按字节拷贝,对于自定义类型调用它的拷贝构造和赋值重载

子类中父类的那一部分,要调用父类的默认成员函数来处理。

class Child:public Parent
{
public:
	Child(int y)
		:Parent(_a)
		,_b(y)
	{}
    
    Child(const Child& c)
		:Parent(c)//直接在初始化列表处传子类对象调用父类拷贝构造即可(切片)
		, _b(c._b)
	{}
    
    Child& operator=(const Child& d)
	{
		if (this != &d)
		{
			Parent::operator=(d);//这里必须要指明类域,否则会因为隐藏的原因造成死循环
			_b = d._b;
		}
		return *this;
	}
    
private:
	int _b;
};

但是析构函数是一个例外,我们不能显示的调用父类的析构函数:

class Parent
{
public:
	Parent(int x)
		:_a(x)
	{}
	~Parent()
	{
		cout << "~Person" << endl;
	}
protected:
	int _a;
};

class Child:public Parent
{
public:
	Child(int y)
		:Parent(_a)
		,_b(y)
	{}

	~Child()
	{
		Parent::~Parent();//前面有提到过:析构函数默认是隐藏,要调用父类的析构要指明类域
		cout << "~Child" << endl;
	}
private:
	int _b;
};

在这里插入图片描述

这里父类的析构被调用了两次,因为这里没有清理资源否=否则是会报错的。

父类的析构不用我们调用,在子类的析构结束时编译器会自动调用父类的析构,这里我们可以通过汇编来看一下:

在这里插入图片描述

需要自己写默认成员函数的情况

1.父类没有默认构造函数,需要自己显示写构造

2.子类存在浅拷贝,需要自己写赋值重载和拷贝构造

3.子类存在资源要释放,需要显示写析构函数

继承与友元及静态成员

1.友元关系不能被继承

2.静态成员虽然可以被继承,但是因为静态成员放在公共代码段,所以子类和父类共享静态的成员

多继承

一个子类继承一个父类是单继承,如果一个子类继承多个父类就是多继承。多继承本身没有错,因为一个类继承多个类也是一件很合理的事情。但是多继承给了别人犯错的机会,这个犯错就是菱形继承

菱形继承

在这里插入图片描述

可以看到这个继承关系就像一个菱形一样,所以就叫菱形继承

菱形继承的问题

因为StudentTeacher都是Person的子类,也就是说这两个类中都有一份Person的成员变量;而Assistant作为这两个类的共同子类,也就继承了两份Person的成员,这会有数据冗余的问题,此外还会导致调用时的二义性(不知道你是要调用Student类中的Person成员,还是Teacher类中的Person成员)。

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

下面通过内存窗口来看一下菱形继承的内存结构:

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 = 1;
	d._d = 2;
	d.B::_a = 3;
	d.C::_a =4;

	return 0;
}

在这里插入图片描述

菱形虚拟继承

存在数据冗余和二义性问题的根本原因就是同样的一份Person成员数据在Assistant中存在了两份,只要在继承的时候加上virtual关键字就可以解决这个问题。下面通过内存窗口来看一下菱形虚拟继承的内存结构:

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

在这里插入图片描述

使用虚拟继承以后,_a只存在一份,这也就没有了二义性的问题,但是继承于B类和C类的_b_c上方多了一串地址,再次要通过内存查找这串地址,发现这串地址之后的位置存放一个数字0x14,这个数字就是继承于B的成员到_a的偏移量,通过这个偏移量,对象d便能到d.B::_a。这样就解决了菱形继承成员冗余的问题。

此时A被称为虚基类,继承的B和C类要找到A中的成员,就要通过虚基表中的偏移量来计算。

在实际使用的时候,不要设计菱形继承,因为这是C++的一个大坑,跳进去就基本上爬不出来了哦。

继承和组合

继承是一个is a的关系,也就是说每一个子类都是一个父类;组合是has a的关系,也就说每一个B类中都有一个A类对象。下面是一个组合示例:

// Car和BMW Car和Benz构成is-a的关系
class Car
{
protected:
    string _colour = "白色"; // 颜色
    string _num = "陕ABIT00"; // 车牌号
};
class BMW : public Car
{
public:
	void Drive() {cout << "好开-操控" << endl;}
};
class Benz : public Car
{
public:
	void Drive() {cout << "好坐-舒适" << endl;}
};
// Tire和Car构成has-a的关系
class Tire
{
protected:
    string _brand = "Michelin"; // 品牌
    size_t _size = 17; // 尺寸
};
class Car
{
protected:
    string _colour = "白色"; // 颜色
    string _num = "陕ABIT00"; // 车牌号
    Tire _t; // 轮胎
};

继承是一种白盒复用,子类对父类的实现细节可见,而且父类和子类的耦合性高,一旦父类做了修改就可能影响到子类的正常使用;组合是一种黑盒复用,无法窥探其内部实现的细节,且组合的耦合度低,只有Car的公有成员被修改才会影响到BMW的使用。

如果一个类既是is a的关系又是has a的关系,优先使用组合。

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

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

相关文章

2172. 最大公约数

Powered by:NEFU AB-IN Link 文章目录 2172. 最大公约数题意思路代码 2022年第十三届决赛真题 2172. 最大公约数 题意 给定一个数组, 每次操作可以选择数组中任意两个相邻的元素 x , y x, yx,y 并将其 中的一个元素替换为 gcd ⁡ ( x , y ) \operatorname{gcd}(x, y)gcd(x,y),…

从月薪5000到月薪20000,自动化测试应该这样学...

绝大多数测试工程师都是从功能测试做起的&#xff0c;工作忙忙碌碌&#xff0c;每天在各种业务需求学习和点点中度过&#xff0c;过了好多年发现自己还只是一个功能测试工程师。 随着移动互联网的发展&#xff0c;从业人员能力的整体进步&#xff0c;软件测试需要具备的能力要…

征稿丨IJCAI‘23大模型论坛,优秀投稿推荐AI Open和JCST发表

第一届LLMIJCAI’23 Symposium征稿中&#xff0c;优秀投稿论文推荐《AI Open》和 《JCST》发表。 大规模语言模型&#xff08;LLMs&#xff09;&#xff0c;如ChatGPT和GPT-4&#xff0c;以其在自然语言理解和生成方面的卓越能力&#xff0c;彻底改变了人工智能领域。 LLMs广泛…

Go语言文件I/O操作

go语言中的io操作主要学习目标 掌握文件的常规操作掌握ioutil包的使用掌握bufio包的使用 在go中使用 FileInfo接口 定义了IO的一些函数 FileInfo接口 源码追溯 //type.go // A FileInfo describes a file and is returned by Stat and Lstat. type FileInfo fs.FileInfo/…

ChatGPT:你真的了解网络安全吗?浅谈攻击防御进行时之传统的网络安全

ChatGPT&#xff1a;你真的了解网络安全吗&#xff1f;浅谈网络安全攻击防御进行时 传统的网络安全总结 ChatGPT&#xff08;全名&#xff1a;Chat Generative Pre-trained Transformer&#xff09;&#xff0c;美国OpenAI 研发的聊天机器人程序&#xff0c;是人工智能技术驱动…

什么是网络安全?如何让小白简单的学习网络安全

一、什么是网络安全 网络安全是一个庞大的学科&#xff0c;如果只是普及网络安全技能是非常枯燥的&#xff0c;所以建议从大众容易接受的网络安全诈骗入手&#xff0c;可以先介绍一下近年来频发的网络安全诈骗案例&#xff0c;钓鱼邮件、中奖短信、冒充公检法等多种诈骗手段&am…

Koala:加州大学BAIR团队使用ChatGPT蒸馏数据和公开数据集微调LLaMA模型得到

自从Meta发布LLaMA以来&#xff0c;围绕它开发的模型与日俱增&#xff0c;比如Alpaca、llama.cpp、ChatLLaMA以及Vicuna等等&#xff0c;相关的博客可以参考如下&#xff1a; 【Alpaca】斯坦福发布了一个由LLaMA 7B微调的模型Alpaca&#xff08;羊驼&#xff09;&#xff0c;训…

SpringBoot+Vue前后端分离项目——订单模块——订单管理页面设计

接口返回数据格式&#xff1a; {"msg": "查询成功","total": 1,"code": 200,"data": [{"orderId": "qwer1234","userId": "1","userName": "admin","ad…

全网最全JAVA面试八股文,终于整理完了,堪称2023最强

当今互联网行业中&#xff0c;Java作为一种广泛应用的编程语言&#xff0c;对于求职者来说仍是一项受欢迎的技能。然而&#xff0c;随着市场上的开发人员数量越来越多&#xff0c;Java面试的竞争也愈加激烈。 目前Java面试有着以下现状&#xff1a; 面试难度加大 与过去相比…

9:00进去,9:05就出来了,这问的也太变态了···

从外包出来&#xff0c;没想到死在另一家厂子了。 自从加入这家公司&#xff0c;每天都在加班&#xff0c;钱倒是给的不少&#xff0c;所以也就忍了。没想到5月一纸通知&#xff0c;所有人不许加班&#xff0c;薪资直降30%&#xff0c;顿时有吃不起饭的赶脚。 好在有个兄弟内推…

第四章 内存管理

4.1 内存的基本知识 4.1.1 知识总览 1、内存的作用&#xff1a; 内存可存放数据。程序执行前需要先放到内存中才能被CPU处理--->缓和CPU与硬盘之前的速度矛盾 2、内存的存放&#xff1a; 内存的存放类似于酒店&#xff0c;给内存的每个存储单元都编号。&#xff08;在多道…

使用柔性数组重写MyString

hello&#xff0c;各位宝子&#xff0c;今天阿崽将使用c和柔性数组的方式重新去写String类 在开始本次知识前&#xff0c;首先给大家介绍下柔性数组这个buff特点&#xff1a; 结构中的柔性数组成员前面至少要包含一个其他成员 sizeof返回的这种结构大小不包括柔性数组的内存 …

不想打工做什么好呢?厌倦打工的不妨试试以下几种赚钱方式

除开一些比较幸运的人&#xff0c;谁不是一边打工养家&#xff0c;一边在找寻好机会&#xff1f;人与人之间本来就存在诸多的不公平。有的人这辈子只能靠打工养家&#xff0c;只有少数人能不靠打工来挣钱养家&#xff0c;但是这极少部分人凭什么是你&#xff1f; 现在社会上&am…

( 数组) 209. 长度最小的子数组——【Leetcode每日一题】

❓209. 长度最小的子数组 难度&#xff1a;中等 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl1, ..., numsr-1, numsr] &#xff0c;并返回其长度。如果不存在符合条件的子数组&#xff0c;…

【ArcGIS Pro二次开发】(30):数据定义语言DDL详解

在之前的文章【ArcGIS Pro二次开发】(19)&#xff1a;创建要素类&#xff08;FeatureClass&#xff09;中有涉及DDL的知识点&#xff0c;随着深入的学习&#xff0c;在这里做一个小总结。 一、DDL基本概念 ArcGIS Pro二次开发中的DDL API是一种【数据定义语言】&#xff0c;主…

【Web服务器集群】Web基础与HTTP协议

文章目录 一、Web基础1.域名概述1.1域名的概念1.2域名解析1.3域名空间结构 2.域名注册3.网页的概念4.HTML概述4.1HTML概念4.2HTML文档的结构 5.网页基本标签6.Web概述7.静态网页与动态网页7.1静态网页7.2动态网页7.3动态网页语言 二、HTTP协议1.概念2.HTTP协议的版本3.HTTP方法…

pytest+requests+Python3.7+yaml+Allure+Jenkins+docker实现接口自动化

目录 接口自动化测试框架&#xff08;用例自动生成&#xff09; 项目说明 技术栈 环境部署 框架流程图与目录结构图及相关说明 1、框架流程图如下 2、代码目录结构图如下 关联详解 函数助手详解 代码设计与功能说明 1、定义运行配置文件 runConfig.yml 2、接口配置…

构建系统安全防线!Genmai安全漏洞检测框架全面解析

01 Genmai是什么&#xff1f; Genmai是由openkylin社区SecurityGovernance SIG为了挖掘、检测、验证麒麟产品的安全漏洞而主导开发的一款开源主机漏洞扫描、网络漏洞扫描以及基线扫描的安全扫描框架。其致力于能在短时间内对主流的操作系统进行安全检测&#xff0c;并确保准确…

Unity3D :使用 UXML 实例作为模板

推荐&#xff1a;将 NSDT场景编辑器 加入你的3D工具链 3D工具集&#xff1a; NSDT简石数字孪生 使用 UXML 实例作为模板 您可以将现有 UXML 文档实例化为 UXML 文档中的模板作为模板实例&#xff0c;类似于预制件 在 Unity 中工作。 使用 UXML 文档作为模板 要将项目中的现有…

「API 接口获取方法」

在创建一个应用程序的过程中&#xff0c;获取数据是非常关键的一步。而通过API接口获取数据通常是最好的方式之一。那么&#xff0c;如何通过关键字获取API接口呢&#xff1f;以下是一些步骤&#xff1a; 1.确定你需要获取的数据类型 首先&#xff0c;你需要确定你需要获取的…