大家好
欢迎来到实验4的下半部分
本章的学习目标
是掌握类与对象的声明
和使用
并了解构造函数 析构函数
类的组合等语法的应用
本部分的实验内容
是通过练习来帮助大家
进一步地理解和掌握
类的组合的使用
以及进一步的熟悉构造函数
析构函数
以及成员函数的调用
和执行过程
对象可以是一个简单的个体
也可以很复杂
有很多对象组成
在C++中支持把一个复杂的对象
分解为很多简单的对象来组成
在本章的前半部分
我们已经习惯于
把基本数据类型
作为类的数据成员
而实际上类的数据成员
既可以是基本数据类型
也可以是自定义类型
当然可以是
自己定义的类的对象
接下来的一个例子
我们就来练习一下
如何用部件组装的方法
来构件一个更复杂的类
并且观察类中
对象成员的构造
和析构顺序
以及成员函数的调用
接下来我们来看例题一
在实验4的上半部分中
我们声明了CPU类 RAM类
CDROM类
那么这一次呢
我们要求大家
在这些类的基础之上
再声明Computer类
有以下两个要求
1 其中要声明私有数据成员
cpu ram
和cdom
分别对应之前声明的那三个类
同时呢要求声明公有成员函数
run和stop
并且要求在其中输出提示信息
此外在main函数中
请大家声明一个
Computer类的对象
调用它的成员函数
接下来我们就来看一下
已经准备好的代码
这里的CPU类
就是我们在第四章的上半部分
给大家介绍过的
这里的是我们声明的RAM类
那么RAM的Type呢
就是它的类型呢
是一个枚举类型
它也有三个私有数据成员
这是它的构造函数
这是它的析构函数
同样地
它也有几个外部接口
分别用于
设置它的三个私有数据成员
和获取
它的三个私有数据成员的值
同样
它也有Run和Stop
分别输出RAM开始运行
和RAM停止运行
和它类似的
我们也声明了CD_ROM类
其中两个数据成员
它的接口类 安装类型
都是枚举类型的变量
在这三个类的基础之上
我们构建了Computer类
那按照题目要求呢
它至少有三个私有数据成员
分别对应我们刚才声明的CPU类
RAM类和CD_ROM类
此外我们还增加了两个
简单类型的数据成员
一个是它的存储容量
以GB为单位的无符号整数
它的带宽以
MB为单位的无符号整数
我们在这里为CPU
设置了一个构造函数
它有五个参数
分别对应
刚才的五个私有数据成员
它还有一个析构函数
在这里打印输出
析构了一个Computer
同样地
它也有Run这样一个
公有成员函数
和Stop这样一个公有成员函数
我们在Run中
让它的三个对象成员
分别地调用自己的Run
并且输出Computer开始运行
在Stop中也是类似的处理
这里我们看到的是
Computer类中
带有五个参数的构造函数
因为我们把这个构造函数
写在那类的外部
所以前面要加上作用域的限定符
那么这五个参数
分别对应它的五个私有数据成员
其中前面三个呢
是内嵌对象
所以我们需要在这里
一个冒号后面
给出一个初始化列表
让这三个内嵌对象呢
分别调用
它们各自类的拷贝构造函数
来完成这三个内嵌对象的初始化
那么
对于简单数据类型的成员呢
我们在圆括号中
通过赋值语句完成初始化
最后打印输出
构造一个Computer
便于观察和调试
接下来我们来看main函数
那main函数的前面三行
大家很熟悉
就是我们第四章上半部分看到的
构造一个CPU的对象
然后让它运行
并且停止
我们在这里输出一个
星号的一行呢
是为了进行后面的文字的分隔
便于我们观察调试的结果
同样地
对于RAM我们构造了一个对象b
对于CD_ROM
我们构造了一个对象c
分别都给它们进行了初始化
当a b c三个对象
都构造完毕之后呢
我们基于它们
又构造了Computer类的对象
my_computer
那么我们在这里调用
就是刚才声明的
含有五个参数的构造函数
最后我们启动my_computer对象
然后Stop
最后是程序的返回
那整个程序的比较长
这是我们第一次
超过100行的程序
接下来我们就用debug方式
来跟踪调试一下
首先我们按下f11
它停在了main函数的第1行
这个时候呢a b c
还有my_computer这四个对象
都还没有初始化
我们按下f10进入第1行语句
按下f11
进入到CPU的这个构造函数内部
依次完成了
它的各个成员的初始化
我们来看一下
这时候就输出构造了一个CPU
再按下f10
返回到main函数的这一行
对象a已经构造完毕
那么
和第四章上半部分类似
我们再去运行a的Run
和a的Stop
我们看一下这个时候
输出的是什么呢
CPU开始运行
CPU停止运行
并且输出了这样一个分隔线
类似地
我们再接连地按下f10
然后构造出RAM的对象b
我们在这里可以察看一下
刚才的对象a
和现在构造出来的对象b
它的每一个成员都已经按照
初始值进行了初始化
接下来我们再来构造
CD_ROM的对象c
那可以看到对象c
也完成了初始化
现在我们到了
我们的组合类
Computer的对象的构造了
那么这个时候我们跟踪一下
按下f11
就进入到了Computer的带有
五个参数的构造函数的
第一行的位置
我们可以看到实参
已经正常地传输过来了
c的值 r的值 CD的值
存储容量的值
以及带宽的值
这里是初始化列表
那么我们可以看到
进行初始化列表
运行完了以后呢
那Computer当前对象的
三个内嵌对象my_cpu
也完成了初始化
my_ram
还有my_cdrom
接下来进入到了
这个构造函数的花括号内部
完成了两个简单数据类型
数据成员的初始化
这个时候storage_size
和bandwidth已经有了初始值
那么在这里会输出
构造了一个COMPUTER
我们看观察一下
这里输出构造了一个COMPUTER
继续按下f10
现在我们的光标又跳回到了
main函数
构造computer类的这个对象
my_computer的这一行
再按下f10呢
输出了分隔线
我们再来观察一下
这里输出了析构类的CPU
析构了一个RAM
析构了一个CD_ROM
为什么会输出这样三个
析构函数的输出语句呢
大家在这里先留一个问号
我们一会再来解释和分析它
同样地
我们去执行my_computer.Run
按下f11进入到Run函数的内部
它会依次地去调用
它的内嵌对象的CPU_RAM
和CPU_ROM.Run
我们可以观察一下
这个时候会输出CPU RAM
和CD_ROM都开始运行了
CPU的Run的最后一行
是输出COMPUTER开始运行
好 我们再按下f10
回到main函数的内部
那么Stop是类似的
我们按下f10跳过
我们可以看到
这里CPU RAM
和CD_ROM都停止运行
这三条语句被输出
最后输出COMPUTER停止运行
再按下f10
这个时候光标指向到了
return语句
那么在return之前
我们还需要做些什么呢
对
因为我们在前面构造了四个对象
a b c
还有my_computer
所以在return之前呢
一定要完成它们的析构
才可以最后结束整个main函数的
那我们在这里按下f11
可以看到
它首先进入了
COMPUTER的析构函数
它会打印输出
析构了一个COMPUTER析构了一个COMPUTER
再按一下f11
这个时候它就析构CD_ROM
接下来析构RAM
再析构CPU
这个时候才回到了
main函数的最末尾
整个main函数即将结束
我们来观察一下
因为我们的程序比较长
然后输出的语句比较多
所以我们可以看到
最后呢
在程序结束之前呢
首先析构了COMPUTER
因为COMPUTER呢
有三个内嵌对象
所以这三个内嵌对象
也分别被析构
然后再分别依次
析构对象a b c对应的
CD_ROM RAM和CPU
整个程序到此就运行完毕
大家可以观察一下
在这里我们的析构函数
和构造函数
严格上调用的次数
应该是完全一致的
因为构造了一个对象
再结束整个程序之前
必定会析构它
那么我们来数一数
是不是一致呢
我们在这里有一个
CPU的构造函数
有一个RAM的构造函数
有一个CD_ROM的构造函数
有一个COPUTER的构造函数
就是这四个类
分别被构造了一次
那么在这里呢
析构了CPU RAM
和CD_ROM
析构了一次
在这里析构了COMPUTER
和这里构造的COMPUTER
是对应的
那因为这个COMPUTER内部呢
内嵌三个对象
所以这三个对象分别被析构
也是可以理解的
而这里的三次析构呢
和程序一开始的三次构造
是对应的
大家发现问题了吗
也就是奇怪的是
我们从打印输出的文字里发现
构造函数的次数
和析构函数的次数并不一致
这是怎么回事呢
对 大家可能会想到
我们在构造COMPUTER的时候
出现了一个什么状况
我们在构造COMPUTER的时候
因为它的这三个参数都是对象
所以我们在形参
和实参结合的时候呢
调用了一次拷贝构造函数
而这个拷贝构造函数呢
是系统自带的 自定义的
它并不会像我们这样
进行文字打印输出
所以尽管调用了拷贝构造函数
它也没有输出说
构造了一个CPU
构造了一个RAM 这样
那么在这个初始化列表里头呢
我们又怎么样
我们又调用了一次
系统默认的拷贝构造函数
那么在这个时候
它也不会打印输出相关的语句
那么把这两次补上以后呢
大家就会理解
这个时候析构的次数
和构造的次数应该是完全一致的
那么我们来调整一下
我们在这里
为CPU增加一个拷贝构造函数
让它能够按照我们的要求
来进行输出
保存一下
然后我们呢
前面的过程就不重复了
我们把断点设在这个地方
然后我们按下f5开始执行
我们可以看到
前面的CPU RAM
和CD_ROM的构造
和之前是完全一样的
接下来就是来构造my_computer
这个时候就调用了
CPU的拷贝构造函数一次
这是完成形实结合
并且输出拷贝构造了一个CPU
然后才进入COMPUTER的内部
那么这个时候呢
两次拷贝构造函数的调用
就已经完成
我们再设一个断点
让运行继续下去
我们可以看到
在构造COMPUTER之前
它的内嵌对象首先要被构造
那么第一次调用的是
形实结合拷贝构造
第二次调用的呢
是初始化列表里头
进行的拷贝构造
那么当这个
COMPUTER的构造函数
结束以后呢
形实结合那个形参
变量的生命周期就结束
所以马上就会执行CPU的析构
那么大家回去可以试一试
你可以把RUN
和CD_ROM
都自己来定义一个拷贝构造函数
来跟踪整个构造
和析构的过程
那么你再数数
它的构造函数
和析构函数调用的次数
就会完全一致了
最后呢
请大家做一个扩展练习
仿照刚才的这个例子
大家可以设计一个
用于人事管理的People（人员）类
考虑到通用性
这里所有类型的人员
都具有以下的属性
包括number编号 性别sex
出生日期birthday
身份证号id
要求出生日期声明为
一个日期类的内嵌对象
用成员函数
实现对人员信息的录入
和显示
需要包括构造函数 析构函数
拷贝构造函数 内联成员函数
以及类的组合
这样一些语法现象
类的组合是描述一个类
内嵌其他对象成员的情况
当创建一个类的时候
如果这个类中嵌有
其他类的对象
那么内嵌对象将首先被创建
这对于理解
构造函数的调用顺序十分重要
了解了这一点
那么就能理解
析构函数的执行过程
与构造函数刚好相反
第四章的下半部分就到这里
我们下次再见