C++系列-浅拷贝和深拷贝

文章讲述了浅拷贝和深拷贝在C++中的概念,强调了浅拷贝可能导致堆区空间重复释放的问题,以及深拷贝通过重新申请内存空间来避免这个问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

浅拷贝和深拷贝


刘禹锡《竹枝词二首·其一》
杨柳青青江水平,闻郎江上踏歌声。(一作唱歌)
东边日出西边雨,道是无晴却有晴。


如果属性有在堆区开辟内存的,一定要自己提供拷贝构造函数,进行深拷贝,以免堆区内存重复释放。

浅拷贝

  • 浅拷贝会带来的问题是堆区空间重复释放

在这里插入图片描述

以下代码分析:

  • Person类中,有两个成员变量,其中一个是指针类型。在有参构造中,此指针类型的成员变量指向的空间开辟在堆区。
  • 在test1()中,Person p1(11, 160)会实例化对象p1, 会调用到有参构造,为p1.p_height开辟堆区空间,假设其指向的地址为0x1000,如上图。
  • 在test1()中,Person p2(p1)会实例化对象p2,会调用到拷贝构造,Person类中没有自定义的构造函数,则会使用默认的构造函数,默认的拷贝构造进行简单的值拷贝,如上图,p2.p_height指向的堆区空间的地址0x1000,也就是说,p1.p_height和p2.p_height指向的是同一块内存。
  • 在程序释放会先调用到p2的析构(栈区先进后出),delete p_height会释放p2.p_height指向的堆区空间0x1000,之后会调用到p1的析构,p1.p_height指向0x1000, 当delete p1.p_height时,0x1000的地址之前已经被p2的析构释放掉了,所以就会报错。
  • 错误的原因是堆区只开辟了一个空间,但却遭遇了重复释放。
因为是浅拷贝,在调用第二个对象的析构函数时,会报错
code:
	#include<iostream>
	using namespace std;
	#include"circle.h"
	class Person
	{
	public:
		int age;
		int* p_height;
		Person(int ref_age, int ref_height)
		{
			age = ref_age;
			p_height = new int(ref_height);
			cout << "堆区开辟的地址为: "  << p_height << endl;
			cout << "有参构造函数被调用," << "年龄是:" << age << ",身高是:" << *p_height << endl;
		}
		~Person()
		{
			cout << p_height << endl;
			if(p_height != NULL)
			{
				delete p_height;
				p_height = NULL;
			}
			cout << "析构函数被调用" << endl;
		}
	};
	void test1()
	{
		Person p1(11, 160);
		Person p2(p1);
	}
	void main()
	{
		test1();
		system("pause");
	}

result:
	堆区开辟的地址为: 0000020FB4F36310
	有参构造函数被调用,年龄是:11,身高是:160
	0000020FB4F36310
	析构函数被调用
	0000020FB4F36310
	报错

为了解决浅拷贝带来的堆区内存重复释放的问题,就可以已使用深拷贝。


深拷贝

  • 自己写拷贝构造函数。
  • 在拷贝构造中重新在堆区申请内存空间
    在这里插入图片描述
    以下代码分析:
  • Person类中,有两个成员变量,其中一个是指针类型。
  • 在有参构造中,此指针类型的成员变量指向的空间开辟在堆区。
  • 在test1()中,Person p1(11, 160)会实例化对象p1, 会调用到有参构造,为p1.p_height开辟堆区空间,假设其地址为0x1000,如上图。
  • 在test1()中,Person p2(p1)会实例化对象p2,会调用到拷贝构造。
  • 在拷贝构造中,p_height = new int(ref_p.p_height) 在堆区开辟内存,假设其内存地址为0x2000,该内存中的内容为ref_p.p_height,(ref_p.p_height是个指针,*ref_p.p_height是160,其为p1.p_height指向的内容),如上图。也就是说,p1.p_height和p2.p_height指向的是不同的内存,但该内存中存放的内容是一样的。
  • 在程序释放会先调用到p2的析构(栈区先进后出),delete p_height会释放p2.p_height指向的堆区空间0x2000,之后会调用到p1的析构,p1.p_height指向0x1000,不会有重复释放的问题。
code:
	#include<iostream>
	using namespace std;
	#include"circle.h"
	class Person
	{
	public:
		int age;
		int* p_height;
		Person(int ref_age, int ref_height)
		{
			age = ref_age;
			p_height = new int(ref_height);
			cout << "堆区开辟的地址为: "  << p_height << endl;
			cout << "有参构造函数被调用," << "年龄是:" << age << ",身高是:" << *p_height << endl;
		}
	
		Person(const Person& ref_p)
		{
			age = ref_p.age;
			p_height = new int(*ref_p.p_height);		// 堆区重新申请空间
			//p_height = ref_p.p_height;	//编译器默认实现的是这行代码
			cout << "拷贝构造函数被调用, " << "堆区开辟的地址为:" << p_height << endl;
		}
	
		~Person()
		{
			cout << p_height << endl;
			if(p_height != NULL)
			{
				delete p_height;
				p_height = NULL;
			}
			cout << "析构函数被调用" << endl;
		}
	};
	
	void test1()
	{
		Person p1(11, 160);
		Person p2(p1);
	}
	
	void main()
	{
		test1();
		system("pause");
	}
result:
	堆区开辟的地址为: 0000021DED9361D0
	有参构造函数被调用,年龄是:11,身高是:160
	拷贝构造函数被调用, 堆区开辟的地址为:0000021DED936990
	0000021DED936990
	析构函数被调用
	0000021DED9361D0
	析构函数被调用

总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值