C++::实现String类的深拷贝形式 - 高小调博客

C++::实现String类的深拷贝形式

上一篇文章分享了String类的浅拷贝形式,也分析了一下String浅拷贝会崩溃的问题。

本文来对String类的浅拷贝形式做一些改进,让它能够正常运行.

String类的深拷贝版

上文说道:系统默认合成的拷贝构造函数,只是简单的把指针的值赋了过去.

最终析构函数对同一块内存delete了两次,造成程序崩溃!

既然,系统默认合成的拷贝构造函数会出问题,那我给它重载一下,不就好了?

(注释比较详细,就懒得废话再解释了!)

/*
*本代码版权归高小调博客所有 
*作者:最近头比较大,不知道怎么有效学习C++的高小调
*日期:2016-10-20
*代码功能:String类的深拷贝形式  
*/
#include<iostream>
class String{
public:
	//构造函数,默认初始化为空串
	String(const char *pStr = ""){
		//防止String s(NULL);
		if(NULL != pStr){
			//pStr不为空时,开辟一段内存,并将源字符串拷贝过去
			_pStr = new char[strlen(pStr)+1];
			strcpy(_pStr,pStr);
		}else{
			//pStr为空时,开辟两个字节,创建空串
			//(多申请一个字节,方便析构的时候直接调用delete[]释放内存)
			_pStr = new char[1];
			*_pStr = '\0';
		}
	}
	//拷贝构造函数,初始化列表内开辟新空间
	String(String &s)
		:_pStr(new char[strlen(s._pStr)+1]){
		//将源字符串拷贝过去.
		strcpy(_pStr,s._pStr);
	}
	//赋值运算符重载
	String &operator=(const String &s){
		//判断是否给自己赋值
		if(this!=&s){
			//先开辟一段内存,并将源字符串拷贝过去
			char * pTemp = new char[strlen(s._pStr)+1];
			strcpy(pTemp,s._pStr);
			//释放原有内存,并指向新内存
			delete [] _pStr;
			_pStr = pTemp;
		}
		return *this;
	}
	~String(){
		delete[] _pStr;	
		_pStr = NULL;
	}
private:
	char *_pStr;
};
int main(){
	String s1("Hello");
	String s2(s1);
	String s3;
	s3 = s2;
	return 0;
}

这样的程序运行起来,肯定是没有任何问题的!

但作为一个资深懒人的我发现:这段代码还可以进行简化!

简化版本如下:

String类的深拷贝简化版

String深拷贝的简化版所能简化的的函数有两个:拷贝构造函数与赋值运算符重载.

拷贝构造函数简化的操作:

1.在初始化列表里,将pStr初始化为NULL.

2.以被拷贝对象的pStr指针为参数,构造一个临时对象.

3.直接将this对象的pStr指针(指向NULL)与临时对象的pStr进行交换.

那么问题来了:为什么在初始化列表里必须得将this对象的pStr指针初始化为NULL呢?

(答案详见后文)

赋值运算符重载简化操作:

直接将参数s的pStr与this对象的pStr进行了交换.

这样又为什么可以呢?(详见后文.)

以下是代码部分:

/*
*本代码版权归高小调博客所有 
*作者:最近头比较大,不知道怎么有效学习C++的高小调
*日期:2016-10-20
*代码功能:String类的深拷贝的懒人形式  
*/
#include<iostream>
class String{
public:
	//构造函数,默认初始化为空串
	String(const char *pStr = ""){
		//防止String s(NULL);
		if(NULL != pStr){
			//pStr不为空时,开辟一段内存,并将源字符串拷贝过去
			_pStr = new char[strlen(pStr)+1];
			strcpy(_pStr,pStr);
		}else{
			//pStr为空时,开辟两个字节,创建空串
			//(多申请一个字节,方便析构的时候直接调用delete[]释放内存)
			_pStr = new char[1];
			*_pStr = '\0';
		}
	}
	//拷贝构造函数
	String(String &s)
		:_pStr(NULL){
		//创建一个临时对象
		String pTemp(s._pStr);
		//交换临时对象与this对象的pStr指针
		std::swap(_pStr,pTemp._pStr);
	}
	//赋值运算符重载
	String &operator=(String s){
		//交换临时对象与this对象的pStr指针
		std::swap(_pStr,s._pStr);
		return *this;
	}
	~String(){
		delete[] _pStr;	
		_pStr = NULL;
	}
private:
	char *_pStr;
};
int main(){
	String s1("Hello");
	String s2(s1);
	String s3;
	s3 = s2;
	return 0;
}

在拷贝构造函数中,this对象的pStr与临时对象的pStr交换后,拷贝构造函数调用完毕.

当临时对象即将被销毁时,会调用自己的析构函数,而析构函数内对pStr指针进行了delete[]操作.

如果this对象的pStr指针不初始化为NULL,那么pStr是一个随机值.

与临时对象交换完成后,临时对象调用析构函数释放pStr时,而pStr指向一块非法内存,因此释放pStr时程序发生崩溃!

在赋值运算符重载中,函数参数以值的形式传参,调用了拷贝函数,构造出临时对象.

而在函数体内将this对象的pStr指针只是与临时对象的pStr指针进行了交换.

赋值运算符重载函数调用结束后,最终临时对象被销毁,调用析构函数,释放之前this对象的pStr指针.(有种"借刀杀人"的感觉)

这样看上去好像解决了问题,那有没有再能优化的地方呢?详见下篇文章

ps:整理完String类相关东西后,把new与delete操作符底层实现也整理一下....

上一篇:
下一篇: