C++中关于类的拷贝构造和赋值的深浅拷贝问题

前言

在学习C++类与对象的拷贝构造函数赋值运算符重载这两个成员函数,无法避免的需要考虑的是:深浅拷贝的话题。小编在这里会和大家一起谈到这个话题!

注:下面主要谈拷贝构造函数。赋值运算符重载以此类推!

1. 浅拷贝

浅拷贝: 就是以值的方式进行拷贝,对于默认的(编译器自己生成)拷贝构造函数来说,内置类型就是采用的是值拷贝的方式。来看这样一个示例,例1:

#include<iostream>
using namespace std;
class A
{
public:
	A(int *ptr)
		:_ptr(ptr)
	{}
private:
	int* _ptr;
};

int main()
{
	int a = 1;
	A a1(&a);
	A a2(a1);

	return 0;
}


这个结果是很合理的:采用了值拷贝的形式。将我们的a的地址交给了A a2对象。

这就是浅拷贝

2. 深拷贝

关于上面的示例1,小编是故意使用的是指针类型。那么小编抛出以下问题:

  1. 指针是否可以指向一个数组?

  2. 指针是否可以指向一个堆区开辟的数组空间?

  3. 对于一个指针指向的数组其中的元素(或者其它申请的资源)而言,你希望它的拷贝是一种怎么样的拷贝?进一步来说:一个资源应该被多少个对象来管控?

我们将场景聚焦,例2:

#include<iostream>
using namespace std;
class Array
{
public:
	Array(int size)
	{
		int* tmp = (int*)malloc(sizeof(int) * size);
		if (tmp == nullptr)
		{
			perror("malloc");
			return;
		}
		_ptr = tmp;
	}
	~Array()
	{
		free(_ptr);
	}
	//Array(const Array& obj) 先屏蔽掉
	//{
	//	//……
	//}
private:
	int* _ptr;
};


int main()
{
	Array arr1(5);
	
	Array arr2(arr1); //语句一,你希望的行为是什么?
	
	return 0;
}

不难看出,例2代码实现的是:利用类来包装一个数组(可能还有其它可延展功能)。对于上面代码的语句一

  • 正常来说:我们应该是希望能够得到一个和arr1同等规模的独立数组arr2。但是如果我们实现的是浅拷贝……

来看结果:

在这里插入图片描述

  • 现象:这两个不同类对象的_ptr指针指向地址一模一样
  • 如图理解:

在这里插入图片描述

这样就会造成两个问题:

  1. arr1访问这个数组的时候,会影响arr2中的数据内容!这个读写的影响是相互的!
  2. 注意到析构函数free(_ptr);如果当arr1arr2生命周期走到尽头,那么不管是谁先释放这片空间,都会造成:另一个对象对野指针的释放!!!这样是极度危险的行为!

所以我们需要对这样的类进行深拷贝 — 不仅仅是拷贝其成员变量的值,更需要拷贝其管理的申请的资源……

由于我们上面的那个示例2并没有能够得到当前数组的元素个数的接口,所以还是比较麻烦的,所以在下面的示例中,添加这个字段帮组我们获得数组的大小!
例3:

#include<iostream>
using namespace std;
class Array
{
public:
	Array(int size)
		:_size(size)
	{
		int* tmp = (int*)malloc(sizeof(int) * size);
		if (tmp == nullptr)
		{
			perror("malloc");
			return;
		}
		_ptr = tmp;
	}
	~Array()
	{
		free(_ptr);
		_ptr = nullptr;
	}
	Array(const Array& obj)
		:_size(obj._size)
	{
		_ptr = (int*)malloc(sizeof(int) * _size);

		//拷贝
		memcpy(_ptr, obj._ptr, sizeof(int) * _size);
		//……
	}
private:
	int* _ptr = nullptr;
	int _size;
};


int main()
{
	Array arr1(5);

	Array arr2(arr1);

	return 0;
}

在这里插入图片描述

  • 这样就完成了深拷贝的需求。当然不同场景下,深拷贝的方式是不同的,所以各位读者还是需要按需求来!

故此这里总结一下:

  1. 不需要进行深拷贝的类

    • 不涉及动态资源申请的类
  2. 需要进行深拷贝的类

    • 涉及动态资源申请的类

注: 如果有自定义类型不要担心,编译器默认生成的拷贝构造函数可以帮你调用其的拷贝构造函数。但是需要你的自定义类型成员实现了一个合理的拷贝方式!

完。

  • 小编希望这篇文章能够帮助你!
设计并实现一个动态整型数组Vect,要求: (1)实现构造函数重载,可以根据指定的元素个数动态创建初始值为0的整型数组,或根据指定的内置整型数组动态创建整型数组。 (2)设计拷贝构造函数析构函数,注意使用深拷贝。 (3)设计存取指定位置的数组元素的公有成员函数,并进行下标越界,若越界则输出“out of boundary”。 (4)设计获取数组元素个数的公有成员函数。 (5)设计用于输出数组元素的公有成员函数,元素之间以空格分隔,最后以换行符结束。 在main函数中按以下顺序操作: (1)根据内置的静态整型数组{1,2,3,4,5}构造数组对象v1,根据输入的整型数构造数组对象v2。 (2)调用Vect的成员函数依次输出v1v2的所有元素。 (3)输入指定的下标及对应的整型数,设置数组对象v1的指定元素。 (4)根据数组对象v1拷贝构造数组对象v3。 (5)调用Vect的成员函数依次输出v1v3的所有元素。 设计并实现一个动态整型数组Vect,要求: (1)实现构造函数重载,可以根据指定的元素个数动态创建初始值为0的整型数组,或根据指定的内置整型数组动态创建整型数组。 (2)设计拷贝构造函数析构函数,注意使用深拷贝。 (3)设计存取指定位置的数组元素的公有成员函数,并进行下标越界,若越界则输出“out of boundary”。 (4)设计获取数组元素个数的公有成员函数。 (5)设计用于输出数组元素的公有成员函数,元素之间以空格分隔,最后以换行符结束。 在main函数中按以下顺序操作: (1)根据内置的静态整型数组{1,2,3,4,5}构造数组对象v1,根据输入的整型数构造数组对象v2。 (2)调用Vect的成员函数依次输出v1v2的所有元素。 (3)输入指定的下标及对应的整型数,设置数组对象v1的指定元素。 (4)根据数组对象v1拷贝构造数组对象v3。 (5)调用Vect的成员函数依次输出v1v3的所有元素。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值