一、概念

C++允许一个类拥有多个基类,所以就有了多重继承。多重继承和单一继承很多方面都是类似的。

示例

class zooanimal
{
public:zooanimal(){cout<<__func__<<endl;}virtual ~zooanimal(){cout<<__func__<<endl;}
};class bear:public zooanimal
{
public:bear(){cout<<__func__<<endl;}~bear(){cout<<__func__<<endl;}
};class endangered
{
public:endangered(){cout<<__func__<<endl;}~endangered(){cout<<__func__<<endl;}
};
class panda:public bear, public endangered
{
public:panda(){cout<<__func__<<endl;}~panda(){cout<<__func__<<endl;}
};

上述代码中panda就是继承了bear和endanger

二、规则

1、多重继承的构造函数与析构函数

和单继承一样,子类对象是由每个基类和子类特有的部分共同组成的,上述代码中,子类panda对象的结构如下

当创建一个panda对象时,各个类构造函数和析构函数的执行顺序和单继承的规则一样:根据基类在派生列表中出现的顺序,依次进行构造,派生列表中第一个出现的是bear类,所以先创建bear对象,又发现bear是zooanimal的子类,所以先创建zooanimal的对象,然后创建bear对象。第二个出现在派生列表中的类是endangered,所以创建endangered类对象,最后创建panda中独有的部分,之后,整个panda对象生成完毕。析构函数的调用顺序和构造函数相反

上述代码只是隐式使用bear和endangered的构造函数,可以显示调用

class panda:public bear, public endangered
{
public:panda():bear(),endangered(){cout<<__func__<<endl;}~panda(){cout<<__func__<<endl;}
};

2.多重继承的拷贝构造与operator=

和单继承一样,如果不显示调用基类的拷贝构造,那么在拷贝初始化子类对象时,只会调用基类的构造函数

class zooanimal
{
public:zooanimal(){cout<<__func__<<endl;}zooanimal(const zooanimal &t) {cout<<"zooanimal(const zooanimal &t)"<<endl;}~zooanimal(){cout<<__func__<<endl;}
};class bear:public zooanimal
{
public:bear(){cout<<__func__<<endl;}bear(const bear &t) {cout<<"bear(const bear &t)"<<endl;}~bear(){cout<<__func__<<endl;}
};class endangered
{
public:endangered(){cout<<__func__<<endl;}endangered(const endangered &t) {cout<<"endangered(const endangered &t)"<<endl;}~endangered(){cout<<__func__<<endl;}
};class panda:public bear, public endangered
{
public:panda():bear(),endangered(){cout<<__func__<<endl;}panda(const panda &t){cout<<"panda(const panda &t)"<<endl;}~panda(){cout<<__func__<<endl;}
};int main(int argc, char const *argv[])
{panda t;panda t2=t;return 0;
}

可见,上图中,只调用了子类对象的拷贝构造函数,而没有调用各个基类的拷贝构造函数

显示调用基类中的拷贝构造函数后,拷贝初始化子类对象时就会调用各个基类的拷贝构造

class zooanimal
{
public:zooanimal(){cout<<__func__<<endl;}zooanimal(const zooanimal &t) {cout<<"zooanimal(const zooanimal &t)"<<endl;}~zooanimal(){cout<<__func__<<endl;}
};class bear:public zooanimal
{
public:bear(){cout<<__func__<<endl;}bear(const bear &t):zooanimal(t) {cout<<"bear(const bear &t)"<<endl;}~bear(){cout<<__func__<<endl;}
};class endangered
{
public:endangered(){cout<<__func__<<endl;}endangered(const endangered &t) {cout<<"endangered(const endangered &t)"<<endl;}~endangered(){cout<<__func__<<endl;}
};class panda:public bear, public endangered
{
public:panda():bear(),endangered(){cout<<__func__<<endl;}panda(const panda &t):bear(t),endangered(t){cout<<"panda(const panda &t)"<<endl;}~panda(){cout<<__func__<<endl;}
};

如果panda没有显示定义拷贝构造函数,那么在对panda对象进行拷贝初始化时,会使用编译器提供的拷贝构造函数,此时也会调用基类的拷贝狗构造函数,将上述代码的29行注释掉后,输出结果如下

可见,使用编译器提供的拷贝构造函数时,会调用子类的拷贝构造函数

operator=也是一样,如果各个基类显示定义了operator=,那么如果子类也定义了operator=,那么要在子类中显示调用基类的operator=,否则只会调用子类的operator=

class zooanimal
{
public:zooanimal(){cout<<__func__<<endl;}zooanimal &operator=(const zooanimal &t) {cout<<__func__<<"zooanimal"<<endl;return *this;}~zooanimal(){cout<<__func__<<endl;}
};class bear:public zooanimal
{
public:bear(){cout<<__func__<<endl;}bear &operator=(const bear &t) {cout<<__func__<<"bear"<<endl;return *this;}~bear(){cout<<__func__<<endl;}
};class endangered
{
public:endangered(){cout<<__func__<<endl;}endangered &operator=(const endangered &t) {cout<<__func__<<"endangered"<<endl;return *this;}~endangered(){cout<<__func__<<endl;}
};class panda:public bear, public endangered
{
public:panda():bear(),endangered(){cout<<__func__<<endl;}panda &operator=(const panda &t) {cout<<__func__<<"panda"<<endl;return *this;}~panda(){cout<<__func__<<endl;}
};int main(int argc, char const *argv[])
{panda t;panda t2;t2=t;return 0;
}

上述输出结构是子类定义了operator=,但是没有显示调用的结果,显示调用后的代码和输出结果如下

class zooanimal
{
public:zooanimal(){cout<<__func__<<endl;}zooanimal(const zooanimal &t) {cout<<"zooanimal(const zooanimal &t)"<<endl;}zooanimal &operator=(const zooanimal &t) {cout<<__func__<<"zooanimal"<<endl;return *this;}~zooanimal(){cout<<__func__<<endl;}
};class bear:public zooanimal
{
public:bear(){cout<<__func__<<endl;}bear(const bear &t):zooanimal(t) {cout<<"bear(const bear &t)"<<endl;}bear &operator=(const bear &t) {cout<<__func__<<"bear"<<endl;zooanimal::operator=(t); return *this;}~bear(){cout<<__func__<<endl;}
};class endangered
{
public:endangered(){cout<<__func__<<endl;}endangered(const endangered &t) {cout<<"endangered(const endangered &t)"<<endl;}endangered &operator=(const endangered &t) {cout<<__func__<<"endangered"<<endl;return *this;}~endangered(){cout<<__func__<<endl;}
};class panda:public bear, public endangered
{
public:panda():bear(),endangered(){cout<<__func__<<endl;}panda(const panda &t):bear(t),endangered(t){cout<<"panda(const panda &t)"<<endl;}panda &operator=(const panda &t) {cout<<__func__<<"panda"<<endl;bear::operator=(t);endangered::operator=(t);return *this;}~panda(){cout<<__func__<<endl;}
};

如果子类没有显示定义operator=,那么,一般情况,编译器也会自动生成一个operator=,此时对子类对象进行赋值时,也会调用基类的operator=,将上述代码的operator=注释掉的输出结果如下

3.多重继承的问题

和单一继承一样,因为多重继承,所以子类很多个基类,所以,子类对象可以切割转换成任意基类对象,任意基类的指针或引用都可以指向子类。但是可访问的成员依然是由对象,指针,和引用的静态类型决定

访问成员时,查找成员的顺序也和单一继承一样,见博客https://blog.csdn.net/Master_Cui/article/details/109849186

但是,因为有很多个基类,当不同基类中出现了同名函数时,会出现二义性错误

class zooanimal
{
public:zooanimal(){cout<<__func__<<endl;}~zooanimal(){cout<<__func__<<endl;}
};class bear:public zooanimal
{
public:bear(){cout<<__func__<<endl;}void func() {cout<<__func__<<endl;}~bear(){cout<<__func__<<endl;}
};class endangered
{
public:endangered(){cout<<__func__<<endl;}void func() {cout<<__func__<<endl;}~endangered(){cout<<__func__<<endl;}
};class panda:public bear, public endangered
{
public:panda():bear(),endangered(){cout<<__func__<<endl;}~panda(){cout<<__func__<<endl;}
};int main(int argc, char const *argv[])
{panda t;t.func();return 0;
}

上述代码中,endangered和bear都定义了func,所以,子类对象无法确定该调用哪个,所以出现二义性错误

想避免这种二义性,有两种办法:

1.添加作用域

将上述代码中的34行改为如下

t.bear::func();

此时,指定了func的作用域,指定调用bear中的func,此时不会出现二义性

2.在子类中实现一个同名函数

在子类中实现一个func

void func() {cout<<__func__<<endl;}

此时也不会报错,因为查找func时,会先在子类panda中查找,找到了就不找了,所以不会报错

此外,和单一继承一样,不要让子类和基类中出现名字相同但形参列表不同的函数。示例见博客https://blog.csdn.net/Master_Cui/article/details/109849186

参考

《C++ Primer》

欢迎大家评论交流,作者水平有限,如有错误,欢迎指出

C++知识点52——多重继承相关推荐

  1. 《系统集成项目管理工程师》必背100个知识点-52成功的项目团队特点

    成功的项目团队特点? (1)团队的目标明确,成员清楚自己的工作对目标的贡献: (2)团队的组织结构清晰,岗位明确: (3)有成文或习惯的工作流程和方法,而且流程简明有效: (4)项目经理对团队成员有明 ...

  2. C++57个入门知识点_50 菱形继承与虚继承(C++中语法允许多重继承造成菱形继承;会造成近亲结婚的问题;可以通过虚继承的方式解决;实际项目中不多用多重继承)

    上篇C++57个入门知识点_49 多重继承与组合(一个类同时具有多个类的属性的方法:多重继承或者组合:多重继承:一个类同时继承多个类:多重继承构造和析构的顺序与普通继承类似:组合:类中包含多个成员对象 ...

  3. python求一元三次方程的根_【九年级 】知识点8 一元二次方程根的判别式的应用...

    知识点8 一元二次方程根的判别式的应用 [题目预览] [视频讲解] [分析点评] 此题考查一元二次方程根的判别式与一元二次方程根的情况,当判别式的值大于0时,方程有两个不相等的实数根,当判别式的值等于 ...

  4. 【Effection C++】读书笔记 条款40:明智而审慎的使用多重继承

    [Effection C++]继承与面向对象设计 条款40:明智而审慎的使用多重继承 首先介绍一个个小知识,在C++解析一个名称的时候,首先是名字查找(涉及到作用域),然后是类型匹配,从中找到最佳匹配 ...

  5. java 变量值变化检测_Java 9.5 测试复盘

    <9.5测试复盘> 3.对于一个已经不被任何变量引用的对象,当垃圾回收器准备回收该对象所占用的内存时,将自动调用该对象的哪个方法(A) A finalize B notify C noti ...

  6. 密码学博士必须掌握的52个知识点(二):多核处理器和矢量处理器之间的不同

      原文链接:http://bristolcrypto.blogspot.com/2014/10/52-things-number-2-what-is-difference.html   在一开始,你 ...

  7. Docker学习总结(52)—— Docker容器环境变量相关知识点的总结

    一.前言 了解Docker容器的运行环境非常重要,我们把应用放在容器里执行,环境变量会直接影响程序的执行效果.所以我们要知道容器内部的环境变量,也要知道如何改变这些环境变量. 二.查看环境变量 方式一 ...

  8. 知识点总结(基础篇)

    知识点总结1 PEP8 规范 每一级缩进使用4个空格. 空格是首选的缩进方式. 行限制的最大字符数为79 使用下划线分隔的小写字母 类名一般使用首字母大写的约定 异常名后面加上"Error& ...

  9. 面经——C/C++常见面试知识点总结附面试真题

    参考:C/C++ 面试题 作者:zhaouc 发布时间: 2015-02-15 15:51:00 网址:https://blog.csdn.net/zhaouc/article/details/438 ...

最新文章

  1. dogdoggo搜索引擎_Giphy:专搜GIF动态图的搜索引擎
  2. 为了边缘计算,亚马逊、谷歌、微软已正面交锋!
  3. python多包运行_如何组织包含多个包的python项目,以便包中的每个文件仍然可以单独运行?...
  4. cs架构用什么语言开发_用Rust语言开发微信小程序
  5. 通过Python脚本理解系统进程间通信
  6. Mac截图高端操作,这些技巧你绝对不知道!
  7. 在桌面应用中使用JAVA DB[组图]
  8. gateway nacos注册服务_使用Nacos作为微服务注册中心和配置中心
  9. linux 线程编译指令i,linux线程篇之(一):线程的创建与应用
  10. 人工智能——微粒群优化算法
  11. JSP开发模型(JavaWed)
  12. 重磅开源:带屏幕LCD脱机下载器离线下载器!
  13. 曾经的荣誉,偶然被唤醒
  14. 用爬虫模拟登陆urp教务处系统
  15. 逆向思维赚钱法则 真正赚钱的暴利项目
  16. 秋招C++开发学习之路day10
  17. 常见HTTP/FTP/WebSockets状态码大全
  18. 指法练习软件ECAI使用指南
  19. sql 凭证明细表 科目余额表_科目余额SQL
  20. 【小记】LaTex 语法说明

热门文章

  1. win7下安装linux(centos6.5)双系统详细小白教程
  2. 编程之美2.10:寻找数组中的最大值和最小值
  3. Python - while语句和if语句 的 用法 及 代码
  4. python之变量操作
  5. 一位软件工程师的6年总结【转】
  6. Python filter() 函数
  7. JS 面向对象 ~ 创建对象的 9 种方式
  8. 网络攻击与防御技术第三次实验
  9. Linux 中echo格式控制、重定向 、管道 | 简介
  10. 神箭手爬虫学习笔记(二)