一、菱形继承

  • 在介绍虚继承之前介绍一下菱形继承
  • 概念:A作为基类,B和C都继承与A。最后一个类D又继承于B和C,这样形式的继承称为菱形继承
  • 菱形继承的缺点:
    • 数据冗余:在D中会保存两份A的内容
    • 访问不明确(二义性):因为D不知道是以B为中介去访问A还是以C为中介去访问A,因此在访问某些成员的时候会发生二义性
  • 缺点的解决:
    • 数据冗余:通过下面“虚继承”技术来解决(见下)
    • 访问不明确(二义性):通过作用域访问符::来明确调用。虚继承也可以解决这个问题

演示案例

class A
{
public:A(int a) :m_a(a) {}int getMa() { return m_a; }
private:int m_a;
};class B :public A
{
public:B(int a, int b) :A(a), m_b(b) {}
private:int m_b;
};class C :public A
{
public:C(int a, int c) :A(a), m_c(c) {}
private:int m_c;
};class D :public B, public C
{
public:D(int a, int b, int c, int d) :B(a, b), C(a, c), m_d(d) {}void func(){/*错误,访问不明确std::cout << getMa();*///正确,通过B访问getMa()std::cout << B::getMa();}
private:int m_d;
};

二、虚继承

  • 虚继承的作用:为了保证公共继承对象在创建时只保存一分实例
  • 虚继承解决了菱形继承的两个问题:
    • 数据冗余:顶级基类在整个体系中只保存了一份实例
    • 访问不明确(二义性):可以不通过作用域访问符::来调用(原理就是因为顶级基类在整个体系中只保存了一份实例)
  • 共享的基类对象成为“虚基类”
  • 说明:虚继承不会影响派生类本身,只是对虚基类进行的说明
  • 通过在继承列表中使用virtual关键字来说明,virtual与继承说明符(public、protected、private)的位置可以互换

演示案例

  • 下面的ZooAnimal是一个虚基类,Bear和Raccoon分别虚继承于ZooAnimal

class ZooAnimal {}; //虚基类class Bear :public virtual ZooAnimal {};    //虚继承
class Raccoon :public virtual ZooAnimal {}; //虚继承//Panda只保存一份ZooAnimal的定义
class Panda :public Bear, public Raccoon, public Endangered {};

三、虚继承中的类型转换

  • 虚继承中也可以将派生类抓换为基类,用基类的指针/引用指向于派生类

菱形继承中的类型转换

  • 菱形继承中会发生错误,不能将派生类转换为基类
  • 原理是差不多的,就是因为派生类中拥有多份基类的实体,因此不能转换,会产生二义性
class A {};class B: publi A {};class C: publi A {};class D: public B, public C {};int main()
{D d;A* pa = &d; //错误return 0;
}

虚继承中的类型转换

class ZooAnimal {};class Bear :public virtual ZooAnimal {};
class Raccoon :public virtual ZooAnimal {};class Panda :public Bear, public Raccoon, public Endangered {};void dance(const Bear&);
void rummage(const Raccoon&);
ostream& operator<<(ostream&, const ZooAnimal&);int main()
{Panda ying_yang;dance(ying_yang);  //正确,把一个Panda对象当成Bear传递rummage(ying_yang);//正确,把一个Panda对象当成Raccoon 传递cout << ying_yang; //正确,把一个Panda对象当成ZooAnimal传递return 0;
}

四、虚基类成员的可见性与隐藏

  • 规则如下:

    • 虚基类的成员没有被任何派生类隐藏,那么该成员可以直接访问,并且不会产生二义性
    • 如果虚基类的成员只被一条派生路径隐藏,则我们仍然可以直接访问这个被隐藏的版本
    • 如果虚基类的成员多多个派生路径隐藏,则会产生二义性
  • 例如,D1和D2虚继承与B,D继承于D1和D2,并且B有一个x成员:
    • 如果D1和D2都没有x的定义:此时对x的访问不会产生二义性,因为只含有x的一个实例
    • 如果D1中有x的定义而D2没有:同样没有二义性,派生类的x比虚基类B的x优先级更高(或者D1中没有x的定义而D2有x的定义)
    • 如果D1和D2都有x的定义:对x的访问会产生二义性

  • 解决二义性最好的办法就是在派生类为成员自定义新的实例

五、虚继承的构造函数

  • 虚继承中的构造函数与普通继承的构造函数不一样:

    • 普通继承:派生类可以不为间接基类(基类的基类)进行构造函数的调用
    • 虚继承:不论派生类属于哪一层,派生类都需要对虚基类进行构造
  • 原因:假设以下间接派生类没有为虚基类进行构造,那么当间接派生类进行构造时,会对虚基类进行重复的构造函数的调用(例如下面的演示案例D如果不显式构造A,那么当构造B和C的时候,B和C都会构造一次A,从而造成错误)。因此我们需要在间接派生类中为虚基类进行构造,从而避免了重复构造的二义性

演示案例

//普通继承
class A {
public:A(int a);
};class B :public A {
public:B(int a):A(10) {}
};class C :public B {
public:C() :B(10) {} //可以不为A进行构造,因为A的构造已经交给B了
};
//虚继承
class A {
public:A(int a);
};class B :virtual public A {
public:B(int a):A(10) {}
};class C :virtual public A {
public:C(int a) :A(10) {}
};class D :public B,public C {
public://D() :B(10), C(20) {} 错误的,必须显式为A进行构造D() :A(5), B(10), C(20) {} //正确
};

构造函数的执行顺序

  • 规则:虚基类总是先于非虚基类构造,与它们在继承体系中的次序和位置无关
  • 例如,在上面的演示案例中,构造顺序为:A-->B-->C-->D
  • 下面再演示一个有多个虚基类的例子,其构造函数执行熟悉怒为:
    • ZooAnimal
    • ToyAnimal
    • Character
    • BookCharacter
    • Bear
    • TeddyBear

class Character {};
class BookCharacter :public Character {};class ZooAnimal {};
class Bear :public virtual ZooAnimal {};class ToyAnimal {};class ReddyBear :public BookCharacter, public Bear, public virtual ToyAnimal {};

析构函数

  • 析构函数的执行顺序与构造函数执行顺序相反

C++:94---类继承(菱形继承、虚继承(virtual虚基类))相关推荐

  1. c++------------之---【虚函数和抽象基类的应用】

    /*************************************************************************************************** ...

  2. C++中派生类隐式调用与显式调用基类的构造函数

    通过派生类的构造函数调用基类的构造函数有两种方式,隐式和显式两种. 所谓隐式方式就是在派生类的构造函数中不指定对应的基类的构造函数,这个时候调用的是基类的默认构造函数(即含有默认参数值或不带参数的构造 ...

  3. asp.net 的page 基类页面 做一些判断 可以定义一个基类页面 继承Page类 然后重写OnPreLoad事件...

    public class BasePage:Page protected override void OnPreLoad(EventArgs e) {      base.OnPreLoad(e); ...

  4. C++中基类的析构函数为什么要用virtual虚析构函数

    知识背景 要弄明白这个问题,首先要了解下C++中的动态绑定. 关于动态绑定的讲解,请参阅:  C++中的动态类型与动态绑定.虚函数.多态实现 正题 直接的讲,C++中基类采用virtual虚析构函数是 ...

  5. c++ 纯虚成员函数+抽象基类

    公共接口是指一系列成员函数的集合,支持该接口的类必须以合适的方式重新定义这些成员函数,否则就无法创建对象.C++ 中可以通过抽象基类来实现公共接口,为了介绍抽象基类,我们需要先来了解一下纯虚成员函数. ...

  6. python虚函数_Python进阶话题杂谈(十三)纯虚函数与抽象基类

    纯虚函数与抽象类都是Python面向对象中重要的编程范式,用于对继承类做强制性接口实现约定. abc模块包含了一系列与抽象基类与纯虚函数相关的方法.Python中通过修改元类进行抽象基类的设定.这里P ...

  7. C++经验(四)-- 基类构造函数和析构函数中调用virtual虚函数?

    class Base {public:Base();virtual void oneFunction() = 0;... };Base::Base() {...oneFunction(); }clas ...

  8. java 单例基类_PHP基于单例模式实现的数据库操作基类

    本文实例讲述了PHP基于单例模式实现的数据库操作基类.分享给大家供大家参考,具体如下: 配置文件: $db = array( 'host'=>'localhost', 'user'=>'r ...

  9. android mvp框架基类,Android MVP架构项目搭建封装,基类封装

    综述 对于MVP (Model View Presenter)架构是从著名的MVC(Model View Controller)架构演变而来的.而对于Android应用的开发中本身可视为一种MVC架构 ...

  10. 31.基类的公有成员在派生类中的访问权限由 决定.java_基类的公有成员在派生类中的访问权限由派生方式决定。()...

    金属弯曲试验用以检验金属承受规定弯曲程度为__性能,并显示其__. 莫尔法测定Cl-含量时,要求介质的pH值在6.5~10范围内,若酸度过高则 取标示量为25mg的盐酸氯丙嗪片20片,除去糖衣后精密称 ...

最新文章

  1. 使用Slf4j集成Log4j2构建项目日志系统的完美解决方案
  2. 关闭串口_USART串口通信,DMA方式,一分钟从入门到大师
  3. 7月10日云栖精选夜读丨ApsaraCache开源之路,阿里云Redis团队LC3全球顶级开源峰会获CRUG开源社区最具影响力奖...
  4. 计算机应用办公软件实训报告,办公软件实习报告
  5. java中的servlet是线程安全的嘛_Java面试题:Servlet是线程安全的吗?(转)
  6. 小哥送一单外卖应该拿多少钱?
  7. [原创软件]手机截屏及格式转换工具
  8. java在文件里搜字段_Java 如何找出两个文本文件中有相同字段的行
  9. 大厨揭示Google成功秘诀:免费啤酒和寿司
  10. JS操作iframe元素
  11. 德国计算机专业英语授课,德国留学——细数德国亚琛工大的英语授课硕士专业...
  12. 阿里云MVP专家孙玄:一文讲透 MySQL 存储架构设计之道
  13. 清理系统垃圾缓存BAT
  14. ISE14.7固化程序(生成.mcs文件再固化)
  15. 导出微信添加的自定义表情(动图)
  16. 大数据日志分析Hadoop项目实战
  17. Java 数字转汉字
  18. c# ArrayList 和 Hashtable 的使用
  19. tomcat重启机制
  20. selenium提取数据的方法总结

热门文章

  1. 主流多媒体制作软件一览
  2. PLC网关金鸽BL102:采集三菱FX-5U数据如何转成MQTT上报?
  3. 从修HP打印机看中外服务差距
  4. android 本地视频加密软件,使用教程
  5. [架构之路-143]-《软考-系统分析师》- 线索与脉络解读
  6. DataWhale_Pandas Task10 时序数据
  7. screen投屏怎么用_Screen怎么投屏电脑?Screen投屏电脑的方法
  8. python的发展及应用前景论文_Python在物联网中的应用与发展综述
  9. 微信小程序学习-黑马商城程序(更新中~)
  10. 智慧课堂app(一)Flutter+springboot 实现考勤码+gps考勤签到功能