深浅拷贝问题

问题描述:C++中的深浅拷贝可谓炙手可热的经典题型之一,是许多公司面试中喜欢提及的问题,对于一般的对象例如:int a=10; int b=20;直接赋值和复制没有什么问题,但是当对象上升为类对象时,其类的内部可能存在各种类型的成员变量,在拷贝过程中就存在了深浅拷贝这一问题。


#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstring>
using namespace std;


class String
{
public:
	String(const char* psc=NULL)
		:m_psc(strcpy(new char[strlen(psc?psc:"")+1],psc?psc:""))     //(psc?psc:"")利用三目运算符的性质解决了空串和非空串的情况
	{
		cout << "String 构造" << endl;
	}
	~String()
	{
		if (m_psc)
		{
			m_psc = NULL;
		}
		cout << "String 析构" << endl;
	}
	char* Back_str()
	{
		return m_psc;
	}


private:
	char* m_psc;
};


int main()
{
	String s1("hello");     //构造s1对象并初始化
	String s2(s1);          //利用s1拷贝构造出s2,使得s2的内容与s1一致
	cout << "S1 " << s1.Back_str() << endl;
	cout << "S2 " << s2.Back_str() << endl;


	return 0;
}


       此代码可谓是漏洞百出,也是初学者最易写出的代码,因为没有显示定义拷贝构造函数,所以在用s1拷贝构造s2对象时系统自动调用默认的构造函数,所以这也就引出了浅拷贝的问题。什么是浅拷贝?浅拷贝就是指简单的拷贝赋值,因为调用的是系统的拷贝构造函数,所以只是将s1对象的指针m_psc直接拷贝了过去,而原该指针所指向的内容字符串“hello”并未拷贝,这就使得s2对象中的指针并未分配内存空间存储拷贝的字符串,使得这两个指针的指向为同一块内存空间(字符串“hello”的存储空间)。



该拷贝方式会引发两个严重的问题:

①在析构s2对象时,将该指针所指向的空间释放返还给操作系统;这样使得s1成为野指针,指向了一块此时已经不属于自己的非法空间,在析构s1对象时,必然会引起程序的崩溃。


②这两个指针指向的是同一块空间,若当s1的指针修改其字符串的值时,s2也会跟着修改,它没有拷贝字符串,所以该串两者共用, 一改俱改。


由此来看,为解决浅拷贝的问题,便有了深拷贝的拷贝方式。我们发现,只要指针拷贝的同时,为其正确分配内存空间以存储拷贝来的字符串即可,这样两指针分别都指向自己的空间,不会引发以上浅拷贝带来的问题。所以就须得我们自己定义拷贝构造函数,而不借助系统默认自动调用的拷贝构造函数。


String(const String& obj) : m_psc(strcpy((new char[strlen(obj.m_psc) + 1]), obj.m_psc)){
	cout << "String拷贝构造" << endl;
}


★当函数存在对象型的参数或对象型的返回值时都会用到拷贝构造函数。

一般在类中还经常有自主定义的运算符的重载函数,以方便对象间的赋值,例如:String s2; s3=s2;这时系统会自动调用默认的运算符重载函数(opertaor+重载的操作符),此例中调用的原型为void operator=(const String& obj){};

所以我们可以自主定义赋值运算符重载函数:

void String(const String&obj)
{
	m_psc = strcpy(new char[strlen(obj.m_psc) + 1], obj.m_psc);
}


这样定义的话有两方面的问题:

①函数没有返回值,所以不支持链式访问,也就是说对于连等的对象赋值则会出错,例如:s3=s2=s1;所以解决这个问题的办法就是利用引用的性质,使得函数返回为对象的引用,此时的返回值变成一个左值进而可接受其他的值。

②这样的代码打眼一看漏洞不少。其一没有考虑自己给自己赋值的情况,其二在赋值完成后不释放以前的存储字符串的内存空间,使得m_psc指向新开辟的这片空间,造成了内存泄漏,其三即便开辟了空间,然后释放了原来的空间,一旦空间开辟失败,又释放了原来的空间,就等于是把m_psc指针“卖了”,既不给你空间,原来的你也找不回来了。


所以综合以上的种种问题,我们不得不将代码进行优化,以尽可能多的考虑各种可能的情况。

	String& operator=(const String&obj)<span style="white-space:pre">		</span>//加引用使得返回值为可接受参数的左值<span style="white-space:pre">	</span>
	{
		if (&obj != this)<span style="white-space:pre">			</span>//排除了自己给自己赋值的情形
		{
			char* tmp = strcpy(new char[strlen(obj.m_psc) + 1], obj.m_psc);<span style="white-space:pre">	</span>//先将开辟的空间由指针tmp指向,若空间分配失败,则会抛出一个异常
			delete[] m_psc;<span style="white-space:pre">			</span>//释放掉原来的指针所指向的空间
			m_psc = tmp;<span style="white-space:pre">			</span>//再将新开辟并由tmp指向的空间赋值给m_psc
		}
		return *this;
	}


其实还有一种更好的方法:

String& operator=(const String& obj)
{
    if (&obj != this) 
	{       
	 String str(obj);
         char *tmp = m_psc;
	 m_psc = str.m_psc;
<span style="white-space:pre">	</span> str.m_psc = tmp;
	}
	return *this;
}

这种方法巧妙了利用了局部变量的性质,在函数内部利用拷贝构造函数构造了一个str临时对象(出了其作用域会自动调用析构函数),走了一个类似于swap函数的过程,str对象保存了原来的m_psc指向的内容,而m_psc此时保存的由str拷贝构造来的新m_psc指向的值,而后出了作用域str对象被析构,只留下了obj对象的m_psc。

这种方法可谓6的飞起,其实在掌握了基础的写法后再学习这种高大上的写法,会让一个人的编写代码能力更上一个层次的!!!





  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值