3.6 析构函数
析构函数(destructor)是与类同名的另一个特殊成员函数,作用与构造函数相反,用于在对象生存期结束时,完成对象的清理工作。如用delete删除对象分配的自由空间,清除某些内存单元的内容等。析构函数的名字由“~”+“类名”构成,形式如下:
class X{ …… public: ~X(); //析构函数 …… };
在类外定义析构函数的形式如下:
X::~X(){ …… }
析构函数具有以下特点:析构函数的名字就是在类名前加上“~”,不能是其他名字;析构函数没有返回类型(void也不行),没有参数表;析构函数不能重载,一个类只能有一个析构函数;析构函数只能由系统自动调用,不能在程序中显式调用析构函数。
当创建一个对象时,C++将首先为数据成员分配存储空间,接着调用构造函数对成员进行初始化工作;当对象生存期结束时,C++将自动调用析构函数清理对象所占据的存储空间,然后才销毁对象。
【例3-12】 析构函数和构造函数的应用。
//Eg3-12.cpp #include <iostream> using namespace std; class A{ private: int i; public: A(int x){ i=x; cout<<"constructor: "<<i<<endl; } ~A(){ cout<<"destructor : "<<i<<endl; } }; void main(){ A a1(1); A a2(2); A a3(3); } //L1
本程序的运行结果如下:
constructor: 1 constructor: 2 constructor: 3 destructor : 3 destructor : 2 destructor : 1
当程序执行到语句L1位置时,所有对象的生存期到此结束,将按照与构造相反的次序调用析构函数完成对象销毁前的清理工作,程序的运行结果也证实了这一情况。
说明:① 若有多个对象同时结束生存期,C++将按照与调用构造函数相反的次序调用析构函数。
② 构造函数和析构函数都可以是inline()函数。
③ 在通常情况下,析构函数与构造函数都应该被设置为类的公有成员,虽然它们都只能被系统自动调用,但这些调用都是在类的外部进行的。
④ 每个类都应该有一个析构函数。如果没有显式定义析构函数,C++编译器将产生一个最小化的默认析构函数,类似下面的情况:
X::~X(){ }
在多数情况下,默认析构函数都能够满足对象析构的要求。但在有些情况下,需要编写析构函数完成对象销毁前的资源清理工作,最常见的是用来释放由构造函数分配的自由存储空间。
【例3-13】 用析构函数释放构造函数分配的自由存储空间。
//Eg3-13.cpp #include <iostream> using namespace std; class B{ private: int *a; char *pc; public: inline B(int x){ a=new int[10]; pc=new char; } inline ~B(){ delete []a; delete pc; } }; void main(){ B x(1); }
对于例3-13而言,如果没有析构函数,程序也能正常地编译运行。但是,当用类B建立的对象结束生存期时,系统不会回收由B的构造函数分配的自由存储空间,会产生内存泄漏。
例3-13就是应该为类提供析构函数的典型情况,即若在构造函数中用new或malloc分配了存储空间,就应该在析构函数中用delete或free释放这些存储空间。