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.mzph.cn/news/588348.shtml

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

相关文章

【多传感器融合导航论文阅读】

多传感器融合导航论文积累 知识点总结因子图一致因子图 文献阅读笔记[IF 18.6] 知识点总结 因子图 Factor Graph 是概率图的一种&#xff0c;是对函数因子分解的表示图&#xff0c;一般内含两种节点&#xff0c;变量节点和函数节点。 因子图存在着&#xff1a;两类节点&#…

主浏览器优化之路1——你现在在用的是什么浏览器?Edge?谷歌?火狐?360!?

上一世&#xff0c;我的浏览器之路 引言为什么要用两个浏览器为什么一定要放弃火狐结尾给大家一个猜数字小游戏&#xff08;测运气&#xff09; 引言 小时候&#xff0c;我一开始上网的浏览器是2345王牌浏览器吧&#xff0c; 因为上面集成了很多网站&#xff0c;我记得上面有7…

<Icon-ResizER>support

If you get any questions in using app, email me caohechunhotmail.com.

vscode调试 反汇编c/c++ 查看汇编代码gdb/lldb

先看下流程&#xff01; 先看下流程&#xff01; 有问题请留言&#xff01; 文章目录 必备F5开启调试左侧侧边栏->确保打开回调栈右键函数栈->查看反汇编 方法二&#xff1a;手动输入命令查看 必备 使用c/c 插件&#xff0c;这应该是必备的。 F5开启调试 左侧侧边栏-&…

[Verilog] 加法器实现

1. 4位的加法器 先来一个最基本的的Verilog加法器 设计代码 module adder_4bit (input [3:0] a, b, output [3:0] sum, output carry);assign

react18框架笔记

React React 是 facebook 出的一款针对视图层的库(library)。它是基于单向数据流思想开发的&#xff0c;主要的一个功能就是针对视图显示&#xff0c;让我们把一个项目拆分成一个一个组件进行开发维护。 官网 目前我们讲的 react 是基于 18.2 的版本。react 每一个版本更新之…

Java多线程<二>多线程经典场景

leetcode 多线程刷题 上锁上一次&#xff0c;还是上多次&#xff1f; 同步的顺序。 1. 交替打印字符 使用sychronize同步锁使用lock锁使用concurrent的默认机制使用volitale关键字 Thread.sleep() / Thread.yield机制使用automic原子类 方式1 &#xff1a;使用互斥访问st…

window 服务使用powershell 调用office进行文档内存不够的处理

在项目中为了实现office文件的预览&#xff0c;专门做了个service进行文件的定时转换。 在测试时发现&#xff0c;服务程序 双击执行的时候&#xff0c;文件的转换一切正常&#xff0c;但是当把服务程序安装为服务的时候吗&#xff0c;就会出现如下错误&#xff1a; $PowerPo…

Matlab figure窗口最大化 窗口全屏 图表窗口最大化

我有一个项目&#xff0c;需要把多个数据文件画成的曲线一个个保存为图片&#xff0c;然后再进行集中对比分析。程序运行后&#xff0c;打开目录下保存的图片&#xff0c;发现图片的尺寸都很小&#xff0c;画质也不清晰&#xff0c;后来发现原来matlab显示图片的时候&#xff0…

UCi数据集处理技巧记录

如何起步使用UCI数据集 这里记录一下如何把带分号的数据变成经常使用的csv形式。这里使用wine的例子 https://archive.ics.uci.edu/dataset/186/winequality 原始数据 Wine UCI数据操作 这种带分号的使用python的不好阅读&#xff0c;可以尝试以下步骤&#xff1a; 转变为t…

2023-12-20 LeetCode每日一题(判别首字母缩略词)

2023-12-20每日一题 一、题目编号 2828. 判别首字母缩略词二、题目链接 点击跳转到题目位置 三、题目描述 给你一个字符串数组 words 和一个字符串 s &#xff0c;请你判断 s 是不是 words 的 首字母缩略词 。 如果可以按顺序串联 words 中每个字符串的第一个字符形成字符…

微信小程序-父子页面传值

父子页面传值 父页面向子页面传值 方法一&#xff1a; 父页面&#xff1a; 1. /page/xxx/xxx?id1子页面&#xff1a; onLoad:function(option){ }方法二 <bindtap“func” data-xxx””> 子页面向父页面传值 定义父子页面 父页面&#xff1a;hotspot 子页面&a…

网安面试三十道题(持续更新)

91 mof提权 ## 是mysql的提权方式&#xff0c;在Linux下不能用&#xff0c;就是利用了 c:/windows/system32/wbem/mof/目录下的nullevt.mof文件&#xff0c;每分钟都会在一个特定的时间去执行一次的特征 sql语句&#xff1a; ## 通过shell上传这个文件&#xff0c;通过sql语句写…

惨案后续之---重装python 3.8版本的一系列操作

AssertionError: The environment must specify an action space. 报错 引发的惨案-CSDN博客https://blog.csdn.net/qq_38480311/article/details/135210089 总结&#xff1a; 接上昨日惨案&#xff0c;大意就是 为了解决一个错误&#xff0c;要安装gym0.18.0&#xff0c;经历了…

小信跳房子的题解

原题描述&#xff1a; 时间&#xff1a;1s 空间&#xff1a;256M 题目描述&#xff1a; 小信在玩跳房子游戏&#xff0c;已知跳房子游戏的图表现为一颗完美的具有个节点的二叉树。从根节点依次编号为。节点的左子节点编号为&#xff0c;右子节点编号为。 小信从从节点出发&…

Docker之镜像上传和下载

目录 1.镜像上传 1) 先上百度搜索阿里云 点击以下图片网站 2) 进行登录/注册 3) 使用支付宝...登录 4) 登录后会跳转到首页->点击控制台 5) 点击左上角的三横杠 6) 搜索容器镜像关键词->点击箭头所指 ​ 编辑 7) 进入之后点击实例列表 8) 点击个人实例进入我们的一个…

C++每日一练(8):图像相似度

题目描述 给出两幅相同大小的黑白图像&#xff08;用0-1矩阵&#xff09;表示&#xff0c;求它们的相似度。 说明&#xff1a;若两幅图像在相同位置上的像素点颜色相同&#xff0c;则称它们在该位置具有相同的像素点。两幅图像的相似度定义为相同像素点数占总像素点数的百分比。…

【HarmonyOS】ArkTS语言介绍与组件方式运用

从今天开始&#xff0c;博主将开设一门新的专栏用来讲解市面上比较热门的技术 “鸿蒙开发”&#xff0c;对于刚接触这项技术的小伙伴在学习鸿蒙开发之前&#xff0c;有必要先了解一下鸿蒙&#xff0c;从你的角度来讲&#xff0c;你认为什么是鸿蒙呢&#xff1f;它出现的意义又是…

设计模式:抽象工厂模式(讲故事易懂)

抽象工厂模式 定义&#xff1a;将有关联关系的系列产品放到一个工厂里&#xff0c;通过该工厂生产一系列产品。 设计模式有三大分类&#xff1a;创建型模式、结构型模式、行为型模式 抽象工厂模式属于创建型模式 上篇 工厂方法模式 提到工厂方法模式中每个工厂只生产一种特定…

NFS的基本使用

#江南的江 #每日鸡汤&#xff1a;岁月匆匆&#xff0c;时光荏苒&#xff0c;感悟人生路漫漫&#xff0c;不忘初心方得始终。 #初心和目标&#xff1a;和从前的自己博弈。 NFS(存储共享服务) 本文要点摘要&#xff1a; 下面将讨论什么是NFS&#xff0c;如何配置NFS&#xff0c;…