大家好,在这一小节中呢我们来具体介绍一下成员对象和封闭类
所谓成员对象呢,具体就是指
一个类的成员变量呢,其实是另一个类的对象。
也就是说,一个类里面的具体的某一个成员呢,实际上是另外一个我们定义的类的对象。
而不再是一些传统意义上常规类型的变量了。
那么包含成员对象的这样的类呢,又被称为叫做封闭类。
那么有了成员对象和封闭类这个概念之后, 我们就会看到说,其实类和类之间呢也是可以交织在一起的。
我们具体看一个例子,我们首先呢,定义了一个轮胎类。
CTyre,那么CTyre呢它包含了两个private的成员变量,分别是
半径,int radius和宽度,int width,
那么有了这样两个成员变量之后呢,
我们在相应的构造函数里面CTyre呢,我们要对其进行
初始化。那么初始化呢,我们在这个边介绍一个新的方式
叫做初始化列表。那么它不再是简单地用
啊,radius等于r,width
等于w
这样的两个赋值语句来进行这样的一个初始化,
而是使得CTyre这样的一个构造函数里面呢,
后面跟着相应的需要初始化的成员变量,
并且呢,把相应进行初始化的值呢放在这个小括号里面,
我们推荐大家说呢,使用这样的初始化 方式呢,会比进行赋值的风格呢看起来更好一些。
那么除了轮胎类之外呢,我们还有一个叫做引擎类,CEngine,
定义好了这两个类之后呢,你自然而然就会想到,这样两个一定会构成另外一个类,
那么就是CCar这样的一个汽车类, 在这个汽车类里面呢,除了一些传统的成员变量,
比如说int型的price之外呢,
我们还有两个成员对象,那么分别是CTyre
这样的一个类,实例化的这样的一个对象tyre以及CEngine这样的一个类,
实例化的一个对象,engine, 那么这样两个成员变量呢,它都是
不同类的对象。
那么有了这样的两个,三个成员变量的定义之后呢
我们看到,对于CCar这样的一个构造函数而言的话,
那么它的初始化,在构造函数里的初始化, 我们也同样去使用了
这个初始化列表。初始化列表的话呢 我们在构造函数里面要初始化三个参数,
分别是对应了price,以及tyre
这样的一个成员对象里面的radius 和width这样两个具体的
成员变量。所以呢,我们在构造函数里面就
通过初始化列表,把p初始化给price,
把相应的这个tr和w呢,去初始化给tyre这样的一个对象中间的
radius和width,那么有了这样的一个构造函数的话呢,那么相应的CCar里面的
每一个成员变量都会有其相应参数的一个初始化。
那么汽车类呢,就被称之为叫做封闭类,因为它包含了
这样的一个成员对象,
那么所以在main函数里头如果我们去定义一个Car这样的一个新的
实例化的对象的话呢,我们希望能够对它进行初始化,
分别是20000的价格,
17的半径,和225宽度的话呢,我们只需要在括号里面 传入这样三个常量的参数,
就可以在相应的构造函数调用的时候呢把它们赋给相应对应的不同的成员变量。 那么如果我们在CCar这样的一个类里面, 不去定义构造函数,啊,比方说我们去使编译器
用默认的构造函数的话, 那么编译器呢就会报错,为什么呢?因为编译器不知道car.tyre
这样的一个成员对象里面相应的那些变量都应该怎么样进行初始化。
而CEngine呢,car.engine呢相应倒是没有什么问题,
因为呢它本身是没有对应的参数的,所以可以使用默认的构造函数就可以了。
所以我们在生成封闭类对象的语句的时候呢,要非常明确
对象中间的那些成员对象是否都需要进行相应的初始化。
那么怎么样来完成这样的一个工作呢?
我们主要就是通过刚才初步在代码里头看到了介绍的那个叫做初始化列表 来实现的。
我们在定义封闭类的构造函数的时候呢,通过去添加
初始化列表,也就是在构造函数的参数表之后,
使用一个冒号去定义一系列的成员变量,
并且呢利用这个成员变量后面小括号里面的参数表 来对相应的成员变量来进行初始化。
那么就可以完成封闭类构造函数的设计。
那么大家注意,成员对象初始化列表中间的参数呢
它其实是可以是任意复杂的表达式,
那么它也可以是函数,变量,或者是表达式中的函数等等。 那么注意呢变量是要有定义的。
所以呢,我们就可以通过这样的初始化列表的形式完成整个封闭类构造函数的设计。
那么在生成封闭类对象的时候的话呢, 它会有一个构造和析构函数调用的顺序。
那么我们在生成封闭类对象的时候呢,step1首先执行的 是所有的成员对象自身的构造函数。
啊,只有把成员对象的构造函数执行完之后,
step2才回去执行相应 外层的这个封闭类的构造函数。
那么,如果本身这个封闭类里面包含了多个成员对象的话,
那么这几个成员对象的构造函数又是怎么样来调用的呢?它的这个调用顺序啊,
是和成员对象在类中的说明顺序保持一致的。
注意,它和你成员本身在初始化列表里面出现的顺序是没有关系的。
那么它只和你在类中的说明顺序是一致的。
那么有了这样的一个啊,生成过程中间的一个构造函数调用之后呢,
我们再看一看对象消亡的时候,啊,这个封闭类对象消亡的时候,
它首先执行的是相应封闭类的析构函数的调用。
之后呢,才会执行所有成员对象的析构函数的调用。
那么这个设计呢,其实仍旧满足了
之前我们介绍的构造函数和析构函数的一个设计思想,也就是,
先构造的后析构, 后构造的先析构,这样的一个思想。
所以呢,这就是封闭类对象生成和消亡的一个,调用的一个顺序。
我们说呢,这个析构函数和构造函数的调用顺序呢,
正好是相反的。我们来看一个封闭类的例子。
啊,我们有一个class CTyre,这样的一个轮胎类,
那么我们来看一看在这个构造函数里头呢我们让它去打印输出CTyre
constructor, 然后在析构函数里面呢让它去输出CTyre
destructor, 同样的呢我们在CEngine里面也相应的让它去输入
输出,这个constructor和destructor这样的
这个语句,去标识呢,构造和析构函数调用,
之后我们再CCar这样的一个封闭类里面,啊,
去看一看它是怎么样实际去调用的。那么当然我们在CCar的构造和析构函数里面呢
也会去分别输出,那么如果我们只是简单的去定义一个Car
这样的一个实例化的对象,当然它是CCar类型的,那么它是一个封闭类类型的,
这样的一个对象的时候,按照我们刚才给大家介绍的一个呃,调用函数的一个生成顺序的话,
那么因为它里面包含了一系列的这个成员对象,
所以呢,我们首先应该执行的就是成员对象的
构造函数。成员对象的构造函数呢,又是按照
这个在类里面的说明顺序来执行的。所以呢,首先就是CEngine
constructor,然后是CTyre constructor,
之后呢会去执行封闭类自身的构造函数,啊,CCar
constructor, 那么当这个程序结束的时候,这个对象Car要消亡的时候呢,
程序首先调用的是CCar destructor,啊,那么这个
封闭类的析构函数首先被调用到,
完了之后呢,又逆序啊,按照跟之前的这个顺序正好相反,
我们呢调用的是CTyre
destructor, 最后呢,是CEngine destructor,
所以呢,这个例子呢就会很明显的看出,我们的这个调用顺序呢分别是成员对象,
封闭类的一个构造函数调用,之后是封闭类成员对象的析构函数的调用,
那么注意构造函数和析构函数的调用顺序呢是正好相反的,
而在,分别这两种函数调用的时候呢,它们的这个
相应的成员变量的顺序呢又是根据在类中间的说明顺序来依次定义的。