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 ...
随机推荐
- Programming In Scala笔记-第四章、类和对象
类似于Java,Scala中也有类和对象的概念. 一.类.属性和方法 1.类 类是对一类事物的抽象,当一个类被定义后,就可以以该定义为模板,定义该类的一系列对象.比如说有以下一个模板 人类: 有姓名: ...
- 【SSH系列】初识spring+入门demo
学习过了hibernate,也就是冬天,经过一个冬天的冬眠,当春风吹绿大地,万物复苏,我们迎来了spring,在前面的一系列博文中,小编介绍hibernate的相关知识,接下来的博文中,小编将继续介绍 ...
- Redis集群功能预览
目前Redis Cluster仍处于Beta版本,Redis 3.0将会加入,在此可以先对其主要功能和原理进行一个预览.参考<Redis Cluster - a pragmatic approa ...
- 探究java接口中的变量与方法
关于变量 java接口里的变量都是默认 pubic static final的 为啥? public 接口得能被所有对象调用 static 这个变量是属于接口本身,而不是实现了接口的对象的 具体来说 ...
- JBOSS EAP6 系列二 客户端访问位于EAR中的EJB时,jndi name要遵守的规则
EJB 的 jndi语法(在整个调用远程ejb的过程中语法的遵循是相当重要的) 参见jboss-as-quickstarts-7.1.1.CR2\ejb-remote\client\src\main\ ...
- 【移动开发】ViewPager缓存机制
1. 实现ViewPager的页面懒加载:在某些情况下,例如使用ViewPager查看多张大图,此时多张图片不能一次性载入,只有在浏览该页面时才载入(或者预先载入下一页面)页面的具体内容.2. ...
- [openwrt] uci 的shell和lua接口
uci是openwrt上配置操作的接口,不管是自动化的shell脚本,还是使用luci来二次开发配置界面,都会用到这部分知识. uci提供了lua, shell, c接口,这里主要用到了前两种 she ...
- Java基础---Java---IO流-----File 类、递归、删除一个带内容的目录、列出指定目录下文件夹、FilenameFilte
File 类 用来将文件或者文件夹封装成对象 方便对文件与文件夹进行操作. File对象可以作为参数传递给流的构造函数 流只用操作数据,而封装数据的文件只能用File类 File类常见方法: 1.创建 ...
- 【原创】Nginx+PHP-FPM优化技巧总结
php-fpm的安装很简单,参见PHP(PHP-FPM)手动编译安装.下面主要讨论下如何提高Nginx+Php-fpm的性能. 1.Unix域Socket通信 之前简单介绍过Unix Domain S ...
- 初探linux子系统集之timer子系统(一)
一般来说要让整个linux系统跑起来,那么一个必须的就是linux的时钟,也就是时间子系统了,这里正好工作需要,那么就研究下linux下的时间子系统了. linux内核必须完成两种主要的定时测量.一个 ...