【C++】STL——string的模拟实现、常用构造函数、迭代器、运算符重载、扩容函数、增删查改

1.模拟实现string

string使用文章
在这里插入图片描述

1.1构造函数

  这里我们实现常用的第四个string(const char* s)和析构函数

在这里插入图片描述
在这里插入图片描述

class string {
    
    
public:
	//初始化列表赋值
	//string(const char* str = "\0")
	//	:_size(strlen(str))
	//	,_capacity(_size)
	//	,_str(new char[_capacity+1])
	//{
    
    
	//	memcpy(_str, str, _size + 1);
	//}
	
	//函数体内赋值
    string(const char* str = "\0") 
    {
    
    
        _size = strlen(str); // 获取字符串长度
        _capacity = _size; // 设置容量为长度
        _str = new char[_capacity + 1]; // 分配内存空间
        memcpy(_str, str, _size + 1); // 复制字符串内容
       	//strcpy(_str, str);
       	//这里最好不要使用strcpy,因为strcpy是C语言中的函数
       	//在检测到/0直接结束,若字符串中有/0,会导致部分字符串无法复制
    }

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

private:
    int _size; // 字符串长度
    int _capacity; // 字符串容量
    char* _str; // 字符串内容
};


  拷贝构造函数实现:

在这里插入图片描述

  在堆上使用new关键字为当前对象的成员变量_str分配内存空间,大小为s._capacity + 1字节,即字符串的容量加上一个结束符\0的空间。

  我们使用深拷贝而不是浅拷贝,浅拷贝会导致两个对象的指针指向同一块内存空间,当其中一个对象被析构时,会释放内存空间,导致另一个对象指向的内存变成悬空指针。 因此,在复制构造函数中使用了深拷贝来确保每个对象都有独立的内存空间存储字符串内容,避免出现悬空指针和程序崩溃的问题。

string(const string& s)
{
    
    
	this->_size = s._size;
	this->_capacity = s._capacity;
	
	//浅拷贝,两个指针指向同一块空间,且调用两次析构函数,程序崩溃
	//this->_str = s._str;

	//深拷贝 实现
	this->_str = new char[s._capacity + 1];
	//strcpy(_str, s.c_str());
	memcpy(_str, s.c_str(), _size + 1);
}

1.2迭代器

在这里插入图片描述
在这里插入图片描述

  我们通过迭代器来访问字符串的每个字符。begin 函数返回指向字符串的起始位置的迭代器,end 函数返回指向字符串的结束位置的迭代器。这样,就可以使用标准的迭代器操作来遍历字符串,如使用循环来遍历每个字符。

  对于普通的string类型,编译器调用上面两个类型iterator的迭代器,表示可以对该字符串可读可写;而对于const类型的string,编译器就会调用下面两个类型const_iterator的迭代器,表示对该字符串只有可写权限。

//实现迭代器和const迭代器
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;
}

1.3运算符重载

operator[]

在这里插入图片描述
  和上面的迭代器实现类似,我们也需要实现两个operator[]。

  重载的普通版本的 operator[] 函数接受一个 size_t 类型的位置参数 pos。它首先使用 assert 断言来检查指定的位置是否位于有效的范围内,即小于字符串的长度 _size。然后,它返回 _str[pos],也就是字符串中指定位置 pos 处的字符的引用。因为该函数返回的是一个引用,所以可以通过该引用对字符进行修改。

  重载的const版本的 operator[] 函数与版本类似,但是函数本身被声明为const成员函数,以确保不会修改对象的成员变量。这意味着对const对象时operator[]只能进行读的权限,但不能通过返回的引用来修改字符。

  通过重载 [] 操作符,可以像操作数组一样方便地访问类中的字符。例如,对于字符串对象 str,可以使用 str[0] 来访问第一个字符,str[1] 来访问第二个字符,以此类推。

//重载[]操作符
char& operator[](size_t pos)
{
    
    
	assert(pos < _size);

	return _str[pos];
}
 
//重载const[]操作符
const char& operator[](size_t pos) const
{
    
    
	assert(pos < _size);

	return _str[pos];
}

赋值运算符

在这里插入图片描述

在这里插入图片描述

  传统写法中的赋值操作符重载函数首先进行自我赋值的判断,以确保在自我赋值的情况下不会出现问题。 然后,创建一个临时的字符数组 tmp,大小为 s._capacity + 1,并使用 memcpy 函数将 s._str 的内容复制到 tmp 中。接下来,删除当前对象的 _str 所指向的内存空间,并将其指针指向新分配的 tmp。最后,更新当前对象的 _size 和 _capacity。

  现代写法中的赋值操作符重载函数使用了拷贝构造函数来简化实现。 它首先创建一个临时的字符串对象 tmp,并使用参数 s 初始化 tmp。然后,调用 swap 函数来交换当前对象的成员变量和 tmp 对象的成员变量。这样做的好处是,通过交换指针,可以避免进行手动的内存分配和释放,并且保证异常安全性。最后,返回当前对象的引用。

//传统写法
/*string& operator=(const string& s)
{
	if (this != &s)
	{
		char* tmp = new char[s._capacity + 1];
		memcpy(tmp, s._str, s._size + 1);
		delete[] _str;
		_str = tmp;

		_size = s._size;
		_capacity = s._capacity;
	}

	return *this;
}*/

//现代写法
void swap(string& s)
{
    
    
	std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}

string& operator=(const string& s)
{
    
    
	if (this != &s)
	{
    
    
		string tmp(s);

		//this->swap(tmp);
		swap(tmp);
	}

	return *this;
}

//更简单的写法
//string& operator=(string tmp)
//{
    
    
//	swap(tmp);
//
//	return *this;
//}

其他用于string比较的运算符重载:

bool operator<(const string& s) const
{
    
    
	int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
	return ret == 0 ? _size < s._size : ret < 0;
}

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

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

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

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

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

1.4扩容函数

reserve和resize

在这里插入图片描述
在这里插入图片描述

  这两个函数的作用是在需要扩容或调整字符串大小的情况下,确保字符串对象具有足够的容量和正确的大小。其中,reserve 函数用于扩容容量,resize 函数用于调整大小,并在必要时进行内存分配和字符填充。

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

		delete[] _str;
		_str = tmp;
		_capacity = n;
	}
}

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

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

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

1.5增删查改

push_back和append和+=运算符重载

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  这些函数的作用是向字符串对象中添加字符或字符数组。其中,push_back 可以用于逐个添加字符到字符串末尾,append 可以将字符数组追加到字符串中,operator+= 运算符重载则提供了一种简便的方式对字符串和字符进行连接。

//实现尾插
void push_back(const char ch)
{
    
    
	//先扩容
	if (_size == _capacity)
	{
    
    
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}

	this->_str[_size] = ch;

	++_size;
	this->_str[_size] = '\0';
}

//实现append
void append(const char* str)
{
    
    
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
    
    
		//至少要扩容到_size+len
		reserve(_size + len);
	}

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

//复用实现+=的运算符重载
string& operator+=(const char ch)
{
    
    
	push_back(ch);
	return *this;
}


完整实现(包含insert、erase、find、substr、operator<<、operator>>)

#include<assert.h>

namespace str
{
    
    
	class string
	{
    
    
	public:
		//实现迭代器和const迭代器
		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;
		}

		构造函数为空值
		//string()
		//	:_size(0)
		//	, _capacity(_size)
		//	, _str(new char[1])
		//{
    
    
		//	_str[0] = '\0';
		//}

		构造函数传字符串
		//string(const char* str)
		//	:_size(strlen(str))
		//	,_capacity(_size)
		//	,_str(new char[_capacity+1])
		//{
    
    
		//	memcpy(_str, str, _size + 1);
		//}

		//合并string()和string(const char* str)
		//并且在函数体内赋值
		string(const char* str = "\0")//->"\0"\0
		{
    
    
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];
			//为空时,实参未显示赋值,我们给str缺省参数"\0"
			//str传给_str"\0",系统设为空
			//"\0"为字符串以指针传递、'\0'为单个字符以单个字符传递
			//strcpy(_str, str);
			memcpy(_str, str, _size + 1);
		}

		string(const string& s)
		{
    
    
			this->_size = s._size;
			this->_capacity = s._capacity;
			
			//浅拷贝,两个指针指向同一块空间,且调用两次析构函数,程序崩溃
			//this->_str = s._str;

			//深拷贝 实现
			this->_str = new char[s._capacity + 1];
			//strcpy(_str, s.c_str());
			memcpy(_str, s.c_str(), _size + 1);
		}

		//传统写法
		/*string& operator=(const string& s)
		{
			if (this != &s)
			{
				char* tmp = new char[s._capacity + 1];
				memcpy(tmp, s._str, s._size + 1);
				delete[] _str;
				_str = tmp;

				_size = s._size;
				_capacity = s._capacity;
			}

			return *this;
		}*/

		//现代写法
		void swap(string& s)
		{
    
    
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

		//string& operator=(const string& s)
		//{
    
    
		//	if (this != &s)
		//	{
    
    
		//		string tmp(s);
		//
		//		//this->swap(tmp);
		//		swap(tmp);
		//	}
		//
		//	return *this;
		//}

		string& operator=(string tmp)
		{
    
    
			swap(tmp);

			return *this;
		}

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

		//const返回c类型的字符串
		const char* c_str() const
		{
    
    
			return _str;
		}

		//const返回str的大小
		size_t size() const
		{
    
    
			return _size;
		}

		//重载[]操作符
		char& operator[](size_t pos)
		{
    
    
			assert(pos < _size);

			return _str[pos];
		}
		 
		//重载const[]操作符
		const char& operator[](size_t pos) const
		{
    
    
			assert(pos < _size);

			return _str[pos];
		}

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

				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}

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

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

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

		//实现尾插
		void push_back(const char ch)
		{
    
    
			//先扩容
			if (_size == _capacity)
			{
    
    
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}

			this->_str[_size] = ch;

			++_size;
			this->_str[_size] = '\0';
		}

		//实现append
		void append(const char* str)
		{
    
    
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
    
    
				//至少要扩容到_size+len
				reserve(_size + len);
			}

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

		//复用实现+=的运算符重载
		string& operator+=(const char ch)
		{
    
    
			push_back(ch);
			return *this;
		}

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

		void insert(size_t pos, size_t n, char ch)
		{
    
    
			assert(pos <= _size);

			if (_size + n > _capacity)
			{
    
    
				reserve(_size + n);
			}

			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 (_size + len > _capacity)
			{
    
    
				// 至少扩容到_size + len
				reserve(_size + len);
			}

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

			for (size_t i = 0; i < len; i++)
			{
    
    
				_str[pos + i] = str[i];
			}

			_size += len;
		}

		void erase(size_t pos, size_t len = npos)
		{
    
    
			assert(pos <= _size);

			if (len == npos || pos + len >= _size)
			{
    
    
				//_str[pos] = '\0';
				_size = pos;

				_str[_size] = '\0';
			}
			else
			{
    
    
				size_t end = pos + len;
				while (end <= _size)
				{
    
    
					_str[pos++] = _str[end++];
				}
				_size -= len;
			}
		}

		size_t find(char ch, size_t pos = 0)
		{
    
    
			assert(pos < _size);

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

			return npos;
		}

		size_t find(const char* str, size_t pos = 0)
		{
    
    
			assert(pos < _size);

			const char* ptr = strstr(_str + pos, str);
			if (ptr)
			{
    
    
				return ptr - _str;
			}
			else
			{
    
    
				return npos;
			}
		}

		string substr(size_t pos = 0, size_t len = npos)
		{
    
    
			assert(pos < _size);

			size_t n = len;
			if (len == npos || pos + len > _size)
			{
    
    
				n = _size - pos;
			}

			string tmp;
			tmp.reserve(n);
			for (size_t i = pos; i < pos + n; i++)
			{
    
    
				tmp += _str[i];
			}

			return tmp;
		}

		void clear()
		{
    
    
			_str[0] = '\0';
			_size = 0;
		}

		//bool operator<(const string& s)
		//{
    
    
		//	size_t i1 = 0;
		//	size_t i2 = 0;
		//	while (i1 < _size && i2 < s._size)
		//	{
    
    
		//		if (_str[i1] < s._str[i2])
		//		{
    
    
		//			return true;
		//		}
		//		else if (_str[i1] > s._str[i2])
		//		{
    
    
		//			return false;
		//		}
		//		else
		//		{
    
    
		//			++i1;
		//			++i2;
		//		}
		//	}

		//	/*if (i1 == _size && i2 != s._size)
		//	{
    
    
		//		return true;
		//	}
		//	else
		//	{
    
    
		//		return false;
		//	}*/

		//	//return i1 == _size && i2 != s._size;
		//	return _size < s._size;
		//}

		bool operator<(const string& s) const
		{
    
    
			int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
			return ret == 0 ? _size < s._size : ret < 0;
		}

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

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

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

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

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

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

	public:
		const static size_t npos;
	};

	const size_t string::npos = -1;

	ostream& operator<<(ostream& out, str::string& s)
	{
    
    
		//for (size_t i = 0; i < s.size(); i++)
		//{
    
    
		//	out << s[i];
		//}

		for (auto ch : s)
		{
    
    
			out << ch;
		}

		return out;
	}

	istream& operator>>(istream& in, std::string& s)
	{
    
    
		s.clear();

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

		//in >> ch;
		char buff[128];
		int i = 0;

		while (ch != ' ' && ch != '\n')
		{
    
    
			buff[i++] = ch;
			if (i == 127)
			{
    
    
				buff[i] = '\0';
				s += buff;
				i = 0;
			}

			//in >> ch;
			ch = in.get();
		}

		if (i != 0)
		{
    
    
			buff[i] = '\0';
			s += buff;
		}

		return in;
	}
};

猜你喜欢

转载自blog.csdn.net/Crocodile1006/article/details/131948790