C++成员对象与封闭类
成员对象:一个类的成员变量是另一个类的对象。
封闭类:包含成员对象的类叫封闭类。
封闭类构造函数的初始化列表:
定义封闭类的构造函数时,添加初始化列表:
类名::构造函数(参数表):成员变量1(参数表),成员变量2(参数表),...{
.....
}
成员对象初始化列表中的参数
任意复杂的表达式
函数/变量/表达式中的函数,变量有定义
调用顺序:
当封闭类对象生成时,
第一步:执行所有成员对象的构造函数
第二步:执行封闭类的构造函数
成员对象的构造函数调用顺序与成员对象在类中的声明顺序一致与初始化列表中出现的顺序无关。
当封闭类的对象消亡时:
第一步:执行封闭类的析构函数
第二步:执行成员对象的析构函数
析构函数的调用顺序与构造函数调用顺序相反。
#include <iostream>
using namespace std;
class CTyre //轮胎类
{
private:
int radius; //半径
int width; //宽度
public:
CTyre(int r, int w) : radius(r), width(w) { //构造函数
cout<<"CTyre contructor"<<endl;
}
~CTyre(){ //析构函数
cout<<"CTyre contructor"<<endl;
}
};
class CEngine //引擎类
{
public:
CEngine(){
cout<<"CEngine contructor"<<endl;
}
~CEngine(){
cout<<"CEngine contructor"<<endl;
}
};
class CCar { //汽车类(封闭类)
private:
int price; //价格
CTyre tyre;
CEngine engine;
public:
CCar(int p, int tr, int tw);
CCar(){
cout<<"CCar contructor"<<endl;
}
~CCar(){
cout<<"CCar contructor"<<endl;
}
};
CCar::CCar(int p, int tr, int tw) : price(p), tyre(tr, tw)
{
};
int main()
{
CCar car;
CCar car(20000, 17, 225);
return 0;
}
构造函数添加了初始化列表,将 radius 初始化成 r,width 初始化成 w。这种写法比在函数体内用 r 和 w 对 radius 和 width 进行赋值的风格更好。建议对成员变量的初始化都使用这种写法。
CCar 是一个封闭类,有两个成员对象:tyre 和 engine。在编译时候,编译器需要知道 car 对象中的 tyre 和 engine 成员对象该如何初始化。
编评器已经知道这里的 car 对象是用上面的 CCar(int p, int tr, int tw) 构造函数初始化的,那么 tyre 和 engine 该如何初始化,就要看CCar(int p,int tr,int tw) 后面的初始化列表了。该初始化列表表明,tyre 应以 tr 和 tw 作为参数调用 CTyre(intr, hit w) 构造函数初始化,但是并没有说明 engine 该如何处理。在这种情况下,编译器就认为 engine 应该用 CEngine 类的无参构造函数初始化。而 CEngine 类确实有一个编译器自动生成的默认无参构造函数,因此,整个 car 对象的初始化问题就都解决了。
总之,生成封闭类对象的语句一定要让编译器能够弄明白其成员对象是如何初始化的,否则就会编译错误。
在上面的程序中,如果 CCar 类的构造函数没有初始化列表,那么CCar car(20000, 17, 225);会编译出错,因为编译器不知道该如何初始化 car.tyre 对象,因为 CTyre 类没有无参构造函数,而编译器又找不到用来初始化 car.tyre 对象的参数。
封闭类对象生成时,先执行所有成员对象的构造函数,然后才执行封闭类自己的构造函数。成员对象构造函数的执行次序和成员对象在类定义中的次序一致,与它们在构造函数初始化列表中出现的次序无关。
当封闭类对象消亡时,先执行封闭类的析构函数,然后再执行成员对象的析构函数,成员对象析构函数的执行次序和构造函数的执行次序相反,即先构造的后析构,这是 C++处理此类次序问题的一般规律。