【C++】string 类的实现

构造函数

无参数构造和传参构造

  • 通过对参数设置缺省值为空串""同时满足无参构造和传参构造
  • 成员 _size 和 _capacity 均是针对有效位的
  • 开辟空间时除有效位外,要多预留一字节空间给 ‘\0’
//设置缺省参数
string(const char* str = "")
	:_size(strlen(str))
{
	//注意size和capacity均是针对有效位的,
	//但是有效位占空间时要多预留一位给'\0'
	//多的一位只通过开空间时进行预留
	_capacity = _size == 0 ? 4 : _size;
	_str = new char[_capacity + 1];
	strcpy(_str, str);
}
//拷贝构造
string(const string& s)
	: _size(s.size())
	, _capacity(s.capacity())
{
	_str = new char[s.capacity() + 1];
	strcpy(_str, s._str);
}

赋值重载

关于浅拷贝

若不主动显示赋值重载,会导致浅拷贝问题

  • 两对象的 _str 相同,都指向同一块空间,改变其中一个对象内容会相互影响
  • 两对象生命周期结束时,都会调用析构函数,对同一空间析构两次
    在这里插入图片描述
  • 若对本身赋值,直接返回本身
  • 开辟临时空间拷贝内容,避免new失败破坏原数据
  • 将原空间释放,同时 _str 指向拷贝好的临时空间
//赋值运算符重载
string& operator=(string& s)
{
	if (this != &s)
	{
		//_str的空间不一定为空,所以要用临时空间拷贝s._str的内容
		//再释放_str的原空间,同时_str指向开好的临时空间
		char* tmp = new char[s._capacity + 1];
		strcpy(tmp, s._str);
		delete[] _str;
		_str = tmp;

		_size = s._size;
		_capacity = s._capacity;
	}
	return *this;
}

迭代器

迭代器是类里内嵌的 typedef ,区分普通迭代器和 const 迭代器(标准库还有反向迭代器)
typedef char* iterator;
typedef const char* const_iterator;

  • 遍历可使用下标遍历、迭代器遍历、范围for
  • 范围for 本质上是迭代器,会自动识别普通迭代器和const迭代器
iterator begin() const
{
	return _str;
}
iterator end() const
{
	return _str + size();
}
//使用迭代器遍历
string::iterator it = s2.begin();
while (it != s2.end())
{
	cout << (*it) << " ";
	++it;
}
cout << endl;

容量相关

容量相关函数不会轻易缩容!!

reserve

更改容量

void reserve(size_t n)
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);
		//拷贝后将原空间释放
		//cout << typeid(_str).name << endl;
		delete[] _str;
		_str = tmp;

		_capacity = n;
	}
}

resize

  • 若参数 n 小于 _size ,再 n 位置设置 ‘\0’ ,改变 _size 为 n,实现缩短串的长度
  • 若参数 n 大于 _size 小于 _capacity,将剩余空间设置为参数字符
  • 若参数 n 大于 _capacity,reserve扩容再设置字符
//resize
void resize(size_t n, char ch)
{
	if (n <= _size)
	{
		_str[n] = '\0';
		_size = n;
	}
	else
	{
		if (n > _capacity)
			reserve(n);
		size_t i = _size;
		while (i < n)
		{
			_str[i] = ch;
			++i;
		}
		_size = n;
		_str[_size] = '\0';
	}
}

修改

push_back

  • 尾部插入字符
  • 无多余空间则进行扩容
void push_back(const char ch)
{
	if (_size == _capacity)
	{
		reserve(_capacity * 2);
	}
	_str[size()] = ch;
	++_size;
	_str[size()] = '\0';
}

append

  • 尾部插入字符串
  • 同样空间不够先扩容
  • 关于用strcpy而不用strcat
    strcat 追加内容前会先查找 ‘\0’ 的位置,会降低效率,使用strcpy可直接指定拷贝位置
void append(const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}
	//这里如果用strcat进行追加,
	//strcat会先查找'\0'的位置,会降低效率
	//所以这里使用strcpy直接指定拷贝位置
	strcpy(_str + _size, str);
}	

insert

  • 挪动数据时,应考虑0位置插入特殊情况
  • 区分插入字符与插入串进行重载
//insert区分字符与串进行重载
void insert(size_t pos, char ch)
{
	assert(pos <= _size);
	if (_size + 1 > _capacity)
	{
		reserve(_capacity + 1);
	}
	size_t end = _size + 1;
	//注意控制结束条件
	//考虑0位置插入情况
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		--end;
	}
	_str[pos] = ch;
	++_size;
}
void insert(size_t pos, const char* str)
{
	assert(pos <= _size);
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}
	size_t end = _size + len;
	//注意各条件的控制
	while (end > pos + len - 1)
	{
		_str[end] = _str[end - len];
		--end;
	}
	strncpy(_str + pos, str, len);
	_size += len;
}

erase

关于npos

string::npos是一个静态成员常量,表示size_t的最大值(Maximum value for size_t)。该值表示“直到字符串结尾”,作为返回值它通常被用作表明没有匹配。
static const size_type npos = -1;
 
erase 函数参数为开始删除位置、删除长度

void erase(size_t pos, size_t len = npos)
{
	assert(pos < _size);
	if (len != npos && len < _size - pos)
	{
		strcpy(_str + pos, _str + pos + len);
		_size -= len;
	}
	else
	{
		_str[pos] = '\0';
		_size = pos;
	}
}

流运算符重载

流插入

//注意使用const和引用
ostream& operator<<(ostream& out, const string& s)
{
	for (auto e : s)
	{
		out << e;
	}
	return out;
}

流提取

  • 对于 ' ''\n'----
    C++认为是多个字符间的间隔,cin 和 scanf 提取时会忽略
    为了能将' ''\n'提取到缓冲区以便条件判断,使用get函数接收输入内容
  • 操作前先将原串 clear ----
    若不clear,对于"hello\0xxx\0"这样的串,扩容时的strcpy会以第一个'\0'结束,会造成不可预见的后果
  • 考虑插入内容较长:
    用一个buffer 串接受插入内容,buffer每满一次拷贝一次到待插入对象
    这样就减少了扩容次数
istream& operator>>(istream& in, string& s)
{
	s.clear();
	//get会将' '和'\n'都提取进缓冲区,方可进行while循环判断
	char ch = in.get();
	//char buffe	r[10];
	char buffer[128];

	size_t i = 0;
	while (ch != ' ' && ch != '\n')
	{
		buffer[i++] = ch;
		if (i == 127)
		{
			//!!注意设置'\0'
			buffer[i] = '\0';
			s += buffer;
			i = 0;
		}
		ch = in.get();
	}
	if (i != 0)
	{
		//!!注意设置'\0'
		buffer[i] = '\0';
		s += buffer;
	}
	return in;
}

 
 

笔记

在这里插入图片描述
 
 

完整代码

namespace mystring
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;


		//构造函数,传参或无参
		string(const char* str = "")
			:_size(strlen(str))
		{
			//注意size和capacity均是针对有效位的,
			//但是有效位占空间时要多预留一位给'\0'
			//多的一位只通过开空间时进行预留
			_capacity = _size == 0 ? 4 : _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		//拷贝构造
		string(const string& s)
			: _size(s.size())
			, _capacity(s.capacity())
		{
			_str = new char[s.capacity() + 1];
			strcpy(_str, s._str);
		}

		const size_t& size() const//const成员适用于所有对象
		{
			return _size;
		}
		const size_t& capacity() const
		{
			return _capacity;
		}



		//迭代器根据自身对象是否为const实现
		//普通迭代器和const迭代器重载
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}

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


		//c_str以'\0'结束,而string本身是根据_size
		const char* c_str()
		{
			return _str;
		}


		//下标访问重载,区分普通和const
		char& operator[](size_t pos) 
		{
			//检查下标有效性
			assert(pos < size());
			return _str[pos];
		}
		const char& operator[](size_t pos) const
		{
			//检查下标有效性
			assert(pos < size());
			return _str[pos];
		}


		//比较运算符重载
		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);
		}
		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);
		}


		//resize
		void resize(size_t n, char ch)
		{
			//如果n小于_size,设置'\0',重置_size
			if (n <= _size)
			{
				_str[n] = '\0';
				_size = n;
			}
			//n大于_size,先查看是否需要扩容,再将超出原_size部分设置为ch
			else
			{
				if (n > _capacity)
				{
					reserve(n);

				}

				size_t i = _size;
				while (i < n)
				{
					_str[i] = ch;
					++i;
				}
				_size = n;
				_str[_size] = '\0';
			}
		}

		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				//依然通过临时变量实现扩容
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				//拷贝后将原空间释放
				//cout << typeid(_str).name << endl;
				delete[] _str;
				_str = tmp;

				_capacity = n;
			}
		}

		//尾插字符
		void push_back(const char ch)
		{
			/*if (_size == _capacity)
			{
				reserve(_capacity * 2);
			}
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';*/

			insert(_size, ch);
		}

		//追加串
		void append(const char* str)
		{
			//size_t len = strlen(str);
			//if (_size + len > _capacity)
			//{
			//	reserve(_size + len);
			//}
			这里如果用strcat进行追加,
			strcat会先查找'\0'的位置,会降低效率
			所以这里使用strcpy直接指定拷贝位置
			//strcpy(_str + _size, str);
			//_size += len;
			insert(_size, str);
		}

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

		//insert区分字符与串进行重载
		void insert(size_t pos, char ch)
		{
			assert(pos <= _size);
			if (_size + 1 > _capacity)
			{
				reserve(_capacity + 1);
			}
			size_t end = _size + 1;
			//插入前先挪动数据腾出位置
			//注意控制结束条件
			//考虑0位置插入情况
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				--end;
			}
			_str[pos] = ch;
			++_size;
		}
		void insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			size_t end = _size + len;
			//注意各条件的控制
			//挪动数据
			while (end > pos + len - 1)
			{
				_str[end] = _str[end - len];
				--end;
			}
			strncpy(_str + pos, str, len);
			_size += len;
		}

		void erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			if (len != npos && len < _size - pos)
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
			else
			{
				_str[pos] = '\0';
				_size = pos;
			}
		}
		

		//find区分字符和串查找的重载
		size_t find(char ch, size_t pos = 0)
		{
			assert(pos < _size);
			//从pos位置开始找
			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);
			//注意返回的是相对于起始的位置
			char* ret = strstr(_str + pos, str);
			if (ret == NULL)
			{
				return npos;
			}
			return ret - _str;
		}

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

		//直接使用标准库的swap
		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

		//赋值运算符重载
		string& operator=(string s)
		{
			//不是本身则进行拷贝,否则直接返回
			if (this != &s)
			{
				_str的空间不一定为空,所以要用临时空间拷贝s._str的内容
				再释放_str的原空间,再将_str指向开好的临时空间
				//char* tmp = new char[s._capacity + 1];
				//strcpy(tmp, s._str);
				//delete[] _str;
				//_str = tmp;

				//_size = s._size;
				//_capacity = s._capacity;
				swap(s);
				return *this;
			}
			return *this;
		}

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

	private:
		char* _str;
		size_t _size;
		size_t _capacity;
		static const size_t npos = -1;
	};

	//注意使用const和引用
	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto e : s)
		{
			out << e;
		}
		return out;
	}


	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		//get会将' '和'\n'都提取进缓冲区,方可进行while循环判断
		char ch = in.get();
		//char buffer[10];
		char buffer[128];

		size_t i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buffer[i++] = ch;
			if (i == 127)
			{
				//!!注意设置'\0'
				buffer[i] = '\0';
				s += buffer;
				i = 0;
			}
			ch = in.get();
		}
		if (i != 0)
		{
			//!!注意设置'\0'
			buffer[i] = '\0';
			s += buffer;		
		}
		return in;
	}
}
  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值