对于一个派生类
它的构造函数和析构函数的执行
有一些特别的限定
构造函数 它的执行顺序是这么规定的
首先 它要调用基类的构造函数
因为它是一个派生 有一个层次树嘛
所以它的基类构造函数的调用顺序
是与基类在这个派生类中继承的顺序
是相关的 完全一样
基类在派生类里边
它是按照什么样的继承顺序来的
那么它的构造函数
就按照什么样的顺序来构造
所以说 B类继承A类 C类继承B类
那么它构造的时候首先调用的是
A类的构造函数 然后调用B类的构造函数
然后才能调用C类的构造函数
首先调用基类的构造函数
第二个 调用派生类的新增数据对象的构造函数
如果派生类里面你增加了一个数据对象
这个数据对象本身是类的一个对象
那么这个对象也有构造函数
所以完成基类构造函数之后
它就会调用这些成员的构造函数
来去构造它的所有的成员
这个构造的顺序和这些对象
在派生类里边它的定义顺序是一样的
你先定义哪个 就先构造哪个
因为它的内存里边就按照这个顺序排的
它构造的时候
就对那个数据区域进行初始化
它肯定一个接着一个去做
所以颠倒做肯定不如顺序做方便
也不如那个效率高
所以啊 它就跟那个顺序是相同的
最后 它才是调用派生类自己的构造函数
它是按照这样一个方式去构造的
如果这些量写在初始化列表里边
它就用初始化列表里面的数据去构造它
最后调用我们的函数体
构造函数的函数体
这就是派生类的构造函数的执行顺序
析构函数是反过来的
首先 它调用派生类的析构函数
先把派生类的新增那些东西给它消掉
然后调用派生类的
新增的对象成员的析构函数
调用的顺序刚好和它的构造顺序是相反的
你如果派生类中有三个数据成员
a、b、c 都是类的对象
那么构造的时候是按照a、b、c的顺序
析构的时候就是c、b、a的顺序
一个接着一个地来
当把所有这些新增数据成员全部析构之后
它才能够调用基类的析构函数
如果它的继承层次是A、B、C
那么它析构的时候就是C、B、A
还是倒过来的
这个就是派生类的构造函数
与析构函数的执行顺序
同学们一定要深刻地理解这一点
否则的话你的程序就有可能有问题
类的赋值兼容性非常重要
因为我们在写面向对象的程序的时候
我们实际上是建构了一个类库的层次
而我们操纵这个类库层次的时候
我们往往会使用指向基类的一个指针
或者是一个基类的一个引用
公有派生的时候
任何基类的对象可以出现的位置
都可以用派生类的对象来代替
这保证了广泛的赋值兼容性
为我们编程提供极大的灵活性
这是必不可少的一个东西
涉及到三种情况
一个 可以将一个派生类的对象
赋值给一个基类的对象
这个时候它仅赋值它的基类部分
可以赋值 但有一些信息丢了
用派生类的对象
来初始化一个基类的一个引用的时候
我们仅能操作它的基类部分
一样地 一个基类的一个引用
它也只能引用它的基类的那一部分的数据
我们把一个派生类的对象初始化给它
那它引用的仍然是它的基类的那一小部分
所有的派生类数据它是访问不到的
看不见 这是非常重要的
将指向基类的指针
指向一个派生类的对象的时候
我们也仅能引领它的基类部分
指针也一样
你只能操作它的目标基类的那一部分
后续的那一部分你访问不到的
也就是说不管怎么样
当你把这个派生类的对象赋值为基类对象的时候
要么是赋值基类的部分
要么是只能操作那一部分
要么是只能引领它的那一部分
派生类的那个部分都没有办法去处理的
特别注意这一条
保护派生和私有派生
本身因为它的访问控制的原因
是不允许你直接赋值的
所以这种时候
它就完全地打破了我们类继承的层次
它的那个编程的灵活性
我们在设计这个类库继承层次的时候
需要的就是这个将派生类的对象
或派生类的引用
或者指向派生类的指针
来赋值给一个基类的对象、一个基类的引用、
一个指向基类的指针
如果缺了这个类的赋值兼容性
那么实际上 面向对象的这个继承的架构
它的好处就已经取消了一大部分
所以真正地派生的时候
使用保护派生和私有派生场合是极少极少的
绝大多数情况下我们都应该公有派生
你明确了 我们应该单继承、公有派生
这是写面向对象程序中
保证自己不犯错误的最简单的方式
反正这种方式能够解决任何问题
那不就行了嘛 我们看这样一个例子
我定义一个基类class Base
它有构造函数 有一个Print函数
我们要打印它的数据
定义了一个保护的数据成员str_a
然后有一个派生类classDerived
公有派生publicBase 公有派生下来
当然了 它有自己的构造函数
有个Derived(strings1，strings2)
特别需要说明的就是
我们的派生类构造函数里边
首先要构造它的基类的部分
而这个基类的构造函数怎么构造呢
就应该写在这个构造函数的
初始化列表里边
要写在这个构造函数的初始化列表里边
重要的事情说三遍
要写在这个类的构造函数的初始化列表里边
用类名字Base(s1)
其实调用的就是基类的构造函数
来构造基类的那一部分
构造完 然后构造派生类的自有对象 str_b
那个也是protected的string
string是标准模板库里面
替我们提供的一个类——string类
我们前面解释过 按照这个模式
这个Base只能写初始化列表里边
str_b倒是可以写在花括号体里边
但是那个写法会导致两次构造 效率降低了
所以也应该写在这个初始化列表里边
Print是它派生类里边提供的函数
输出的格式就和刚才那个就不一样了
接下来就看主函数的实现
Derived d1 构造一个派生类的对象d1
然后我定义一个基类的对象
用d1去构造它 这个构造是个拷贝构造
因为我们传的是一个d1的对象
把一个派生类的一个对象
传给一个基类的对象 然后构造它
它就要拷贝那个派生类中的那个基类部分
把它拷贝给它的基类那个对象 b1
所以呢 它要调用的就是一个拷贝构造函数
我们派生类到基类
只会复制它的基类的那个部分
所以你在d1上面调用Print
打印的将是“Hello World”
在b1上面调用 打印的将是“Hello”
因为它只构造那个“Hello”那个串
而不会复制我们的“World”
那个第二个字串 特别注意这一点
当我们使用这个Base的一个引用的时候
定义b2 然后把它初始化成d1
那就是b2将引用是d1
但是因为这个引用的型
它实际上是一个Base 所以它实际上
只能引用d1这个对象的那个基类部分
所以我们在d1上面调Print
当然还是输出“Hello World”
在b2上面调用Print
当然仍然只能输出“Hello”
如果是一个指向基类的一个指针b3
我把它初始化成d1的地址
在这种情况下边 我们引领b3的Print
打印出来的仍然是“Hello”
因为我们只能引领d1对象的基类部分
访问不到“World”那个字符串的
这是类的赋值兼容性