• 析构函数详解



    我们主要从三个方面来学习析构函数的工作原理:

    • 析构函数的内部工作机制
    • 默认析构函数的内部工作机制
    • 析构函数的调用

    1、析构函数的内部工作机制

    众所周知,在对象的生命周期结束时会自动调用析构函数用于清理对象所申请的资源,那么它是如何清理的呢?

    析构函数会调用delete函数释放对象中new出来的空间,即析构函数通过delete函数来清理对象所申请的资源,当然如果对象没有申请资源,那么就无需调用delete函数

    由于是new出来的对象是在堆上分配空间的,即使离开了作用域,其依然存在,我们必须在析构函数中主动delete来释放new出来的在堆上的空间,否则对象消亡后,离开了作用域后,指向该空间的数据成员(指针)就会消失,我们失去了对这片空间的控制权,别人也无法使用这片空间,这就会造成内存泄漏。

    如果对象是new出来的,那么它就是一个堆对象,不会被操作系统自动回收,需要我们手动调用delete函数释放该堆对象,在delete函数中会先调用析构函数释放该对象所申请的资源,那么析构函数如何释放对象所申请的资源呢,就是前面所说的,析构函数又会调用delete函数来释放对象所申请的资源,当然如果对象没有申请资源,那么就无需调用delete函数。

    #include 
    using namespace std;
    class Person {
    public:
        Person() {
            cout <<  "调用了Person的构造函数" << endl;
        }
        ~Person() {
            cout << "调用了Person的析构函数" << endl;
        }
    private:
        int name;
    };
    
    int main() {
        Person* person = new  Person();
        delete person;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述

    2、默认析构函数的内部工作机制

    我们清楚了析构函数的内部工作机制后,继续思考一个问题,默认析构函数它的内部是怎么工作的呢?

    我们先来看看下面这段代码

    class Time
    {
    public:
    	~Time()
    	{
    		cout << "~Time()" << endl;
    	}
    private:
    	int _hour;
    	int _minute;
    	int _second;
    };
    class Date
    {
    private:
    	// 基本类型(内置类型)
    	int _year = 1970;
    	int _month = 1;
    	int _day = 1;
    	// 自定义类型
    	Time _t;
    };
    int main()
    {
    	Date d;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    程序运行结束后输出:~Time(),在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?

    这就涉及到默认析构函数的内部工作机制了。

    默认析构函数,对于基本类型成员不做处理,对于自定义类型成员会去调用它的析构函数。

    总结: 默认构造函数对基本类型不做处理,对自定义类型会调用它的默认构造函数,默认拷贝构造函数对基本类型是按照字节方式直接拷贝的,对自定义类型是调用其拷贝构造函数完成拷贝的,默认析构函数对基本类型不做处理,对自定义类型会调用它的析构函数。默认赋值运算符对基本类型成员变量以值的方式逐字节拷贝。而对自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

    默认构造函数只有一个,析构函数只有一个,拷贝构造函数只有一个,赋值运算符重载只有一个

    如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,什么事都不会干,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏

    3、析构函数的调用

    析构函数在对象消亡时被调用,以清理对象所申请的资源,那具体它在何时被调用呢?

    析构函数主要在以下4种情况下会被调用:

    对象生命周期结束,此时会自动调用析构函数。

    #include
    using namespace std;
    class Person {
    public:
        Person() {
            cout << "调用了Person的构造函数" << endl;
        }
        ~Person() {
            cout << "调用了Person的析构函数" << endl;
        }
    private:
        int name;
    };
    
    int main() {
        Person person;
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    运行结果如下图所示:

    在这里插入图片描述

    该运行结果说明,在对象的生命周期结束后,会自动调用对象的析构函数。

    成员关系:对象car是对象person的成员,person的析构函数被调用时,对象car的析构函数也被调用。

    #include 
    using namespace std;
    class Car {
    public:
        Car() {
            cout << "调用了Car的构造函数" << endl;
        }
        ~Car() {
            cout << "调用了Car的析构函数" << endl;
        }
    private:
        int name;
    };
    class Person {
    public:
        Person() {
            cout << "调用了Person的构造函数" << endl;
        }
        ~Person() {
            cout << "调用了Person的析构函数" << endl;
        }
    private:
        int name;
        Car car;
    };
    
    
    int main() {
        Person person;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    在这里插入图片描述
    先调用的是包含类的析构函数,然后调用的是成员对象的析构函数

    继承关系:当Person是Student的父类,调用Student的析构函数,会调用Person的析构函数。

    #include 
    using namespace std;
    
    class Person {
    public:
        Person() {
            cout << "调用了Person的构造函数" << endl;
        }
        ~Person() {
            cout << "调用了Person的析构函数" << endl;
        }
    private:
        int name;
    
    };
    class Student :public Person {
    public:
        Student() {
            cout << "调用了Student的构造函数" << endl;
        }
        ~Student() {
            cout << "调用了Student的析构函数" << endl;
        }
    private:
        int name;
        string no;
    };
    
    int main() {
        Student student;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    在这里插入图片描述
    先调用的是派生类的析构函数释放派生类的资源,然后调用的才是父类的析构函数释放父类成员所指向的资源

    值得注意的是,如果派生类对象是new出来的对象,基类指针指向一个new生成的派生对象,通过delete销毁基类指针指向的派生类对象时,有以下两种情况:

    1、 如果基类析构函数不是虚析构函数,则只会调用基类的析构函数,派生类的析构函数不被调用,此时派生类中申请的资源不被回收。

    2、 如果基类析构函数为虚析构函数,则释放基类指针指向的对象时会调用基类及派生类析构函数,派生类对象中的所有资源被回收。

  • 相关阅读:
    简述DRM
    MySQL常用字符串函数
    【Linux】《Linux命令行与shell脚本编程大全 (第4版) 》笔记-Chapter2-走进 shell
    解决wpf ScrollViewer中嵌套其他(DataGrid、ItemsControl等等)控件后,鼠标滚动不响应
    vue+element-plus完美实现跨境电商商城网站
    C# 窗体设计中 调用 控制台输出
    HCL Domino/Notes专业课程和认证体系介绍
    空间复杂度(数据结构)
    SATA系列专题之四:4.0 Command Layer命令层概述
    将下拉弹层渲染节点固定在触发器的父元素中
  • 原文地址:https://blog.csdn.net/weixin_44049823/article/details/127928420