class Test
{
int mi;
public:
Test(int i)
{
mi = i;
printf("Test(): %d\n", mi);
}
~Test()
{
printf("~Test(): %d\n", mi);
}
};
int main()
{
Test t(1);
Test* pt = new Test(2);
delete pt;
return 0;
}
程序输出结果为:
Test(): 1
Test(): 2
~Test(): 2
~Test(): 1
为什么是这样呢?
首先创建了一个对象t,被1初始化,再在堆空间里创建了一个对象pt,被2初始化,所以先打印Test():1,再打印Test():2。当执行到delete pt;时,程序销毁pt所在的堆空间,所以对象也会被销毁,然后析构函数被自动调用,打印~Test():2,最后在return 0;之前发现对象t也该被销毁了,所以t对象对应的析构函数被调用,打印出Test():1。
那什么时候需要自定义析构函数呢?
当类中自定义了构造函数,并且构造函数中使用了系统资源(如:内存申请,文件打开等),则需要自定义析构函数。
所以,析构函数是对象销毁时进行清理的特殊函数,在对象销毁时自动被调用,它是对象释放资源的保障。
析构顺序
当程序中存在多个对象的时候,如何确定程序的析构顺序?
1、单个对象创建时构造函数的调用顺序
(1)调用父类的构造过程
(2)若一个类中含有另一个类的对象时则调用成员变量的构造函数,调用顺序与成员变量的声明顺序相同
(3)调用类自身的构造函数
析构函数与对应构造函数的调用顺序相反。
2、多个对象析构时,调用顺序与多个对象调用构造函数的顺序相反
实例分析:
class Member
{
const char* ms;
public:
Member(const char* s)
{
printf("Member(const char* s): %s\n", s);
ms = s;
}
~Member()
{
printf("~Member(): %s\n", ms);
}
};
class Test
{
Member mA;
Member mB;
public:
Test() : mB("mB"), mA("mA")//初始化顺序只与声明顺序有关
{
printf("Test()\n");
}
~Test()
{
printf("~Test()\n");
}
};
Member gA("gA");
int main()
{
Test t;
return 0;
}
分析一下程序运行后最终会打印些什么出来?
首先创建了一个全局的Member对象,所以最先调用的是gA的构造函数,然后在main函数中定义了一个Test类对象,因为它不存在继承的关系,所以没有父类的构造函数可以调用,接着它的类成员变量又包含另一个类对象,根据规则,先调用其他类对象的构造函数,调用顺序由类对象作成员变量时的声明顺序决定,即依次为mA、mB的构造函数,最后调用自身类构造函数,析构函数的调用过程与构造函数的调用顺序相反,看一下运行的程序结果:
Member(const char* s): gA
Member(const char* s): mA
Member(const char* s): mB
Test()
~Test()
~Member(): mB
~Member(): mA
~Member(): gA
和我们的分析是一样的。
对于栈对象和全局对象,类似于入栈和出栈的顺序,最后的构造对象被最先析构;对于堆对象的析构发生在使用delete的时候,与delete的使用顺序相关。