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之间的时间决定的。