【C++】String中的深浅拷贝问题;深拷贝的两种写法(传统写法、现代写法)

一、浅拷贝问题

浅拷贝

首先定义一个Sting类,它包含一个成员变量,一个char*的指针。 

namespace CPP
{
	class String
	{
        public:
	private:
		char* _str;
	};
}

对于String类的拷贝构造函数及operator=函数来说,当用一个String对象拷贝构造或赋值给另一个String对象时,就是将这个对象里的指针的值赋值给另一个对象里的指针。将一个指针值赋值给另一个指针,就会使得两个指针指向同一块空间,这就产生了浅拷贝(值拷贝)。 

namespace CPP
{
	class String
	{
	public:
		//拷贝构造---浅拷贝
		String(const String& s)
			:_str(s._str)
		{}
		//赋值
		String& operator=(const String& s)
		{
			if (this != &s)
			{
				_str = s._str;
			}
			return *this;
		}
		//析构函数
		~String()
		{
			if (_str)
			{
				delete[] _str;
			}
		}
	private:
		char* _str;
	};
	void TestString1()
	{
		String s1("hello");
		String s2(s1);//未写拷贝构造函数时,使用默认的,为浅拷贝(值拷贝)
		cout << s2.c_str() << endl;

		String s3("world");
		s2 = s3;
		cout << s2.c_str() << endl;
	}
}

这时其实程序已经崩溃了。 问题在于以下两个方面:

① 两个(或两个以上)指针指向同一块空间,这个内存就会被释放多次;(例如下面定义了一个String对象s1,以浅拷贝的方式拷贝构造了一个String对象s2,则s1和s2里面的指针_str就会指向同一块空间;当出了作用域,s2先调用析构函数,而上面代码中析构函数里面进行了空间的释放,也就是这个空间就会被s2释放,接下来会调用s1的析构函数,也会去释放这块空间,释放一块已经不属于我的空间就会出错) 

② 另一方面,当两个指针指向同一块空间时,一旦一个指针修改了这块空间的值,另一个指针指向的空间的值也会被修改。 

二、深拷贝及其两种写法(传统、现代)

1.传统写法

若用一个s2对象拷贝构造给s3对象,s3(s2)以及s4赋值给s3,s3=s4,当涉及到浅拷贝的问题时:

  • 对于拷贝构造函数来说,s3先开一块和s2一样大的空间;然后把s2的内容原封不动的拷贝下来,再让s3的_str指向这段新开的空间。出了作用域,各自释放各自的。
  • 而对于赋值运算符重载函数来说s3已经存在,则必须先释放s3的空间然后才让s3开与s4一样大的空间(否则就会导致s3里面的指针没有释放),然后再把s4拷贝过去。

 

 

优点:直观。

//深拷贝--传统写法
		//s3(s2) s3是this指针,s2是s
		String(const String& s)
		{
			_str = new char[strlen(s._str) + 1];//_str是this指针,新开一个s2大小的空间
			strcpy(_str, s._str);//把s2中的各个字节拷贝给this(s3)
		}

		//赋值 s3=s4
		String& operator=(const String& s)
		{
			if (this != &s)//判断不要自己给自己赋值
			{
				delete[] _str;//因为不知道空间大小,为避免内存泄漏,先释放掉s3原来的空间
				_str = new char[strlen(s._str) + 1];
				strcpy(_str, s._str);
			}
			return *this;
		}
void TestString1()
	{
                String s2("hello");
		String s3(s2);//未写拷贝构造函数,使用默认的,即为浅拷贝(值拷贝)
		cout << s3.c_str() << endl;

		String s4("world");
		s3 = s4;
		cout << s3.c_str() << endl;
	}

 

2.现代写法(推荐)

调用其他的函数来实现自己的功能

  • 本质:让别人去开空间,去拷数据,而我将你的空间与我交换就可以。 
  • 实现:例如用s2拷贝构造一个s3对象s3(s2),可以通过构造函数将s2里的指针_str构造一个临时对象tmp(构造函数不仅会开空间还会将数据拷贝至tmp),此时tmp就是我们想要的哪个对象,然后将新tmp的指针_ptr与自己的指针进行交换。
  • 至于赋值,就可以直接调用swap()交换~ 

对于构造函数来说,因为String有一个带参数的构造函数,则用现代写法写拷贝构造时可以调用构造函数,而对于没有无参的构造函数的类只能采用传统写法(开空间然后拷数据)。

//深拷贝--现代写法
		//s3(s2) tmp开辟一个和s2相同的空间,拷过来,s2是s
		String(const String& s)
			:_str(nullptr)
		{
			String tmp(s._str);//调构造
			swap(_str, tmp._str);
		}

		////赋值 s3=s2
		//String& operator=(const String& s)
		//{
		//	if (this != &s)//判断不要自己给自己赋值
		//	{
		//		String tmp(s._str);
		//		strcpy(_str, tmp._str);
		//	}
		//	return *this;
		//}

		//更简单的赋值
		String& operator=(String s)
		{
			swap(_str, s._str);
			return *this;
		}
void TestString1()
	{
		
		String s2("hello");
		String s3(s2);//未写拷贝构造函数,使用默认的,即为浅拷贝(值拷贝)
		cout << s3.c_str() << endl;

		String s4("world");
		s3 = s4;
		cout << s3.c_str() << endl;
	}

优点:简洁+复用

 

最后本文涉及代码如下:

String.h

#define _CRT_SECURE_NO_WARNINGS 
#include  <iostream>
using namespace std;
#pragma once
namespace CPP
{
	class String
	{
	public:
		////无参的构造函数
		//String()
		//	//:_str(nullptr)//不可取,会有空指针问题
		//	:_str(new char[1])
		//{
		//	_str[0] = '\0';
		//}

		////带参的构造函数
		//String(const char* str)
		//	:_str(new char[strlen(str)+1])
		//{
		//	strcpy(_str, str);// while (*dst++ = *src++)'\0'已经拷贝过去了
		//}

		//推荐使用全缺省的带参的构造函数初始化
		String(const char* str = "")//不能给nullptr,因为strlen会崩溃
			:_str(new char[strlen(str) + 1])
		{
			strcpy(_str, str);// while (*dst++ = *src++)'\0'已经拷贝过去了
		}

		//拷贝构造---浅拷贝
		String(const String& s)
			:_str(s._str)
		{}
		//赋值
		String& operator=(const String& s)
		{
			if (this != &s)
			{
				_str = s._str;
			}
			return *this;
		}

		//深拷贝-传统写法
		//s3(s2) s3是this指针,s2是s
		String(const String& s)
		{
			_str = new char[strlen(s._str) + 1];//_str是this指针,新开一个s2大小的空间
			strcpy(_str, s._str);//把s2中的各个字节拷贝给this(s3)
		}

		//赋值 s3=s2
		String& operator=(const String& s)
		{
			if (this != &s)//判断不要自己给自己赋值
			{
				delete[] _str;//因为不知道空间大小,为避免内存泄漏,先释放掉s3原来的空间
				_str = new char[strlen(s._str) + 1];
				strcpy(_str, s._str);
			}
			return *this;
		}

		//深拷贝--现代写法
		//s3(s2) tmp开辟一个和s2相同的空间,拷过来,s2是s
		String(const String& s)
			:_str(nullptr)
		{
			String tmp(s._str);//调构造
			swap(_str, tmp._str);
		}

		////赋值 s3=s2
		//String& operator=(const String& s)
		//{
		//	if (this != &s)//判断不要自己给自己赋值
		//	{
		//		String tmp(s._str);
		//		strcpy(_str, tmp._str);
		//	}
		//	return *this;
		//}
		//更简单的赋值
		String& operator=(String s)
		{
			swap(_str, s._str);
			return *this;
		}

		//析构函数
		~String()
		{
			if (_str)
			{
				delete[] _str;
			}
		}

		//输出c形式的字符串
		char* c_str()
		{
			return _str;
		}
		//计算大小size
		size_t Size()
		{
			return strlen(_str);
		}

		char& operator[](size_t pos)//返回别名,除了可读还可写
		{
			return _str[pos];
		}
	private:
		char* _str;
	};
	void TestString1()
	{
		String s1;
		String s2("hello");
		cout << s1.c_str() << endl;//s1是一个空对象,它的_str是一个空指针
		cout << s2.c_str() << endl;

		for (size_t i = 0; i < s2.Size(); ++i)
		{
			cout << s2[i] << " ";
		}
		cout << endl;

		String s3(s2);//未写拷贝构造函数,使用默认的,即为浅拷贝(值拷贝)
		cout << s3.c_str() << endl;

		String s4("world");
		s3 = s4;
		cout << s3.c_str() << endl;
	}
}

test.cpp

#define _CRT_SECURE_NO_WARNINGS 
#include <iostream>
using namespace std;
#include "String.h"

int main()
{
	CPP::TestString1();
	system("pause");
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章