上期我们讨论了构造函数。认识了它是什么以及如何使用它。如果你没有看上一期,那么你一定要回去看一下。
今天我们要讨论一下它的“孪生兄弟”,析构函数,它们在某些方面非常相似。
构造函数是你创建一个新的实例对象时运行,而析构函数是在销毁对象时运行。所以当一个对象要被销毁时,析构函数会被调用。
构造函数通常是用于设置变量或做任何你需要的初始化,对应的析构函数是用于卸载变量等东西,清理你使用过的内存。
析构函数同时适用于栈和堆分配的对象。如果你使用 New 分配一个对象,当你调用 delete 时析构函数也会被调用。而如果是一个栈对象,当作用域结束时栈对象会被删除,这时析构函数也会被调用。
例子时间
让我们深入看一些例子。
我们直接使用构造函数那一期中使用的代码。
在这个例子中我们创建了 Entity 类,这里还有多个构造函数。
让我们再添加一个析构函数。
你可以看到,构造函数和析构函数在声明与定义时的唯一区别就是放在析构函数前面的波浪号。有了这个符号,你就知道这是析构了。
在这个例子中,我们只有一个简单的类,有两个成员X、Y。当我们为这两个浮点变量申请内存的时候,完全没有考虑之后怎么清除内存。在之后的系列中我们会继续讨论内存分配等所有这些复杂的问题。
继续。
我们添加一条打印消息,用于告诉我们对象已经被删除。在构造函数中,我也添加了一条信息。删除第二个构造函数,这样我们就不会搞混了。
在主函数中,实例 e 是栈分配的,只有当主函数退出时,析构函数才会被调用,所以我们实际上不会看到,因为我们的程序会在那之后立刻结束。
为了看到这个过程,我要写一个函数 Function,它将执行 Entity 的相关操作。
运行代码之后,我们可以看到下面的结果。
让我们更深入的看看它是如何工作的。
设置断点来调试一下。
你会看到 Entity 的实例 e 被创建出来,然后 X 和 Y 的位置被打印出来,最后作用域结束了,黄色箭头跳回到 Function 调用结束后的地方。
因为它的对象是在栈上创建的,当超出作用域时,它会被自动销毁。你可以看到当函数执行完成的时候,会输出删除 Entity 的信息,因为析构函数被调用了。
这就是析构函数的本质。
它只是一个特殊函数或特殊方法,在对象被销毁时被调用。
为什么要写析构函数
那么我们为什么要写析构函数呢?
因为如果在构造函数中调用了特定的初始化代码,你可能想要在析构函数中卸载或销毁所有这些东西。因为如果你不这样做,可能会造成内存泄露。
一个很好的例子是在堆上分配的对象。如果你已经在堆上手动分配了任何类型的内存,那么你需要手动清理。
如果在 Entity 类使用中或构造中分配了内存,你可能会要在析构函数中删除它们,因为当析构函数调用时,那个 Entity 实例对象会消失。
你也可以手动调用析构函数,不过我没见过很多人这样做,这样做有点奇怪。
如果你手工调用析构函数,我唯一能想到的这种情况是,如果你使用了new 来分配内存,然而当你delete 它时,你决定用 free 函数之类的东西,然后你也想手动调用它,这种情况很不常见。
你还可以像下面那样调用,就像它是任何其它函数一样。
运行之后,你可以看到有两次删除的输出。
看起来我们并没有释放任何资源,所以它不会崩溃,只是打印了这条消息两次。——析构函数只是在对象被销毁时调用,不是调用了就一定销毁对象。
这种做法不是我推荐的,不建议大家这样使用。
析构函数就是这样。
大家有其他想法可以发在评论区哈,下期再见。