1、析构函数
析构函数的定义方式
函数名和类名相同,在类名前加~,没有返回值类型,没有函数形参(不能重载)
当对象生命周期结束的时候,系统会自动调用析构函数
先调用析构函数,再释放对象的空间
析构函数实现
#include <iostream>
using namespace std;
class Person
{
private:
int mAge;
public:
//无参构造
Person(){
cout<<"无参构造:"<<endl;
}
//无参构造
Person(int age){
mAge = age;
cout<<"有参构造:"<<mAge<<endl;
}
//没有 void 和返回值
~Person(){
cout<<"析构函数:"<<mAge<<endl;
}
};
void test(){
Person(20);
{
Person(30);
}
Person(40);
}
int main(int argc, char *argv[])
{
Person p1(10);
test();
return 0;
}
构造函数和析构函数的执行次序(面试题)
说白了函数从上到下执行,先执行到的先进栈,先执行有参或者无参构造,后退栈的后执行,执行析构函数
一般情况下,空的析构函数就足够,但是如果一个类有指针成员,这个类必须写析构函数,释放指针成员所指向空间
因为一个对象结束,系统默认回收的是这个对象本身的空间,但是它不会回收这个对象中指针成员指向的空间,所以如果一个类有指针成员,这个类必须写析构函数,释放指针成员所指向空间
#include <iostream>
#include <string.h>
#include <stdlib.h>
using namespace std;
class Person
{
private:
char *mName;
public:
Person(){
mName = NULL;
}
Person(char *name){
mName = new char [strlen(name)+1]; //申请堆区空间
strcpy(mName,name);
cout<<"有参构造:"<<mName<<endl;
}
//没有 void 和返回值
~Person(){
if(mName != NULL){
delete [] mName;
cout<<"释放 Person 类的指针成员所指行向的 堆区空间"<<endl;
}
}
};
int main(int argc, char *argv[])
{
Person ml("ml");
return 0;
}
最后调用析构函数释放掉 Person 类的指针成员所指行向的 堆区空间
2、拷贝构造
实现拷贝构造
//Person类中的深拷贝
Person(const Person &ob){
mName = new char [strlen(ob.mName)+1]; //申请堆区空间
strcpy(mName,ob.mName);
cout<<"有参构造:"<<ob.mName<<endl;
}
Person ml("ml");
Person m = ml; //深拷贝
拷贝构造本质是构造函数
在上面的代码中,旧对象给新对象初始化就会调用拷贝构造函数,ob就是旧对象的引用
系统默认的拷贝是浅拷贝,能完成基本的赋值操作,一旦实现了拷贝构造函数必须实现赋值操作,因为系统默认的函数以及无效了
const Person &ob 代表的是当前类的常引用,因为我们不希望改变参数值,所以是 const 类型的
实现深拷贝的必要性
当执行下面的代码的时候,新对象 m 中的成员指针 和 旧对象 ml 中的成员指针都会指向 堆区空间字符串为 “ml”的地址,那么 ml 在结束的时候会调用析构函数释放掉该堆区空间,而 m 在结束的时候也会调用析构函数释放掉该堆区空间
Person ml("ml");
Person m = ml; //深拷贝
所以上面的情况就是,新旧对象中的指针成员指向同一块堆区空间,于是新旧对象对同一块堆区空间释放掉了两次,造成了堆区空间的多次释放,这就是浅拷贝存在的问题
这个时候就需要使用到深拷贝,让新对象中的指针成员指向新的堆区空间,那么新旧对象释放的时候就不会多次释放同一块堆区空间,这就解决了浅拷贝存在的问题
//Person类中的深拷贝
Person(const Person &ob){
mName = new char [strlen(ob.mName)+1]; //新对象申请属于自己的堆区空间
strcpy(mName,ob.mName);
cout<<"有参构造:"<<ob.mName<<endl;
}
#include <iostream>
#include <string.h>
#include <stdlib.h>
using namespace std;
class Person
{
private:
char *mName;
public:
//无参构造
Person(){
mName = NULL;
}
//有参构造
Person(char *name){
mName = new char [strlen(name)+1]; //申请堆区空间
strcpy(mName,name);
cout<<"有参构造:"<<mName<<endl;
}
//深拷贝
Person(const Person &ob){
mName = new char [strlen(ob.mName)+1]; //新对象申请属于自己的堆区空间
strcpy(mName,ob.mName);
cout<<"有参构造:"<<ob.mName<<endl;
}
//析构函数
~Person(){
if(mName != NULL){
delete [] mName;
mName = NULL;
cout<<"释放 Person 类的指针成员所指行向的 堆区空间"<<endl;
}
}
};
int main(int argc, char *argv[])
{
Person ml("ml");
Person m = ml; //深拷贝
return 0;
}
3、注意事项
析构函数没有 void 和 返回值
如果用户定义了有参构造或者拷贝构造,都会屏蔽系统默认无参构造,所以这种情况最好是有自定义的无参构造,不会编译会出错
如果用户定义了有参构造或者无参构造,不会屏蔽拷贝构造
默认的拷贝都是浅拷贝(能完成基本的操作)
如果类中没有指针成员,不用实现构造拷贝和析构函数
如果类中有指针成员而且指向堆区空间,必须实现析构函数释放指针成员指向的堆区空间,必须实现拷贝构造完成深拷贝动作