C++_复习04(拷贝构造函数的深拷贝和浅拷贝)

一、为什么会出现深拷贝和浅拷贝

     在我们没有自定义拷贝构造函数的时候,传递对象给函数或者函数返回对象都能进行是因为C++编译器会给我们自动的产生一个拷贝构造函数这就是“默认拷贝构造函数”,这个构造函数很简单,仅仅是使用“老对象”的值对“新对象”的数据成员依次进行赋值操作。

二、浅拷贝问题抛出

2.1 问题描述及代码

在不写拷贝构造函数的时候,使用C++编译器的默认拷贝构造函数运行下面代码,会出现跑断的现象。就是代码会自动断点进入析构函数那一块。

#include<iostream>
using namespace std;

class Name {
public:
    //构造函数,p指向堆中分配的一块内存
	Name(const char*_p) { 
		len = strlen(_p) + 1; //要加上结束字符
		p = (char*) malloc(len);
		strcpy_s(p, len, _p);	
	}
    //析构函数,释放动态分配的内存
	~Name(){
		if (p!=NULL) {
			free(p);
			p = NULL;
			len = 0;
		}
	}
	
private:
	char *p;
	int len;//定义指针指向的内存空间有多长
};

void objplay() {
	Name obj1("abcdefg");//调用有参数构造函数初始化
	Name obj2 = obj1; //调用拷贝构造函数,用obj1去初始化obj2
//在对象析构的时候断掉了,析构过程中先析构obj2就把内存空间析构了,这样obj1就指向了垃圾的内存空
//间。再析构obj1的时候就找不到内存了
//失败,浅拷贝。动态分配的内存消失等于出现了个野指针。内存泄漏!!	
}

int main()
{
	objplay();
	cout << "finish!" << endl;
	system("pause");
	return 0;
}

2.2 问题解析

出现这种原因是因为默认的拷贝构造函数为浅拷贝。所谓浅拷贝,指的是在对象复制时,只是对象中的数据成员进行简单的赋值。浅拷贝,只是把指针变量的值拷贝过来了,并没有重新开辟内存空间(不要忘记全局区也存放着一个常量字符串!!)

 

析构的过程中:先析构obj2就把内存空间析构了,这样obj1就指向了垃圾的内存空间。再析构obj1的时候就找不到内存了
所以动态分配的内存消失等于出现了个野指针。内存泄漏!!

两个对象的析构函数将对同一个内存空间释放两次,这就是错误出现的原因。我们需要的不是两个p有相同的值,而是两个p指向的空间有相同的值,解决办法就是使用“深拷贝”。

三、用深拷贝解决问题

3.1 深拷贝

  在深拷贝的情况下,对于对象中动态成员就不能仅仅简单地赋值了,而应该重新动态分配空间,所以上诉例子改为深拷贝如下:

#include<iostream>
using namespace std;

class Name {
public:
	Name(const char*_p) {
		len = strlen(_p) + 1; //要加上结束字符
		p = (char*) malloc(len);
		strcpy_s(p, len, _p);	
	}
	~Name(){
		if (p!=NULL) {
			free(p);
			p = NULL;
			len = 0;
		}
	}
	//解决方案,手工的编写拷贝构造函数使用深拷贝
	//显示的写一个拷贝构造函数,不用C++编译器默认的拷贝构造函数,这样就解决了浅拷贝的问题。
	//手写拷贝构造函数深拷贝
	Name(const Name& obj1) {
		int len = obj1.len;
		p = (char*)malloc(len); //重新分配内存空间!!
		strcpy_s(p, len, obj1.p);
	}

private:
	char *p;
	int len;//定义指针指向的内存空间有多长
};

void objplay() {
	Name obj1("abcdefg");//内存地址
	Name obj2 = obj1;
	Name obj3("obj3");
	//此时需要重载等号操作符
	obj3 = obj1;//等号操作:C++把对象1的属性拷贝成对象3的属性(也是浅拷贝!!)

}

int main()
{
	objplay();
	cout << "finish!" << endl;
	system("pause");
	return 0;
}

3.2 图示分析

此时析构的时候就不会出现野指针,图示蓝色区域。obj2在初始化的过程中是重新开辟内存空间,这样析构的过程中就不会出现因为析构两次而出现野指针的状态!

特别注意:

obj3 = obj1;  //等号操作:C++把对象1的属性拷贝成对象3的属性(也是浅拷贝!!) 

//此时需要重载等号操作符

导致内存泄漏 又会出现断点。需要重载等号操作符

四、总结

防止默认拷贝发生! 

技巧: 通过对对象复制的分析,我们发现对象的复制大多在进行“值传递”时发生,这里有一个小技巧可以防止按值传递——声明一个私有拷贝构造函数。甚至不必去定义这个拷贝构造函数,这样因为拷贝构造函数是私有的,如果用户试图按值传递或函数返回该类对象,将得到一个编译错误,从而可以避免按值传递或返回对象。

// 防止按值传递
class CExample 
{
private:
	int a;

public:
	//构造函数
	CExample(int b)
	{ 
		a = b;
		cout<<"creat: "<<a<<endl;
	}

private:
	//拷贝构造,只是声明
	CExample(const CExample& C);

public:
	~CExample()
	{
		cout<< "delete: "<<a<<endl;
	}

    void Show ()
	{
        cout<<a<<endl;
    }
};

//全局函数
void g_Fun(CExample C)
{
	cout<<"test"<<endl;
}

int main()
{
	CExample test(1);
	//g_Fun(test); 按值传递将出错
	
	return 0;
} 

五、参考

https://blog.csdn.net/lwbeyond/article/details/6202256

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值