继承
继承是由父类和子类(或称之为基类与派生类)两种角色构成,子承父业,就是说派生类会继承基类的所有的成员,并拥有自己特有的成员。
用一段代码来解释说明继承关系
#include <iostream>
using namespace std;
class Base
//基类
{
public:
Base()
{
cout<<"Base()"<<endl;
}
int _b;//这里的_b暂且设置为公有属性
};
class Derived:public Base//以共有继承方式继承于Base,权限不写默认为private
的派生类
{
public:
Derived()
{
cout<<"Derived()"<<endl;
}
void test()
{
_b = 10;//可以编译且运行,证明继承了基类的成员变量
}
private:
int _d;
};
int main()
{
Derived d;
return 0;
}
执行结果如下图所示:
先创建一个派生类对象,运行结果中调用了派生类和基类的构造函数,说明,派生类继承了基类的成员函数和成员变量,在这里虽然继承了成员变量可以在派生类内直接访问,但是其他继承权限则不然,下面会有相关介绍
三种继承权限
若将权限等级排序一下:public>protected>private,则继承之后派生类继承于基类的成员权限将会发生降级,继承关系如下所示:
1.子类对象可以赋值给父类对象(切割/切片)
2. 父类对象不能赋值给子类对象
3. 父类的指针/引用可以指向子类对象
4. 子类的指针/引用不能指向父类对象(可以通过强制类型转)
对象模型: 派生类对象模型中,继承于基类的结构在前,属于自身的结构在后;
多继承
一个子类有两个或以上直接父类时称这个继承关系为多继承
菱形虚拟继承
菱形继承(钻石继承)
如上图所示,在菱形继承中,因为C1、C2同时继承Base,所以在D中会存在二义性问题,也就是D同时存在两个来自Base中的成员,这是我们引入一个虚拟继承的方法来解决二义性问题,称之为菱形虚拟继承:
首先他的代码实现如下(virtual):
#include <iostream>
using namespace std;
class B
{
public:
B()
:_b(0)
{
cout<<"Base()"<<endl;
}
~B()
{
cout<<"~B()"<<endl;
}
int _b;
};
class C1:virtual public B//继承于Base
{
public:
C1()
:_c1(1)
{
cout<<"C1()"<<endl;
}
~C1()
{
cout<<"~C1()"<<endl;
}
int _c1;
};
class C2:virtual public B
{
public:
C2()
:_c2(2)
{
cout<<"C2()"<<endl;
}
~C2()
{
cout<<"~C2()"<<endl;
}
int _c2;
};
class D:public C1,public C2
{
public:
D()
:_d(3)
{
cout<<"D()"<<endl;
}
~D()
{
cout<<"~D()"<<endl;
}
int _d;
};
int main()
{
D d;
return 0;
}
以上代码中调用构造与析构时分别打印调用到的函数名,virtual是要加在菱形继承的上半部分,也就是C1、C2继承Base的时候,我们先来对比一下虚拟继承与非虚拟继承的运行结果的区别
<1>虚拟继承 <2>非虚拟继承
虚拟继承与非虚拟继承的区别:
1. 虚拟继承少调用了一次构造和一次析构
2. 虚继承解决了在菱形继承体系里面子类对象包含多份父类对象的数据冗余和浪费空间的问题。
下面我们来看一下虚拟继承是如何操作的:
在D的构造中,D的对象模型中有如下操作:
那么这两个压入的地址是什么呢,调出内存查看:
可以看到这两个地址指向的是上图所示内存块,这个内存块里存的就是实现虚拟继承的核心-偏移量表格:
上图很清晰的能看到虚拟继承的工作原理,红色框里就是具体的偏移量表,而在D的对象模型中存储了各自的偏移量表格的地址。