C++ 学习之——深浅拷贝的认识

[本节内容]

浅拷贝与深拷贝的比较

1.拷贝

拷贝就是我们平时说的复制,就如同“克隆”,用一个已有的对象快速地复制出多个完全相同的对象。
一般我们在以下三种情况下会用到对象的复制:
1.拷贝构造函数:用一个已有的对象去创建同类的新对象并对它初始化
2.对象作为函数的参数进行调用,这时调用此函数时使用的是值传递,也会产生对象的复制
3.函数的返回值是类的对象,在函数调用结束时,需要将函数中的对象复制一个临时对象并传给改函数的调用处

2.浅拷贝

浅拷贝,指的是在对象复制时,只对对象中的数据成员进行简单的值拷贝(改变指针使它指向要拷贝的值),之前讲到的默认拷贝构造函数执行的就是浅拷贝。大多数情况下只需要“浅拷贝”就能完成所需工作。
但是若对象存在了动态成员,那么浅拷贝就会出问题了,让我们考虑如下一段代码:

class A
{
public:
	A()      // 构造函数,p主动要求分配空间
	{
		p = new int(10);
	}
	~A()     // 析构函数,释放动态分配的空间
	{
		cout << "~A()" << endl;
			delete p;
	}
private:
	int w;
	int h;
	int *p;     // 指针成员
};

int main()
{
	A a1;
	A a2(a1);   // 调用默认拷贝构造函数,创建新对象
	return 0;
}

运行结果:
在这里插入图片描述
由运行结果可知,程序在这里崩溃了,原因在于类A的析构函数调用了两次。我们来简单分析一下:

在这里插入图片描述
这里,由于创建a1对象时调用的构造函数中有一个动态分配的语句,所以它在堆上有自己独立的一块空间。但是创建a2对象时调用的是默认拷贝构造函数中并没有动态开辟空间的语句,而是直接进行值拷贝,将自己对象的指针指向a1之前开辟的空间,此时两个对象就共用了堆内的同一块空间。
他们在使用时没有什么错误,但是在使用后的销毁过程中,a1先调用自己的析构函数释放掉了自己开辟的空间,当a2再调用析构函数时也要释放空间但却找不到这块空间,因此程序发生崩溃现象。
在这里,我们期望的是a1,a2所指向的两个空间有相同的值,它们能执行自己的析构函数,而不是简单的让a1,a2有相同的值,所以我们需要深拷贝来防止默认拷贝构造函数的发生。

3.深拷贝

在“深拷贝”的情况下,对于对象中动态成员,就不是仅仅简单地赋值了,而是要重新动态分配空间后再进行值的拷贝。如上面的例子按照如下的方式进行深拷贝处理——自己写一个拷贝构造函数

class A
{
public:
	A()      // 构造函数,p指向堆中分配的一空间
	{
		p = new int(10);
	}
	~A()     // 析构函数,释放动态分配的空间
	{
		cout << "~A()" << endl;
		delete p;
	}
	A(const A &a)
	{
		w=a.w;
		h = a.h;
		p = new int(10);
		*p = *(a.p);
	}
private:
	int w;
	int h;
	int *p;     // 指针成员
};

int main()
{
	A a1;
	A a2(a1);   // 复制对象
	system("pause");
	return 0;
}

此时对象完成拷贝复制的情况大概如下;
在这里插入图片描述
由图可知,此时a1,a2各有一块空间,且空间内的内容相同,这时无论使用还是销毁,都不会再出现程序因内存崩溃的问题。这就是所谓的深拷贝。所以一般来说深拷贝比浅拷贝的代价更大一些。

猜你喜欢

转载自blog.csdn.net/ly_6699/article/details/88066422