C++的类中有两种函数非常特别,一种是构造函数(constructor),另一种是析构函数(deconstructor)。在上篇文章中已经讲述了构造函数,本文将讨论析构函数。

当我们定义了类的一个对象时,就会隐式的调用构造函数,构造函数执行完成后,对象就有了资源。当我们不需要该对象时,即程序运行到对象作用域之外时,会隐式的调用析构函数,析构函数执行完成后,对象的资源就被释放。

一、析构函数(deconstructor)

构造函数的英文名为constructor,它是起到构造对象的作用的函数。析构函数的英文名为deconstructor,析构函数的作用与构造函数的作用相反,它是用来销毁对象的。(至于它为什么叫析构函数,而不叫销毁函数这样的函数名,我也不知道,自己想吧)

析构函数定义方式为: ~类名(){...}

二、析构函数的特点

析构函数有很多特点,有的和构造函数一样,有的不同,本文将会做一些对比。

(1)析构函数的定义方式。

A、析构函数的函数名和构造函数几乎相同,只是在类名前加了一个波浪号(tilde ~),以示区别。

B、析构函数没有参数。这点和构造函数不同,因为没有参数,所以析构函数也不能像构造函数一样重载。因此一个类中不可能像构造函数那样,有多种析构函数。我们只可为类提供一个析构函数。

C、可以显式的定义析构函数,也可以不定义。这一点和构造函数相同。如果我们显式的定义了析构函数,我们可以在函数体中书写一些语句,用于显示对象在释放前的值等。如果我们没有定义析构函数,编译器会为我们生成一个析构函数。

(2)析构函数的调用。

当类的对象离开其作用域时,析构函数被调用,用于释放对象资源并销毁资源。

值得注意的是,析构函数只会删除真实对象的真实资源。(这句话是我自己想出来的,下面是这句话的注释)

A、只有真实存在的对象离开其作用域时才会调用析构函数,对象的引用,指向对象的指针离开其作用域时,不会调用析构函数。这是为了安全起见,因为很多时候可能对象的引用,指向对象的指针离开作用域时,对象还在其作用域。为了减少程序的bug,建议当对象离开其作用域后,我们让对象的引用,指向对象的指针失效,或者干脆就不再使用它。

B、使用new运算符创建的对象的资源,只有使用delete运算符删除指向它的指针时,才会调用它的析构函数,释放它的资源。这点要特别注意,当我们在类中显式定义析构函数时,函数体中通常就包含delete语句。

C、类中的静态成员属于类,不属于类的对象,它们的资源不会被析构函数释放。

析构函数的调用与构造函数的调用有明显不同:析构函数可以被显式调用,而构造函数不能。显式调用析构函数和调用类的其它成员函数没什么不同。当析构函数被显式调用时,只执行它的函数体,而不删除对象的资源。也就是说,当析构函数被显式调用时,它就是一个普通的成员函数,没有析构功能。

三、析构函数的Rules of Three

通常情况下,我们不需要显式定义析构函数,除非我们需要它完成一些工作。(这一点在下部分讲述)

Rules of Three:如果一个类需要手动定义一个析构函数,那么通常情况下,这个类也需要手动定义复制构造函数和赋值运算符重载函数。

我们在上一篇文章中讲过,复制构造函数用于对象的复制,赋值运算符重载函数的功能和复制构造函数几乎一样。通常,我们将复制构造函数和赋值运算符重载函数绑定,定义了一个,另一个也必须出现。

析构函数、复制构造函数和赋值运算符重载函数,这三个函数是C++类的复制控制(copy-control)成员。复制控制,就是控制类的对象的复制。其中复制构造函数和赋值运算符重载函数是用来复制对象,析构函数是用来删除对象。

通常,使用复制构造函数或者赋值运算符重载函数创建一个对象时,会获得资源,有时必须显式定义析构函数才能释放这样的对象的资源。

四、编译器生成的析构函数

析构函数最特别的一点是,编译器总是为我们生成一个析构函数,不管我们是否显式的定义一个析构函数。这一点和构造函数非常不同。当我们显式的定义了析构函数以后,编译器仍然为我们生成一个析构函数。程序执行过程中,先调用用户显式定义的析构函数,再调用编译器生成的析构函数。

五、显示定义的析构函数不析构

根据上文,可以提出这样两个问题:

1、显式定义的析构函数为什么可以显式调用,而构造函数不能?

2、编译器为什么总是为我们生成一个析构函数?既然这样,用户自定义的析构函数有什么用?

也许很多人都有这样的疑问,我想,弄懂了这两个问题,就算真正明白了析构函数。

第一个问题,只有显示定义了析构函数,我们才能显式调用析构函数。当显式调用析构函数的时候,执行的是析构函数体内的语句,没有执行析构功能。我们可以写如下程序来测试:

class Test
{
public:
Test():x(10),y(10){}
~Test()
{
cout<<"deconstructor"<<endl;
cout<<x<<" "<<y<<endl;
}
private:
int x;
int y;
};
int main()
{
Test app;
app.~Test();
return 0;
}

上述程序中,我们在类中显式定义了析构函数,在主函数中,我们显式的调用了一次析构函数,之后对象被销毁,又调用了析构函数。可以看到结果出现了两次,而且均相同。

这个结果说明了什么呢?

第一次显式调用析构函数,函数和普通成员函数一样被执行,没有析构。第二次隐式调用析构函数,输出的结果与第一次一样。之后对象释放资源,对象被销毁。那么,我们思考一下,对象是在什么时候释放的资源,是在什么时候被销毁的?

首先,对象肯定不会在析构函数执行之初和执行之时便释放资源,因为之后我们仍然可以输出对象的成员变量的值。当然,也可以认为执行析构函数时创建了一个临时对象,将对象复制后保存在临时对象中,对象此时已被删除。但析构函数执行完成后,临时对象被删除。但是这样一来,无疑加重了内存的负担,假设对象非常之大,临时对象也讲会很大,而且复制的过程也会很长,语言应该不会设计成这样。

其次,对象可能在析构函数体内的语句执行完毕后开始释放资源。这样需要维持一个值,用来表示析构函数是显式调用还是隐式调用,如果是显式调用,那么不能释放资源,如果是隐式调用,必须释放资源。

我们不能排除这种情况,但是于理不可。如果是这样,大可让析构函数和构造函数一样,不能被显式调用,这样多方便。而且,现在我们也没看出来显式调用析构函数有什么用。

最后,对象可能在析构函数执行完成后释放资源。和上种情况一样,我们仍然需要维持一个值,来表示它是显式还是隐式调用。

通过以上分析,我们可以得出这样一个结论:显式定义的析构函数可能根本就不执行析构功能。乍一看,很神奇,显式定义的析构函数不析构,那由谁析构?

我们来看第二个问题。为什么显式定义了析构函数,编译器还要为我们生成一个析构函数?编译器定义的析构函数有什么用?在C++ Primer第四版中,作者提到:当类的对象被销毁时,显式定义的析构函数先运行,当它运行完成后,编译器生成的析构函数开始运行。编译器生成的析构函数会销毁对象的成员变量,对于类类型的成员,如string类的成员,会调用它所属类的析构函数来释放该成员所占用的内存,对于内置类型成员,编译器生成的析构函数什么也不做而销毁它(does nothing to destroy them)。销毁一个内置类型的成员没有任何影响,特别是,编译器生成的析构函数不会删除指针成员指向的对象。

从书中那段话可以看出,显式定义的析构函数可能与对象的销毁无关,析构的过程可能由编译器生成的析构函数完成的。这也就解释了为什么显式定义的析构函数可以显式调用,因为显式定义的析构函数只是虚有其表,名不符实,它和普通的成员函数没什么大的区别,唯一的不同就是在对象的生存周期中,它一定会被调用至少一次(这一次就是在对象销毁时)。

我无法考证这段话的正确性,几乎没有书讲到这一点,也很难用C++程序证明这一点。这个结论是根据书本推论出来的,没有严格的证明,只能算是推论。

六、显式定义析构函数

上述部分告诉我们:如非必要,不要定义析构函数,以免引起不必要的错误。

显式定义析构函数多用于以下两种情况:

1、用于查看对象在销毁的前一刻保存的内容。有时候为了测试程序,会用到。

2、在类中用new运算符动态分配了内存,可以在析构函数中使用delete运算符释放内存。这种情况是最常用的。因为编译器生成的析构函数是不会销毁new出来的动态对象,这一点是因为new出来的对象保存在内存中的堆(heap)区,而编译器生成的析构函数只会释放内存中的栈(stack)区。举个例子:

class Test
{
public:
Test()
{
p=new int[10];
}
~Test()
{
delete []p;p=null;
}
private:
int *p;
};

当我们定义的类中含有指针成员,并在成员函数中使用new运算符动态分配了内存,我们一定要记得使用delete运算符删除之。使用了new运算符,就一定要使用delete运算符,这是一个好的编程习惯。

当然,我们可以在其它的函数中使用delete运算符,甚至我们可以单独定义一个函数如safe_del(){delete []p;p=null;},然后显式的调用它。但是如果我们把它写在析构函数中,那么即使我们忘了删除new出来的对象,程序运行时也不会忘。虽说显式定义的析构函数名不符实,但是我们还是尽量让它实现析构功能吧。

总结:本文讲了析构函数的一些特性,前面的部分很多书都会涉及到,最后的一部分是笔者的学习心得,才是本文的重点。为了文章的完整性,才阐述了前面部分的内容。

1、显式定义的析构函数可以被显式调用,但是显式调用它没有什么意义。

2、显式定义的析构函数的作用不像显式定义的构造函数那么有用,显示定义的析构函数完全可以用别的函数代替,但是,为了使用方便,为了其它编程人员的使用,在需要显示定义析构函数的情况下,还是定义它比较好,这样符合通用编程风格。

3、Rules of Three:如果需要定义析构函数,那么通常也需要定义复制构造函数和赋值运算符重载函数。

The End

学习心得——析构函数相关推荐

  1. openfoam学习心得——openfoam编程进阶

    openfoam学习心得--openfoam编程重新学 1.OpenFOAM编程入门:setRootCase都干了些啥? setRootCase都干了些啥 2.blockMesh > log.b ...

  2. C++面向对象程序设计 学习心得

    C++面向对象程序设计 学习心得: 这学期学了C++面向对象程序设计,通过成绩单处理,ATM,通讯录,图书管理系统这几个简单系统,慢慢地对系统的设计有了一个初步的认识. 简单说一下设计系统具体的过程以 ...

  3. Java EE学习心得

    –Java EE学习心得   1.    称为编程专家的秘诀是: 思考-----编程--------思考------编程--.. 编程不能一步到位,不能一上来就编,必须先思考如何写,怎样写?然后再编程 ...

  4. Assembly学习心得

    http://blog.csdn.net/etmonitor/ Assembly学习心得 说明: 最近开始准备把学到的.NET知识重新整理一遍,眼过千遍不如手过一遍,所以我准备记下我的学习心得,已备参 ...

  5. 对于mysql存储过程感想_存储过程学习心得

    存储过程学习心得 (2014-12-28 17:28:06) 标签: it 我使用过几次SQL Server,但所有与数据库的交互都是通过应用程序的编码来实现的.我不知到在哪里使用存储过程,也不了解实 ...

  6. 好程序员Web前端教程分享Vue学习心得

    为什么80%的码农都做不了架构师?>>>    好程序员Web前端教程分享Vue学习心得,Vue是一套用于构建用户界面的渐进式框架.与其它大型框架不同的是,Vue 被设计为可以自底向 ...

  7. 大学计算机课英语心得体会,关于计算机网络辅助大学英语教学的思考学习心得...

    关于计算机网络辅助大学英语教学的思考学习心得 [摘要]计算机网络辅助教学可以增强学生的学习兴趣,提高他们自觉学习的能力,因而得到了大多数学生的认可,取得了比较显著的成效.计算机网络辅助教学也对大学英语 ...

  8. 我的MYSQL学习心得(十六) 优化

    原文:我的MYSQL学习心得(十六) 优化 我的MYSQL学习心得(十六) 优化 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看 ...

  9. Spring Framework------version4.3.5-----Reference学习心得-----总结

    1.Spring Framework概述: 有很多可用版本,网址http://projects.spring.io/spring-framework/       2.Spring Framework ...

最新文章

  1. 1102面向对象和类原型
  2. 信息系统项目管理案例2
  3. 01:操作系统(centos,redhat):性能监控和网络命令
  4. Android 平台架构
  5. NET 对象生命周期
  6. Docker-07-docker compose
  7. swift项目调用OC库 和OC项目 在swift文件里面全局调用OC库
  8. 拓端tecdat|R语言关联挖掘实例(购物篮分析)
  9. Gartner预测公有云将迎来“双头垄断”局面
  10. 安全测试-Drozer安全测试框架实践记录篇
  11. 海康、大华视频流地址格式
  12. 斐讯E1刷K2版老毛子Padavan,完美实现中继教程
  13. [转载]生命科学-人体生物磁场及特异作用
  14. 月圆之夜,愿永无bug
  15. Authing 背后的计算哲学
  16. 从零开始用android studio
  17. Docker探赜索隐
  18. idea配置springboot
  19. 激活MDI中已经打开过的文件
  20. 洛谷P1860——新魔法药水

热门文章

  1. Cool JavaScript Tricks
  2. 正式对标苹果,小米 12 系列三箭齐发,MIUI 欲成为跨设备操作系统!
  3. 怎样查看C语言的程序内容,什么手机软件能看c语言文件?
  4. 携程CEO孙洁:17年后的红色警报
  5. 《数据处理与知识发现》章节测验复习
  6. 几位大佬的搞钱经历,太绝了
  7. 让我们走进游戏测试的世界吧!
  8. JAVA面向对象课堂总结
  9. ln火线零线_LN哪个代表零线哪个代表火线
  10. 知不足者好学耻下问者自满_对抗开发人员自满情绪的有效方法