浅谈C++的浅拷贝和深拷贝问题

本文详细解释了C++中的浅拷贝和深拷贝概念,指出浅拷贝可能导致的问题,如资源共享冲突,然后提供了自定义拷贝构造函数和赋值运算符以实现深拷贝。通过实例展示了如何避免浅拷贝带来的问题和正确实现对象复制。
摘要由CSDN通过智能技术生成

今天我们来谈谈C++的浅拷贝和深拷贝问题,这里先上定义,可以直接浏览下面的表格,比较直观😊😊😊 。在C++中,浅拷贝和深拷贝是两种对象复制的方式,其中🐱浅拷贝(Shallow Copy)是指将一个对象的指赋值到另一个对象中,但只赋值对象的成员变量的值,并不对复制对象的动态分配内存(如堆内存)等外部资源,这也就意味着当原对象修改自己指向的外部资源时,可能会影响到另一个对象;🐶深拷贝(Deep Copy)是指在复制对象时,不仅复制对象中的值,还复制指向动态分配内存的指针所指向的内存。这样每个对象都有自己的独立内存,它们之间不会相互干扰。自定义的复制构造函数和赋值运算符通常会进行深拷贝。

对象复制的方式定义
🐱浅拷贝(Shallow Copy)将一个对象的指赋值到另一个对象中,但只赋值对象的成员变量的值,并不对复制对象的动态分配内存(如堆内存)等外部资源
🐶深拷贝(Deep Copy)将一个对象的值复制给另一个对象,包括了对象成员变量的值以及对象的动态分配内存(如堆内存)外部资源

从上述定义中我们可以想到,要是通过浅拷贝的方式拷贝对象,当发生原对象修改自己指向的外部资源时,由于另一个对象并不会复制原对象的外部资源,而是与原对象指向同一块外部资源,如图所示:
在这里插入图片描述
因此在程序结束调用析构函数时,很可能会对指向的外部资源进行二次析构而导致系统崩溃,下面给出一段错误代码🫤:

#include <iostream>
using namespace std;

class SeqStack
{
public:
	SeqStack(int size = 5)
	{
		cout << this << " SeqStack() " << endl;
		_pstack = new int[size];
		_top = -1;
		_size = size;
	}
	~SeqStack() 
	{
		cout << this << " ~SeqStack() " << endl;
		delete[] _pstack;
		_pstack = nullptr;
	}
	
private:
	int* _pstack;
	int _top;
	int _size;
};

int main()
{
	SeqStack s;  //没有提供任何构造函数时,编译器会提供默认构造和析构函数
	SeqStack s1(5);  
	SeqStack s2 = s1; // #1  拷贝构造函数,做内存拷贝
	//SeqStack s3(s1); #2 = #1

	return 0;
}

根据上述描述,我们可以推导出,在程序准备进行析构时,会先调用 s2 的析构函数(释放*_pstack),此时*_pstack变成了空指针,再进行s1的析构,由于它们指向同一块外部资源,s1的析构会导致系统崩溃💦💦💦,结果如下:
在这里插入图片描述
因此在对象成员有开辟了外部资源的前提下,我们进行拷贝构造时需要自定义构造函数来避免出现此类问题,下面为自定义拷贝构造函数的代码:

//自定义拷贝构造函数
SeqStack(const SeqStack& src)
{
	_pstack = new int[src._size];
	for (int i = 0; i <= src._top; i++)  
		_pstack[i] = src._pstack[i];
		
	//memcpy() realloc()
	_top = src._top;
	_size = src._size;
}

加上这段代码,此时就会在复制对象时新开辟一块独立于原对象的外部资源,做了一次深拷贝,防止出现浅拷贝问题(二次析构),猜猜现在的程序是否能够正常运行呢?

🤔🤔🤔思考一下:为什么我们要自定义拷贝构造函数使用for循环而不直接使用 memcpy 或者 realloc 函数来进行拷贝呢?

同理

  • memcpy函数只是简单的将一块内存中的内容复制到另一块内存中,不会处理动态内存的分配和释放,也不会对对象中的成员进行初始化和析构, 会导致指针成员指向同一块内存。
  • realloc函数用于重新分配已经分配的动态内存,它会尝试扩大或缩小内存块的大小,同时保持原有内存块中的数据不变。虽然在需要调整动态内存大小的时候非常有用,但是它并不能正确地处理C++对象的构造和析构。

我们再来看看一个简单的赋值操作,可以在main函数中在 s2 做完深拷贝之后,加一条s2 = s1语句,如下:

int main()
{
	SeqStack s;  //没有提供任何构造函数时,编译期会提供默认构造和析构函数
	SeqStack s1(5);   // #1  拷贝构造函数,做内存拷贝
	SeqStack s2 = s1; // #2
	
	s2 = s1;  //这里!!!!
	return 0;
}

看看运行结果,令人发指 😧 😧 😧

在这里插入图片描述

这块的话其实也是浅拷贝问题的一种,因为我们没有在类中自定义赋值函数,C++编译器会调用它的默认赋值函数,做的也是数据的内存拷贝(即浅拷贝),直接把对象s2*_pstack指向了对象s1*_pstack,而原先对象s2*_pstack所指向的另一块外部资源被丢弃了,使之无法释放,如下同所示:
在这里插入图片描述
于是乎我们想到🤔,还要得重载一下赋值函数,同时释放那块即将被丢弃的外部资源:

//重载赋值函数
void operator=(const SeqStack& src) //大致跟拷贝构造函数类似
{
	cout << "operatot=" << endl;
	//if (this == &src) return; //防止自赋值 s1 = s1 而引起非法访问

	delete[]_pstack; //需要把原来指向的外部资源释放

	_pstack = new int[src._size];
	for (int i = 0; i <= src._top; i++)  _pstack[i] = src._pstack[i];
	_top = src._top;
	_size = src._size;
}

我们常说理论指导实践,在看完上面的论述后,将给出一个应用实例作为练习,需要实例代码的话可以私信我😊
在这里插入图片描述
哦对,这里

最后来做个总结吧: 概念总结

🐱 浅拷贝:

  • 只复制对象的成员变量的值,不复制动态分配的资源。
  • 原对象和副本对象共享同一块堆内存。
  • 修改其中一个对象会影响到另一个对象。

🐶 深拷贝:

  • 复制对象的成员变量的值以及动态分配的资源。
  • 原对象和副本对象拥有各自独立的资源。
  • 修改其中一个对象不会影响到另一个对象。

🌻🌻🌻以上就是有C++浅拷贝和深拷贝的有关问题,如果聪明的你浏览到这篇文章并觉得文章内容对你有帮助,请不吝动动手指,给博主一个小小的赞和收藏 🌻🌻🌻

  • 51
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
浅拷贝深拷贝是在进行对象的复制操作时常用的概念。 浅拷贝(Shallow Copy)是指创建一个新对象,将原始对象的非引用类型属性(如基本数据类型、字符串等)直接复制到新对象中,而对于引用类型属性(如对象、数组等),只复制其引用而不是复制对象本身。简单来说,浅拷贝只复制对象的表面层级。 深拷贝(Deep Copy)是指创建一个新对象,并且递归地复制原始对象的所有引用类型属性及其嵌套属性,使得新对象与原始对象完全独立,互不影响。深拷贝会复制对象的所有层级,包括嵌套的对象。 下面是一个示例代码来说明浅拷贝深拷贝的区别: ```python import copy class Person: def __init__(self, name, age): self.name = name self.age = age person1 = Person("Alice", 25) person2 = copy.copy(person1) # 浅拷贝 print(person1.name, person1.age) # 输出:"Alice 25" print(person2.name, person2.age) # 输出:"Alice 25" person2.name = "Bob" person2.age = 30 print(person1.name, person1.age) # 输出:"Alice 25",原始对象未受影响 print(person2.name, person2.age) # 输出:"Bob 30" person3 = copy.deepcopy(person1) # 深拷贝 print(person1.name, person1.age) # 输出:"Alice 25" print(person3.name, person3.age) # 输出:"Alice 25" person3.name = "Charlie" person3.age = 35 print(person1.name, person1.age) # 输出:"Alice 25",原始对象未受影响 print(person3.name, person3.age) # 输出:"Charlie 35",新对象与原始对象完全独立 ``` 在上述代码中,通过浅拷贝创建了`person2`对象,并修改其属性后,并不会影响到原始的`person1`对象。而通过深拷贝创建了`person3`对象,即使修改了`person3`的属性,也不会影响到原始的`person1`对象。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

leisure-pp

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

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

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

打赏作者

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

抵扣说明:

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

余额充值