AlarmClock类的对象需要知道何时响钤,因此可以将一个Time对象作为类成员这种功能称为复合(composition)。类可以将其他类对象作为自己的成员。
复合是软件复用的一种形式,就是一个类将其他类对象作为自己的成员。生成对象时,自动调用其构造函数,因此要指定参数如何传递给成员对象的构造函数。成员对象按声明的顺序(而不是在构造函数的成虽初始化值列表中列出的顺序)并在建立所包含的类对象(也称为宿主对象,host object)之前建立。
图7.4用Employee类和Date类演示一个类作为其他类对象的成员。Employee类包含private数据成员firstName、lastName、birthDate和hireDate。成员birthDate和hireDate是Date类的const类型的对象,该Data类包含private数据成员month、day和year。程序实例化一个Employee对象,并初始化和显示其数据成员。注意Employee构造函数定义中函数首部的语法:
Employee::Employee( char *fname, char *lname,
int bmonth, int bday, int byear,
int hmonth, int hday, int hyear )
:birthDate (bmonth, bday, byear),
hireDate (hmonth, hday, hyear )
该构造函数有八个参数(fname、lname、bmonth、bday、byear、hmonth、hday和hyear)。首部中的冒号(:)将成员初始化值与参数表分开。成员初始化值指定Employee的参数传递给成员对象的构造函数。参数bmonth、bday和byear传递给birthDate构造函数。参数hmonth、hday和hyear传递给hireDate构造函数。多个成员的初始化值用逗号分开。
// Fig. 7.4: datel.h
// Declaration of the Date class.
// Member functions defined in datel.cpp
#ifndef DATE1_H
#define DATE1_H
class Date {
public:
Date( int = 1, int = 1, int = 1900 ); // default constructor
void print() const; // print date in month/day/year format
~Date(); // provided to confirm destruction order
private:
int month; // 1-12
int day;
int year; // any year
// utility function to test proper day for month and year
int checkDay( int );
};
#endif
// Fig. 7.4: date.cpp
// Member function definitions for Date class.
#include <iostream.h>
#include "date1.h"
// Constructor: Confirm proper value for month;
// call utility function checkDay to confirm proper
// value for day.
Date::Date( int mn, int dy, int yr )
{
if ( mn > 0 && mn <= 12 ) // validate the month
month = mn;
else {
month = 1;
cout << "Month "<< mn <<" invalid. Set to month 1.\n";
}
year = yr; // should validate yr
day = checkDay( dy ); // validate the day
cout << "Date object constructor for date ";
print(); // interesting: a print with no arguments
cout << endl;
}
// Print Date object in form month/day/year
void Date::print() const
{ cout << month << '/' << day << '/' << year; }
// Destructor: provided to confirm destruction order
Date::~Date()
{
cout << "Date object destructor for date ";
print();
cout << endl;
}
// Utility function to confirm proper day value
// based on month and year.
// Is the year 2000 a leap year?
int Date::checkDay( int testDay )
{
static const int daysPerMonth[ 13 ] =
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if ( testDay > 0 && testDay <= daysPerMonth[ month ] )
return testDay;
if ( month == 2 &&
testDay == 29 &&
( year % 400 == 0 || // year 2000?
( year % 4 == 0 && year % 100 != 0 ) ) ) // year 2000?
return testDay;
cout << "Day" << testDay << "invalid. Set to day 1.\n";
return 1; // leave object in consistent state if bad value
}
// Fig.7.4:emply1.h
// Declaration of the Employee class.
// Member functions defined in emplyl.cpp
#ifndef EMPLY1_H
#define EMPLY1_H
#include "date1.h"
class Employee {
public:
Employee( char *, char *, int, int, int, int, int, int );
void print() const;
~Employee(); // provided to confirm destruction order
private:
char firstName[ 25 ];
char lastName[ 25 ];
const Date birthDate;
const Date hireDate;
};
#endif
// Fig. 7.4: emplyl.cpp
#include "emplyl.h"
Employee::Employee( char *fname, char *lname,
int bmonth, int bday, int byear,
int hmonth, int hday, int hyear )
: birthDate( bmonth, bday, byear ),
hireDate( hmonth, hday, hyear )
{
// copy fname into firstName and be sure that it fits
int length = strlen( fname );
length = ( length < 25 ? length : 24 );
strncpy( firstName, fname, length );
firstName[ length ] = '\0';
// copy lname into lastName and be sure that it fits
length = strlen( lname );
length = ( length < 25 ? length : 24 );
strncpy( lastName, lname, length );
lastName[ length ] = '\0';
cout << "Employee object constructor:"
<< firstName << ' ' << lastName << endl;
}
void Employee::print() const
{
cout << lastName << ", "<< firstName << "\nHired: ";
hireDate.print();
cout <<" Birth date: ";
birthDate.print();
cout << endl;
}
// Destructor: provided to confirm destruction order
Employee::~Employee()
{
cout << "Employee object destructor:"
<< lastName << ", "<< firstName << endl;
}
// Fig. 7.4: figO7_O4.cpp
#include < iostream.h>
#include "emply1.h"
int main()
{
Employee e( "Bob", "Jones", 7, 24, 1949, 3, 12, 1988 );
cout << '\n';
e.print();
cout << "\nTest Date constructor with invalid values:\n";
Date d( 14, 35, 1994 ); // invalid Date values
cout << endl;
return O;
}
输出结果:
Date object constructor for date 7/24/1949
Date object Constructor for date 3/12/1988
Employee object constructor Bob Jones
Jones, Bob
Hired: 3/12/1988 Birth date 7/24/1949
Test Date constructor with invalid values:
Month 14 invalid. Set to month 1.
Day 35 invalid. Set to day 1.
Date object constructor for date 1/1/1994
Date object destructor for date 1/1/1994
Employee object destructor: Jones, Bob
Date object destructor for date 3/12/1988
Date object destructor for date 7/24/1949
图7.4使用成员对象的初始化值
记住,const成员和引用也在成员初始化值列表中初始化(第9章会讲到,派生类的基类部分也是这样初始化的)。Date类和Employee类各有一个析构函数,分别在删除Date和EmpLoyee对象时打印一个消息。这样就可以从程序输出中确认对象由内向外建立,由外向内删除(即先删除Employee对象,再删除其中包含的Date对象)。
成员对象不需要通过成员初始化值显式初始化。如果不提供成员初始化值,则隐式调用成员对象的默认构造函数。默认构造函数建立的值(若有)可以用set函数重定义。
没有为成员对象提供初始化值的情况下,也没有为成员对象提供默认的构造函数,这样就会产生语法错误。
通过成员初始化值显式初始化成员对象,这样可以消除两次初始化成员对象的开销,一次是在调用成员对象的默认构造函数时,一次是在用set函数初始化成员对象时。
如果一个类用其他类对象作为成员,则将这个成员对象定为public不分破坏该成员对象private成员的封装与隐藏。
注意第43行调用Date成员函数print。C++中许多类的成员函数不需要参数。这是因为每个成员函数包含所操作对象的隐式句柄(指针形式)。7.5节将介绍隐式指针this。
在第一版的Employee类中(为了便于编程),我们用两个25字符数组表示EmpLoyee的姓和名。这些数组如果存储短名称可能浪费内存空间(记往每个数组中有一个字符是字符串的null终止符 '\0',),超过24个字符的姓名要截尾之后才能放得下。本章稍后将介绍另一种形式的Employee类,动态生成适合姓和名的数组长度,还可以用两个string对象表示姓名。第19章详细介绍string标准库类。