析构函数
前面有构造函数进行对象的初始化工作,现在再增加一个特殊的成员函数,进行扫尾工作。
析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象的一些资源清理工作。
特性
-
析构函数名是在类名前加上字符 ~。
-
无参数无返回值。
-
一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数(不像构造函数一样支持函数重载,原因:无参数)(此时,对于内置类型成员变量不做处理,而对于自定义类型成员变量回去调用它的析构函数)
// 两个栈实现一个队列 class MyQueue { public: // 默认生成构造函数和析构函数会对自定义类型成员调用他的构造和析构 void push(int x) { } private: Stack pushST; Stack popST; }
-
对象生命周期结束时,C++编译系统系统自动调用析构函数。
总结
析构函数,完成对象中资源的清理。如果类对象需要资源清理,才需要自己实现析构函数。
析构函数对象生命周期(局部变量在出作用域的前一刻被释放,全局变量在main函数结束后被释放)到了,以后自动调用,如果你正确实现了析构函数,才能保证了类对象中资源被清理。
我们不实现,编译器会自动生成默认的构造函数,我们实现了,编译器就不会生成了。
对于我们不写时,编译器自动生成的析构函数:
- 对于内置类型的成员变量不处理
- 对于自定义类型的成员变量会调用该变量自己的默认析构函数。
拷贝(复制)构造函数
现在实例化对象,可不可以用简洁的方式实例化出一个之前就存在的对象呢?
答案是有的,就是拷贝构造函数。
构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
特征
-
拷贝构造函数是构造函数的一个重载形式。
-
拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
#include <iostream> using namespace std; class Date { public: Date(int y, int m, int d) { this->_year = y; this->_month = m; this->_day = d; } Date(Date d) { _year = d._year; _month = d._month; _day = d._day; } private: int _year; int _month; int _day; }; int main() { Date d1(2022, 1, 1); Date d2(d1); return 0; }
当不使用引用时:传参时会发生
Date d = d1
,而这个语句又会调用拷贝构造函数,形成无限的嵌套。
当使用引用时:语句变成
Date& d = d1
,这个引用语句不会调用拷贝构造函数。为什么建议加上
const
?因为
d
是d1
的引用,在拷贝构造函数中对d
的改动是能够影响到d1
的。而且一般,拷贝构造函数进行的就是单纯地拷贝,加上const
是为了防止误操作,即防止影响到d1
。 -
若未显示定义,系统生成默认的拷贝构造函数。
对于内置类型,会完成按字节序的拷贝(浅拷贝)。
对于自定义类型,会调用它的拷贝构造。#include <iostream> using namespace std; class Stack { public: Stack(int capacity = 4) { _a = (int*)malloc(sizeof(int) * capacity); if (_a == nullptr) { cout << "malloc fail\n" << endl; exit(-1); } _top = 0; _capacity = capacity; } void Push(int x) {} ~Stack() { free(_a); _a = nullptr; _top = _capacity = 0; } private: int* _a; size_t _top; size_t _capacity; }; int main() { Stack st1(10); Stack st2(st1); return 0; }
上面的程序报错:
原因就是因为不写构造函数,而造成的浅拷贝问题。在默认的构造函数中,两个对象内的指针成员就简单的进行了赋值操作,使得两者都指向了同一个内存区域。
当进行析构时,也会对同一个内存区域进行两次
free
,此时发生错误。
总结
拷贝构造:使用同类型的对象区初始化实例对象
如果我们不是实现,编译器会生成一份默认的拷贝构造函数。
对于默认生成的拷贝构造:
- 内置类型完成按字节序的值拷贝(浅拷贝)。
- 自定义类型的成员变量,会去调用它的拷贝构造。
其实上面这个例子,我们更需要地是在Stack
类中实现深拷贝。