【C++深度探索】继承机制详解(二)

hello hello~ ,这里是大耳朵土土垚~💖💖 ,欢迎大家点赞🥳🥳关注💥💥收藏🌹🌹🌹
在这里插入图片描述

💥个人主页:大耳朵土土垚的博客
💥 所属专栏:C++入门至进阶

这里将会不定期更新有关C++的内容,希望大家多多点赞关注收藏💖💖

目录

  • 1.继承与友元
  • 2.继承与静态成员
  • 3.单继承与多继承
  • 4.菱形继承与虚拟继承
  • 5.虚拟继承
  • 6.继承和组合
  • 7.笔试面试题
  • 8.结语

1.继承与友元

基类的友元关系不能被子类继承,也就是说基类友元不能访问子类私有和保护成员

在C++的继承中,友元函数并不具有继承的特性。当一个类继承另一个类时,它只会继承基类的成员函数和数据成员,而不会继承基类中声明的友元函数。

例如:

class Student;//声明
//基类
class Person
{
public:
	friend void Display(const Person& p, const Student& s);//友元函数
protected:
	string _name; // 姓名
};
//子类
class Student : public Person
{
protected:
	int _stuNum; // 学号
};

void Display(const Person& p, const Student& s)
{
	cout << p._name << endl;//可以访问基类的成员
	cout << s._stuNum << endl;//不可以访问子类的成员
}
int main()
{
	Person p;
	Student s;
	Display(p, s);
	return 0;
}

结果如下:

在这里插入图片描述

因此,友元函数不能被继承。每个类都需要单独定义其自己的友元函数。

2.继承与静态成员

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

静态成员是属于类而不是对象的成员。它们与类相关联,而不是与类的每个对象相关联。静态成员可以是静态变量和静态函数。

例如:

class Person
{
public:
	Person() { ++_count; }	//默认构造,每次创建对象自动调用
protected:
	string _name; // 姓名
public:
	static int _count; // 静态成员变量_count,统计人的个数
};

int Person::_count = 0;//初始化静态成员变量


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

class Graduate : public Student
{
protected:
	string _seminarCourse; // 研究科目
};

void TestPerson()
{
	Student s1;
	Student s2;
	Student s3;
	Graduate s4;
	cout << " 人数 :" << Person::_count << endl;
	Student::_count = 0;
	cout << " 人数 :" << Person::_count << endl;
}

结果如下:

在这里插入图片描述

可以看出类的静态成员与普通成员不同,它独立于各个对象,存放在静态存储区,即使基类有多个子类,静态成员也不会被拷贝多次,是它们公共使用的。

3.单继承与多继承

  • 单继承:一个子类只有一个直接父类时称这个继承关系为单继承
    如下图所示:
    在这里插入图片描述

上图中,Student类只有一个直接父类Person,PostGraduate类也只有一个直接父类Student

  • 多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
    如下图所示:
    在这里插入图片描述

上图中,Assistant类有两个直接父类Student类和Teacher类

4.菱形继承与虚拟继承

  • 菱形继承:菱形继承是多继承的一种特殊情况

菱形继承是指在C++中使用多重继承时,出现了多个派生类继承同一个基类,而最终有一个类同时继承了这些派生类,形成了一个菱形的继承结构。

例如:

//菱形继承
class Person
{
public:
	string _name; // 姓名
};
class Student : public Person
{
protected:
	int _num; //学号
};
class Teacher : public Person
{
protected:
	int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};

如下图所示:
在这里插入图片描述

Assistant类有两个直接父类Student类和Teacher类,而这两个类又都继承自Person类

当然不是所有的菱形继承都是一个标准的菱形结构,也可能是别的更复杂的结构,只要出现了多个派生类继承同一个基类,而最终有一个类同时继承了这些派生类,就形成了一个菱形的继承结构。

菱形继承带来的问题

菱形继承可以带来一些问题,主要是关于数据冗余和二义性。

  • 数据冗余
    上图中Student类和Teacher类继承自同一父类Person,有相同的数据成员,那么在Assistant类中就会有两份相同的数据成员,会引起数据冗余
    在这里插入图片描述

  • 二义性
    如果Student类和Teacher类都有相同的成员,那么当在Assistant类中调用这个成员时,会出现二义性。编译器不知道应该调用Student类中的成员还是Teacher类中的成员。
    例如:

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

在这里插入图片描述

5.虚拟继承

为了解决这些问题,C++提供了虚继承的机制。通过在中间派生类Student和Teacher的继承声明中加上关键字virtual,可以实现虚继承。虚继承可以解决菱形继承带来的二义性问题,确保在最终派生类Assistant中只有一份数据成员和函数。需要注意的是,虚拟继承不要在其他地方去使用。

例如:

class Person
{
public:
	string _name; // 姓名
};

//虚拟继承
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._name = "peter";
}

在这里插入图片描述

我们看到这时再通过对象a去访问_name成员不会出现二义性,并且当给_name赋值时,发现在监视中所继承的_name都改变,这说明Assistant类中只有一份数据成员和函数,不会出现数据冗余

关于菱形虚拟继承的原理解释涉及到虚拟基类表与偏移量,大家有兴趣可以查看资料,这里就不过多介绍。

有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题。

6.继承和组合

  • 组合是一种对象关系,一个类可以包含其他类的对象作为其成员变量。这种关系不是通过继承来实现,而是通过在一个类中创建另一个类的对象来实现。
  • 而继承是一种面向对象编程中的机制,允许一个类(称为派生类或子类)从另一个类(称为基类或父类)继承属性和行为。通过继承,子类可以继承父类的特征和功能,并且可以添加或修改自己的特性和功能。

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

class Person
{
public:
	string _name; // 姓名
};
class Student : public Person
{
protected:
	int _num; //学号
};

Student s;

上述代码中,Student对象s是一个Person类的对象;放在现实生活中就是学生是一个人

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

class A {
//...
};

class B {
	A a;
	//...
};
  • 在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高
  • 对象组合要求被组合的对象具有良好定义的接口。因为对象的内部细节是不可见的,组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。

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

7.笔试面试题

  1. 什么是菱形继承?菱形继承的问题是什么?
    ①菱形继承是多继承的一种特殊情况。菱形继承是指在C++中使用多重继承时,出现了多个派生类继承同一个基类,而最终有一个类同时继承了这些派生类,形成了一个菱形的继承结构。
    ②数据冗余和二义性。

  2. 什么是菱形虚拟继承?如何解决数据冗余和二义性的?
    ①为了解决数据冗余和二义性问题,C++提供了虚继承的机制。通过在中间派生类的继承声明中加上关键字virtual,将共同继承的基类标记为虚拟继承。
    ②虚拟继承确保在最终派生类中只有一份数据成员和函数,解决了数据冗余与二义性问题。

  3. 继承和组合的区别?什么时候用继承?什么时候用组合?
    ①public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
    ②选择继承还是组合取决于具体的需求和设计问题。一般来说,当两个类之间存在明确的层次关系,并且子类是父类的一种特例时,可以使用继承。但是,继承可能导致类之间的耦合度较高,代码结构较为复杂,因此需要慎重使用。而当两个类之间没有明确的层次关系,或者需要构建复杂对象时,可以使用组合。组合可以帮助代码实现更灵活的结构,并且可以随时替换或扩展不同的组件。另外要实现多态,也必须要继承。

8.结语

在C++中,继承支持单继承与多继承,对于多继承中又包含菱形继承,如果实现了菱形继承就必须实现虚拟继承来解决数据冗余与二义性的问题,所以在实践中我们要尽量减少使用菱形继承,此外对于继承与组合的区别也需要好好掌握一下,以上就是今天所有的内容啦~ 完结撒花~ 🥳🎉🎉

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深入解析:抖音视频标题的Python爬虫提取方法

引言 随着短视频的兴起&#xff0c;抖音已经成为全球最受欢迎的社交媒体平台之一。对于数据分析师、市场研究人员以及内容创作者来说&#xff0c;能够从抖音上抓取数据是一项宝贵的技能。本文将深入解析如何使用Python编写爬虫程序来提取抖音视频的标题。 爬虫基础 在开始编…

K8S 上部署大数据相关组件

文章目录 一、前言二、Redis 一、前言 Artifact Hub 是一个专注于云原生应用的集中式搜索和发布平台。它旨在简化开发者在 CNCF&#xff08;Cloud Native Computing Foundation&#xff09;项目中寻找、安装和分享包与配置的过程。用户可以通过这个平台方便地发现、安装各类云原…

为什么需要重写equals和如何重写equals

首先先看Java中的 &#xff0c;比较的两个对象的地址值。 如果是基本数据类型&#xff0c;那么就是比较的是值。 如果是引用数据类型&#xff0c;比较的就是地址. object类中的equals方法也是用的&#xff1b; 所以要比较两个对象的大小&#xff0c;去调用默认的equals方法…

Apache Spark分布式计算框架架构介绍

目录 一、概述 二、Apache Spark架构组件栈 2.1 概述 2.2 架构图 2.3 架构分层组件说明 2.3.1 支持数据源 2.3.2 调度运行模式 2.3.3 Spark Core核心 2.3.3.1 基础设施 2.3.3.2 存储系统 2.3.3.3 调度系统 2.3.3.4 计算引擎 2.3.4 生态组件 2.3.4.1 Spark SQL 2.…

关系型数据库MySQL和时序数据库的区别?

时序数据库和关系型数据库是两种不同类型的数据库系统&#xff0c;它们在设计理念、存储结构、性能优化等方面有显著差异&#xff0c;以适应不同的应用场景和需求。具体对比如下&#xff1a; 数据存储结构 时序数据库&#xff1a;使用列式存储&#xff0c;每条记录通常包含时间…

MySQL资源组的使用方法

MySQL支持创建和管理资源组&#xff0c;并允许将服务器内运行的线程分配给特定的组&#xff0c;以便线程根据组可用的资源执行。组属性允许控制其资源&#xff0c;以启用或限制组中线程的资源消耗。DBA可以针对不同的工作负载适当地修改这些属性。 目前&#xff0c;CPU时间是一…

田地行走-美团2023笔试(codefun2000)

题目链接 田地行走-美团2023笔试(codefun2000) 题目内容 塔子哥是一个农民&#xff0c;他有一片 nm 大小的田地&#xff0c;共 n 行 m 列&#xff0c;其中行和列都用从 1 开始的整数编号&#xff0c;田地中有 k 个格子中埋有土豆。我们记第 a 行第 b 列的格子为 (a,b) 。塔子哥…

LM2596/LM2596S多路降压稳压DC-DC开关电源芯片详解(第二部分:电路设计)(12V转5V、12V转3.3V、任意电压转任意电压)

目录 一、固定电压&#xff08;3.3/5/12V&#xff09;模块设计实例 1.设计条件&#xff1a;VOUT5V&#xff0c;VIN(MAX)12V&#xff0c;ILOAD(MAX)3A 2.设计步骤&#xff1a; &#xff08;1&#xff09;电感的选择&#xff08;L1&#xff09; &#xff08;2&#xff09;输…

C++入门基础

前言 本篇博客讲解一下c得入门基础 &#x1f493; 个人主页&#xff1a;普通young man-CSDN博客 ⏩ 文章专栏&#xff1a;C_普通young man的博客-CSDN博客 ⏩ 本人giee:普通小青年 (pu-tong-young-man) - Gitee.com 若有问题 评论区见&#x1f4dd; &#x1f389;欢迎大家点赞&…

掌握计算机网络基础:从零开始的指南

计算机网络是现代信息社会的重要基石。本文将以简洁明了的方式为基础小白介绍计算机网络的基本概念、分类、以及其在信息时代中的重要作用。 计算机网络在信息时代中的作用 21世纪是以数字化、网络化、信息化为重要特征的信息时代。 计算机网络作为信息的最大载体和传输媒介&…

微信自动加好友工具

批量导入数据到后台&#xff0c;可设置添加速度、间隔时间、验证信息和自动备注等&#xff0c;任务执行时间&#xff0c;后台会自动执行操作。

ubuntu 分区情况

ubuntu系统安装与分区指南 - Philbert - 博客园 (cnblogs.com)https://www.cnblogs.com/liangxuran/p/14872811.html 详解安装Ubuntu Linux系统时硬盘分区最合理的方法-腾讯云开发者社区-腾讯云 (tencent.com)https://cloud.tencent.com/developer/article/1711884

基于flask的猫狗图像预测案例

&#x1f4da;博客主页&#xff1a;knighthood2001 ✨公众号&#xff1a;认知up吧 &#xff08;目前正在带领大家一起提升认知&#xff0c;感兴趣可以来围观一下&#xff09; &#x1f383;知识星球&#xff1a;【认知up吧|成长|副业】介绍 ❤️如遇文章付费&#xff0c;可先看…

uni-app 封装http请求

1.引言 前面一篇文章写了使用Pinia进行全局状态管理。 这篇文章主要介绍一下封装http请求&#xff0c;发送数据请求到服务端进行数据的获取。 感谢&#xff1a; 1.yudao-mall-uniapp: 芋道商城&#xff0c;基于 Vue Uniapp 实现&#xff0c;支持分销、拼团、砍价、秒杀、优…

2024年6月总结 | 软件开发技术月度回顾(第一期)

最新技术资源&#xff08;建议收藏&#xff09; https://www.grapecity.com.cn/resources/ Hello&#xff0c;大家好啊&#xff01;随着欧洲杯和奥运会的临近&#xff0c;2024 年下半年的序幕也随之拉开。回顾 2024 年上半年的技术圈&#xff0c;我们看到了一系列令人振奋的进展…

ELfK logstash filter模块常用的插件 和ELFK部署

ELK之filter模块常用插件 logstash filter模块常用的插件&#xff1a; filter&#xff1a;表示数据处理层&#xff0c;包括对数据进行格式化处理、数据类型转换、数据过滤等&#xff0c;支持正则表达式 grok 对若干个大文本字段进行再分割成一些小字段 (?<字段名…

51单片机嵌入式开发:5、按键、矩阵按键操作及protues仿真

按键、矩阵按键操作及protues仿真 1 按键介绍1.1 按键种类1.2 按键应用场景 2 按键电路3 按键软件设计3.1 按键实现3.2 按键滤波方法3.3 矩阵按键软件设计3.4 按键Protues 仿真 4 按键操作总结 提示 1 按键介绍 1.1 按键种类 按键是一种用于控制电子设备或电路连接和断开的按…

LLM之RAG实战(四十一)| 使用LLamaIndex和Gemini构建高级搜索引擎

Retriever 是 RAG&#xff08;Retrieval Augmented Generation&#xff09;管道中最重要的部分。在本文中&#xff0c;我们将使用 LlamaIndex 实现一个结合关键字和向量搜索检索器的自定义检索器&#xff0c;并且使用 Gemini大模型来进行多个文档聊天。 通过本文&#xff0c;我…

Face_recognition实现人脸识别

这里写自定义目录标题 欢迎使用Markdown编辑器一、安装人脸识别库face_recognition1.1 安装cmake1.2 安装dlib库1.3 安装face_recognition 二、3个常用的人脸识别案例2.1 识别并绘制人脸框2.2 提取并绘制人脸关键点2.3 人脸匹配及标注 欢迎使用Markdown编辑器 本文基于face_re…

Python 安装Numpy 出现异常信息

文章目录 前言一、包源二、安装完成异常 前言 安装Python Numpy包出现异常问题 Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location. 一、包源 使用默认的包源出现超时异常&#xff0c;改用清华包源 pip …