C++多重继承与虚拟继承
本文只是粗浅讨论一下C++中的多重继承和虚拟继承。
多重继承中的构造函数和析构函数调用次序
我们先来看一下简单的例子:
#include <iostream>
using namespace std; class A
{
private:
char idA; public:
A(){
idA = 'A';
cout << "Constructor of A is called!" << endl;
}
~A() { cout << "Destructor of A is called!" << endl; }
}; class B : public A
{
private:
char idB; public:
B(){
idB = 'B';
cout << "Constructor of B is called!" << endl;
}
~B() { cout << "Destructor of B is called!" << endl; }
}; class C : public A
{
private:
char idC; public:
C(){
idC = 'C';
cout << "Constructor of C is called!" << endl;
}
~C() { cout << "Destructor of C is called!" << endl; }
}; class D : public B, public C
{
private:
char idD; public:
D(){
idD = 'D';
cout << "Constructor of D is called!" << endl;
}
~D() { cout << "Destructor of D is called!" << endl; }
}; int main()
{
D d;
return ;
}
上述程序的输出为:

由上边结果可以看出,析构函数调用次序跟构造函数是相反的。另外,构造函数调用次序跟类D继承B、C次序(public B, public C)相关。
可能我们也发现了,对于类D的实例d来说,它其实有两个重复的A实例。我们应该要去掉其中一个以节省空间。具体做法就是采用虚拟继承的方法:
class B : public virtual A
{
...
}; class C : public virtual A
{
...
};
这是程序的输出就会变成:

可见这个时候类D的实例d就只有一个类A实例。
二义性
请看下边程序:
#include <iostream>
using namespace std; class A
{
private:
char idA; public:
A(){
idA = 'A';
cout << "Constructor of A is called!" << endl;
}
~A() { cout << "Destructor of A is called!" << endl; }
char getID() { return idA; }
}; class B : public virtual A
{
private:
char idB; public:
B(){
idB = 'B';
cout << "Constructor of B is called!" << endl;
}
~B() { cout << "Destructor of B is called!" << endl; }
char getID() { return idB; }
}; class C : public virtual A
{
private:
char idC; public:
C(){
idC = 'C';
cout << "Constructor of C is called!" << endl;
}
~C() { cout << "Destructor of C is called!" << endl; }
char getID() { return idC; }
}; class D : public B, public C
{
private:
char idD; public:
D(){
idD = 'D';
cout << "Constructor of D is called!" << endl;
}
~D() { cout << "Destructor of D is called!" << endl; }
// char getID() { return idD; }
}; int main()
{
D d;
cout << d.getID() << endl; return ;
}
在main函数中,第63行的d.getID()会优先在类D中查找有没有getID()的定义,如果没有就会到其父类查找;而恰好其父类B、C(同级)均定义了相同的getID()(类A的getID()定义存不存在都没关系),这时d.getID()就不知道要调用B类中的getID()还是C类中的,从而导致二义性。
不过我们可以通过d.B::getID()、d.C::getID()来指明具体要调用哪一个类的getID。但我们总不会想到这样子去做,而且这样子做也比较麻烦。
虚函数
对于多重继承的虚函数同样存在二义性。
先看一下程序:
#include <iostream>
using namespace std; class A
{
private:
char idA; public:
A(){
idA = 'A';
cout << "Constructor of A is called!" << endl;
}
~A() { cout << "Destructor of A is called!" << endl; }
char getID() { return idA; }
}; class B : public virtual A
{
private:
char idB; public:
B(){
idB = 'B';
cout << "Constructor of B is called!" << endl;
}
~B() { cout << "Destructor of B is called!" << endl; }
char getID() { return idB; }
}; class C : public virtual A
{
private:
char idC; public:
C(){
idC = 'C';
cout << "Constructor of C is called!" << endl;
}
~C() { cout << "Destructor of C is called!" << endl; }
char getID() { return idC; }
}; class D : public B, public C
{
private:
char idD; public:
D(){
idD = 'D';
cout << "Constructor of D is called!" << endl;
}
~D() { cout << "Destructor of D is called!" << endl; }
char getID() { return idD; }
}; int main()
{
D d;
A a = d;
B b = d;
C c = d;
cout << a.getID() << endl;
cout << b.getID() << endl;
cout << c.getID() << endl;
cout << d.getID() << endl; return ;
}
程序输出如下:

上边程序第63~65行相当于a、b、c将d进行了分割(函数是否是虚函数在这里并无关系,而且注意这里的a、b、c、d都不是指针),分割出属于自己的部分,所以调用getID()的时候能正确反映具体的类。
我们再来看一个程序:
#include <iostream>
using namespace std; class A
{
private:
char idA; public:
A(){
idA = 'A';
cout << "Constructor of A is called!" << endl;
}
~A() { cout << "Destructor of A is called!" << endl; }
virtual char getID() { return idA; }
}; class B : public virtual A
{
private:
char idB; public:
B(){
idB = 'B';
cout << "Constructor of B is called!" << endl;
}
~B() { cout << "Destructor of B is called!" << endl; }
virtual char getID() { return idB; }
}; class C : public virtual A
{
private:
char idC; public:
C(){
idC = 'C';
cout << "Constructor of C is called!" << endl;
}
~C() { cout << "Destructor of C is called!" << endl; }
virtual char getID() { return idC; }
}; class D : public B, public C
{
private:
char idD; public:
D(){
idD = 'D';
cout << "Constructor of D is called!" << endl;
}
~D() { cout << "Destructor of D is called!" << endl; }
virtual char getID() { return idD; }
}; int main()
{
D *d = new D();
A *a = d;
B *b = d;
C *c = d;
cout << a->getID() << endl;
cout << b->getID() << endl;
cout << c->getID() << endl;
cout << d->getID() << endl; delete d;
return ;
}
程序的输出如下:

从输出结果可以看出,类D的getID覆盖其所有父类的getID。需要注意的是,当我们在类D的一个父类,如A中不设定getID为虚函数,则“A *a = d”的效果仍然跟分割d指向的内存的效果一样。
C++多重继承与虚拟继承的更多相关文章
- 图文例解C++类的多重继承与虚拟继承
文章导读:C++允许为一个派生类指定多个基类,这样的继承结构被称做多重继承. 在过去的学习中,我们始终接触的单个类的继承,但是在现实生活中,一些新事物往往会拥有两个或者两个以上事物的属性,为了解决这个 ...
- 《挑战30天C++入门极限》图文例解C++类的多重继承与虚拟继承
图文例解C++类的多重继承与虚拟继承 在过去的学习中,我们始终接触的单个类的继承,但是在现实生活中,一些新事物往往会拥有两个或者两个以上事物的属性,为了解决这个问题,C++引入了多重继承的概念 ...
- 浅析GCC下C++多重继承 & 虚拟继承的对象内存布局
继承是C++作为OOD程序设计语言的三大特征(封装,继承,多态)之一,单一非多态继承是比较好理解的,本文主要讲解GCC环境下的多重继承和虚拟继承的对象内存布局. 一.多重继承 先看几个类的定义: 01 ...
- C++ 虚拟继承
1.为什么要引入虚拟继承 虚拟继承是多重继承中特有的概念.虚拟基类是为解决多重继承而出现的.如:类D继承自类B1.B2,而类B1.B2都继 承自类A,因此在类D中两次出现类A中的变量和函数.为了节省内 ...
- 关于C++中的虚拟继承的一些总结
1.为什么要引入虚拟继承 虚拟继承是多重继承中特有的概念.虚拟基类是为解决多重继承而出现的.如:类D继承自类B1.B2,而类B1.B2都继承自类A,因此在类D中两次出现类A中的变量和函数.为了节省内存 ...
- 虚拟继承C++
C++中虚拟继承的概念 为了解决从不同途径继承来的同名的数据成员在内存中有不同的拷贝造成数据不一致问题,将共同基类设置为虚基类.这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数 ...
- c++面试常用知识(sizeof计算类的大小,虚拟继承,重载,隐藏,覆盖)
一. sizeof计算结构体 注:本机机器字长为64位 1.最普通的类和普通的继承 #include<iostream> using namespace std; class Parent ...
- C++中的多重继承与虚继承的问题
1.C++支持多重继承,但是一般情况下,建议使用单一继承. 类D继承自B类和C类,而B类和C类都继承自类A,因此出现下图所示情况: A A \ / B C ...
- c++,为什么要引入虚拟继承
虚拟基类是为解决多重继承而出现的. 以下面的一个例子为例: #include <iostream.h> #include <memory.h> class CA { i ...
随机推荐
- MongoDb 用 mapreduce 统计留存率
MongoDb 用 mapreduce 统计留存率(金庆的专栏)留存的定义采用的是新增账号第X日:某日新增的账号中,在新增日后第X日有登录行为记为留存 输出如下:(类同友盟的留存率显示)留存用户注册时 ...
- cassandra eclipse 环境构建
摘要 本文主要介绍如何在eclipse中搭建cassandra环境 更多cassandra,nosql 相关知识请访问http://www.webpersonaldeveloper.cn 正文 1.f ...
- activiti 任务节点 处理人设置
1.1.1. 前言 分享牛原创(尊重原创 转载对的时候第一行请注明,转载出处来自分享牛http://blog.csdn.net/qq_30739519) 我们在使用activiti 工作流引擎的时候, ...
- Android Multimedia框架总结(四)MediaPlayer中从Java层到C++层类关系及prepare及之后其他过程
转载请把头部出处链接和尾部二维码一起转载,本文出自:http://blog.csdn.net/hejjunlin/article/details/52420803 前言:在上篇中,分析了MediaPl ...
- Android实现自动更新功能
Android实现自动更新功能 Android自动更新的功能可以使用第三方的SDK来实现,但是类似友盟,就不支持x86手机的自动更新,科大讯飞,弹窗是全局的,小米手机就会默认把弹窗权限关掉不允许弹出提 ...
- 如何查看App provision profile文件中的钥匙链访问组名称
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 我们因为某些原因希望安全的在多个App中共享一些信息,我们可以 ...
- FFmpeg源代码结构图 - 解码
===================================================== FFmpeg的库函数源代码分析文章列表: [架构图] FFmpeg源代码结构图 - 解码 F ...
- iOS完整预装字体清单
iOS完整预装字体清单:http://iosfonts.com/
- mysql数据库连接池使用(一)dbcp方式的配置
Apache的数据库连接池 DBCP的常用配置说明,因为项目中用到了需要对其封装,所以必须先了解怎么配置以及各个配置字段的含义,理解的基础上开发我们自己的数据库连接池.可以参考官网dbcp官网. db ...
- Android 5.x 权限问题解决方法
android 5.x开始,引入了非常严格的selinux权限管理机制,我们经常会遇到因为selinux权限问题造成的各种avc denied困扰. 本文结合具体案例,讲解如何根据log来快速解决9 ...