「C/C++ 01」 深拷贝和浅拷贝

目录

一、概念 

1. 浅拷贝

2. 深拷贝

3. 深浅拷贝问题

4. 总结 

二、C++的类中涉及的深拷贝

1. 拷贝构造函数 中实现深拷贝

a. 自己开辟一个新空间,然后将内容拷贝到新空间

b. 借助构造函数来实现深拷贝

2. operator= 中实现深拷贝 

a. 自己开辟一个新空间,然后将内容拷贝到新空间

b. 借助构造函数来实现深拷贝


一、概念 

1. 浅拷贝

a. 是什么?

        浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象成员属性值的一份精确拷贝。如果成员是基本类型,拷贝的就是基本类型的值;如果属性是指针类型,拷贝的就是内存地址(即原来的指向) ,因此如果其中一个对象改变了指针指向的区域,就会影响到另一个对象。

        在浅拷贝中,只是复制了对象的引用或指针,而不是实际的数据。这意味着原始对象和复制的对象都指向同一块内存区域。因此,如果修改了其中一个对象,另一个对象也会受到影响。


b. 怎么做?

        在C语言中,如果使用赋值运算符(=)来完成结构体的拷贝就是浅拷贝;在C++中编译器默认生成的拷贝构造函数和赋值运算符重载函数都是使用浅拷贝。

2. 深拷贝

a. 是什么?

        深拷贝则完全复制了对象的数据,创建了一个全新的、独立的副本。修改其中一个对象不会影响另一个对象。


b. 怎么做?

        实现深拷贝的关键是对于原结构体或原对象中的每一个指针成员,都开辟一个新空间,然后再将原空间的内容拷贝到新空间中,然后让新结构体/对象中的指针成员指向这个新空间。

3. 深浅拷贝问题

        深浅拷贝问题主要是出现在对象或结构体的拷贝过程中。这个问题不仅仅出现在C/C++中,Java、Python等也有类似的问题。

        在C/C++中,结构体/对象的拷贝通常使用赋值运算符(=)来完成。如果结构体中包含指针,那么默认的拷贝操作是浅拷贝,通过上面的讲解可知如果直接用=来创建结构体/对象的拷贝,如果其一个结构体/对象对指针成员指向的内容进行修改就会影响到其他的结构体/对象的拷贝。因此在创建结构体/对象的拷贝需要使用深拷贝,需要手动实现数据的复制。

4. 总结 

       以代码仓库为例:浅拷贝就像是给原始代码仓库添加一个管理者让他也能操作这个仓库,但是,如果原始仓库被删除,那么所有人都无法访问这个仓库;深拷贝就是新建一个仓库,然后把原始仓库的内容拷贝到里面,这样把自己的仓库删掉了也不影响别人的仓库。


       所以实际的编程中,深拷贝通常需要更复杂的实现,因为它需要创建新的内存空间来存储复制的内容。而浅拷贝则相对简单,因为它只是复制了指针或引用,而不是实际的数据内容。


二、C++的类中涉及的深拷贝

        实现深拷贝的关键是自己开辟一个新空间,然后再将原空间的内容拷贝到新空间。以下以stack类为例,演示如何在类中实现深拷贝。

1. 拷贝构造函数 中实现深拷贝

a. 自己开辟一个新空间,然后将内容拷贝到新空间

b. 借助构造函数来实现深拷贝。

       在构造函数中自然要动态成员变量开辟空间,所以在拷贝构造函数中可以使用构造函数创建一个临时对象,然后交换对象和临时对象的动态成员


       但这有个小问题,就是当前对象未初始化,直接交换数值可能会导致程序崩溃,所以加上初始化列表,在交换数值前先初始化。

最后再给new的失败加一个提示或抛异常。

2. operator= 中实现深拷贝 

 a. 自己开辟一个新空间,然后将内容拷贝到新空间

       如果不重载,依靠编译器自动生成的 operator=,还是浅拷贝。(析构时仍会出现“双重删除”问题)


以下将一步一步在operator= 中实现标准的深拷贝 :

错误一:更改指针指向前一定要释放原空间,不然会造成内存泄漏。

错误二:上面样写没有考虑到自己给自己赋值的问题,所以还要加一个判断。

错误三:还没有考虑new失败的问题。(需要的内存过大可能会导致new失败)

按照上面的写法,new失败后,不但没有成功给str赋值,str原来的内容也被销毁了。

这里我们先开辟空间再释放资源,这样如果new失败了,函数会终止,原来的str也还没有被销毁。

最后再给new的失败加一个提示或抛异常。

b. 借助构造函数来实现深拷贝。

        当然也可以像拷贝构造一样,借助构造函数构造一个临时变量,再彻底交换,临时变量结束时也会调用析构函数释放原有的空间。(注,这里的swap是需要自己实现的对象交换函数)


更简洁的写法:考虑到形参会调用拷贝构造,我们直接使用形参替代上面的临时变量tmp。这个写法的不足在于:自己给自己赋值时效率没上面的高。


【源代码】 

class stack
{
public:
	int* _arr;
	// 构造函数
	stack(int* arr = nullptr)
	{
		int len = sizeof(arr) / sizeof(arr[0]);
		_arr = new int[len];

		copy(arr, arr + len, _arr);
	}
	~stack()
	{
		cout << "删除地址为:" << _arr << "的数组。" << endl;
		delete(_arr);
	}
	// 1. 自己开辟一个新空间,然后将内容拷贝到新空间。
	stack(const stack &s)
	{
		//开辟新空间
		int len = sizeof(s._arr) / sizeof(s._arr[0]);
		_arr = new int[len];

		//拷贝内容
		copy(s._arr, s._arr + len, _arr);
	}

	 2. 借助构造函数创建中间对象来实现深拷贝。
	//stack(const stack& s)
	//{
	//	//调用构造函数创建中间对象(系统会开辟好空间)
	//	stack tmp(s._arr);
	//	swap(_arr, tmp._arr);
	//}
	 但这有个小问题,就是当前对象未初始化,
	 直接交换数值可能会导致程序崩溃,所以加上初始化列表,在交换数值前先初始化。
	//stack(const stack& s)
	//	:_arr(nullptr)
	//{
	//	//调用构造函数创建中间对象
	//	stack tmp(s._arr);
	//	swap(_arr, tmp._arr);
	//}

	// 传统写法
	//stack& operator= (const stack& s)
	//{
	//	//判断是否自己给自己赋值。
	//	//自己给自己赋值时无需开辟新空间。
	//	if (this != &s)
	//	{
	//		//开辟新空间
	//		int len = sizeof(s._arr) / sizeof(s._arr[0]);
	//		_arr = new int[len];
	//		//拷贝内容
	//		copy(s._arr, s._arr + len, _arr);

	//		//更改指针指向前一定要释放原空间,不然会造成内存泄漏。
	//		delete this->_arr;
	//	}
	//	
	//	//返回当前对象支持连续赋值
	//	return *this;
	//}
	
	// 现代写法
	//stack& operator= (const stack& s)
	//{
	//	// 自己给自己赋值时无需开辟新空间。
	//	if (this != &s)
	//	{
	//		//调用构造函数创建中间对象(系统会开辟好空间)
	//		stack tmp(s._arr);
	//		swap(_arr, tmp._arr);
	//	}
	//	return *this;
	//}
	stack& operator= (stack s)
	{
		swap(_arr, s._arr);
		return *this;
	}
};

int main()
{
	int* arr = new int[5];
	arr[0] = 0;
	stack st1(arr);
	stack st2 = st1;
	
	cout << "原数组的地址 :" << arr << endl \
		 << "st1数组的地址:" << st1._arr << endl \
		 << "st2数组的地址:" << st2._arr << endl;

	st2 = st1;
	cout << "原数组的地址 :" << arr << endl \
		<< "st1数组的地址:" << st1._arr << endl \
		<< "st2数组的地址:" << st2._arr << endl;
	return 0;
}


------------------------END-------------------------

才疏学浅,谬误难免,欢迎各位批评指正。

  • 22
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

烛火萤辉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值