继承:继承是指对象的复用的重要手段,通过继承定义一个类,继承是类型之间的关系建模,共享共有的东西,实现各自本质不同的东西。
继承的三种不同的情况:
在三种继承关系下基类成员在派生类中的访问情况:
区别重写/覆盖,重定义/隐藏,重载:
?public继承is-a
继承后弗雷德属性不变,子类可以赋给父类对象/指针/引用,会发生切片。是一个接口继承,每个父类可用的对象子类也可用,因为每个子类对象也都是父类对象。
?private/protect继承has-a
父类在子类里面访问限定符改变,子类无法赋给父类。是一个实现继承,基类的部分成员并未完全成为子类接口的一部分。
赋值兼容规则:
①子类对象可以赋值给父类对象,父类对象不可以赋值给子类对象
②父类指针/引用可以指向子类对象,子类指针/引用不能指向父类对象
注:可以通过强制类型转换使子类指针/引用指向父类对象,但不能通过子类指针指向子类成员,
派生类中的默认成员函数:
构造函数、析构函数、拷贝构造函数、赋值运算符重载、取地址操作符重载、const修饰的取地址操作符重载
注:当基类构造函数不带参数时,派生类不一定需要自定义构造函数;当基类的构造函数有参数时,派生类必须自定义构造函数。
如果派生类的基类也是一个派生类,每个派生类只需要负责其直接基类数据成员的初始化。
多继承与菱形继承出现的二义性和数据冗杂现象:
#include "stdafx.h"
#include<iostream>
using namespace std;
class A {
public:
int _a;
};
class B :public A
//class B:virtual public A
{
public:
int _b;
};
class C :public A
//class C:virtual public B
{
public:
int _c;
};
class D :public B, public C
{
public:
int _d;
};
int main()
{
D dd;
cout << sizeof(dd) << endl;
dd.B::_a = 1;
dd.B::_b = 2;
dd.C::_a = 3;
dd.C::_c = 4;
dd.D::_d = 5;
//cout << dd._a <<endl;
return 0;
}
为什么dd的长度会是20呢?他明明只有a,b,c,d,四个成员。
那一起看一下他的内存分布吧!
通过监视窗口可以知道dd里面有B继承的A的_a,和C继承的A的_a,当执行cout<<dd._a;语句时就会出现_a不明确的提示;下面来看一下内存分布:
我们将程序改为虚继承,程序的运行结果为:
我们再来看一下内存分布:
所以虚继承可以解决二义性问题。