当前课程知识点:基于Linux的C++ > 第九讲 类与对象 > 9.11 继承(三) > LinuxCPP0911
对于一个派生类
它的构造函数和析构函数的执行
有一些特别的限定
构造函数 它的执行顺序是这么规定的
首先 它要调用基类的构造函数
因为它是一个派生 有一个层次树嘛
所以它的基类构造函数的调用顺序
是与基类在这个派生类中继承的顺序
是相关的 完全一样
基类在派生类里边
它是按照什么样的继承顺序来的
那么它的构造函数
就按照什么样的顺序来构造
所以说 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”那个字符串的
这是类的赋值兼容性
-1.1 提纲
-1.2 程序设计的基本概念
-1.3 简单C/C++程序介绍
-1.4 程序设计的基本流程
-1.5 基本语法元素
-1.6 程序设计风格
-1.7 编程实践
-第一讲 C/C++基本语法元素--编程实践提交入口
-2.1 提纲
-2.2 结构化程序设计基础
-2.3 布尔数据
-2.4 分支结构
-2.5 break语句
-2.6 循环结构
-2.7 编程实践
-第二讲 程序控制结构--编程实践提交入口
-3.1 提纲
-3.2 函数声明、调用与定义
-3.3 函数调用栈框架
-3.4 编程实践
-第三讲 函数--编程实践提交入口
-4.1 提纲
-4.2 算法概念与特征
-4.3 算法描述
-4.4 算法设计与实现
-4.5 递归算法(一)
-4.6 递归算法(二)
-4.7 容错与计算复杂度
-4.8 编程实践
-第四讲 算法--编程实践提交入口
-5.1 提纲
-5.2 库与接口
-5.3 随机数库(一)
-5.4 随机数库(二)
-5.5 作用域与生存期
-5.6 典型软件开发流程(一)
-5.7 典型软件开发流程(二)
-5.8 编程实践
-第五讲 程序组织与开发方法--编程实践提交入口
-6.1 提纲
-6.2 字符
-6.3 数组(一)
-6.4 数组(二)
-6.5 结构体
-6.6 编程实践
-第六讲 复合数据类型--编程实践提交入口
-7.1 提纲
-7.2 指针基本概念
-7.3 指针与函数
-7.4 指针与复合数据类型(一)
-7.5 指针与复合数据类型(二)
-7.6 字符串
-7.7 动态存储管理(一)
-7.8 动态存储管理(二)
-7.9 引用
-7.10 编程实践
-第七讲 指针与引用--编程实践提交入口
-8.1 提纲
-8.2 数据抽象(一)
-8.3 数据抽象(二)
-8.4 链表(一)
-8.5 链表(二)
-8.6 链表(三)
-8.7 链表(四)
-8.8 函数指针(一)
-8.9 函数指针(二)
-8.10 抽象链表(一)
-8.11 抽象链表(二)
-8.12 编程实践
-第八讲 链表与程序抽象--编程实践提交入口
-9.1 提纲
-9.2 程序抽象与面向对象
-9.3 类类型
-9.4 对象(一)
-9.5 对象(二)
-9.6 类与对象的成员(一)
-9.7 类与对象的成员(二)
-9.8 类与对象的成员(三)
-9.9 继承(一)
-9.10 继承(二)
-9.11 继承(三)
-9.12 多态(一)
-9.13 多态(二)
-9.14 编程实践
-第九讲 类与对象--编程实践提交入口
-10.1 提纲
-10.2 四则运算符重载(一)
-10.3 四则运算符重载(二)
-10.4 关系与下标操作符重载
-10.5 赋值操作符重载(一)
-10.6 赋值操作符重载(二)
-10.7 赋值操作符重载(三)
-10.8 赋值操作符重载(四)
-10.9 赋值操作符重载(五)
-10.10 流操作符重载(一)
-10.11 流操作符重载(二)
-10.12 流操作符重载(三)
-10.13 操作符重载总结
-10.14 编程实践
-第十讲 操作符重载--编程实践提交入口
-11.1 提纲
-11.2 泛型编程概览
-11.3 异常处理机制(一)
-11.4 异常处理机制(二)
-11.5 运行期型式信息(一)
-11.6 运行期型式信息(二)
-11.7 模板与型式参数化
-11.8 题外话:术语翻译
-11.9 泛型编程实践(一)
-11.10 泛型编程实践(二)
-11.11 泛型编程实践(三)
-11.12 泛型编程实践(四)
-11.13 泛型编程实践(五)
-11.14 泛型编程实践(六)
-11.15 泛型编程实践(七)
-11.16 泛型编程实践(八)
-11.17 泛型编程实践(九)
-11.18 泛型编程实践(十)
-11.19 编程实践
-第十一讲 泛型编程--编程实践提交入口
-12.1 提纲
-12.2 程序执行环境(一)
-12.3 程序执行环境(二)
-12.4 程序执行环境(三)
-12.5 程序执行环境(四)
-12.6 输入输出(一)
-12.7 输入输出(二)
-12.8 文件系统
-12.9 设备
-12.10 库(一)
-12.11 库(二)
-12.12 makefile文件(一)
-12.13 makefile文件(二)
-12.14 makefile文件(三)
-12.15 编程实践
-第十二讲 Linux系统编程基础--编程实践提交入口
-13.01 提纲
-13.02 进程基本概念
-13.03 信号
-13.04 进程管理(一)
-13.05 进程管理(二)
-13.06 进程管理(三)
-13.07 进程间通信(一)
-13.08 进程间通信(二)
-13.09 进程间通信(三)
-13.10 进程间通信(四)
-13.11 进程池
-13.12 编程实践
-第十三讲 进程编程--编程实践提交入口
-14.1 提纲
-14.2 线程基本概念
-14.3 线程管理(一)
-14.4 线程管理(二)
-14.5 线程管理(三)
-14.6 线程管理(四)
-14.7 线程同步机制(一)
-14.8 线程同步机制(二)
-14.9 C++11线程库(一)
-14.10 C++11线程库(二)
-14.11 C++11线程库(三)
-14.12 C++11线程库(四)
-14.13 C++11线程库(五)
-14.14 编程实践
-第十四讲 线程编程--编程实践提交入口
-15.1 提纲
-15.2 Internet网络协议
-15.3 套接字(一)
-15.4 套接字(二)
-15.5 编程实践
-第十五讲 网络编程--编程实践提交入口