STL string模拟实现及深浅拷贝问题

文章目录

    • 深浅拷贝问题
    • 现代版写法的String类
    • string 增删查改等实现

模拟实现string类,最主要是实现String类的构造、拷贝构造、赋值运算符重载以及析构函数。又因为string是要需要动态管理资源的,模拟实现时就不得不考虑深浅拷贝的问题。

深浅拷贝问题

浅拷贝
当创建一个对象,然后把它赋给另一个变量的时候,没有拷贝这个对象,而只是拷贝了这个对象的引用,我们称之为浅拷贝。
浅拷贝的问题:一般普通的变量可以这样使用,但是一旦涉及到动态资源(指向开辟内存的指针等)的拷贝问题,就会造成两个指针变量指向同一块内存,当其中一个指针变量指向的内存被释放后,另一个指向还是没变,但是此时就会变成指向的是一个被释放的空指针,而当这个指针变量也要释放内存时,编译器就会出现free 这个空指针的问题而崩溃报错。
STL string模拟实现及深浅拷贝问题_第1张图片

要解决浅拷贝问题,C++中引入了深拷贝。
深拷贝:
给每个对象独立分配资源,保证多个对象之间不会因共享资源而造成多次释放造成程序崩溃问题。

STL string模拟实现及深浅拷贝问题_第2张图片


class String
{
public:
	String( char* str = "")
	{
		// 构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言下
		if (nullptr == str)
		{
			assert(false);
			return;
		}
		//_str = str;//浅拷贝形式,必然会报错

		//深拷贝形式
		_str = new char[strlen(str) + 1];//另开空间
		strcpy(_str, str);//再把值拷贝到新空间
	}
	~String()
	{
		if (_str)
		{
			delete[] _str;//free空间
			_str = nullptr;//置空
		}
	}
private:
	char* _str;
};
// 测试
void TestString()
{
	String s1("hello world!!");
	//String s2(s1); //要是没自己显示定义拷贝构造,系统就会调用默认的浅拷贝方式,也会报错
	//String s3;
	//s3=s1;//同理

}

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

说明:上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终由于浅拷贝,s1、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃。s1赋值给s3时同理。

   String(const String& s)//拷贝构造
		:_str(new char[strlen(s._str) + 1])
	{
		strcpy(_str, s._str);
	}
	String& operator=(const String& s)	//赋值运算符重载
	{
		if (this != &s)//防止自己给自己赋值
		{
			_str =  new char[strlen(s._str) + 1]; //得到新空间
			strcpy(_str, s._str);//拷贝数据
		}
		return *this;
	}

以上都是传统版的写法,而现代版写法的String类更加优化和简洁

现代版写法的String类

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(nullptr)
	{
		String strTmp(s._str);//复用构造函数,构造一个临时对象
		swap(_str, strTmp._str);//交换两者的指向,注意对象s的_str指向没有变,传参用的引用,变的是临时对象strTmp的_str的指向,且会临时对象会自动销毁的,不造成影响
	}
	 对比下和上面的赋值那个实现比较好?
	String& operator=(String s)//传参的时候,就会调用构造函数,s相当于一个临时对象形参
	{
		swap(_str, s._str);//出作用域临时对象自动销毁
		return *this;
	}
	
	//String& operator=(const String& s)//传引用
	//{
	//	if(this != &s)
	//	{
	//		String strTmp(s);//显式调用一次拷贝构造 ,而在拷贝构造里,会调用一次构造函数,相比于上面的实现性能上有损失
	//		swap(_str, strTmp._str);
	//	}
	//	return *this;
	//}
	
	~String()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}
private:
	char* _str;
};

void TestString()
{
	String s1("hello world!!");
	String s2(s1);
	String s3;
	s3 = s1;

}

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

string 增删查改等实现

#define  _CRT_SECURE_NO_WARNINGS 1 

#include
#include
#include
using namespace std;
namespace mystring
{
	class String
	{
	public:
		typedef char* iterator;  //迭代器
		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}
	public:

		//浅拷贝只是单纯的把变量值拷贝一份再赋值过去,要是涉及动态开辟的内存,就是把指针的地址拷贝一份再赋值给另一个变量,这样就会有两个指针变量指向同一个内存,当其中一个被释放后,但是另一个还指向这个地方,就会出错  
		//例如 :拷贝构造函数和运算符重载时,用s1拷贝构造s2时调用默认拷贝构造函数,共用同一个内存,在析构时一块内存被释放两次会报错误

		//所以要使用深拷贝
		//String(const char* str==nullptr) 这是错误的缺省 strlen(str)会崩溃的
		String(const char* str = "")

		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];   //动态开辟空间      //开空间时要给\0留一个位置
			strcpy(_str, str); //另开空间再把数据拷贝过去
		}
		String(const String& s)
		{
			//String tmp = s;
			_str = new char[s._capacity + 1];
			_size = s._size;
			_capacity = s._capacity;
			strcpy(_str, s._str);
		}
		String& operator=(const String& s)   //对=重载
		{
			if (this != &s)//防止自己给自己赋值
			{
				delete[] _str;//先把自己原来的就空间释放掉
				_str = new char[s._capacity + 1];//重新开空间
				strcpy(_str, s._str);
				_size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}
		~String()  //析构
		{
			if (_str)
			{
				delete[] _str;  //调用delete释放空间
				_str = nullptr;  //指针置空
				_size = _capacity = 0;
			}
		}

		// modify   修改操作
		void PushBack(char c) //尾插一个字符
		{
			if (_size == _capacity)//判断是否扩容
			{
				Reserve(_capacity * 2);
			}
			_str[_size++] = c;
			_str[_size] = '\0'; //末尾补上终止符
		}
		void Append(const char* str)   //链接字符串
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
				Reserve(_size + len);
			strcpy(_str + _size, str);
			_size += len;
		}
		String& operator+=(char c)  //重载 += 尾插一个字符
		{
			this->PushBack(c);
			return *this;
		}

		String& operator+=(const char* str)// 重载 += 链接字符串
		{
			this->Append(str);
			return *this;
		}
		void Clear()  //清除元素
		{
			_size = 0;
			_str[_size] = '\0';
		}
		void Swap(String& s)     //对象互相转换
		{
			swap(_str, s._str);
			swap(_size, s._size);
			swap(_capacity, s._capacity);
		}
		const char* c_str() const  //转换成c格式字符串
		{
			return _str;
		}

		// capacity   对元素和容量空间操作
		size_t Size()const    //获取 元素个数
		{
			return _size;
		}
		size_t Capacity()const   //获取容量大小
		{
			return _capacity;
		}
		bool Empty()const   //判断是否为空
		{
			return _size == 0;
		}

		void resize(size_t n, char ch = '\0')
		{
			if (n < _size) // 删除数据
			{
				_size = n;
				_str[_size] = '\0';
			}
			else // 插入数据
			{
				// 空间不够先增容
				if (n > _capacity)
					Reserve(n);

				for (size_t i = _size; i < n; ++i)
					_str[i] = ch;
				_size = n;
				_str[_size] = '\0';
			}
		}
		void Reserve(size_t newCapacity)
		{
			if (newCapacity > _capacity)   //也是另开空间存数据
			{
				char * tmp = new char[newCapacity + 1];
				strcpy(tmp, _str);
				//销毁旧空间
				delete[] _str;
				_str == nullptr;
				_str = tmp;
				_capacity = newCapacity;
			}
		}
		String& insert(size_t pos, const char ch)//插字符
		{
			assert(pos < _size);
			if (_size == _capacity)
			{
				Reserve(_capacity * 2);
			}
			size_t end = _size;
			while (end>pos)
			{
				_str[end + 1] = _str[end];//pos之后的都后移一位
			}
			_str[pos] = ch;
			_size += 1;
			return *this;
		}
		String& insert(size_t pos, const char* str) //插字符串
		{
			assert(pos < _size);
			int len = strlen(str);
			if (_size + len >= _capacity)
			{
				Reserve(_size + len);
			}
			size_t end = _size;
			while (end>pos)
			{
				_str[end + len] = _str[end];
				end--;
			}
			for (int i = 0; i < len; i++)
			{
				_str[i + pos] = str[i];
			}
			//strncpy(_str+pos,str,len);
			_size = _size + len;
			return *this;
		}

		// access   访问操作
		char& operator[](size_t index)
		{
			assert(index < _size);
			return _str[index];
		}
		const char& operator[](size_t index)const
		{
			assert(index < _size);
			return _str[index];
		}

		//   关系运算
		// s1 < s2  s1==s2
		// 实现这两个,其他的比较复用实现
		bool operator<(const String& s)
		{
			return strcmp(_str, s._str) < 0;
		}

		bool operator==(const String& s)
		{
			return strcmp(_str, s._str) == 0;
		}

		bool operator<=(const String& s)
		{
			return *this < s || *this == s;
		}

		bool operator>(const String& s)
		{
			return !(*this <= s);
		}

		bool operator>=(const String& s)
		{
			return !(*this < s);
		}

		bool operator!=(const String& s)
		{
			return !(*this == s);
		}

		// 删除pos之后的元素
		String& erase(size_t pos, size_t len = npos)//缺省参数
		{
			if (pos + len > _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
			return *this;
		}
		// 返回字符在string中第一次出现的位置
		size_t find(const char ch, size_t pos = 0)
		{
			for (int i = pos; i < _size; i++)
			{
				if (_str[i] == ch)
					return i;
			}
			return -1;
		}
		// 返回子串在string中第一次出现的位置
		size_t find(const char* str, size_t pos = 0)
		{
			const char* tmp = strstr(_str + pos, str);
			if (tmp == nullptr)
				return -1;
			return tmp - _str;
		}
	private:
		friend ostream& operator<<(ostream& out, const String& s);
		friend istream& operator>>(istream& in, String& s);
	private:
		char * _str;
		size_t _capacity;
		size_t _size;
		static size_t npos;
	};


	size_t String::npos = -1;

	ostream& mystring::operator<<(ostream& out, const mystring::String& s)
	{
		for (size_t i = 0; i < s.Size(); ++i)
		{
			out << s[i];
		}

		return out;
	}

	istream& mystring::operator>>(istream& in, mystring::String& s)
	{
		while (1)
		{
			char ch = in.get();
			if (ch == ' ' || ch == '\n')
				break;
			else
				s += ch;
		}

		return in;
	}
}
int main()
{
	mystring::String s1("hello");
	s1.PushBack('x');
	s1.PushBack('y');
	s1.Append("world");
	s1 += '!';
	s1 += "hello world";
	cout << s1 << endl;

	//// 遍历+读写
	//for (size_t i = 0; i < s1.Size(); ++i)
	//{
	//	s1[i] += 1;
	//	cout << s1[i] << " ";
	//}
	//cout << endl;

	//// 遍历+读写
	//mystring::String::iterator it1 = s1.begin();
	//while (it1 != s1.end())
	//{
	//	*it1 -= 1;
	//	cout << *it1 << " ";
	//	++it1;
	//}
	//cout << endl;

	//// 遍历+读写  范围for是有迭代器支持的,这里会被编译器转换成迭代器遍历
	//for (auto& ch : s1)
	//{
	//	ch += 1;
	//	cout << ch << " ";
	//}
	//cout << endl;

	//mystring::String s2("hello");
	//s2 += 'x';
	//s2.resize(3);
	//s2.resize(7, 'x');
	//s2.resize(15, 'x');

	return 0;
}

你可能感兴趣的:(c++)