浅拷贝与深拷贝

159 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第11天,点击查看活动详情

1、问题背景

当我们创建一个类是,编译器会为我们自动编写默认构造函数、默认析构函数和默认的拷贝函数。默认的拷贝函数,我们称之为浅拷贝。

image.png

2、浅拷贝

我们创建一个Person类,设置一个整型变量age和一个指针变量 hight(在堆区开辟,需要程序员自己去释放内存)。当我们把p1拷贝给p2时,编译器会逐字节地将p1内存中的数据复制到p2中。这就导致p1和p2 的higth指针都指向了堆区的同一块内容。在程序结束前,编译器会自动调用Person的析构函数,去释放内存,析构函数与构造函数的调用顺序是相反的,所以p2的内存会先被释放,此时堆区的0x0011被释放一次,等到p1被析构时,会再一次释放0x0011,这是一个非法操作,所以编译器抛出异常。为了解决这个问题,就不能再使用编译器默认的拷贝函数,需要程序员自己重写拷贝函数——深拷贝。

image.png

3、深拷贝

image.png

深拷贝时,我们会在堆区新开辟一个空间去储存p2的指针变量hight,就不会造成某一块内存别重复释放。

class Person
{
public:
	int age;
	int *higth;//开辟到堆区
	Person(int age,int h);
	~Person();
	Person(const Person &p);
};
Person::Person(const Person &p)//自己写一个拷贝构造函数来解决浅拷贝的问题
{
	cout << "深拷贝构造函数调用" << endl;
	age = p.age;
	//higth=p.higth//浅拷贝默认实现拷贝函数的方式
	higth = new int (*p.higth);//在堆区开去一个新内存
}
Person::Person(int a,int h)
{
	higth = new int(h);//什么是new??
	age = a;
	cout << "Person含参构造函数的调用!" << endl;
}
Person::~Person()
{
	if (higth != NULL)//因为对取数据需要程序员自己进行释放
	{
		delete higth;//释放堆区数据
		higth = NULL;
	}
	cout <<"Person的析构函数调用!"<<endl;
}
void test01()
{
	Person p1(19, 170);
	cout << "p1的年龄;" << p1.age << " p1的身高" << *p1.higth << endl;
	Person p2(p1);
	cout << "p2的年龄;" << p2.age << " p2的身高" << *p2.higth << endl;
        //如使用浅拷贝,先释放p2,p1也就没有了,再次释放就是非法操作。
}

image.png 如果成员变量有在堆区开辟的,那么一定要自己书写拷贝构造函数,否则会出现浅拷贝问题。

4、赋值运算重载解决浅拷贝问题

拷贝构造函数是将p1的东西赋给p2,所以我们可以赋值运算符进行重载解决浅拷贝问题(在我的上一篇文章介绍了运算符重载可以类比这看一看,谢谢!!!)。

//赋值运算符重载
#include <iostream>
using namespace std;
class Person
{
public:
	Person(){}
	Person(int a)
	{
		age = new int(a);
	}
	~Person()
	{
		if (age != NULL)
		{
			delete age;
			age = NULL;
		}
	}
	Person& operator=(Person &p)
	{
		if (age != NULL)
		{
			delete age;
			age = NULL;
		}
		age = new int(*p.age);//这一部分跟深拷贝函数写法一样。
		return *this;//实现链式赋值
	}
	int *age;
};
void test01()
{
	Person p1(18);
	Person p2;
	p2 = p1;
	Person p3;
	p3 = p2 = p1;//链式赋值

	cout << "p1:" << *p1.age << endl;
	cout << "p2:" << *p2.age << endl;
	cout << "p3:" << *p3.age << endl;
}
int main()
{
	test01();
}