C++继承与派生——(8)多继承

归纳编程学习的感悟,
记录奋斗路上的点滴,
希望能帮到一样刻苦的你!
如有不足欢迎指正!
共同学习交流!
🌎欢迎各位→点赞 👍+ 收藏⭐ + 留言​📝
苦难和幸福一样,都是生命盛开的花朵!

一起加油!

目录

一、前言:

二、多继承的定义:

三、多继承的构造函数以及调用顺序:

💦例:多继承下构造函数和析构函数的调用顺序 。

🔑说明:

四、多继承中的同名隐藏和二义性问题: 

⚡注意:

💦例:多继承同名隐藏示例。 

🔑说明:

 ?思考:

🔑说明:

💦例:复杂版本的多继承同名隐藏示例。

五、虚基类:

💦例:虚基类应用示例。

⚡注意:

 六、总结:

七、共勉:


一、前言:

        根据派生类继承基类的个数,将继承分为单继承和多继承。之前我们主要以单继承为例学习了派生类的定义以及使用中应注意的问题。多继承可以看成是单继承的组合,它们有很多相似的特征。

二、多继承的定义:

        多继承的基类不只一个,而是有多个,派生类与每个基类之间的关系可以看作是一个单继承。多继承的定义格式如下:


class <派生类名>:<继承方式><基类名 1>,..,<继承方式><基类名 n>

                                {
                                                <派生类新定义的成员>

                                }

三、多继承的构造函数以及调用顺序:

        在多继承方式下,派生类构造函数要负责为每一个基类构造函数传入初始化的参数,派生类的构造函数格式如下:

class  <派生类名>(<总参数表>):<基类名 1>(<参数表 1>),...,<基类名 n>(<参数表 n>)

                                        {

派生类数据成员的初始化

                                        }

        其中,<总参数表>必须包含完成所有基类初始化所需的参数。
        由于存在多个基类,多继承规定派生类包含多个基类时,构造函数的调用顺序是:先调用所有基类的构造函数,再调用对象成员的构造函数(如果有对象成员 ),最后调用派生类自己的构造函数。其中,处于同一继承层次的各基类构造函数的调用顺序取决于定义派生类时所指定的基类的顺序,与派生类构造函数中所定义的成员初始化列表顺序无关。如果类中有对象成员,那么,对象成员构造函数的调用顺序与对象在类中声明的顺序一致。 

💦例:多继承下构造函数和析构函数的调用顺序 。

#include<iostream>
using namespace std;
class Base1
{public:Base1(int i){b1=i;cout<<"construct Base1"<<endl;}void display(){cout<<"b1="<<b1<<endl;}~Base1(){cout<<"destruct Base1"<<endl;}private:int b1;
};
class Base2
{public:Base2(int i){b2=i;cout<<"construct Base2"<<endl;}void display(){cout<<"b2="<<b2<<endl;}~Base2(){cout<<"destruct Base2"<<endl;}private:int b2;
};
class Derive:public Base2,public Base1
{public:Derive(int m):Base1(m+2),Base2(m-2){d=m;cout<<"construct Derive"<<endl;}void display(){Base1::display();Base2::display();cout<<"d="<<d<<endl;}~Derive(){cout<<"destruct Derive"<<endl;}private:int d; 
};
int main()
{Derive d(10);d.display();return 0;
}

🔑说明:

        构造函数的调用顺序是: Base2、Basel、Derive,析构函数的调用顺序是: Derive、Base1、Base2。

四、多继承中的同名隐藏和二义性问题: 

        上例中派生类中定义了与基类同名的函数 display,对于在不同作用域声明的标识符,可见性原则是:如果存在两个或多个包含关系的作用域,外层声明了一个标识符,而内层没有再次声明同名标识符,那么外层标识符在内层依然可见;如果在内层声明了同名标识符,则外层标识符在内层不可见,此时称内层标识符隐藏了外层同名标识符,这种现象被称为同名隐藏规则
        在类的派生层次结构中,基类的成员和派生类新增加的成员都具有类作用域,两者的作用范围不同,是相互包含的两个层,派生类在内层。这时,在基类 Basel、Base2 中都定义了 display函数,在派生类中也定义了 display 函数。如果在类外通过派生类对象 d 去调用 display 函数,派生类新成员就会隐藏外层同名的成员,直接使用成员名只能访问派生类的成员。若派生类中声明了与基类成员函数同名的新函数,即使函数的形参表不同,也不构成重载的关系,从基类继承过来的同名函数也会被隐藏。若要访问被隐藏的成员,就需要使用作用域操作符和基类名来限定。

⚡注意:

        当派生类中定义了与基类同名但具有不同的形参的函数时(形参个数不同,或者形参类型不同),不属于函数重载,这时派生类中的函数使基类中的函数隐藏,调用父类中的函数必须使用父类名称来限定。只有在相同作用域中定义的函数才可以构成重载。 

💦例:多继承同名隐藏示例。 

#include<iostream>
using namespace std;
class Base1
{public:Base1(int i){b1=i;cout<<"construct Base1"<<endl;}void display(){cout<<"b1="<<b1<<endl;}~Base1(){cout<<"destruct Base1"<<endl;}private:int b1;
};
class Base2
{public:Base2(int i){b2=i;cout<<"construct Base2"<<endl;}void display(){cout<<"b2="<<b2<<endl;}~Base2(){cout<<"destruct Base2"<<endl;}private:int b2;
};
class Derive:public Base2,public Base1
{public:Derive(int m):Base1(m+2),Base2(m-2){d=m;cout<<"construct Derive"<<endl;}void display(){cout<<"d="<<d<<endl;}~Derive(){cout<<"destruct Derive"<<endl;}private:int d; 
};
int main()
{Derive d(10);d.Base1::display();d.Base2::display();d.display();return 0;
}

🔑说明:

        在主函数中、定义了派生类对象 d,根据同名隐藏规则,如果通过派生类对象访问display 函数,只能访问派生类新添加的成员,从基类继承过来的成员由于处于外层作用域而被隐藏。此时要通过d 访问从基类继承过来的成员,就必须使用类名和作用域操作符;访问 Base1中的 display,使用 Base1::display;访问 Base2 中的 display,使用Base2::display。

        通过作用域操作符,明确且唯一地标识了派生类中由基类继承过来的成员,解决了同名隐藏的问题。

 ?思考:

        如果在派生类中没有定义 display,是不是就不存在同名隐藏,那么,通过d可以访问到的是 Base1的display,还是 Base2的display?请改写程序,验证你的想法。

        假如我们把派生类定义的 display 函数删除,此时派生了继承了来自 Base1的 display 和 Base2的 display,由于 display 存在二义性,依然无法直接通过派生类对象d直接访问基类的成员 display。

        如果某个派生类的部分或者直接基类是从另一个共同的基类派生而来,在这些间接基类中从上一级基类继承来的成员拥有相同的名称,在派生类中也会产生同名的现象。这种同名也需要通过作用域操作符来进行标识,而且必须用直接基类来进行限定。 

🔑说明:

        基类 A 中声明了数据成员a、构造函数、析构函数和函数 fun0,A 派生出了 B1和B2,再以 B1、B2 作为基类共同派生出新类 C,在派生类中都没有添加新的同名成员。这时的C类,包含通过B1,B2 继承过来的基类A 中的同名成员 fun0,类的关系图及派生类的结构图如图所示,其中“+”号表示公有,“-”号表示私有,保护的用“#”号表示。

多层继承下的派生类关系图及成员构成图

        对于派生类中成员 a 和 fun0 的访问,只能通过直接基类 B1或者 B2 的名称来限定才可以不能通过基类A 来限定,因为通过 A 限定无法表明成员是从 B1继承的,还是从 B2 继承的。

💦例:复杂版本的多继承同名隐藏示例。

#include<iostream>
using namespace std;
class A
{public:int a;void fun0(){ cout<<"A function is called"<<endl;}A(){cout<<"construct A"<<endl;}~A(){cout<<"destruct A"<<endl;}
}; 
class B1:public A
{public:int b1;B1(){cout<<"construct B1"<<endl;}~B1(){cout<<"destruct B1"<<endl;}
}; 
class B2:public A
{public:int b2;B2(){cout<<"construct B2"<<endl;}~B2(){cout<<"destruct B2"<<endl;}
}; 
class C:public B1,public B2
{public:int c;void fun0(){ cout<<"C function is called"<<endl;}	C(){cout<<"construct C"<<endl;}~C(){cout<<"destruct C"<<endl;}			
};
int main()
{C c;c.B1::a=10;c.B1::fun0() ;c.B2::a=20;c.B2::fun0() ;return 0;
}

        在主函数中定义了派生类对象 c,如果只通过成员名称来访问该类的成员 a 和 fun0,系统就无法唯一确定要引用的成员。这时必须通过作用域操作符,通过直接基类来确定要访问的从基类
继承来的成员。

        此时,在内存中,派生类对象同时拥有两个 a 的空间,这两个a 可以分别通过 B1和B2调基类 A 的构造函数进行初始化,能够存放不同的数值。也可以使用作用域操作符通过直接基类行区分,分别进行访问。但是,在大多数情况下,我们不需要两个同名副本,只需要保留一个即可,C++提供了虚基类技术来解决此问题。 

五、虚基类:

        上例中当我们定义一个派生类对象 c 时,它会构造 B1 类,B2 类,B1,B2 类都有一个父类,因此A 类被构造了两次,在 c中,A 中的数据成员 a 有两个副本,A 中的成员函数 fun0 也有两个映射。一般可以将共同基类 A 设置为虚基类,这时从不同的路径继承过来的同名数据成员在内存中就只有一个空间,同一个函数名也只有一个映射,在构造派生类对象 c 时,A 类只会构造一次。

💦例:虚基类应用示例。

#include<iostream>
using namespace std;
class A
{public:int a;void fun0(){ cout<<"A function is called"<<endl;}A(){cout<<"construct A"<<endl;}~A(){cout<<"destruct A"<<endl;}
}; 
class B1:virtual public A
{public:int b1;B1(){cout<<"construct B1"<<endl;}~B1(){cout<<"destruct B1"<<endl;}
}; 
class B2:virtual public A
{public:int b2;B2(){cout<<"construct B2"<<endl;}~B2(){cout<<"destruct B2"<<endl;}
}; 
class C:public B1,public B2
{public:int c;void fun0(){ cout<<"C function is called"<<endl;}	C(){cout<<"construct C"<<endl;}~C(){cout<<"destruct C"<<endl;}			
};
int main()
{C c;c.a=10; c.fun0();return 0;
}

注意:

        虚基类并不是将基类声明为虚基类,只是在类的派生过程中使用了 virtual 关键字 

        在具体程序设计过程中,如果不需要重复的副本,可以选择虚基类,如果需要更多副本空间存在不同数据,则可以采用作用域操作符方式区别访问。一般采用虚基类可以使得程序更加简洁同时节省更多内存空间。 

 六、总结:

  • 根据派生类继承基类的个数,将继承分为单继承和多继承。
  • 多继承可以看成是单继承的组合。
  • 处于同一继承层次的各基类构造函数的调用顺序取决于定义派生类时所指定的基类的顺序,与派生类构造函数中所定义的成员初始化列表顺序无关。
  • 如果在内层声明了同名标识符,则外层标识符在内层不可见,此时称内层标识符隐藏了外层同名标识符。
  • 若派生类中声明了与基类成员函数同名的新函数,即使函数的形参表不同,也不构成重载的关系。
  • 只有在相同作用域中定义的函数才可以构成重载。 
  • 通过作用域操作符,明确且唯一地标识了派生类中由基类继承过来的成员,解决了同名隐藏的问题。
  • 虚基类并不是将基类声明为虚基类,只是在类的派生过程中使用了 virtual 关键字 。
  • 一般采用虚基类可以使得程序更加简洁同时节省更多内存空间。 

七、共勉:

        以上就是我对C++继承与派生——(8)多继承的理解,希望本篇文章对你有所帮助,也希望可以支持支持博主,后续博主也会定期更新学习记录,记录学习过程中的点点滴滴。如果有不懂和发现问题的小伙伴,请在评论区说出来哦,同时我还会继续更新对C++继承与派生的理解,请持续关注我哦!!! 

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

接入Cloudflare后Nginx和Django获取用户真实IP的办法

可以用Nginx的real_ip的相关命令来实现这个需求。 01-real_ip命令集详解 real_ip命令的使用分为两个步骤: 01-1-设置从哪些代理IP获取真实IP 第1个步骤&#xff1a;通过set_real_ip_from命令设置从哪些代理IP请求获取真实的IP,比如下面的命令&#xff1a; set_real_ip_from…

深入解析泛型

一、泛型的诞生 在C#1 中我们还没有泛型的时候我们收集数据通常需要使用到数组&#xff0c;或者使用封装好的数组集合Hashtable ArrayList。 举个例子&#xff1a; 我们在读取文件的时候就会需要一个数组来储存读取的数据的内容 但我们并不知数据的具体长度也就无法在声明的…

信息安全概论考试题目

文章目录 一、计算题二、网络安全协议应用(30 分)三、材料分析(15 分)四、系统安全配量(共 15 分) 一、计算题 1、Playfair 算法属于经典对称加密方式。曾经在相当长的一段时期内&#xff0c;Playfair算法被认为是一种牢不可破的加密方法。现有明文 introduction to informati…

python使用openpyxl操作excel

文章目录 前提读取已有excel创建一个excel工作簿对象创建excel工作簿中的工作表获取工作表第一种&#xff1a;.active 方法第二种&#xff1a;通过工作表名获取指定工作表​​​​​​第三种&#xff1a;.get_sheet_name() 修改工作表的名称数据操作写入数据按单元格写入通过指…

Java 基础学习(十九)网络编程、反射

1 Socket编程 1.1 Socket编程概述 1.1.1 Socket简介 在网络编程中&#xff0c;Socket&#xff08;套接字&#xff09;是一种抽象概念&#xff0c;它用于在不同计算机之间进行通信。Socket可以看作是一种通信的端点&#xff0c;可以通过Socket与其他计算机上的程序进行数据传…

2021-06-21 C51的模拟羽毛球游戏设计

缘由C51的模拟羽毛球游戏设计求解_嵌入式-CSDN问答 #include "REG52.h" sbit K2 P1^6; sbit K1 P1^7; bit k1; unsigned char code SmZiFu[]{63,6,91,79,102,109,125,7,127,111,128};//0-9. unsigned char Js0,fen0;//中断计时 unsigned int miao8,dfj0,dfy0; voi…

计算机科学速成课【学习笔记】(2)——电子计算机

本集课程B站链接 2. 电子计算机-Electronic Computing_哔哩哔哩_bilibili2. 电子计算机-Electronic Computing是【计算机科学速成课】[40集全/精校] - Crash Course Computer Science的第2集视频&#xff0c;该合集共计40集&#xff0c;视频收藏或关注UP主&#xff0c;及时了…

FA模板制作流程

1、FA模板制作的流程&#xff08;完整复制模板制作&#xff09; 总结&#xff1a; FA完整复制云桌面模板流程&#xff1a; 1、安装一个全新的Windows&#xff0c;挂载并安装tools 2、关闭防火墙、启动administrator本地超管用户 3、挂载FusionAccess_WindowsDesktop_Instal…

SpringBoot实用篇

SpringBoot实用篇 1、热部署 什么是热部署&#xff1f; 所谓热部署&#xff0c;就是在应用正在运行的时候升级软件&#xff0c;却不需要重新启动应用。对于Java应用程序来说&#xff0c;热部署就是在运行时更新Java类文件。 热部署有什么用&#xff1f; 节约时间&#xff0c;热…

以太网转RS485通讯类库封装

最近选用有人科技的以太网转RS485模块做项目&#xff0c;设备真漂亮&#xff0c;国货之光。调通了通讯的代码&#xff0c;发到网上供大家参考&#xff0c;多多交流。 以下分别是配套的头文件与源文件&#xff1a; /*******************************************************…

【Leetcode】1154. 一年中的第几天

文章目录 题目思路代码 题目 1154. 一年中的第几天链接 思路 题目要求是给定一个字符串 date&#xff0c;它代表一个日期&#xff0c;采用标准的 YYYY-MM-DD 格式。需要计算这个日期是当年的第几天。 首先&#xff0c;我们可以通过字符串的索引来提取年、月和日的数值&…

2023-12-23 LeetCode每日一题(移除石子使总数最小)

2023-12-23每日一题 一、题目编号 1962. 移除石子使总数最小二、题目链接 点击跳转到题目位置 三、题目描述 给你一个整数数组 piles &#xff0c;数组 下标从 0 开始 &#xff0c;其中 piles[i] 表示第 i 堆石子中的石子数量。另给你一个整数 k &#xff0c;请你执行下述…

Centos7:Jenkins+gitlab+node项目启动(2)

Centos7&#xff1a;Jenkinsgitlabnode项目启动(1) Centos7&#xff1a;Jenkinsgitlabnode项目启动(1)-CSDN博客 Centos7&#xff1a;Jenkinsgitlabnode项目启动(2) Centos7&#xff1a;Jenkinsgitlabnode项目启动(2)-CSDN博客 Centos7&#xff1a;Jenkinsgitlabnode项目启…

爬虫工作量由小到大的思维转变---<第三十章 Scrapy Redis 第一步(配置同步redis)>

前言: 要迈向scrapy-redis进行编写了;首要的一步是,如何让他们互通?也就是让多台电脑连一个任务(这后面会讲); 现在来做一个准备工作,配置好redis的同步!! 针对的是windows版本的redis同步,实现主服务和从服务共享一个redis库; 正文: 正常的redis for windows 的安装这里就…

扩散模型基础

扩散模型发展至今日&#xff0c;早已成为各大机器学习顶会的香饽饽。本文简记扩散模型入门相关代码&#xff0c;主要参阅李忻玮、苏步升等人所编著的《扩散模型从原理到实战》 文章目录 1. 简单去噪模型1.1 简单噪声可视化1.2 去噪模型1.3 小结 2 扩散模型2.1 采样过程2.2 上科…

Gin 源码深度解析及实现

介绍 什么是 gin &#xff1f; 一个轻量级高性能 HTTP Web 框架。 Introduction | Gin Web Framework (gin-gonic.com) Gin 是一个用 Go (Golang) 编写的 HTTP Web 框架。 它具有类似 Martini 的 API&#xff0c;但性能比 Martini 快 40 倍。 为什么使用 gin &#xff1f; In…

C#进阶-IIS应用程序池崩溃的解决方案

IIS是微软开发的Web服务器软件&#xff0c;被广泛用于Windows平台上的网站托管。在使用IIS过程中&#xff0c;可能会遇到应用程序池崩溃的问题&#xff0c;原因可能有很多&#xff0c;包括代码错误、资源不足、进程冲突等。本文将为大家介绍IIS应用程序池崩溃的问题分析和解决方…

目标检测损失函数:IoU、GIoU、DIoU、CIoU、EIoU、alpha IoU、SIoU、WIoU原理及Pytorch实现

前言 损失函数是用来评价模型的预测值和真实值一致程度&#xff0c;损失函数越小&#xff0c;通常模型的性能越好。不同的模型用的损失函数一般也不一样。损失函数主要是用在模型的训练阶段&#xff0c;如果我们想让预测值无限接近于真实值&#xff0c;就需要将损失值降到最低…

机器学习系列--R语言随机森林进行生存分析(1)

随机森林&#xff08;Breiman 2001a&#xff09;&#xff08;RF&#xff09;是一种非参数统计方法&#xff0c;需要没有关于响应的协变关系的分布假设。RF是一种强大的、非线性的技术&#xff0c;通过拟合一组树来稳定预测精度模型估计。随机生存森林&#xff08;RSF&#xff0…

HTML与CSS

目录 1、HTML简介 2、CSS简介 2.1选择器 2.1.1标签选择器 2.1.2类选择器 2.1.3层级选择器(后代选择器) 2.1.4id选择器 2.1.5组选择器 2.1.6伪类选择器 2.2样式属性 2.2.1布局常用样式属性 2.2.2文本常用样式属性 1、HTML简介 超文本标记语言HTML是一种标记语言&…