析构函数
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
特性
- 析构函数名时在类名前面加上字符
~
class Date
{
public:
Date(int year, int month , int day)
{
_year = year;
_month = month;
_day = day;
}
~Date()
{
......
}
private:
int _year = 0;
int _month = 0;
int _day = 0;
}
-
无参数无返回值类型,不带void
-
一个类只能由一个析构函数,如果未显式定义,系统会自动生成默认的析构函数。
-
析构函数不支持重载。
-
对象声明周期结束时,C++编译系统自动调用析构函数。
-
编译器生成的默认析构函数,对自定义类型成员调用它的析构函数。对内置类型不做处理。
-
如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数。有资源申请时,一定要写,否则会造成资源泄露。
对象的栈帧的开辟和销毁都是由编译器进行的。不需要析构
动态开辟的空间资源才需要析构。
拷贝构造
拷贝构造函数:只有单个形参,该形参试对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
特征
- 浅拷贝:值拷贝
- 深拷贝:深拷贝本质拷贝指向的资源,让两个被拷贝和拷贝者都有一样大的空间,一样大的值,但不是同一个空间。
-
拷贝构造函数是构造函数的一个重载形式
-
拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
-
若未显式定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
-
编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝,还需要自己显实现吗?如果像日期类这种类没有必要的,但是如果是别的需要开辟空间的呢?
创建一个stack的对象s1,s1调用构造函数,申请了一个空间。s2使用s1拷贝构造,而Stack中没有显式定义的拷贝构造函数,此时编译器默认生成了一个拷贝构造函数,默认的拷贝构造函数是按照值拷贝的方式进行拷贝的,即将s1中的成员变量存储的值原封不动的赋值给了s2中的成员变量,拷贝结束之后,s2中的成员变量指向了和s1成员变量指向的同一片空间。
当程序结束的时候,s2先销毁,s1后销毁,s2先调用析构函数对这片申请的空间进行了析构,然后s1销毁,此时s1并不知道这片内存空间已经销毁了,再次对这片空间进行了析构,此时就会崩溃。对一片内存空间进行了多次释放。
浅拷贝
- 类中如果没有设计资源申请时,拷贝构造函数是否写都可以;
- 一旦涉及到资源申请,则拷贝构造函数是一定要写的,否则就是浅拷贝。
- 拷贝构造的典型调用场景:
- 使用已存在的对象创建对象
- 函数参数类型未类类型对象
- 函数返回值类型为类类型对象
-
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。
-
拷贝构造主要用于解决深拷贝的问题。
-
深拷贝本质拷贝指向的资源,让两个被拷贝和拷贝者都有一样大的空间,一样大的值,但不是同一个空间。
深拷贝
//Stack st2(st1);
Stack(const Stack& st)
{
_a = (int*)malloc(sizeof(int) * st._capacity);
if(_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
memcpy(_a,st._a,sizeof(int)*st._top);
_top = st._top;
_capacity = st._capacity;
}
- 两个已经存在的对象拷贝,赋值拷贝
- 一个已经存在的对象拷贝初始化另一个要创建的对象,就是拷贝构造
A aa1(1);
A aa2(aa1); //拷贝构造
A aa3 = aa1; //拷贝构造
aa2 = aa3; //赋值拷贝