再上一篇:9.5 this指针
上一篇:第十章 构造函数和析构函数
主页
下一篇:10.3 实现类型转换和拷贝的构造函数
再下一篇:10.4 构造函数与对象成员
文章列表

10.2 析构函数

《VC++程序设计基础》,讲述C++语言的语法和标准库,以及Visual C++ 函数库和MFC类库的使用,并附相关代码示例。

从前面介绍的情况可知,产生对象时系统要为对象分配存储空间;在对象结束其生命期或结束其作用域(静态存储类型的对象除外)时,系统要收回对象所占用的存储空间,即要撤消一个对象。此项工作是由析构函数来完成的。

10.2.1 定义析构函数

析构函数也是类的成员函数,定义析构函数的格式为:

ClassName::~ClassName( )

{

......

//函数体;

}

对于析构函数,说明以下几点:

1、系统约定:析构函数名必须与类名相同,并在其前面加上字符“~”,以便和构造函数名相区别。

2、析构函数不能带有任何参数,不能有返回值,函数名前也不能用关键字void。换言之,析构函数是唯一的,析构函数不允许重载。

3、析构函数是在撤消对象时由系统自动调用的,它的作用是:在撤消对象之前做好结束工作。在析构函数内要终止程序的执行时,不能使用库函数exit,但可以使用函数abort。这是因为exit函数要做终止程序前的结束工作,它又要调用析构函数,形成无修止的递归;而abort函数不做终止程序前结束工作,直接终止程序的执行。

例10.7 调用析构函数的例子。

//Ex10_7.h

#include

class Q {

int x,y;

public:

Q(int a=0,int b=0)

{

x=a;y=b;

}

void P(void)

{ cout<

~Q( )

{ cout<<"调用了析构函数!"<<'\n'; }

};

//Ex10_7.cpp

void main(void )

{

Q q(50,100);

q.P();

cout<<"退出主函数!\n";

}

执行程序后的输出为:

50 100

退出主函数!

调用了析构函数!

在程序的执行过程中,当遇到某一对象的生存期结束时,系统自动调用析构函数,然后再收回为对象分配的存储空间。在上面的程序中,对象q的生存期在遇到函数main的“}”时结束,这时调用析构函数。

例10.8 使用析构函数,收回动态分配的存储空间。

//ex10_8.h

#include

#include

class String {

char *Buffer;

public:

String(char * s)

{

if( s) {

Buffer=(char *) new char[strlen(s)+1];

strcpy(Buffer,s);

}

else Buffer =0;

}

~String( )

{

if( Buffer) delete []Buffer;

}

void ShowString()

{

cout <<"Buffer=" << Buffer <<'\n';

}

};

//ex10_8.cpp

//#include "ex10_8.h"

void main(void)

{

String s("教师!");

s.ShowString();

}

在构造函数中或在程序的执行过程中,使用了new运算符为对象的某一个指针成员分配了动态申请的内存空间时,在类中必须定义一个析构函数,并在析构函数中使用delete删除由new分配的内存空间。如上面的程序中,在撤消对象时,调用运算符delete来收回为字符串所分配的存储空间。这是完全必要的,因为在撤消对象时,系统自动收回为对象所分配的存储空间,而不自动收回由Buffer所指向的动态申请的存储空间。

例10.9 析构函数与delete运算符。

//Ex10_9.cpp

#include “Ex10_7.h”

void main(void )

{

Q *Pobj=new Q(500,1000);

Pobj->P();

delete Pobj;

cout<<"退出主函数!\n";

}

执行程序后的输出以下3行:

500 1000

调用了析构函数!

退出主函数!

当使用运算符delete删除一个由new动态产生的对象时,它首先调用该对象的析构函数,然后再释放这个对象占用的内存空间。这与用new 建立动态对象的过程正好相反。10.2.2 不同存储类型的对象何时调用构造函数及析构函数

通常,在产生对象时,调用构造函数;而在撤消对象时调用析构函数。但对于不同存储类型的对象,调用构造函数与析构函数的情况有所不同:

1、对于全局定义的对象(在函数外定义的对象),在程序开始执行时,调用构造函数;到程序结束时,调用析构函数。

2、对于局部定义的对象(在函数内定义的对象),当程序执行到定义对象的地方时,调用构造函数;在退出对象的作用域时,调用析构函数。

3、用static定义的局部对象,在首次到达对象的定义时调用构造函数;到程序结束时,调用析构函数。

4、对于用new运算符动态生成的对象,在产生对象时调用构造函数,只有使用delete运算符来释放对象时,才调用析构函数。若不使用delete来撤消动态生成的对象,程序结束时,对象仍存在,并占用相应的存储空间,即系统不能自动地调用析构函数来撤消动态生成的对象。

10.2.3 用delete运算符撤消动态生成的对象数组

用delete来撤消单个对象与撤消对象数组时,其用法有所不同,我们用例子来说明。

例10.10 用delete释放动态产生的对象数组。

//Ex10_10.cpp

#include “Ex10_7.h”

void main(void )

{

Q *p1=new Q[2];

delete [ ] p1; //A

cout<<"退出主函数!\n";

}

执行程序后的输出以下3行:

调用了析构函数!

调用了析构函数!

退出主函数!

用new运算符来动态生成对象数组时,自动调用构造函数,而用delete运算符来释放p1所指向的对象数组占用的存储空间时,在指针变量的前面必须加上[ ],如A行所示。若把A行改为:

delete p1 ;

时,则仅释放对象数组的第0个元素,即仅调用数组的第0个元素的析构函数,其它元素所占用的空间并不释放。在VC++环境下,将产生运行错误。

10.2.4 缺省的析构函数

与缺省的构造函数一样,若在类的定义中没有显式地定义析构函数时,则编译器自动地产生一个缺省的构造函数,其格式为:

ClassName::~ClassName() { };

缺省的析构函数的函数体是空的,即这个缺省的析构函数什么也不做。实际上,任何对象都有构造函数和析构函数,当定义类时,没有定义构造函数或析构函数时,编译器自动产生缺省的构造函数和缺省的析构函数。当产生对象时,不要对数据成员进行初始化时,可以不显式地定义构造函数。在撤消对象时,不要做任何结束工作时,可以不显式地定义析构函数。但在撤消对象时,要释放对象的数据成员用 new运算符分配的动态空间时,必须显式地定义析构函数。