【C++】简单模拟实现string模板

目录

一.前言

二.string.h头文件

1.class构建

2.构造函数和析构函数

①构造函数

②析构函数

3.c_str函数(获取c字符串)

4.empty函数(判断空)

5.push_back函数(尾插)

6.reserve函数(扩容)

 7.append函数(合并)

8.重载‘+=’

9.重载‘=’

10.重载‘[]’

11.insert函数(插入)

①insert字符

②insert字符串

12.find函数(查找)

①find字符

②find字符串

13.erase函数(删除)

14.resize函数(重定义长度)

15.substr函数(获取子串)

16.swap函数(交换)

17.<<流插入与>>流提取

①流插入

②流提取

18.迭代器设置

19.大小比较

 三.源码


一.前言

        本文将通过模拟实现c++中的string模板中一些常用的函数功能,具体包含push_back,reserve等函数的模拟,但并不考虑到所有的情况,因此并没有过多的函数重载(因为string的重载实在是太多了...)

        本文共两部分,一部分用来实现string,头文件命名string.h,另一部分则为源码实现部分(编译器环境为VS2019)。各个函数的返回值类型和参数类型均与库中的string一样。

二.string.h头文件

1.class构建

        string类总共有三个成员,_size表示长度,_capacity表示最大容量,还有最后一个用来表示存储的字符串_str,具体实现如下:

#pragma once
#include <assert.h>

namespace str
{
	class string
	{
    public:

    private:
		size_t _size;
		size_t _capacity;
		char* _str;
    }
};

2.构造函数和析构函数

        说到自定义类,就离不开构造函数和析构函数,那么就简单的实现一下这两种函数吧。

①构造函数

//构造函数(带缺省也可为默认构造)
string(const char* str = "")
{
	_size = strlen(str);
	_capacity = _size;
	_str = new char[_capacity + 1];
	//拷贝_size+1是因为后面还要跟个'\0'
	memcpy(_str, str, _size + 1);
}

//深拷贝
string(const string& str)
{
	_str = new char(str._capacity + 1);
	_size = str._size;
	_capacity = str._capacity;
	memcpy(_str, str._str, _size + 1);
}

         对于常用的构造函数有两种,一种是常量字符串的初始化,另一种是同类型string的初始化,由于是同类型的string初始化,则需要进行深拷贝。

②析构函数

//析构函数
~string()
{
	delete[] _str;
    _str = nullptr;
	_size = _capacity = 0;
}

3.c_str函数(获取c字符串)

        该函数简单易懂,直接返回成员字符串就行。

//c_str函数(返回C字符串)
const char* c_str() const
{
	return _str;
}

4.empty函数(判断空)

        很简单,直接判断大小是否为0。

//empty函数(判断空)
bool empty() const
{
	return _size == 0;
}

5.push_back函数(尾插)

        插入元素最简单的函数,但值得注意的是,类中要时刻注意内存开辟的大小,因此在尾插数据时,一定要检测开辟的空间是否够用。

//push_back函数(尾插)
void push_back(char ch)
{
	if (_size == _capacity)
	    //2倍扩容
	    reserve(_capacity == 0 ? 4 : _capacity * 2);
			
	_str[_size++] = ch;
	_str[_size] = '\0';
}

        此处使用的reserve函数即为扩容函数,具体扩容大小可根据实际情况调整。

6.reserve函数(扩容)

        reserve函数的功能仅为扩容,如果调用该函数时类的大小大于扩容重定义的大小时,则不需要做任何动作,虽然可以销毁并重新构建大小来节省空间,但这样反复的拷贝将使运行的时间大大增加,因此并不建议任何情况都重构。仅需扩大容量即可。实现如下:

//reserve函数(扩容)
void reserve(size_t n)
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];
		memcpy(tmp, _str, _size + 1);
		delete[] _str;
		_capacity = n;
		_str = tmp;
	}
}

 7.append函数(合并)

        该函数与上面的push_back实现功能差别不大差别不大,不过是在类的最后处加上目标字符串,此处则不多赘述。

//append函数(合并)
void append(const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		//至少扩容至刚好
		reserve(_size + len);
	}

	memcpy(_str + _size, str, len + 1);
	_size += len;
}

8.重载‘+=’

        实现与库函数string类中+=常用的有两种:+=字符、+=字符串,其实现过程很简单,字符直接调用前面的push_back函数,而字符串则调用append函数。

//重载'+='
string& operator+=(const char ch)
{
	push_back(ch);
	return *this;
}

string& operator+=(const char* str)
{
    append(str);
	return *this;
}

9.重载‘=’

        因为需要深拷贝,所有得重载=,可以将原类大小置空,再使用append函数添加到类中。

//重载'='
string& operator=(const char* ch)
{
	_size = 0;
	_str[0] = '\0';

	append(ch);
	return *this;
}

10.重载‘[]’

        []即获取对应下部储存的数据,与数组字符串获取下标无异,直接获取即可。

//获取对应下标
char& operator[](size_t pos)
{
	assert(_str[pos]);

	return _str[pos];
}

const char& operator[](size_t pos) const
{
	assert(_str[pos]);

	return _str[pos];
}

11.insert函数(插入)

        insert函数共有三个参数,常用插入的有字符和字符串,因此需要重载一个该函数。具体实现如下,需要的注意点都在以下代码有所标记。

①insert字符

//insert函数(插入字符)
void insert(size_t pos,size_t n,char ch)
{
	//排除下标越界
	assert(pos <= _size);
			
	//检查扩容
	if (_size + n > _capacity)
	{
		reserve(_size + n);
	}

	//挪动数据
	//此处防止整形提升,故使用size_t类型,并使用npos加以区分
	size_t end = _size;
	while (end >= pos && end != npos)
	{
		_str[end + n] = _str[end];
		--end;
	}

	//插入数据
	for (size_t i = 0; i < n; ++i)
	{
		_str[pos + i] = ch;
	}

	_size += n;
}

②insert字符串

//insert函数(插入字符串)
void insert(size_t pos, const char* str)
{
	//排除下标越界
	assert(pos <= _size);

	//检查扩容
	size_t len = strlen(str);
	if (pos + len > _capacity)
	{
		reserve(pos + len);
	}

	//挪动数据
	size_t end = _size;
	while (end >= pos && end != npos)
	{
		_str[end + len] = _str[end];
		end--;
	}

	//插入数据
	for (int i = 0; i < len; ++i)
	{
		_str[pos + i] = str[i];
	}

	_size += len;
}

        其中的npos为我们在该类命名空间中定义的静态区无符号整型,将其在string类中声音,在命名空间中定义,大小为"-1"。

12.find函数(查找)

        查找函数与insert类似,常查找分为字符和字符串,另一个参数则为从哪开始,返回值为目标下标,具体实现如下:

①find字符

//find函数(查找字符)
size_t find(char ch, size_t pos = 0)
{
	assert(pos < _size);

	size_t end = _size;
	while (pos < end)
	{
		if (_str[pos] == ch)
			return pos;
		pos++;
	}
	return npos;
}

②find字符串

//find函数(查找字符串)
size_t find(const char* str, size_t pos = 0)
{
	size_t len = strlen(str);
	assert(pos + len < _size);
			
	//使用strstr比较字符串
	const char* tmp = strstr(_str + pos, str);
	if (tmp)
	{
		return tmp - _str;
	}
	return npos;
}

13.erase函数(删除)

        该函数功能是从目标下标删除指定数量的大小的数据,如果没有指定大小,则默认删除到保存的数据末尾,该函数最需要注意的就是对目标下标的判断和指定删除大小的判断。

//erase函数(删除)
void erase(size_t pos, size_t len = npos)
{
	if (len == npos || pos + len >= _size)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		size_t end = pos + len;
		while (end <= _size)
		{
			_str[pos++] = _str[end++];
		}
		_size -= len;
	}
}

14.resize函数(重定义长度)

        这个函数与reserve很相似,但不同的是,reserve的功能只有扩容;resize可以不仅可以扩容(当然,减容一样做不到),而且还能更新类中成员字符串的长度哦。

//resize函数(重定义长度)
void resize(size_t n, char s = '\0')
{
	if (n >= _size)
	{
		reserve(n);

		for (size_t i = _size; i < n; ++i)
		{
			_str[i] = s;
		}
	}

	_size = n;
	_str[n] = '\0';
}

15.substr函数(获取子串)

        子字符串的获取需要目标下标和子串大小这两个参数。此处的返回便使用了构造函数的深拷贝,如果没有则会直接报错。

//substr函数(子字符串获取)
string substr(size_t pos = 0, size_t len = npos)
{
	assert(pos < _size);

	//特殊情况更改后一起处理
	size_t n = len;
	if (len == npos || len + pos > _size)
	{
		n = _size - pos;
	}

	string retun;
	retun.reserve(n);
	for (int i = pos; i < pos + n; ++i)
	{
		//此处使用+=更方便
		retun += _str[i];
	}

	return retun;
}

16.swap函数(交换)

        调用swap函数可直接调用库中的字符串swap函数辅以实现,需要注意的是_size和_capacity并不会因此交换。

//swap函数(交换)
void swap(string& str)
{
	std::swap(_str, str._str);

	size_t tmp = _size;
	_size = str._size;
	str._size = tmp;

	tmp = _capacity;
	_capacity = str._capacity;
	str._capacity = tmp;
}

17.<<流插入与>>流提取

        流插入与流提取中需要注意的有两点:第一个是两者需要定义在类外面的命名空间中,其次两者的第一个参数类型为插入流\提取流的别名,因为祖师爷设置了防拷贝的功能...实现如下:

①流插入

//流插入
ostream& operator<<(ostream& out, const str::string& str)
{
	for (auto i : str)
	{
		out << i;
	}

	return out;
}

        此处的for使用了迭代器,但是我们并没有自己定义,将在下一点设置。

②流提取

        流提取与流插入不同,需要考虑扩容的问题,可以直接不断调用reserve函数,但运行效率实在太差,因此以下扩容为优化版本。

//流提取(定义在类外的命名空间内)
istream& operator>>(istream& in, str::string& str)
{
	str.clear();

    //使用get()防止无法写入' '
	char ch = in.get();
	while (ch == ' ' || ch == '\n')
	{
		ch = in.get();
	}

	int i = 0;
	char tmp[128];
	while (ch != ' ' && ch != '\n')
	{
		tmp[i++] = ch;
		
		if (i == 127)
		{
			tmp[i] = '\0';
			str += tmp;

			i = 0;
		}
		ch = in.get();
	}
	if (i != 0)
	{
		tmp[i] = '\0';
		str += tmp;
	}

	return in;
}

//清空数据(定义在类内)
void clear()
{
	_str[0] = '\0';
	_size = 0;
}

18.迭代器设置

        迭代器没什么好说的,设置好初始位置和末尾位置即可,但命名必须为begin和end等与原先别名一样的,否则祖师爷的迭代器可不认识哦~

//迭代器设置
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
	return _str;
}

iterator end()
{
	return _str + _size;
}

const_iterator begin() const
{
	return _str;
}

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

19.大小比较

        比较大小的函数重载只需要两个就行了,其他都可以一一推出来,比较如下:

//大小比较
bool operator<(const string& str) const
{
	return _size < str._size && memcmp(_str, str._str, _size) < 0;
}

bool operator==(const string& str) const
{
	return _size == str._size && memcmp(_str, str._str, _size) == 0;
}

bool operator>(const string& str) const
{
	return !(*this < str || *this == str);
}

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

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

bool operator!=(const string& str) const
{
	return !(*this == str);
}

 三.源码

#pragma once
#include <assert.h>

namespace str
{
	class string
	{
	public:
		//构造函数(带缺省也可为默认构造)
		string(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];
			//strcpy(_str, str);
			memcpy(_str, str, _size + 1);
		}

		//深拷贝
		string(const string& str)
		{
			_str = new char(str._capacity + 1);
			_size = str._size;
			_capacity = str._capacity;
			memcpy(_str, str._str, _size + 1);
		}

		//析构函数
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}

		//迭代器设置
		typedef char* iterator;
		typedef const char* const_iterator;
		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		const_iterator begin() const
		{
			return _str;
		}

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

		//reserve函数(扩容)
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				//new的[]是容量大小,()是初始化内容
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_capacity = n;
				_str = tmp;
			}
		}

		//push_back函数(尾插)
		void push_back(char ch)
		{
			if (_size == _capacity)
				//2倍扩容
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			
			_str[_size++] = ch;
			_str[_size] = '\0';
		}

		//append函数(合并)
		void append(const char* str)
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				//至少扩容至刚好
				reserve(_size + len);
			}

			memcpy(_str + _size, str, len + 1);
			_size += len;
		}

		//重载'='
		string& operator=(const char* ch)
		{
			_size = 0;
			_str[0] = '\0';

			append(ch);
			return *this;
		}

		//重载'+='
		string& operator+=(const char ch)
		{
			push_back(ch);
			return *this;
		}

		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}

		//insert函数(插入)
		void insert(size_t pos,size_t n,char ch)
		{
			//排除下标越界
			assert(pos <= _size);
			
			//检查扩容
			if (_size + n > _capacity)
			{
				reserve(_size + n);
			}

			//挪动数据
			//此处防止整形提升,故使用size_t类型,并使用npos加以区分
			size_t end = _size;
			while (end >= pos && end != npos)
			{
				_str[end + n] = _str[end];
				--end;
			}

			//插入数据
			for (size_t i = 0; i < n; ++i)
			{
				_str[pos + i] = ch;
			}

			_size += n;
		}

		void insert(size_t pos, const char* str)
		{
			//排除下标越界
			assert(pos <= _size);

			//检查扩容
			size_t len = strlen(str);
			if (pos + len > _capacity)
			{
				reserve(pos + len);
			}

			//挪动数据
			size_t end = _size;
			while (end >= pos && end != npos)
			{
				_str[end + len] = _str[end];
				end--;
			}

			//插入数据
			for (int i = 0; i < len; ++i)
			{
				_str[pos + i] = str[i];
			}

			_size += len;
		}

		//erase函数(删除)
		void erase(size_t pos, size_t len = npos)
		{
			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				size_t end = pos + len;
				while (end <= _size)
				{
					_str[pos++] = _str[end++];
				}
				_size -= len;
			}
		}

		//find函数(查找字符或字符串)
		size_t find(char ch, size_t pos = 0)
		{
			assert(pos < _size);

			size_t end = _size;
			while (pos < end)
			{
				if (_str[pos] == ch)
					return pos;
				pos++;
			}
			return npos;
		}

		size_t find(const char* str, size_t pos = 0)
		{
			size_t len = strlen(str);
			assert(pos + len < _size);
			
			//使用strstr比较字符串
			const char* tmp = strstr(_str + pos, str);
			if (tmp)
			{
				return tmp - _str;
			}
			return npos;
		}

		//substr函数(子字符串获取)
		string substr(size_t pos = 0, size_t len = npos)
		{
			assert(pos < _size);

			//特殊情况更改后一起处理
			size_t n = len;
			if (len == npos || len + pos > _size)
			{
				n = _size - pos;
			}

			string retun;
			retun.reserve(n);
			for (int i = pos; i < pos + n; ++i)
			{
				//此处使用+=更方便
				retun += _str[i];
			}

			return retun;
		}

		//resize函数(重定义长度)
		void resize(size_t n, char s = '\0')
		{
			if (n >= _size)
			{
				reserve(n);

				for (size_t i = _size; i < n; ++i)
				{
					_str[i] = s;
				}
			}

			_size = n;
			_str[n] = '\0';
		}

		//大小比较
		bool operator<(const string& str) const
		{
			return _size < str._size && memcmp(_str, str._str, _size) < 0;
		}

		bool operator==(const string& str) const
		{
			return _size == str._size && memcmp(_str, str._str, _size) == 0;
		}

		bool operator>(const string& str) const
		{
			return !(*this < str || *this == str);
		}

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

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

		bool operator!=(const string& str) const
		{
			return !(*this == str);
		}

		//swap函数(交换)
		void swap(string& str)
		{
			std::swap(_str, str._str);

			size_t tmp = _size;
			_size = str._size;
			str._size = tmp;

			tmp = _capacity;
			_capacity = str._capacity;
			str._capacity = tmp;
		}

		//empty函数(判断空)
		bool empty() const
		{
			return _size == 0;
		}

		//清空数据
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}

		//c_str函数(返回C字符串)
		const char* c_str() const
		{
			return _str;
		}

		//获取对应下标
		char& operator[](size_t pos)
		{
			assert(_str[pos]);

			return _str[pos];
		}

		const char& operator[](size_t pos) const
		{
			assert(_str[pos]);

			return _str[pos];
		}

		
	private:
		size_t _size;
		size_t _capacity;
		char* _str;

	public:
		const static size_t npos;
	};

	const size_t string::npos = -1;
};

//流插入
ostream& operator<<(ostream& out, const str::string& str)
{
	for (auto i : str)
	{
		out << i;
	}

	return out;
}

//流提取
istream& operator>>(istream& in, str::string& str)
{
	str.clear();

	char ch = in.get();
	while (ch == ' ' || ch == '\n')
	{
		ch = in.get();
	}

	int i = 0;
	char tmp[128];
	while (ch != ' ' && ch != '\n')
	{
		tmp[i++] = ch;
		
		if (i == 127)
		{
			tmp[i] = '\0';
			str += tmp;

			i = 0;
		}
		ch = in.get();
	}

	if (i != 0)
	{
		tmp[i] = '\0';
		str += tmp;
	}
	return in;
}

猜你喜欢

转载自blog.csdn.net/qq_74641564/article/details/131666872
今日推荐