1. 概述
上篇我们讨论了构造函数,以及他们是什么,以及如何使用。本篇我们来讨论他的孪生兄弟,析构函数。它们很相似。
构造函数是当我们创建一个新的实例对象时运行,而析构函数是在销毁对象时运行。所以任何时候,一个对象要被销毁时,析构函数将被调用。构造函数通常是设置变量或者做任何我们需要的初始化。同样的,析构函数使我们卸载变量的东西,并清理我们使用过的内存。析构函数同时适用于栈和堆分配的对象。如果我们使用new分配一个对象,当我们调用delete时,析构函数会被调用。而如果只是一个栈对象,当作用域结束时,栈对象将被删除,这时,析构函数也会被调用。让我们深入到一些例子中。
2. 案例
1. 准备项目
接上篇文章构造函数的项目例子。内容如下
我们删除掉log类相关的代码
在构造函数的篇章中,我们创建了这个Entity类。
这里有多个构造函数。
2. 开始案例
下面,让我们添加一个析构函数。析构函数写为~
,然后是我们类的名称~Entity()
。
构造函数和析构函数在声明与定义上的唯一区别,就是析构函数前面有个波浪号~
。有了这个~
符号,我们就知道这是析构函数。
在这个例子中,我们只有一个简单的类,有两个成员,X和Y。当我们为这两个浮点变量申请内存的时候,完全没有考虑之后怎么清除内存。我们之后会继续讨论内存分配等所有这些复杂的问题。
我们在析构函数中添加一条消息,告诉我们对象已经被删除了。
在我们的构造函数中,我们写一条消息,告诉我们对象被创建了。
我们去掉其他的构造函数,这样我们就不会搞混了。
我们在构造中将X和Y的值初始化
然后实例化
因为这是栈分配的,只有当主函数main退出时,析构函数才会被调用。所以我们实际上不会看到析构函数打印的内容,因为我们的程序会在那之后立即结束。
所以,我们要写一个函数Function,它将执行Entity的相关操作。
这样Function函数运行结束,Entity的析构函数会被调用。
我们F5运行程序
可以看到Created Entity!
被打印,然后看到Print函数输出X和Y,最后是析构函数打印的Destroyed Entity!
。
让我们更深入的看看它是如何工作的。我们在28行Entity e;
放置一个断点。
按下F5
现在控制台什么都没有。如果我们按下F10
可以看到Created Entity!
被打印了。
继续F10
可以看到Entity的X和Y也被打印出来了。
最后,作用域到此就结束了,我们要跳回34行Function();
,我们函数返回的地方。因为Entity的对象是在栈上创建的。
所以超出作用域时,Entity对象,也就是e
会自动被销毁。
如果我们按下F10
可以看到Destroyed Entity!
输出到控制台了,因为析构函数被调用了。
这就是析构函数的本质,他只是一个特殊函数或特殊方法,在对象被销毁时调用。
为什么我们要写析构函数呢?因为如果在构造函数中调用了特定的初始化代码,我们可能想要在析构函数中,卸载或销毁所有的这些东西。因为如果我们不这样做,我们可能会造成内存泄漏。
一个很好的例子是在堆上分配的对象,如果我们在堆上手动分配了任何类型的内存,那么我们需要手动清理。如果在Entity类使用中或者构造中分配了内存,我们可能会要在析构函数中删除它们(内存),因为当析构函数调用时,那个Entity实例对象消失了。
我们也可以手动调用析构函数,我没见过多少人这样做,因为这样有点奇怪,如果你最终做了这样的事(手工调用析构函数),我唯一能想到这种情况的是使用new来分配内存。然而,当我们delete对象时,我们决定用free函数之类的东西,然后你也想手动调用它,这种情况我不常用。我们可以像这样调用,e.~Entity();
,就像它是任何其他函数一样
如果F5我运行代码,我们会调用析构函数
可以看到,它会Destroyed Entity!
两次。看起来我们并没有释放任何资源,所以它不会崩溃,它只是打印了这条消息两次。这不是我推荐的,也不是我经常说的,很少见,我很少见过这样的东西。