当前位置: 首页 > news >正文

C++多继承与虚继承

        14.1        多继承中的二义性问题
        在派生类中对基类成员的访问应该是唯一的。但是在多继承情况下,可能造成对基类中某个成员的访问出现了不唯一的情况,这时就成对基类成员的访问产生了二义性。

class Base1
{
public:void fun();
};
class Base2
{
public:void fun();
};
class Dervied :public Base1, public Base2 {};
int main()
{Dervied d;d.fun();return 0;
}

        在d对象访问fun()函数时,由于无法确定访问的是那个函数会产生二义性。
        要解决这个问题有两种方法:
        (1)通过作用域符(::)明确指出访问的是基类那个函数。
        如:d.Base1::fun();
        (2)在类中定义同名成员。

class Dervied
{
public:void fun(){Base1::fun();}
};

        在类的继承层次结构中,基类的成员和派生类新增的成员都具有类作用域,二者的作用范围不同,是相互包含的两层,派生类在内层。这时如果派生类定义了一个和某个基类成员同名的新成员,派生的新成员就改写了外层同名成员,直接使用成员名只能访问到派生类的成员。
        例1        分析下列程序的输出结果

#include<iostream>
using namespace std;
class Base1
{
public:void fun() { cout << "Base1" << endl; }int data;
};
class Base2
{
public:void fun(){cout << "Base2" << endl;}int data;
};
class Derived:public Base1,public Base2
{
public:int data;void fun(){ cout << "Derived" << endl; }
};
int main()
{Derived obj;obj.data = 1;obj.fun();obj.Base1::data = 2;obj.Base1::fun();obj.Base2::data = 3;obj.Base2::fun();return 0;
}

        产生二义性问题的另外一中情况时当派生类从多个基类派生,而这些基类又有一个共同的基类,当共同的基类中说明的成员进行访问时,可能出现二义性。
        由于二义性检测在访问控制权限或类型检查之前进行,因此访问控制权限不同或类型不同不能解决二义性问题。
        13.2        虚基类的定义
        当一个派生类从多个基类派生,而这些基类又有一个共同的基类,当对该基类中说明的成员进行访问时,可能出现二义性。虚基类就是为了解决这种二义性问题提出来的。
        虚基类的说明格式如下:
                class<类名>:virtual<继承方式><基类名>
        其中关键字virtual与继承方式的位置无关,但必须位于虚基类名之前。且virtual只对紧随其后的基类名起作用。
        13.3        虚基类的构造函数
        使用虚基类解决二义性问题的关键是在派生类中只产生一个虚基类子对象。
        为初始化基类子对象,派生类的构造函数要调用基类的构造函数。对于基类的对象中只有一个虚基类子对象,所以,在建立一个派生类的对象时,为保证虚基类子对象只被初始化一次,这个虚基类构造函数必须只被调用一次。虽然继承结构的层次可能很深,但要建立的对象所属的类只是这个继承结构中间的某个类,因此将在建立对象时所指定的类称为最派生类。虚基类子对象由最派生类的构造函数通过虚基类的构造函数实现。所以最派生类的构造函数初始化列表中必须列出对虚基类的构造函数的调用;如果为列出则表示使用该虚基类的默认构造函数
        由于派生类总是相对的,因此,从虚基类直接或间接派生的派生类的构造函数的初始化列表中都要列出对虚基类构造函数的调用。但只有用于建立对象的最派生类的构造函数才能调用虚基类的构造函数,此时最派生类的所有基类中列出的对虚基类的构造函数的调用在执行过程中都被忽略,从而保证对虚基类子对象只初始化一次。
        当在构造函数的初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数。
        例2        分析下列程序的输出结果

#include<iostream>
using namespace std;
class Base
{
public:Base(char i);~Base();
};
Base::Base(char i)
{cout << "Base构造"<<i << endl;
}
Base::~Base()
{cout << "Base析构" << endl;
}
class Derived1 :virtual public Base
{
public:Derived1(char i, char j);~Derived1();
};
Derived1::Derived1(char i, char j) :Base(i)
{cout << "Derived1构造"<<j << endl;
}
Derived1::~Derived1()
{cout << "Derived1析构" << endl;
}
class Derived12 :virtual public Base
{
public:Derived12(char i, char j);~Derived12();
};
Derived12::Derived12(char i, char j) :Base(i)
{cout << "Derived12构造"<<j << endl;
}
Derived12::~Derived12()
{cout << "Derived12析构" << endl;
}
class Derived2 :public Derived1,public Derived12
{
public:Derived2(char i, char j,char k,char l,char n);~Derived2();
private:Base aa;
};
Derived2::Derived2(char i, char j, char k, char l, char n):Derived1(i,j),Derived12(k,l),Base(i),aa(n)
{cout << "Derived2构造"<<n << endl;
}
Derived2::~Derived2()
{cout << "Derived2析构" << endl;
}
int main()
{Derived2 d('a', 'b', 'c', 'd', 'e');return 0;
}

        当同时出现虚基类和非虚基类构造函数的调用时,首先执行虚基类构造函数。
        因此先执行Base的构造函数,再执行Derived1和Derived12的构造函数。
        因此首先是Derived2对Base进行初始化,所以Derived1和Derived2不需要进行初始化。

参考《全国计算机等级考试二级教程——C++语言程序设计》

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Artix7系列FPGA实现SDI视频编解码+UDP以太网传输,基于GTP高速接口,提供工程源码和技术支持
  • Unity UGUI 之 Canvas画布
  • 深入理解TCP/IP协议中的三次握手
  • GD32 MCU是如何进入中断函数的
  • Android 10.0 蓝牙音乐获取歌手、歌曲等信息功能实现
  • 微服务重启优化kafka+EurekaNotificationServerListUpdater
  • 【Docker】Docker-compose 单机容器集群编排工具
  • Armpro搭建教程全开源版的教程
  • 【BUG】已解决: KeyboardInterrupt
  • Ubuntu16.04环境下Baxter机器人开发环境搭建要点说明
  • windows ssh的登录,私钥权限太开放 WARNING: UNPROTECTED PRIVATE KEY FILE!
  • 在 CI/CD Pipeline 中实施持续测试的最佳实践!
  • C 语言实例 - 使用引用循环替换数值
  • 【LeetCode】填充每个节点的下一个右侧节点指针 II
  • PHP 表单验证:邮件和URL
  • -------------------- 第二讲-------- 第一节------在此给出链表的基本操作
  • 【个人向】《HTTP图解》阅后小结
  • 3.7、@ResponseBody 和 @RestController
  • cookie和session
  • CSS魔法堂:Absolute Positioning就这个样
  • Java 23种设计模式 之单例模式 7种实现方式
  • JavaScript的使用你知道几种?(上)
  • passportjs 源码分析
  • React Transition Group -- Transition 组件
  • React中的“虫洞”——Context
  • Redash本地开发环境搭建
  • 表单中readonly的input等标签,禁止光标进入(focus)的几种方式
  • 面试题:给你个id,去拿到name,多叉树遍历
  • 实习面试笔记
  • -- 数据结构 顺序表 --Java
  • No resource identifier found for attribute,RxJava之zip操作符
  • raise 与 raise ... from 的区别
  • ​io --- 处理流的核心工具​
  • #微信小程序(布局、渲染层基础知识)
  • (1)Nginx简介和安装教程
  • (1)STL算法之遍历容器
  • (8)STL算法之替换
  • (BAT向)Java岗常问高频面试汇总:MyBatis 微服务 Spring 分布式 MySQL等(1)
  • (C语言)strcpy与strcpy详解,与模拟实现
  • (转)IIS6 ASP 0251超过响应缓冲区限制错误的解决方法
  • (转)关于pipe()的详细解析
  • (转)全文检索技术学习(三)——Lucene支持中文分词
  • (转载)从 Java 代码到 Java 堆
  • ***测试-HTTP方法
  • 、写入Shellcode到注册表上线
  • .[hudsonL@cock.li].mkp勒索加密数据库完美恢复---惜分飞
  • .Net的DataSet直接与SQL2005交互
  • .NET开发不可不知、不可不用的辅助类(一)
  • .NET企业级应用架构设计系列之技术选型
  • .net实现头像缩放截取功能 -----转载自accp教程网
  • .vue文件怎么使用_我在项目中是这样配置Vue的
  • /proc/interrupts 和 /proc/stat 查看中断的情况
  • ::什么意思
  • ?php echo $logosrc[0];?,如何在一行中显示logo和标题?
  • @Async 异步注解使用