类的组合其实描述的就是在一个类里内嵌了其他类的对象作为成员的情况,它们之间的关系是一种包含与被包含的关系。简单说,一个类中有若干数据成员是其他类的对象。以前的教程中我们看到的类的数据成员都是基本数据类型的或自定义数据类型的,比如int、float类型的或结构体类型的,现在我们知道了,数据成员也可以是类类型的。
如果在一个类中内嵌了其他类的对象,那么创建这个类的对象时,其中的内嵌对象也会被自动创建。因为内嵌对象是组合类的对象的一部分,所以在构造组合类的对象时不但要对基本数据类型的成员进行初始化,还要对内嵌对象成员进行初始化。
组合类构造函数定义(注意不是声明)的一般形式为:
类名::类名(形参表):内嵌对象1(形参表),内嵌对象2(形参表),...
{
类的初始化
}
其中,“内嵌对象1(形参表),内嵌对象2(形参表),...”成为初始化列表,可以用于完成对内嵌对象的初始化。其实,一般的数据成员也可以这样初始化,就是把这里的内嵌对象都换成一般的数据成员,后面的形参表换成用来的初始化一般数据成员的变量形参,比如,Point::Point(int xx, int yy):X(xx),Y(yy) { },这个定义应该怎么理解呢?就是我们在构造Point类的对象时传入实参初始化xx和yy,然后用xx的值初始化Point类的数据成员X,用yy的值初始化数据成员Y。
声明一个组合类的对象时,不仅它自身的构造函数会被调用,还会调用其内嵌对象的构造函数。那么,这些构造函数的调用是什么顺序呢?首先,根据前面说的初始化列表,按照内嵌对象在组合类的声明中出现的次序,依次调用内嵌对象的构造函数,然后再执行本类的构造函数的函数体。比如下面例子中对于Distance类中的p1和p2就是先调用p1的构造函数,再调用p2的构造函数。因为Point p1,p2;是先声明的p1后声明的p2。最后才是执行Distance构造函数的函数体。
如果声明组合类的对象时没有指定对象的初始值的话,就会自动调用无形参的构造函数,构造内嵌对象时也会对应的调用内嵌对象的无形参的构造函数。析构函数的执行顺序与构造函数正好相反。
class DateTimeType{ //自定义类
DateType date; //类DateType的类对象date作为其数据成员
TimeType time;
public:
DateTimeType (int y0=1,int m0=1,int d0=1,int hr0=0,int mi0=0,int se0=0);
DateType &getDate(){return date;} //返回本类的私有数据对象data
DateType &getTime(){return time;} //返回本类的私有数据对象time
void incrementSecond(int s);
void inputDateTime();
void printDateTime();
};
可以看到。每一个自定义的DateTimeType类对象中总包含一个DateType类对象(对象成员)以及一个TimeType类对象(对象成员)。他们属于整体和部分的关系。
使用DateTimeType需要注意:一是注意构造函数和析构函数的执行次序以及DateTimeType类构造函数应有的责任:要对所包含的每一个对象成员的初始化负责。二是注意通过DateTimeType类对象调用其对象成员的公有成员函数(或公有数据成员)时,必须使用如下的调用方式:<DateTimeType类对象>.<对象成员>.<对象成员所属类的公有成员>。
之所以设置了公有的类成员函数getDate和getTime,是为类外如主函数使用该类的私有数据成员date和time提供方便(否则,类外无法直接访问该类的私有数据成员)。另外,两成员函数返回的都是引用,为的是可将返回对象当作一个独立变量来使用(可用来做左值等)。
因为类型不匹配,如下两种赋值都是不允许:
<对象成员所属类的对象>=<组合类对象>;
<组合类对象>=<对象成员所属类的对象>;