一个类的对象作为另一个类的数据成员。
一个类中的数据成员除了可以是int, char, float等这些基本的数据类型外,还可以是某一个类的一个对象。用子对象创建新类。
在C++中,当把一个类的对象作为新类的数据员时,则新类的定义格式可表示为:
class X
{ 类名1 成员名1;
类名2 成员名2;
……
类名n 成员名n;
……//其它成员
};
(3)如果一个类A的对象作为另一个类B的数据成员,则在类B的对象创建过程中,调用其构造函数的过程中,数据成员(类A的对象)会自动调用类A的构造函数。
但应注意:如果类A的构造函数为有参函数时,则在程序中必须在类B的构造函数的括号后面加一“:”和被调用的类A的构造函数,且调用类A的构造函数时的实参值必须来自类B的形参表中的形参。这种方法称为初始化表的方式调用构造函数。如:以上面定义的类X为例,在对类X的对象进行初始化时,必须首先初始化其中的子对象,即必须首先调用这些子对象的构造函数。因此,类X的构造函数的定义格式应为:
X::X(参数表0):成员1(参数表1),成员2(参数表2),…,成员n(参数表n)
{ ……}
其中,参数表1提供初始化成员1所需的参数,参数表2提供初始化成员2所需的参数,依此类推。并且这几个参数表的中的参数均来自参数表0,另外,初始化X的非对象成员所需的参数,也由参数表0提供。
在构造新类的对象过程中,系统首先调用其子对象的构造函数,初始化子对象;然后才执行类X自己的构造函数,初始化类中的非对象成员。对于同一类中的不同子对象,系统按照它们在类中的说明顺序调用相应的构造函数进行初始化,而不是按照初始化表的顺序。
以下定义了三个Student、Teacher和Tourpair,其中Student类的对象和Teacher类的对象作为了Tourpair的数据成员,观察对象的构造过程和构造函数被执行的顺序。
#include <iostream.h>
class Student
{ public:
Student()
{ cout<<”construct student.\n”;
semeshours=100;
gpa=3.5; }
protected:
int semeshours;
float gpa;
};
class Teacher
{ public:
Teacher()
{ cout<<”construct Teacher.\n”;
}
};
class Tourpair
{public:
Tourpair()
{cout<<”construct tourpair.\n”;
nomeeting=0; }
protected:
Student student;
Teacher teacher;
int nomeeting;
};
void main()
{Tourpair tp;
cout<<”back in main.\n”; }
其执行结果是:
construct student.
construct teacher.
construct tourpair.
back in main.
由此可见:主函数main()运行开始时,遇到要创建Tourpair类的对象,于是调用其构造函数Tourpair(),该构造启动时,首先分配对象空间(包含一个Student对
象、一个Teacher对象和一个int型数据),然后根据其在类中声明的对象成员的次序依次调用其构造函数。即先调用Student()构造函数,后调用Teacher()构造函数,最后才执行它自己的构造函数的函数体。
由于上例中Tourpair类的数据成员student和teacher的构造函数都是无参函数,所以系统在构造student和teacher对象时会自动调用各自的构造函数Student()和Teacher(),而不需要用初始化表的方式去调用。
【例3-7】试分析以下程序的执行结果:
#include <iostream.h>
#include <string.h>
class Student
{ public:
Student(char *pName="No name")
{ cout<<"构造新同学:"<<pName<<endl;
strncpy(name,pName,sizeof(name)); char * strncpy(char *dest, char *src, size_t n); 将字符串src中最多n个字符复制到字符数组dest中(它并不像strcpy一样遇到NULL才停止复制,而是等凑够n个字符才开始复制),返回指向dest的指针。 C语言中判断数据类型长度符
name[sizeof(name)-1]='\0';
}
Student(Student &s)
{ cout<<"构造copy of "<<s.name<<endl;
strcpy(name, " copy of "); extern char *strcpy(char *dest,char *src); 把src所指由NULL结束的字符串复制到dest所指的数组中
strcat(name,s.name); extern char *strcat(char *dest,char *src); 把src所指字符串添加到dest结尾处(覆盖dest结尾处的'\0')并添加'\0'。
}
~Student()
{ cout<<"析构 "<<name<<endl; }
protected:
char name[40]; };
class Tutor
{ public:
Tutor(Student &s):student(s)//此为初始化表,调用
//Student的拷贝构造函数
{ cout<<"构造指导教师 \n"; }
protected:
Student student;
};
void main()
{ Student st1; //此处调用Student的构造函数Student(char
*pName="No name")
Student st2("zhang"); //同上
Tutor tutor(st2); //此处调用Tutor的构造函数Tutor(Student &s)
//在构造tutor对象的过程中,用初始化表调用
//Student类的拷贝构造函数Student(Student &s)
}
执行结果如下:
构造新同学:No name
构造新同学:zhang
构造copy of zhang
构造指导教师
析构 copy of zhang
析构 zhang
析构 No name
3.2.7 利用初始化表对常量数据成员或引用成员提供初值
如前所述,构造函数可对对象的数据成员进行初始化,但若数据成员为常量成员或引用成员时,就有所不同,如:
class Sillyclass
{ public :
Sillyclass() // 此构造函数对成员ten和refi的初始化错误。
{ ten=10;
refi=i; }
protected:
const int ten; //常量数据成员ten
int &refi; //引用refi
};
说明:
1. 造成以上错误的原因是在Sillyclass类的构造函数进入之后(开始执行其函数体时),对象结构已经建立,数据成员ten和refi已存在,而其数据成员ten为const,而refi为引用,所以在构造函数体内不能再对其指派新的值。
2. 解决以上问题的方法是利用初始化表:在构造函数的括号后面加一“:”和初始化表,初始化表的格式是:
数据成员名(值),如果有多个时,需要用逗号隔开。
【例3-8】 类employee中包括私有数据成员x, 和2个公有函数成员example、show。程序中使用初始化表是x(215)。
# include <windows.h>
# include <iostream.h>
// 定义类 employee
class employee
{private:
const int x;
public:
employee ();
void show();
};
// employee的类外定义
employee :: employee () : x (215) // 初始化表
{ }
// show()的定义。
void employee :: show()
{ cout << "\n x的值是:"<< x << endl; }
// 主函数
void main()
{ //生成对象并为x赋予初始值
employee obj;
//调用show显示x的值
obj.show();
}
一个类中的数据成员除了可以是int, char, float等这些基本的数据类型外,还可以是某一个类的一个对象。用子对象创建新类。
在C++中,当把一个类的对象作为新类的数据员时,则新类的定义格式可表示为:
class X
{ 类名1 成员名1;
类名2 成员名2;
……
类名n 成员名n;
……//其它成员
};
(3)如果一个类A的对象作为另一个类B的数据成员,则在类B的对象创建过程中,调用其构造函数的过程中,数据成员(类A的对象)会自动调用类A的构造函数。
但应注意:如果类A的构造函数为有参函数时,则在程序中必须在类B的构造函数的括号后面加一“:”和被调用的类A的构造函数,且调用类A的构造函数时的实参值必须来自类B的形参表中的形参。这种方法称为初始化表的方式调用构造函数。如:以上面定义的类X为例,在对类X的对象进行初始化时,必须首先初始化其中的子对象,即必须首先调用这些子对象的构造函数。因此,类X的构造函数的定义格式应为:
X::X(参数表0):成员1(参数表1),成员2(参数表2),…,成员n(参数表n)
{ ……}
其中,参数表1提供初始化成员1所需的参数,参数表2提供初始化成员2所需的参数,依此类推。并且这几个参数表的中的参数均来自参数表0,另外,初始化X的非对象成员所需的参数,也由参数表0提供。
在构造新类的对象过程中,系统首先调用其子对象的构造函数,初始化子对象;然后才执行类X自己的构造函数,初始化类中的非对象成员。对于同一类中的不同子对象,系统按照它们在类中的说明顺序调用相应的构造函数进行初始化,而不是按照初始化表的顺序。
以下定义了三个Student、Teacher和Tourpair,其中Student类的对象和Teacher类的对象作为了Tourpair的数据成员,观察对象的构造过程和构造函数被执行的顺序。
#include <iostream.h>
class Student
{ public:
Student()
{ cout<<”construct student.\n”;
semeshours=100;
gpa=3.5; }
protected:
int semeshours;
float gpa;
};
class Teacher
{ public:
Teacher()
{ cout<<”construct Teacher.\n”;
}
};
class Tourpair
{public:
Tourpair()
{cout<<”construct tourpair.\n”;
nomeeting=0; }
protected:
Student student;
Teacher teacher;
int nomeeting;
};
void main()
{Tourpair tp;
cout<<”back in main.\n”; }
其执行结果是:
construct student.
construct teacher.
construct tourpair.
back in main.
由此可见:主函数main()运行开始时,遇到要创建Tourpair类的对象,于是调用其构造函数Tourpair(),该构造启动时,首先分配对象空间(包含一个Student对
象、一个Teacher对象和一个int型数据),然后根据其在类中声明的对象成员的次序依次调用其构造函数。即先调用Student()构造函数,后调用Teacher()构造函数,最后才执行它自己的构造函数的函数体。
由于上例中Tourpair类的数据成员student和teacher的构造函数都是无参函数,所以系统在构造student和teacher对象时会自动调用各自的构造函数Student()和Teacher(),而不需要用初始化表的方式去调用。
【例3-7】试分析以下程序的执行结果:
#include <iostream.h>
#include <string.h>
class Student
{ public:
Student(char *pName="No name")
{ cout<<"构造新同学:"<<pName<<endl;
strncpy(name,pName,sizeof(name)); char * strncpy(char *dest, char *src, size_t n); 将字符串src中最多n个字符复制到字符数组dest中(它并不像strcpy一样遇到NULL才停止复制,而是等凑够n个字符才开始复制),返回指向dest的指针。 C语言中判断数据类型长度符
name[sizeof(name)-1]='\0';
}
Student(Student &s)
{ cout<<"构造copy of "<<s.name<<endl;
strcpy(name, " copy of "); extern char *strcpy(char *dest,char *src); 把src所指由NULL结束的字符串复制到dest所指的数组中
strcat(name,s.name); extern char *strcat(char *dest,char *src); 把src所指字符串添加到dest结尾处(覆盖dest结尾处的'\0')并添加'\0'。
}
~Student()
{ cout<<"析构 "<<name<<endl; }
protected:
char name[40]; };
class Tutor
{ public:
Tutor(Student &s):student(s)//此为初始化表,调用
//Student的拷贝构造函数
{ cout<<"构造指导教师 \n"; }
protected:
Student student;
};
void main()
{ Student st1; //此处调用Student的构造函数Student(char
*pName="No name")
Student st2("zhang"); //同上
Tutor tutor(st2); //此处调用Tutor的构造函数Tutor(Student &s)
//在构造tutor对象的过程中,用初始化表调用
//Student类的拷贝构造函数Student(Student &s)
}
执行结果如下:
构造新同学:No name
构造新同学:zhang
构造copy of zhang
构造指导教师
析构 copy of zhang
析构 zhang
析构 No name
3.2.7 利用初始化表对常量数据成员或引用成员提供初值
如前所述,构造函数可对对象的数据成员进行初始化,但若数据成员为常量成员或引用成员时,就有所不同,如:
class Sillyclass
{ public :
Sillyclass() // 此构造函数对成员ten和refi的初始化错误。
{ ten=10;
refi=i; }
protected:
const int ten; //常量数据成员ten
int &refi; //引用refi
};
说明:
1. 造成以上错误的原因是在Sillyclass类的构造函数进入之后(开始执行其函数体时),对象结构已经建立,数据成员ten和refi已存在,而其数据成员ten为const,而refi为引用,所以在构造函数体内不能再对其指派新的值。
2. 解决以上问题的方法是利用初始化表:在构造函数的括号后面加一“:”和初始化表,初始化表的格式是:
数据成员名(值),如果有多个时,需要用逗号隔开。
【例3-8】 类employee中包括私有数据成员x, 和2个公有函数成员example、show。程序中使用初始化表是x(215)。
# include <windows.h>
# include <iostream.h>
// 定义类 employee
class employee
{private:
const int x;
public:
employee ();
void show();
};
// employee的类外定义
employee :: employee () : x (215) // 初始化表
{ }
// show()的定义。
void employee :: show()
{ cout << "\n x的值是:"<< x << endl; }
// 主函数
void main()
{ //生成对象并为x赋予初始值
employee obj;
//调用show显示x的值
obj.show();
}