C++------浅拷贝、深拷贝和写时拷贝

5 篇文章 0 订阅

##浅拷贝

先来看一个例子:

class String 
{
public:    
	String(const char * pData = "") 
		:_pData(new char[strlen(pData) + 1])    
	{
			strcpy(_pData, pData);
	}    
	~String()    
	{
		if (NULL != _pData)         
		{
			delete[] _pData;             
			m_pData = NULL;
		}
	} 
private:    
	char *_pData;
}; 
int main() 
{
	String s1;    
	String s2("hello");
	String s3(s2);    
	String s4;    
	s4 = s3;
}

运行程序,我们会发现程序崩溃了,为什么会出现这种情况呢?就是因为浅拷贝。

浅拷贝,当类里面有指针对象时,拷贝构造和赋值运算符重载都只进行值拷贝(浅拷贝),两个对象指向同一块内存,对象销毁时该空间被释放了两次,因此程序崩溃
来看张图理解一下:
这里写图片描述

在我们利用s2构造s3的时候,相当于调用了图中的拷贝构造函数。而这个拷贝构造的函数实际上只是将s3的地址指向了s2。

这样就有了很大的缺陷

  • 一旦对s3进行操作,s2的内容也会变化
  • 析构时先析构s3,再析构s2,但是由于s2,s3指向同一片空间,会发生一片空间的二次析构的情况导致出错

解决这种问题的方法就是深拷贝


##深拷贝

深拷贝:重新开辟一块和源空间大小相同的空间,再将源空间的内容拷贝下来,保证了不同的对象指向不同的地址空间
如图:
这里写图片描述
来直接看一下深拷贝的实现:这里实现了传统写法和简洁写法

class String
{
public:

	String(const char* pStr = "")
		:_pStr(new char[strlen(pStr)+1])
	{
		strcpy(_pStr, pStr);
	}
	//深拷贝普通版
	String(const String& s)
		:_pStr(new char[strlen(s._pStr) + 1])
	{
		strcpy(_pStr, s._pStr);
	}
	String& operator=(const String& s)
	{
		if (this != &s)
		{
			char* temp = new char[strlen(s._pStr) + 1];
			strcpy(temp, s._pStr);
			delete[] _pStr;
			_pStr = temp;
		}
		return *this;
	}
	//深拷贝简洁版,利用构造函数
	//String(const String& s)
	//{
	//	String strTemp(s._pStr);
	//	swap(_pStr, strTemp._pStr);
	//}
	//赋值运算符重载
	//String& operator=(const String& s)
	//{
	//	if (this != &s)
	//	{
	//		String strTemp(s);
	//		swap(_pStr, strTemp._pStr);
	//	}
	//	return *this;
	//}
	~String()
	{
		if (_pStr)
		{
			delete[] _pStr;
			_pStr = NULL;
		}
	}
	/******************Access******************/
	size_t Size()const
	{
		return sizeof(_pStr);
	}
	size_t Lengh()const
	{
		return strlen(_pStr);
	}
	char& operator[](size_t index)
	{
		return _pStr[index];
	}
	const char& operator[](size_t index) const
	{
		return _pStr[index];
	}
	bool operator>(const String& s)
	{
		if (strcmp(_pStr, s._pStr) > 0)
			return true;
		if (strcmp(_pStr, s._pStr) < 0)
			return false;
	}
	bool operator<(const String& s)
	{
		return !(_pStr>s._pStr);
	}
	bool operator==(const String& s)
	{
		if (strcmp(_pStr, s._pStr) == 0)
			return true;
		return false;
	}
	bool operator!=(const String& s)
	{
		return !(_pStr, s._pStr);
	}
	void Copy(const String& s)
	{
		strcpy(_pStr, s._pStr);
	}
	String& operator+=(const String& s)
	{
		char* dest;
		dest = new char[strlen(s._pStr) + Lengh() + 1];
		memcpy(dest, _pStr, Lengh());
		memcpy(dest + Lengh(), s._pStr, strlen(s._pStr) + 1);
		delete[] _pStr;
		_pStr = dest;
		return *this;
	}
private:
	char* _pStr;
};

##写时拷贝
写时拷贝 其实还是一种浅拷贝,但是它改进了浅拷贝的缺陷。
写时拷贝引入一个计数器,每片不同内容的空间上都再由一个计数器组成,在构造第一个类时,计数器初始化为1,之后每次有新的类也指向同一片空间时,计数器自增;在析构时判断该片空间对应计数器是否为1,为1则执行清理工作,大于1则计数器自减。如果有需要进行增删等操作时,再拷贝空间完成,有利于提高效率

来看看写时拷贝的几种写法:
####写法一:

class String
{
public:
	String(const char* pStr = "")
		:_pCount(new int(1))
	{
		if (pStr == NULL)
		{
			_pStr = new char[1];
			*_pStr = '\0';
		}
		else
		{
			_pStr = new char[strlen(pStr) + 1];
			strcpy(_pStr, pStr);
		}
	}
	String(const String& s)
		:_pStr(s._pStr)
		, _pCount(s._pCount)
	{
		++(*_pCount);
	}
	String& operator=(const String& s)
	{
		if (this != &s)
		{
			if (--(*_pCount) == 0)
			{
				delete[] _pStr;
				delete _pCount;
			}
			_pStr = s._pStr;
			_pCount = s._pCount;
			++(*_pCount);
		}
		return *this;
	}
	~String()
	{
		if (_pStr&&--(*_pCount) == 0)//判断pCount状态,在进行析构
		{
			delete[] _pStr;
			_pStr = NULL;
			delete _pCount;
			_pCount = NULL;
		}
	}
private:
	char* _pStr;
	int* _pCount;//增加一个成员变量代表计数器
};

图解:
这里写图片描述
我们可以发现,这种方式确实解决了析构多次的问题,但同时也引入了新的问题,每当我们创建一个新的类对象,就会多开辟4个字节,导致空间出现许多内存碎片。

####写法二:

class String
{
public:
	String(const char* pStr = "")
	{
		if (pStr == NULL)
		{
			_pStr = "";
		}
		_pStr = new char[strlen(pStr) + 1 + 4];
		_pStr += 4;
		strcpy(_pStr, pStr);
		GetReference() = 1;
	}
	String(const String& s)
		:_pStr(s._pStr)
	{
		++GetReference();
	}
	String& operator=(const String& s)
	{
		if (this != &s)
		{
			Release();
			_pStr = s._pStr;
			++GetReference();
		}
		return *this;
	}
	~String()
	{
		Release();
	}
private:
	int& GetReference()
	{
		return *(int *)(_pStr - 4);
	}
	void Release()
	{
		if (_pStr&&--GetReference() == 0)
		{
			_pStr -= 4;
			delete[] _pStr;
			_pStr = NULL;
		}
	}
private:
	char* _pStr;
};

这种方式我们没有增加类成员变量,而是在_pStr的首部多开辟四个字节,保存计数信息,如图:
这里写图片描述
注:因为我们将计数器存放在_pStr-4的地址上,析构的时候一定要全部析构,避免内存泄漏

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值