C++析构函数

C++析构函数

一、认识析构函数

析构函数也是一种特殊的成员函数。它执行与构造函数相反的操作,通常用于撤消对象时的一些清理任务,如释放分配给对象的内存空间等。

同样的,我们来看看析构函数的几个特点:

  • 函数名是在类名前加上~,无参数且无返回值。
  • 一个类只能有且有一个析构函数,如果没有显式的定义,系统会生成一个缺省的析构函数(合成析构函数)。
  • 因为无参数无返回值析构函数不能重载。每有一次构造函数的调用就会有一次析构函数的调用。
  • 当撤消对象时,编译系统会自动地调用析构函数。 如果程序员没有定义析构函数,系统将自动生成和调用一个默认析构函数,默认析构函数只能释放对象的数据成员所占用的空间,但不包括堆内存空间。
class Data{
    public:
        Data(int year=2019,int month=9,int day=23):_year(year),_month(month),_day(day){}
        ~Data(){
    cout<<"~Data()"<<this<<endl;
}
    private:
        int _year=1990;
        int _month;
        int _day;
};
void test(){
    Data test1;
}
int main(){
    test();
    return 0;
}

在test()函数中构造了对象d1,那么在出test()作用域d1应该被销毁,此时将调用析构函数,下面是程序的输出。当然在构建对象时是先调用构造函数的,在这里就不加以说明了。

析构函数被调用的两种情况

1)若一个对象被定义在一个函数体内,当这个函数结束时,析构函数会被自动调用。

2)若一个对象在使用过程中运用new运算符进行动态创建,在使用delete释放时,自动调用析构函数。

二、销毁操作

析构函数在作用完类对象离开作用域后释放对象使用的资源,并销毁成员。

void test(){
    int a=1;
    int b=2;
}

在一个函数体内定义一个变量,在test函数中定义a和b两个变量,在出了test函数后,a和b就会被销毁(栈上的操作)。如果是一个指向动态开辟的一块空间的指针(new,malloc),我们都需要进行free,否则就会内存泄露问题。

当类类型对象的成员还有一个类类型对象,那么在析构函数里也会调用这个对象的析构函数。

缺省的析构函数

每个类都必须有一个析构函数。

如果类中没有显式地为一个类定义析构函数,编译系统会自动地生成一个缺省的析构函数

类名::析构函数命(){}

class Date{
    public:
        Date(char *){
            str=new char[max_len];
    }
    ~Date(){ delete []str;}
    void get_info(char *);
    void send_info(char *);
    private:
        char *str;
        int max_len;
};

析构函数阻止该类型对象被销毁

我们如果不想要析构函数来对对象进行释放该怎么做呢,不显式的定义显然是不行的,因为编译器会生成默认的合成析构函数。之前我们知道了如果想让系统默认生成自己的构造函数可以利用default,那么其实还有一个东西叫做delete。

class Date{
    public:
       Date(int year=2019,int month=9,int day=1):_year(year),_month(month),_day(day){}
        ~Date()=delete;
    private:
        int _year=2019;
        int _month;
        int _day;
};

这么写了,又在底下创建Date类型的对象,那么这个对象将是无法被销毁的,其实编译器并不允许这么做,直接会给我们报错。

但可以使用动态创建这个类类型对象的,像这样:Date* p = new Date;虽然这样是可行的,但当你delete p的时候依然会出错。既不能定义一个对象也不能释放动态分配的对象,所以还是不要这么用为好。

一般在显式的定义了析构函数的情况下,应该也把拷贝构造函数和赋值操作显式的定义。

class Date{
    public:
       Date(int year=2019,int month=9,int day=1):_year(year),_month(month),_day(day){
            p=new int;
}
        ~Date(){
            delete p;
}
    private:
        int _year=2019;
        int _month;
        int _day;
        int *p;
};

成员中有动态开辟的指针成员,在析构函数中对它进行了delete,如果不显式的定义拷贝构造函数,当你这样:Date d2(d1)来创建d2,我们都知道默认的拷贝构造函数是浅拷贝,那么这么做的结果就会是d2的成员p和d1的p是指向同一块空间的,那么调用析构函数的时候回导致用一块空间被释放两次,程序会崩溃。

 

调用构造函数与析构函数的顺序

1)一般顺序

调用析构函数的次序正好与调用构造函数的次序相反,最先被调用的构造函数,其对应的构造函数最后被调用,而最后被调用的构造函数,其对应的析构函数最先被调用。

对象1构造函数->对象2的构造函数->对象3的构造函数->对象3的析构函数->对象2的析构函数->对象1的析构函数

2)全局对象

在全局范围中定义的对象(即在所有函数之外定义的对象),它的构造函数在所有函数执行之前调用。在程序流程离开其作用域时,调用该全局对象的析构函数。(包括main函数)

3)auto局部对象

局部自动对象(例函数中定义的对象),则在建立对象时调用其构造函数。如果函数被多次调用,则每次调用时都要调用构造函数。在函数调用结束、对象释放时先调用析构函数。

4)static局部对象

在函数中定义静态局部对象,则只在程序第一次盗用此函数建立对象时调用构造函数一次,在调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用析构函数。

对象的生存期

对象生存期不同分为:局部对象、全局对象、静态对象、动态对象。

(1)局部对象

当对象被定义时,调用构造函数,该对象被创建;当程序退出该对象所在的函数体或程序块时,调用析构函数,对象被释放。

局部对象在被定义在一个函数体或程序块内的,它的作用域限定在函数体或程序块内,生存期比较短。

(2)全局对象

当程序开始运行时,调用构造函数,该对象被创建;当程序结束时,调用析构函数,该对象被释放。

静态对象时被定义在一个文件中,它的作用域从定义是起到文件结束为止,生存期长。

(3)静态对象

当程序中定义静态对象时,调用构造函数,该对象被创建;当整个程序结束时,调用析构函数,对象被释放。

全局对象是被定义在某个文件中,它的作用域包含在该文件的整个程序中,生存期最长。

(4)动态对象

执行new运算符调用构造函数,动态对象被创建;用delete释放对象时,调用析构函数

动态对象由程序员掌握,它作用域与生存期是有new和delete之间的时间决定的。

 

  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值