C++类与对象笔记五:C++中的浅拷贝与深拷贝

拷贝:简单的赋值拷贝操作。

 

拷贝:在堆区重新申请空间,进行拷贝操作。

先看一个类的声明:运行会报错。

#include<iostream>
using namespace std;

class Person
{
public:

	Person(int age, int height)
	{
		mAge = age;
		mHeight = new int(height);   // 堆区的数据由程序员手动开辟,也需要由程序员手动释放。
		cout << "有参构造函数被调用。" << endl;
	}
	~Person()
	{
		// 析构函数的用途:将堆区开辟的数据释放。
		if (mHeight != NULL)
		{
			delete mHeight;
			// 为了防止野指针的出现:
			mHeight = NULL;
		}
		cout << "析构函数被调用。" << endl;
	}
	int mAge;
	int* mHeight;  // 开辟数据要放在堆区。
};

void test01()
{
	Person p1(18, 170);
	cout << "p1的年龄为:" << p1.mAge << "身高为:" << *p1.mHeight << endl;
	Person p2(p1);   // 通过编译器提供的拷贝构造函数,会做浅拷贝。
	cout << "p2的年龄为:" << p2.mAge << "身高为:" << *p2.mHeight << endl;

}

int main()
{

	test01();

	system("pause");
	return 0;
}

堆区的数据由程序员开辟,也要由程序员手动释放。

  • 何时释放?对象销毁之前释放。比如说test01函数执行完了,就需要销毁p1和p2了。
  • 如何销毁?对象销毁时,需要调用析构函数,在析构函数中,增加对内存的释放。
  • 析构函数的用途:将堆区开辟的数据释放。


出错原因:

第33行代码:在上一节中,我们提到:通过编译器提供的拷贝构造函数,会做浅拷贝

对象p1有两个属性:

  1. 第一个属性是mAge,在栈中开辟空间,复制时,直接复制到另一个栈空间中。
  2. 第二个属性是mHeight,指针。在堆中开辟空间,栈中存储的是:在堆中的地址,在拷贝构造时,只是浅拷贝栈中存的地址给复制传过去了。所以p1和p2的第二个指针类型的属性都指向同一块堆区。

 此时,如果我们调用析构函数。析构函数会释放堆中数据mHeight。test01函数中的这个两个局部变量(栈中)都要释放。

栈的规则:先进后出。

因此会先释放p2。p2释放完成后,释放p1。问题出现了。

我们已经在销毁p2时,将堆中数据释放了。销毁了之后,p1还要去销毁释放这块堆区内存,非法操作。堆区内存重复释放

浅拷贝带来的问题就是:堆区内存重复释放。非法操作。



解决办法:浅拷贝的问题要利用深拷贝进行解决。

前面采用编译器自带的拷贝构造函数,所以可以自己写一个拷贝构造函数。然后在堆中重新申请一块内存,然后也存放170数据。

 mHeight = new int(*p.mHeight);

解引用,将值提取,然后new在堆中开辟空间,存入堆区空间,然后赋值。

#include<iostream>
using namespace std;

class Person
{
public:

	Person(int age, int height)
	{
		mAge = age;
		mHeight = new int(height);   // 堆区的数据由程序员手动开辟,也需要由程序员手动释放。
		cout << "有参构造函数被调用。" << endl;
	}
	// 自己实现一个拷贝构造函数 解决浅拷贝带来的问题。
	Person(const Person& p)
	{
		cout << "Person拷贝构造函数的调用。" << endl;
		mAge = p.mAge;
		//mHeight = p.mHeight; // 编译器默认实现的就是这行代码。  会报错。
		// 深拷贝操作:
		mHeight = new int(*p.mHeight);   // 在堆中重新开辟一块空间。来存储这个值。完成赋值。
	}

	~Person()
	{
		// 析构函数的用途:将堆区开辟的数据释放。
		if (mHeight != NULL)
		{
			delete mHeight;
			// 为了防止野指针的出现:
			mHeight = NULL;
		}
		cout << "析构函数被调用。" << endl;
	}
	int mAge;
	int* mHeight;  // 开辟数据要放在堆区。
};

void test01()
{
	Person p1(18, 170);
	cout << "p1的年龄为:" << p1.mAge << "身高为:" << *p1.mHeight << endl;
	Person p2(p1);   // 通过编译器提供的拷贝构造函数,会做浅拷贝。
	cout << "p2的年龄为:" << p2.mAge << "身高为:" << *p2.mHeight << endl;

}

int main()
{

	test01();

	system("pause");
	return 0;
}



总结:

如果有属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值