简单实现string类以及深浅拷贝问题

对象之间可以进行复制操作,包括采用拷贝构造函数的方式用一个对象去构造另一个对象(用一个对象的值初始化一个新的构造的对象),如同指针的复制一样,对象复制也分为浅复制和深复制

对象浅拷贝:

两个对象之间进行复制时,若复制完成后,他们还共同使用着某些资源(内存空间),其中一个对象的销毁会影响另一个对象(动态顺序表)

如果没有显式提供拷贝构造函数与赋值运算符重载,编译器会生成一个默认的拷贝构造函数和运算符重载(默认为位的拷贝,将一个对象中的内容原封不动的拷贝到到另一个对象中。如果类中涉及到资源管理,则会使得多个对象在底层共用同一块资源,在销毁对象时,就会导致一份资源释放多次引起程序崩溃)

如果一个类中涉及到资源,该类必须显式提供拷贝构造含糊,赋值运算符重载函数,析构函数

//类似系统生成的默认拷贝构造函数的方式

​ //值的拷贝方式-----内存的拷贝

​ //后果:多个对象共用同一份资源,在销毁时同一份资源被释放多次而引起程序的崩溃

String(const String& s)

		:str(s.str)   //当前对象的指针和s里的字符串共用同一段空间

	{}

浅拷贝

对象深拷贝:

当两个对象之间进行复制时,若复制完成后,它们不会共享任何资源(内存空间),其中一个对象的销毁不会影响另一个对象

深拷贝

String(const String& s)

		:str(new char[strlen(s.str) + 1])   //先分配一段空间

	{

		strcpy(str,s.str);

	}

此时查看监视,发现s1与s2地址空间并不一样,不会产生内存泄露问题,也可以正常析构销毁

浅拷贝问题的String类

class String
{
public:
	String(const char* str = "")    //创建空的字符串
	{
		//assert(str);         //断言检测是否为空
		if(nullptr == str)
			str = "";           //如果为空那么就当作空字符串
		
		_str = new char[strlen(str) + 1];
		strcpy(_str,str);
		/*
			if(nullptr == str)
			{
				//_str = new char   //分配一个字节的空间,但在下边析构时需要与delete匹配起来使用,为了方便,将其设为以下形式
				_str = new[1] char;
				*_str = "\0"
			}
			else
			{
				_str = new char[strlen(str) + 1];
				strcpy(_str,str);
			}
		*/
	} 

	//类似系统生成的默认拷贝构造函数的方式
	//值的拷贝方式-----内存的拷贝
	//后果:多个对象共用同一份资源,在销毁时同一份资源被释放多次而引起程序的崩溃
	String(const String& s)
		:_str(s._str)   //当前对象的指针和s里的字符串共用同一段空间
	{}

	//类似系统生成的默认的赋值运算符重载的方式
	//问题:1.内存泄露
	//		2.与拷贝构造函数类似
	String& operator=(const String& s)   
	{ 
		if(this != &s)
		{
			_str = s._str;
			return *this;
		}
	}

	~String()
	{
		if(_str)  //判断是否有空间
		{
			delete[] _str;
			_str = nullptr;
		}
	}

private:
	char* _str;
};

void TestString()
{
	String s1("hello");
	String s2(s1);    //用s1拷贝构造s2,因为没有自己给出拷贝构造函数,系统会默认使用类生成的拷贝构造函数进行值的拷贝(浅拷贝),销毁期间会对一段资源销毁两次产生程序而崩溃
	String s2 = s1;   //此时会看到s2本身有一个地址空间,但是在赋值时完全将s1中的东西拷贝,使得s2本来的空间找不到了,产生内存泄漏
}

使用深拷贝进行处理

传统方式
class String
{
public:
	String(const char* str = "")    //创建空的字符串
	{
		//assert(str);         //断言检测是否为空
		if(nullptr == str)
			str = "";           //如果为空那么就当作空字符串
		
		_str = new char[strlen(str) + 1];
		strcpy(_str,str);
		/*
			if(nullptr == str)
			{
				//_str = new char   //分配一个字节的空间,但在下边析构时需要与delete匹配起来使用,为了方便,将其设为以下形式
				_str = new[1] char;
				*_str = "\0"
			}
			else
			{
				_str = new char[strlen(str) + 1];
				strcpy(_str,str);
			}
		*/
	} 

	String(const String& s)
		:_str(new char[strlen(s._str) + 1])   //先分配一段空间
	{
		strcpy(_str,s._str);
	}

	String& operator=(const String& s)
	{
		if(this != &s)
		{
			/*
			释放旧空间,开辟新空间,再进行字符串拷贝
			delete[] _str;			//因为先释放了原来空间,如果开辟新空间失败了,那么会造成影响
			_str = new char[strlen(s._str) + 1];
			strcpy(_str,s._str);
			*/ 
			char* pStr = new char[strlen(s._str) + 1];
			strcpy(_str,s._str);
			delete[] _str;     //释放掉旧空间
			_str = pStr;
		}
		return *this;
	}

	~String()
	{
		if(_str)  //判断是否有空间
		{
			delete[] _str;
			_str = nullptr;
		}
	}

private:
	char* _str;
};


void Test()
{
	String s1("hello");
	String s2(s1);
}

int main()
{
	Test();
	return 0;
}
现代写法
//代码较简洁
class String
{
public:
	String(const char* str = "")    //创建空的字符串
	{
		if(nullptr == str)
			str = "";           //如果为空那么就当作空字符串
		
		_str = new char[strlen(str) + 1];
		strcpy(_str,str);
	} 

	String(const String& s)//注意!本编译器下此时_str没有进行初始化,放的是一个随机值,所以在释放strTemp时出错,所以需要给一个初始值
		:_str(nullptr)
	{
		String strTemp(s._str);
		swap(_str,strTemp._str);  
	}

	/*
	String& operator=(const String& s)
	{
		if(this != &s)
		{
			String strTemp(s);
			swap(_str,strTemp._str);        
		}
		return *this;   //当前对象用的是临时对象的空间,出了作用域销毁临时对象,实际是将当前对象的地址空间释放了
	}
	*/

	String& operator=(String s)
	{
		swap(_str,s._str);
		return *this;
	}

	~String()
	{
		if(_str)  //判断是否有空间
		{
			delete[] _str;
			_str = nullptr;
		}
	}

private:
	char* _str;
};

void Test()
{
	String s1("hello");
	String s2(s1);
	String s3;
	s3 = s2;   //此时实际是临时对象给s3赋值的
}

int main()
{
	Test();
	return 0;
}

写时拷贝

1.在对象中定义一个成员变量来计数

class String
{
public:
	String(const char* str = "")
	{
		if(nullptr == str)
			str = "";

		_str = new char[strlen(str) + 1];
		strcpy(_str,str);
		_count = 1;
	}

	String(String& s)
		:_str(s._str)
		,_count(++s._count)
	{}

	~String()
	{
		if(0 == --_count && _str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}

private:
	char* _str;
	int _count;  //每个对象中均有一份,一个对象修改了其他对象不知道
};

2.使用static修饰成员变量,但是所有对象共享的,而资源有可能会有多分,每调用一次构造就将_count置为1了,不能针对多份资源

//static也不可以,是类中所有对象共享的
class String
{
public:
	String(const char* str = "")
	{
		if(nullptr == str)
			str = "";

		_str = new char[strlen(str) + 1];
		strcpy(_str,str);
		_count = 1;
	}

	String(String& s)
		:_str(s._str)
	{
		++_count;
	}

	~String()
	{
		if(0 == --_count && _str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}

private:
	char* _str;
	static int _count;         //所有对象共享的,但资源有可能会有多分,每调用一次构造就将_count置为1了,不能针对多份资源,如 String s3;
};

int String::_count = 0;

void Test()
{
	String s1("hello");
	String s2(s1);
	String s3; //此时会出现问题,到这里时_count重新被置为1,导致只能将s3释放而无法释放s1和s2
}

int main()
{
	Test();
	return 0;
}

3.写时拷贝(COW copy on write):浅拷贝+引用计数+在向对象写内容时,是否需要给当前对象独立空间


class String
{
public:
	String(const char* str = "")
		:_pCount(new int(1))
	{
		if(nullptr == str)
			str = "";

		_str = new char[strlen(str) + 1];
		strcpy(_str,str);
	}

	String(String& s)
		:_str(s._str)
		,_pCount(s._pCount)
	{
		++*(_pCount);
	}

	String& operator=(const String& s)
	{
		if(this != &s)
		{
			if(0 == --(*_pCount) && _str) //检测拷贝以后自己的资源需不需要释放
			{
				delete[] _str;
				_str = nullptr;

				delete _pCount;
				_pCount = nullptr;
			}
			
			//与被拷贝的资源共享资源
			_str = s._str;
			_pCount = s._pCount;

			//新资源计数+1
			++(*_pCount);
		}
		return *this;
	}

	char& operator[](size_t index)   //返回引用是因为有可能返回后作为左值
	{
		if(*_pCount > 1)
		{
			String str(_str);
			this->Swap(str);
		}
		return _str[index];
	}

	const char& operator[](size_t index)const
	{
		return _str[index];
	}

	~String()
	{
		if(0 == --(*_pCount) && _str)
		{
			delete[] _str;
			_str = nullptr;

			delete _pCount;
			_pCount = nullptr;
		}
	}

	void Swap(String& s)
	{
		swap(_str,s._str);
		swap(_pCount,s._pCount);
	}
private:
	char* _str;
	int* _pCount;         
};

void Test()
{
	String s1("hello");
	
	String s2(s1);

	String s3; 
	//s3 = s1;
	s1 = s3;
	s1[0] = 'H';    //此时一改变会全改变,s1,s2,s3共用一份资源
}

int main()
{
	Test();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值