继承:继承是指对象的复用的重要手段,通过继承定义一个类,继承是类型之间的关系建模,共享共有的东西,实现各自本质不同的东西。

继承的三种不同的情况:


在三种继承关系下基类成员在派生类中的访问情况:


区别重写/覆盖,重定义/隐藏,重载:


?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不明确的提示;下面来看一下内存分布:


我们将程序改为虚继承,程序的运行结果为:


我们再来看一下内存分布:


所以虚继承可以解决二义性问题。