• C++ 类和对象篇(五) 析构函数


    目录

    一、概念

    1. 析构函数是什么?

    2. 为什么要有析构函数?

    3. 怎么用析构函数?

    3.1 创建析构函数

    3.2 调用析构函数

    二、特性

    三、什么时候会调用析构函数

    四、合成析构函数

    五、对象的析构顺序

    1. 局部对象

    2. 动态对象

    3. 全局对象

    【总结】 


    一、概念

    1. 析构函数是什么?

            析构函数是一个特殊的成员函数,在对象销毁时用来释放对象使用的资源(如关闭文件、释放内存)和销毁非static成员变量。

    2. 为什么要有析构函数?

            如何释放对象申请的系统资源?忘记释放怎么办?能不能在销毁对象时自动释放?

    1. 举个小例子:
    2. class Test
    3. {
    4. public:
    5. //构造函数
    6. Test()
    7. {
    8. _arr = (char*)malloc(1024*1024*1024);//申请1G空间
    9. }
    10. //销毁函数:用于释放资源
    11. void Destory()
    12. {
    13. free(_arr);
    14. }
    15. private:
    16. char* _arr;
    17. };
    18. int main()
    19. {
    20. Test* t = new Test;//在堆上创建一个对象
    21. delete t;//销毁一个对象
    22. while (1) {}
    23. return 0;
    24. }
    25. 如果要销毁Test对象,必须先使用Destory公有方法来释放资源,否则会造成内存泄漏,
    26. 这未免有点麻烦,而且容易忘记,那能否在对象销毁的同时释放资源呢?

    将以上程序运行起来,对比前后的内存变化,可以发现销毁对象前如果忘记释放资源,就会造成内存泄漏等问题。

    程序运行前: 

    程序运行后:


            所以为避免C++使用者在销毁对象时忘记释放对象使用的资源,C++引入了析构函数,在析构函数里写释放资源的代码,在对象销毁时编译器会自动调用析构函数释放对象使用的资源。析构函数相对于自己写的销毁函数,其优势在于不需要自己去显示调用。 

            析构函数的目的是确保对象在销毁前将其占用的资源全部释放,以避免资源泄漏和内存泄漏等问题。

    3. 怎么用析构函数?

    3.1 创建析构函数

            创建时要注意析构函数特征:析构函数名是在类名前加上字符~、无参数无返回值。

            类中动态申请的资源需要在析构函数中写相应的代码手动释放。

    1. 用以下例子来说明如何创建无参构造函数和带参构造函数:
    2. 创建时要注意析构函数特征:析构函数名是在类名前加上字符~、无参数无返回值。
    3. class Test
    4. {
    5. public:
    6. //构造函数
    7. Test()
    8. {
    9. _arr = (char*)malloc(1024*1024*1024);//申请1G空间
    10. }
    11. //析构函数:在析构函数里释放资源,对象被销毁时会自动被调用。
    12. ~Test()
    13. {
    14. cout << "析构函数调用成功!" << endl;
    15. free(_arr);
    16. }
    17. private:
    18. char* _arr;
    19. };

    3.2 调用析构函数

            在对象销毁时编译器会自动调用析构函数。所以析构函数是不需要我们去显示调用的,我们只需要记得销毁对象(特指堆上的对象)即可。

    1. 接上面的例子,演示如何调用无参构造函数和带参构造函数:
    2. int main()
    3. {
    4. //在堆上创建对象t
    5. Test* t = new Test;
    6. for (int i = 0; i < 100000; ++i)
    7. {
    8. cout << i << endl;
    9. }
    10. //销毁对象t
    11. delete t;
    12. return 0;
    13. }


    二、特性

            再次强调,析构函数的任务不是销毁对象,而是完成对象中资源的清理工作(释放对象使用的资源,并销毁对象的非static成员变量),对象在销毁时会自动调用析构函数。

    析构函数是特殊的成员函数,其特征如下:

    1. 析构函数名是在类名前加上字符~。

    2. 无参数也不接受参数,所以无法重载。且无返回值。

    3. 一个类只能有一个析构函数,析构函数不能重载。

    4. 对象动态申请的资源需要在析构函数中写相应的代码手动释放。

    5. 若未显式定义,系统会自动生成默认的析构函数。

    6. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

    7. 对于成员变量的销毁不需要我们显示的写在析构函数体中,在析构函数中的语句被执行完后,还有一个隐含的析构阶段,在该阶段中内置类型的成员变量由系统进行回收,自定义类型的成员变量系统会去调用它的析构函数。(所以析构函数的执行分两部分,先执行析构函数体中的语句,然后进入隐含的析构阶段。)


    三、什么时候会调用析构函数

    无论何时一个对象被销毁,就会自动调用其析构函数:

    • 对象在离开其作用域时被析构。
    • 当一个对象被销毁时,其成员被析构。
    • 容器(无论是标准库容器还是数组)被销毁时其元素被析构。
    • 对干动态分配的对象,当对指向它的指针应用delete运算符时被析构。
    • 对于临时对象,当创建它的完整表达式结束时被析构。

    四、合成析构函数

    若未显式定义,系统会自动生成默认的析构函数,叫做合成析构函数


    那什么时候需要显示的写析构函数,什么时候让编译器自动生成呢?

            有资源申请(如使用malloc动态开辟空间)时,一定要写,否则会造成资源泄漏。如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数。


    五、对象的析构顺序

    1. 局部对象

            后实例化的对象先析构,先实例化的对象后析构。因为对象是定义在栈帧里面的,而栈帧的建立遵循后进先出。

    1. class A
    2. {
    3. public:
    4. ~A()
    5. {
    6. cout << "A被析构" << endl;
    7. }
    8. };
    9. class B
    10. {
    11. public:
    12. ~B()
    13. {
    14. cout << "B被析构" << endl;
    15. }
    16. };
    17. class C
    18. {
    19. public:
    20. ~C()
    21. {
    22. cout << "C被析构" << endl;
    23. }
    24. };
    25. int main()
    26. {
    27. A a;
    28. B b;
    29. C c;
    30. return 0;
    31. }

    2. 动态对象

            动态对象的析构发生在使用delete的时候,与delete的使用顺序相关。

    1. int main()
    2. {
    3. A* a = new A();
    4. B* b = new B();
    5. C* c = new C();
    6. delete a;
    7. delete b;
    8. delete c;
    9. return 0;
    10. }

    3. 全局对象

            在一个源文件中的全局对象,先实例化的对象后析构。

    1. A a;
    2. B b;
    3. C c;
    4. int main()
    5. {
    6. return 0;
    7. }


    【总结】 


    ------------------------END-------------------------

    才疏学浅,谬误难免,欢迎各位批评指正。

  • 相关阅读:
    HTML超链接去下划线
    第3章业务功能开发(用户访问项目)
    MySQL json相关函数详解
    企业级node.js开发框架 【egg.js】 实用教程
    js实现将图片转成黑白
    常见的状态码即问题解决
    基于SOCKET编程多人聊天软件HTML+JSON报文
    JavaSE基础1
    【二进制部署k8s-1.29.4】三、etcd集群的安装配置
    python中常用的属性访问机制:__getattribute__、__setattr__、__delattr__和__getattr__
  • 原文地址:https://blog.csdn.net/look_outs/article/details/133590783