模拟实现String

String容器

顾名思义String类就是一个管理字符串的一个类,也可以理解为字符串的自定义类型。

String容器的成员变量

在这里插入图片描述
与之前c语言实现顺序表的结构很像,一个char类型的数组,一个表示字符串大小的_size变量,一个表示数组容量大小的 _capacity,还有一个静态成员变量 npos

String容器的成员方法

reserve:为字符串数组开辟空间

		void reserve(size_t n = 0)
		{
			if (n <= _capacity)
			{
				return;
			}
			if (_size == 0)
			{
				_str = new char[n + 1];
				return;
			}
			char* temp = new char[n + 1];
			strcpy(temp, _str);
			delete[] _str;
			_str = temp;
			_capacity = n;
		}

reserve的实现分为三种情况:
1.传过来的n和原本的capacity相等或比他小 : reserve不支持缩容,直接return,不处理
2.n比原本的capacity大 :开辟大小为n的新空间,把旧空间的元素拷贝到新空间里面去,释放旧空间
3._str为空指针,还没有开辟任何空间:直接对_str开辟空间

构造函数

在这里插入图片描述

默认构造和c-string构造

问:我们能够把构造函数写成这样吗?
在这里插入图片描述
答:不能,因为str是被const char的字符串,不能赋值给char的字符串,这会导致权限放大的问题,编译器会报错,也不能把_str改成const char* 类型这样会导致后面不能执行修改操作。
所以最好的方法是:

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

在初始化列表中,用str的字符串大小初始化_size,然后再去初始化_capacity,再去new _str的空间,这里的cacpacity代表的是不包括‘\0’的大小,所以要new _capacity+1,然后再用strcpy对_str初始化,再给传参位置缺省值,即实现c-string构造和默认构造,还顺带实现单个参数的构造函数支持隐式类型转换

拷贝构造

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

和默认构造同一个逻辑,但是传参不同拷贝构造传的是常引用,因为会在三种情况中产生临时变量 1.类型转换 2.传值返回 3.传值传参,这三种情况都是产生临时变量后,再把临时变量拷贝到他要赋值的地方。
如下图:
在这里插入图片描述

substring构造

		string(const string& str, size_t pos, size_t len = npos)
		{
			_size = len == npos ? str.size() : len;
			if (_size > str.size() - pos)
			{
				_size = str.size() - pos;
			}
			_capacity = _size;
			_str = new char[_capacity + 1];
			memcpy(_str, str.c_str() + pos, _size);
		}

逻辑:先计算要构造的大小,这里值得注意的是_size的大小不能单单被len赋值,最大只能是pos到传参字符串的末尾,然后下面逻辑默认构造一致

n个val构造

		//n个val构造
		string(size_t n, char c)
		{
			reserve(n);
			for (int i = 0; i < n; i++)
			{
				push_back(c);
			}
		}

逻辑:先给_str开辟n个空间然后再push_back n个c进去

inerst:指定位置插入元素

		string& insert(size_t pos, const string& s)
		{
			assert(pos <= _size);
			int len = s._size;
			//判断是否需要增容
			if (_size+len >_capacity)
			{
				reserve(_size + len);
			}

			/* 方法一 */
			保存pos后面的值
			//char* temp = new char[_size - pos + 1];//错误原因:这里没有算\0的大小
			//strcpy(temp, _str + pos);

			直接往pos位置插入
			//strcpy(_str + pos, s._str);
			//strcpy(_str + pos + s._size, temp);
			//delete[] temp;
			//_size += len;

			///* 方法二 */
			//memcpy(_str + pos + len, _str + pos, _size - pos + 1);
			//memcpy(_str + pos, s._str, len);
			//_size += len;
			
			/* 方法三 */
			// 挪动数据
			size_t newend = _size + len;
			size_t oldend = _size;
			while (newend >= pos + len)
			{
				_str[newend] = _str[oldend];
				newend--;
				oldend--;
			}
			memcpy(_str + pos,s._str, len);
			_size += len;
			return *this;
		}

方法一:
主要逻辑:
在这里插入图片描述
遇到的bug:开辟临时变量的时候没有考虑到斜杠0,导致strcpy的时候写入斜杠0越界
方法二:
对方法一的优化,用memcpy,memcpy和strcpy最大的区别是memcpy可以指定复制多少内容而strcpy要把赋值到src的斜杠0位置,而且还省去了临时变量
方法三:
用while循环挪动数据,然后再用memcpy复制指定内容
bug日志:

			size_t end = _size;
			while (end >= pos)
			{
				_str[end + 1] = _str[end];
				--end;
			}

问:上面的拷贝代码能过吗?
答:在pos = 0 情况下不能,因为size_t类型的end无法小于0,无法退出循环。

erase:指定位置删除元素

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

逻辑:分为两个情境:
1.len为缺省值或者len的值大于pos后面的字符串长度,就直接把pos后面的元素全部删除,直接在pos位置置为\0。
2.反之则把要len后面的元素覆盖拷贝到pos位置
如图:
在这里插入图片描述

resize: 将有效字符的个数改成n个,多出的空间用字符C填充

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

在这里插入图片描述

流输入、输出运算符重载

	//<<重载
	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto c : s)
		{
			out << c;
		}
		return out;
	}

	//>>重载
	istream& operator>>(istream& in, string& s)
	{
		s.clear();//cin流提取会清空之前的数据

		char ch = in.get();//如果直接用流提取>>会拿不到空格和换行,因为会把空格和换行当做分隔

		char buff[128]{};//避免频繁扩容
		size_t i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 127)//buff满了
			{
				buff[127] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get();
		}

		if (i != 0)//如果i不等0就代表buff大小在127内,则后加\0并+=
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}

流输出运算符重载:cout<<s1.c_str()<<endl 、cout<<s1<<endl 的区别,cout<<char*类型会输出截止到\0 ,cout<<string类型会输出截止到_size,所以cout<<string类型用范围for实现比较合适。

流输入运算符重载:为了避免反复扩容,用一个buff字符串数组,暂时接收流输入数据,然后等到输入完毕,再把buff内容添加到_str的末尾。

完整代码:代码链接

  • 29
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值