string类模拟实现

目录

模拟实现string类的默认成员函数

模拟实现构造函数

模拟实现拷贝构造函数

模拟实现赋值运算符重载

模拟实现析构函数

string类的增容

模拟实现reserve

 模拟实现resize

string类的遍历和查询

模拟实现const迭代器

模拟实现普通迭代器

模拟实现c_str

模拟实现size 

模拟实现[ ]重载 

模拟实现find,查找字符 

模拟实现find,查找字符串

string类的修改

模拟实现push_back

模拟实现append

 重载+=

模拟实现insert,插入字符

模拟实现insert,插入字符串

string类的删除

模拟实现erase,删除字符或者字符串

模拟实现string类整体代码


在前几期我们学习了库中string类相关函数接口的使用,为了更深层次的理解string类,本期我们需要学习string类的模拟实现。

模拟实现string类的默认成员函数

模拟实现构造函数

string(const char* str="")
		:_str(new char[strlen(str) + 1])
		,_size(strlen(str))
		,_capacity(_size)
	{
		strcpy(_str, str);
	}

构造函数,给缺省值的目的是为了让字符串有默认的"\0",使得编译器能够识别字符串的结束。

模拟实现拷贝构造函数

string(string& s)
		:_str(nullptr)
		,_size(0)
		,_capacity(0)
	    {
		string tmp(s._str);
		std::swap(_str, tmp._str);
		std::swap(_size, s._size);
		std::swap(_capacity, s._capacity);
	    }

创建了对象,交换了中间对象和新创建的对象的成员变量。 

模拟实现赋值运算符重载

string& operator =(string s)
	{
		std::swap(_str, s._str);
		std::swap(_size, s._size);
		std::swap(_capacity, s._capacity);
		return *this;
	}

形参就是一个中间对象,通过实参对象的值传递进行了拷贝构造生成了中间对象。 

模拟实现析构函数

~string()
	{
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}

对象声明周期结束前先调用析构函数完成资源的清理。资源清理后,编译器再进行对象的销毁,与对象的创建和调用构造函数的顺序刚好相反。 

string类的增容

模拟实现reserve

void reserve(size_t n)
	{
		if (n > _capacity)
		{
			//多申请一个空间是为了给'\0'
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}

需要多申请一个空间,是为了留给'\0'。

 模拟实现resize

void resize(size_t n,char ch= '\0')
		{
			if (n <= _size)
			{
				_str[n] = '\0';
			}
			else
			{
				if (n > _capacity)
				{
					reserve(n);

				}
				memset(_str + _size, ch, n - _size);
				_size = n;
				_str[_size] = '\0';
			}
		}

resize扩容时有三种情况,扩容的空间小于原来字符串有效字符的个数,扩容的空间大于原来字符的个数但是小于容量,扩容的空间大于原来字符的容量,后两种的实际操作可以分为同一种。

string类的遍历和查询

模拟实现const迭代器

const_iterator begin()const
	{
		return _str;
	}

	const_iterator end() const
	{
		return _str + _size;
	}

迭代器本质上就是一个指针。 const成员函数const迭代器普通对象和const对象都可以进行访问。

模拟实现普通迭代器

 iterator begin()
	{
		return _str;
	}

	   iterator end()
	{
		return _str + _size;
	}

普通迭代器只有普通对象可以访问。 

模拟实现c_str

const char* c_str()const
	{
		return _str;
	}

重载流提取之外的输出字符串的方法。 

模拟实现size 

 size_t size()const
	{
		return _size;
	}

什么时候设置为const成员函数什么时候设置成普通成员函数,这取决于我们对字符串的操作,求大小,打印,这些不需要改变结构的可以设置成const,方便const对象去进行访问。

模拟实现[ ]重载 

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

模拟实现find,查找字符 

size_t find(char ch)
	{
			for (size_t i = 0; i < _size; ++i)
			{
				if (ch == _str[i])
				{
					return i;
				}
			}

			return npos;
	}

npos一个静态的无符号const成员变量,表示整型的最大值,再找不到元素时返回npos。

模拟实现find,查找字符串

size_t find(const char* s, size_t pos = 0)
	{
	    const char* ptr = strstr(_str + pos, s);
		if (ptr == nullptr)
		{
			return npos;
		}
		else
		{
			return ptr - _str;
		}
	}

因为返回的是下标,所以最终通过指针相减获得了对应字符串的首元素的下标,字符串首元素的下标就是整个字符串的下标,因为字符串是连续存储的。

string类的修改

模拟实现push_back

void push_back(char ch)
	{
		if (_size = _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		_str[_size] = ch;
		_size++;
		_str[_size] = '\0';
	}

与顺序表类似,因为string类本身就是一个数组,可以理解为它就是一个顺序表。因为刚开始会覆盖'\0',最终一定要在最后一个有效字符的下一位置添加上'\0' 。

模拟实现append

 void append(const char* s)
	{
		//先得求出要尾插的字符串的长度,因为此时空间不足的话不能以2倍扩容,因为一个字符串的长度可能远远大于容量
		size_t len = strlen(s);
		if (_size + len > _capacity)
		{
			reserve(_size + len);
		}
		strcpy(_str + _size, s);
		_size += len;
	}

需要注意,扩容时不能像以往一样二倍扩容,因为字符串的长度是不能预期的。 

 重载+=

	//重载+=,尾插字符
string& operator+=(char ch)
	{
		push_back(ch);
		return *this;
	}

	//重载+=,尾插字符串
string& operator+=(const char* str)
	{
		append(str);
		return *this;
	}

本质上就是复用了push_back,append两个函数。 

模拟实现insert,插入字符

string& insert(size_t pos, char ch)
	{
		assert(pos <= _size);

		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		size_t end = _size + 1;
		while (end > pos)
		{
			_str[end] = _str[end - 1];
			--end;
		}
		_str[pos] = ch;
		++_size;

		return *this;
	}

插入字符时与顺序表类似,但是需要注意end的位置,因为pos时无符号整型,所以一般情况下要将end的前一个的位置移动到end位置,而不是把end位置移动到end+1位置,这点当无符号整型判断时要注意。

模拟实现insert,插入字符串

string& insert(size_t pos, const char* s)
	{
		assert(pos <= _size);
		size_t len = strlen(s);
		if (_size + len > _capacity)
		{
			reserve(_size + len);
		}
		size_t end = _size + len;
		while (end > pos)
		{
			_str[end] = _str[end - len];
			--end;
		}
		strncpy(_str + pos, s, len);
		return *this;
	}

我们要注意一定要使用strncpy函数控制字符的个数,如果使用strcpy会将'\0'拷贝过去,导致字符串异常结束。

string类的删除

模拟实现erase,删除字符或者字符串

string& erase(size_t pos = 0, size_t len = npos)
	{
		assert(pos < _size);
		if (len == npos || pos + len >= _size)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			strcpy(_str + pos, _str + pos + len);
			_size -= len;
		}
		return *this;
	}

要注意pos的范围,因为我们一般删除的都是有效字符,所以pos的范围就是有效字符下标的范围。缺省值是npos,所以如果不给定初始值,默认全部删除完。

模拟实现string类整体代码

namespace yjd
{
	class string
	{
	public:
		//迭代器
		typedef char* iterator;
		typedef const char* const_iterator;
		//构造函数,给缺省值的目的是为了让字符串有默认的"\0",使得编译器能够识别字符串的结束
		string(const char* str="")
			:_str(new char[strlen(str) + 1])
			,_size(0)
			,_capacity(0)
		{
			strcpy(_str, str);
		}

		//拷贝构造函数的深拷贝进阶版本
		string(string& s)
			:_str(nullptr)
			,_size(s._size)
			,_capacity(s._capacity)
		{
			string tmp(s._str);
			std::swap(_str, tmp._str);
		}
	
		//赋值运算符重载的深拷贝进阶版本2
		string& operator =(string s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
			return *this;
		}
		//析构函数
		~string()
		{
				delete[] _str;
				_str = nullptr;
				_size = _capacity = 0;
		}
		//const迭代器,const对象和普通对象都可以调用调用
		const_iterator begin()const
		{
			return _str;
		}

		const_iterator end() const
		{
			return _str + _size;
		}
		
		//普通迭代器,普通对象调用
		iterator begin()
		{
			return _str;
		}

	    iterator end()
		{
			return _str + _size;
		}


		//以cout<<对象.c_string的方式打印字符串,什么时候设置成const类型的成员变量取决于我们的用法,如果只是打印,对象自然不会更改,所以我们就设置成const成员函数
		const char* c_string()const
		{
			return _str;
		}

		//求字符串有效字符的个数
		size_t size()const
		{
			return _size;
		}

		//重载[],从而以数组的形式访问字符串的每个元素
		char& operator[] (size_t pos)
		{
			return _str[pos];
		}

		//进行空间的扩容,实现reserve的功能
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				//多申请一个空间是为了给'\0'
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}


		//进行空间的扩容,并且进行初始化,实现resize的功能
		void resize(size_t n,char ch= '\0')
		{
			if (n <= _size)
			{
				_str[n] = '\0';
			}
			else
			{
				if (n > _capacity)
				{
					reserve(n);

				}
				memset(_str + _size, ch, n - _size);
				_size = n;
				_str[_size] = '\0';
			}
		}


		//尾插一个字符,实现push_back的功能
		void push_back(char ch)
		{
			if (_size = _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			_str[_size] = ch;
			_size++;
			_str[_size] = '\0';
		}

		//尾插一个字符,实现append的功能
		void append(const char* s)
		{
			//先得求出要尾插的字符串的长度,因为此时空间不足的话不能以2倍扩容,因为一个字符串的长度可能远远大于容量
			size_t len = strlen(s);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			strcpy(_str + _size, s);
			_size += len;
		}

		//重载+=,尾插字符
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}

		//重载+=,尾插字符串
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}

		//实现find,进行字符的查找
		size_t find(char ch)
		{
				for (size_t i = 0; i < _size; ++i)
				{
					if (ch == _str[i])
					{
						return i;
					}
				}

				return npos;
		}

		//实现find,查找某个字符串
    	size_t find(const char* s, size_t pos = 0)
		{
			const char* ptr = strstr(_str + pos, s);
			if (ptr == nullptr)
			{
				return npos;
			}
			else
			{
				return ptr - _str;
			}
		}

		//插入元素,实现insert
		string& insert(size_t pos, char ch)
		{
			assert(pos <= _size);

			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				--end;
			}
			_str[pos] = ch;
			++_size;

			return *this;
		}

		//实现insert,插入字符串
		string& insert(size_t pos, const char* s)
		{
			assert(pos <= _size);
			size_t len = strlen(s);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}

			size_t end = _size + len;
			while (end > pos)
			{
				_str[end] = _str[end - len];
				--end;
			}
			strncpy(_str + pos, s, len);

			return *this;
		}


		//进行字符串的删除,实现erase
		string& erase(size_t pos = 0, size_t len = npos)
		{
			assert(pos < _size);

			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}

			return *this;
		}
	private:
		 char* _str;
		 size_t _size;
		 size_t _capacity;
		 //定义静态常变量npos,找不到是就返回npos
		 static const size_t npos;
	};
	const size_t string::npos = -1;

以上便是string类常见接口的模拟实现,可以帮助大家对string类更深一步的了解,模拟实现不一定要全部掌握,但是库中的string类中的相关常见接口一定要掌握。

本期内容到此结束^_^

你可能感兴趣的:(知识总结,C++,c++,开发语言)