2009-7-28 1

10
章构造函数和析构函数
2009-7-28 2
构造函数回顾变量和函数参数的初始化。
变量?定义与赋值结合起来:
int number=9902;
函数?应用缺省参数:
void add(int a=0,int b=0);
目的? 便于编程、保证变量的值不会无意义,
减小程序出错的可能性。
2009-7-28 3
对象的数据成员进行初始化的三种方法:
1.使用初始化数据列表的方法;
2.通过构造函数实现初始化;
3.通过对象的拷贝初始化函数。
例 1,使用初始化数据列表的方法对新产生的对象初始化。
101执行后输出:
25 张 三 77.8 99.56
此方法只能对类的 公有数据成员 初始化,而不能对私有的或保护的数据成员进行初始化。通常用构造函数来实现初始化。
2009-7-28 4
定义构造函数类中构造函数的定义? 类名(参数表);
构造函数? 创建并初始化类的数据成员。
类外构造函数的定义? 类名,:类名(参数表);
例 2,使用构造函数对新产生的对象初始化。
102执行后输出:
25 张 三 77.8
99.56
2009-7-28 5
构造函数的特点:
1,与类同名。
2,没有返回值。
3,可以定义多个构造函数。
4,在创建对象时被自动调用。
5,可以不带参数,也可带若干个参数,还可以指定参数的缺省值。
6,当定义的类要说明该类的对象时,构造函数是公有的成员函数;当定义的类仅用于派生其他类时,
可定义为保护的成员函数。
2009-7-28 6
构造函数应用注意点,
1,若自己不定义构造函数,编译程序提供一个缺省构造函数。
2,若程序用到指针,则应自定义构造函数,以减少程序出错机会。
3,构造函数和析构函数应成对定义,以养成良好的编程习惯。
2009-7-28 7
例 3,定义一个矩形类的构造函数。
103构造函数和对象的初始化构造函数属类的成员函数,故对私有成员数据、保护的成员数据和公有的成员数据均能进行初始化。
例 4,自动调用构造函数来初始化对象。
104执行后输出,调用带参数的构造函数!
100 200 300 400
调用不带参数的构造函数!
0 0 0 0
2009-7-28 8
例 5,产生全局对象、静态对象和局部对象。
105执行后输出:
调用带参数的构造函数(全局)!
进入主函数 main( )
200 300 400 500
进入函数 f1( )
调用带参数的构造函数(静态)!
200 200 500 0
调用带参数的构造函数(局部)!
100 100 0 0
调用带参数的构造函数(局部)!
100 200 0 0
调用不带参数的构造数!
0 0 0 0
进入函数 f1( )
200 200 500 0
调用带参数的构造函数(局部)!
100 100 0 0
2009-7-28 9
构造函数和 new运算符用 new可建立对象。此时要自动调用构造函数,
以完成对象的成员数据初始化。
例 6,用 new运算符建立对象时调用构造函数。
106执行后输出:
调用构造函数 D( int,int)!
x=5 y=10
调用构造函数 D()!
x=-842150451 y=-842150451
2009-7-28 10
缺省的构造函数定义类时若没有定义类的构造函数,编译器自动产生一个缺省的构造函数,格式为:
类名,:类名() { }
这是一个函数体为空的构造函数,在产生对象时,
调用的该构造函数不做任何事,新产生对象的数据成员的值是不确定的。
注,1.若定义时定义了类的构造函数,则不产生缺省的构造函数。
2.缺省的构造函数只能有一个。
3.需要对对象的数据成员进行初始化时,必须定义构造函数。
2009-7-28 11
4.任一对象的构造函数必须唯一。如:
class E{
int x,y;
public:
E( int a,int b) {
x=a;
y=b;
}
void P( void) {
cout<<x<<?\t?<<y<<?\n?;
}
};
若有说明:
E e; //错误 。因产生对象 e时,没有合适的构造函数可供调用,若要这样定义,必须有一个缺省的构造函数供调用。
2009-7-28 12
又如:
class Q{
int x,y;
public:
Q( int a=0,int b=0) {
x=a;
y=b;
}
Q() { }
void P( void) {
cout<<x<<?\t?<<y<<?\n?;
}
};
若有说明:
Q q; //错误 。因在编译时,指出两个缺省的构造函数,在产生对象 q时因不知调用哪个而产生了二义性。
2009-7-28 13
又如:
class R{
int x,y;
public:
R( int a,int b) {
x=a;
y=b;
}
R() {
x=y=0;
}
};
若有说明:
R r(); //不认为是定义对象 r,而是函数原型说明,即说明函数 r()的返回值为类 R类型。
故当定义的对象要调用缺省的构造函数时,在对象名后不能有括号。
2009-7-28 14
析构函数完成收回对象所占用的存储空间的工作。
定义析构函数析构函数? 削除对象,释放内存。
析构函数的定义类名( );
例 7,调用析构函数示例。 107
执行后输出:
50 100
退出主函数!
调用了析构函数!
2009-7-28 15
注:
1.析构函数名必须与类名相同。
2.析构函数不能带有任何参数,不能有返回值,函数名前不能用关键字 void。
3.析构函数是在撤消对象时由系统自动调用的。析构函数内不能使用库函数 exit,但可使用函数 abort。
4.在构造函数或程序的执行过程中,若用 new为对象的某一个指针成员动态申请了空间,在类中必须定义一个析构函数,并在析构函数中使用 delete运算符收回为字符串所分配的存储空间。
例 8,使用析构函数,收回动态分配的存储空间。
108
2009-7-28 16
例 9,析构函数与 delete运算符。
109
执行后输出:
500 1000
调用了析构函数!
退出主函数!
2009-7-28 17
例 10,将 文件 student.h 改为
class student
{ public:
student( ); //缺省 构造函数
student(int n,char *na,float s); //构造函数
student( ); //析构函数
void modify(float s);
void display( );
private:
int number;
char *name;
float score; };
注意:没有 input函数。
2009-7-28 18
例 10(续 ) 文件 student.cpp 加上,
#include "student.h "
student::student( )
{ number=0;
score=0;
name= " "; }
student::student(int n,char *na,float s)
{ number=n;
score = s;
name=new char[strlen(na)+1];
strcpy(name,na); }
student::~student( )
{ delete name; }
2009-7-28 19
void main( )
{ student s1; //用缺省构造函数初始化
student s2(9902,“Li,,90);
//创建时用带参数的构造函数初始化
s2.display( ); //公用成员函数
s2.modify(95); //公用成员函数
s2.display( ); }
结果,number,9902
name,Li
score,90
number,9902
name,Li
score,95
2009-7-28 20
不同存储类型的对象调用构造函数及析构函数不同的存储类型对象,调用构造函数和析构函数情况不同。
1.全局对象 程序开始执行时调用构造函数,程序结束时调用析构函数。
2.局部对象 程序执行到定义对象的地方调用构造函数,退出对象的作用域时调用析构函数。
3.用 static定义的局部对象,首次到达对象的定义时调用构造函数,程序结束时调用析构函数。
4.用 new动态生成的对象,产生对象时调用构造函数,只有使用 delete释放对象时调用析构函数。系统不能自动调用析构函数撤消对象。
2009-7-28 21
构造函数和析构函数的调用时间:
对象类型 构造函数调用 析构函数调用全局对象 程序运行 程序结束局部对象 对象定义处 离开程序块静态局部对象 对象定义处 程序结束
new动态创建的对象 创建对象处 delete显式撤消
2009-7-28 22
用 delete运算符撤消动态生成的对象数组以例说明撤消单个对象与撤消对象数组的不同。
例 11,用 delete释放动态产生的对象数组。
1010执行后输出:
调用了析构函数!
调用了析构函数!
退出主函数!
2009-7-28 23
缺省的析构函数类似与构造函数,当类的定义中没有显式地定义析构函数时,编译器自动产生一个缺省的析构函数。
格式:
类名,:~类名() {};
函数体为空,即什么也不执行。
事实上所有对象均有构造函数和析构函数。定义类时若没有定义,则编译器自动地产生缺省的构造函数和析构函数。
2009-7-28 24
实现类型转换和拷贝的构造函数实现类型转换的构造函数例 12,单个参数的构造函数。
1011执行后输出:
50 调用了构造函数!
100 调用了构造函数!
200 调用了构造函数!
调用了析构函数!
标志:
调用了析构函数!
调用了析构函数!
2009-7-28 25
例 13,用构造函数进行强制类型转换。
1012执行后输出:
50 100 调用了构造函数!
300 600 调用了构造函数!
调用了析构函数!
调用了析构函数!
用构造函数进行类型转换的一般格式:
类名(参数 1,参数 2,…,参数 n)
作用是调用含 n个参数的构造函数,产生一个临时对象,用这 n个值初始化该对象后,将该对象作为操作数参加运算。运算结束后自动撤消该临时对象。
2009-7-28 26
问题的提出? 声明创建对象时,用一已知的对象初始化该对象。
即想做到:
student s1(9901,,XU”,88);
student s2=s1; //即为 赋值构造函数
student s3(s1); //即为 拷贝构造函数目的? 使编程更为简便、有效、实用。
完成拷贝功能的构造函数
2009-7-28 27
一般格式:
类名,:类名(类名 &参数)
{…} //函数体完成对应数据成员的赋值例 14,使用完成拷贝功能的构造函数。
1013执行后输出:
调用了构造函数!
调用了完成拷贝的构造函数!
调用了完成拷贝的构造函数!
x=10 y=10
x=10 y=10
x=10 y=10
2009-7-28 28
例 15,使用隐含的完成拷贝功能的构造函数。
1014执行后输出:
调用了构造函数!
x=10 y=10
x=10 y=10
x=10 y=10
该例中由编译器自动地生成一个隐含的完成拷贝功能的构造函数:
Test::Test( Test &t) {
x=t.x; y=t.y; }
2009-7-28 29
例 16,定义完成拷贝功能的构造函数。
1015
例 17,同类型对象之间赋值出错。
1016
当只要拷贝同类型对象的部分成员数据,或类中的成员数据使用 new申请空间进行赋初值时,必须显式地定义完成拷贝功能的构造函数。
2009-7-28 30
例 18,平面点类 point。
//save as point.h
class point
{ public,//公有成员
point( ); //缺省构造函数
point(float x1,float y1); //带参数构造函数
point( const point &p); //拷贝构造函数
void show( ); //打印 显示点
void move(float x1,float y1); //移动点
private,//私有成员
float x; //点的横坐标
float y; //点的纵坐标
}; //类定义结束
2009-7-28 31
(接上页 )
point::point( ) { x=0; y=0; } //缺省构造函数
point::point(float x1,float y1) //参数构造函数
{x=x1; y=y1;}
point::point(const point &p) //拷贝构造函数
{ x=p.x;
y=p.y; }
void point::show( )
{ cout <<?Point(x,y)=(? << x
<<?,? << y <<?)? << endl; } //显示点
void point::move(float x1,float y1) //移动点
{
x=x+x1; y=y+y1;
}
2009-7-28 32
//save as main.cpp
#include,point.h”
void main( )
{ point p1; //调用缺省构造函数,x=0,y=0
point p2(2,2); //调用参数构造函数
point p3(p2); //调用拷贝构造函数
p1.move(5,5);
p1.show( );
p2.show( );
p3.show( ); }
执行后输出:
Point(x,y)=(5,5)
Point(x,y)=(2,2)
Point(x,y)=(2,2)
2009-7-28 33
构造函数和对象成员定义新类时,可将一个已定义类的对象作为该类的成员。产生新类的对象,须通过新类的构造函数对它的所有成员数据初始化。对象成员的初始化,
须通过调用对象成员的构造函数实现。
例 19,初始化对象成员。 1017
执行后输出:
r=25 Hihg=35
x=65 y=100
Length=45
Width=55
2009-7-28 34
定义类时说明对象成员的一般格式:
class 类名 {
类名 1 对象名 1;
类名 2 对象名 2;

类名 n 对象名 n;
public:
类名(形参);

};
上述类名的构造函数形式为:
类名,:类名(形参),对象名 1(实参 1),对象名 2
(实参 2),…,对象名 n(实参 n)
{…} //对其他成员的初始化对象名组成的初始化列表称为 成员初始化列表,
其中所有实参不要类型说明形参必须带类型说明
2009-7-28 35
建立类的对象时,是先调用各个对象成员的构造函数,初始化相应的对象成员,然后才执行类的构造函数,初始化类中的其他成员。析构函数的调用顺序与构造函数相反。
例 21,说明构造函数与析构函数的调用顺序。
1018执行后输出:
400 调用 Obj的构造函数!
300 调用 Obj的构造函数!
100 调用 Con的构造函数!
调用 Con的析构函数!
调用 Obj的析构函数!
调用 Obj的析构函数!
2009-7-28 36
本章小结
1,类中数据成员的初始化,构造函数 和 析构函数 。
2,类的成员可以是对象,如 Date birthday;
称为 对象成员 。由初始化表来初始化,应用对象成员可由简单的类构造复杂的类。