string容器模拟实现

目录

变量

构造函数

有参构造函数

无参构造函数

全缺省构造函数

拷贝构造函数

析构函数

遍历方法

1.[ ]索引访问

2.迭代器访问

普通迭代器访问

const迭代器 

插入数据

对于数据的扩容

尾部插入数据

追加字符串

中间插入数据

插入一个字符

插入字符串

删除数据

查找

查找单个字符

查找子串

得到子串

操作符重载

+=操作符

=操作符

<<操作符

>>操作符

杂项

获取c字符串

获取字符串长度


变量

		size_t _capacity;//对象容量
		size_t _size;//对象实际大小
		char* _str;//字符指针

        const static size_t npos = -1;//定义在类域的静态变量
        //这是一种特殊的静态定义方式,只能是size_t类型,其他类型不可

构造函数

有参构造函数

错误想法:直接将const char*的字符串赋给str

_str是char*类型可读可写,str是const char*类型只读(将str赋给_str,修改_str时会导致修改str,这样是矛盾的)权限放大

		string(const char* str)
			:_str(str)//权限放大
		{}

正确想法:构造新对象时,先开辟一块同样大小的空间,将原来字符串str通过函数(strcpy)拷贝到_str。

初始化列表

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

但注意初始化列表的顺序应该与声明顺序一致,当然也可以不使用初始化列表 ,直接将初始化写在内部。

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

无参构造函数

		string()
		{
            //_str = nullptr  不能置空,因为解引用时候会报错(比如打印字符串的时需要解引用)
			_str = new char[1];
			_size = 0;
			_capacity = 0;
			_str[0] = '\0';
		}

全缺省构造函数

 综合无参构造函数和有参构造函数。

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

拷贝构造函数

法一:传统写法

		string(const string& s)
		{
			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}

法二:现代写法

首先重载swap(可以交换string类型的对象),利用构造函数用s构造tmp,交换tmp与this对象

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

析构函数

销毁原来的空间,并将变量置为初值。

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

清空容器。

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

遍历方法

1.[ ]索引访问

利用传引用返回可以修改返回值,可以直接修改该字符。

而传值返回返回的是一份拷贝,修改的话不会对原来的字符造成影响 。

		char& operator[](size_t pos)
		{
			assert(pos <= _size);

			return _str[pos];
		}

函数重载,如果返回类型是const char只读类型,则加const修饰成员函数 

		const char& operator[](size_t pos) const
		{
			assert(pos <= _size);

			return _str[pos];
		}

2.迭代器访问

普通迭代器访问

string容器迭代器的本质是原生指针,主要原因是string容器存储数据的空间是连续的,直接通过指针就可以遍历所以数据。 

	    typedef char* iterator;//迭代器本质是一个字符指针
        //可以理解为string本质上是一个储存字符的数组(结尾存储‘\0’),
        //地址空间是连续的,可以通过指针逐个遍历
		iterator begin()
		{
			return _str;
		}
		iterator end()//指向的是最后一个数据的下一个,即‘\0’
		{
			return _str + _size;
		}
const迭代器 

可以修改指针,但是不能修改指针的内容。只是将指针类型前加const即可。

		typedef const char* const_iterator;

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

插入数据

对于数据的扩容

本质上是c语言realloc函数的异地扩容

		void reverse(size_t n)//类似于realloc
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}

尾部插入数据

首先考虑扩容问题,然后在结尾直接插入一个字符即可。

        void push_back(char ch)
		{
            //检查容量
			if (_size == _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
				reverse(newcapacity);
			}
            //插入一个字符
			_str[_size] = ch;
			_size++;
			_str[_size] = '\0';
		}

追加字符串

类似于尾插多个字符,但实现时不是利用尾插实现。

首先是考虑扩容问题,然后有足够容量后,直接用strcpy函数进行尾部插入字符串

		void append(const char* str)
		{
            //检查容量
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reverse(_size + len);
			}

            //尾部插入字符串
			strcpy(_str + _size, str);
			//strcat(_str, str);//不太好,可以算出尾部,就不用函数去计算
			_size += len;
		}

strcat函数是追加字符串的函数,但是其内部实现需要计算出原本字符串的尾部再进行追加。

这里可以直接_str+_size计算出尾部的地址,直接用strcpy即可。

中间插入数据

插入一个字符

首先判断是否扩容,然后通过移动字符的方式向中间插入数据

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

            //考虑容量
			if (_size == _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
			}

            //插入数据
			//end的位置就是\0的位置
			//size_t end = _size;  ×报错
            //如果在头部插入,end是无符号的,随着end--,到-1会变成整形最大值,导致死循环
			int end = _size;
			while (end >= (int)pos)//要强转成int,否则整形提升将end变成无符号的,死循环
			{
				_str[end + 1] = _str[end];
				end--;
			}
			_str[pos] = ch;
			_size++;

            //当然也可以,把索引改一下,当end为0时跳出循环,防止出现负数情况
            //size_t end = _size+1;
			//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)
			{
				reverse(_size + len);
			}

            //插入字符串
			int end = _size;
			while (end >= (int)pos)
			{
				_str[end + len] = _str[end];
				end--;
			}
			strncpy(_str + pos, str, len);
			_size += len;
		}

删除数据

删除pos位置以后len个长度的字符串(包括pos位置)

分为两种情况

1.删除中间某个位置后的所有值

在pos位置删除len长度的字符串,len的长度大于pos之后的所有字符串长度时,只要把pos位置后面全部删除,就是把pos位置的值变成'\0',_size改成pos的值即可。

2.删除中间某一段数据

	    void erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			if (pos + len > _size)//情况1
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else//情况2
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
		}

查找

查找单个字符

从pos位置查找某字符。

遍历容器,一一比对,若没有找到则返回整形最大值。

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

			return npos;
		}

查找子串

从pos位置开始向后查找子串,用strstr函数查找子串

		size_t find(const char* str, size_t pos = 0)
		{
			const char* ptr = strstr(_str+pos, str);
			if (ptr == nullptr)//没找到
			{
				return npos;
			}
			else//找到了
			{
				return ptr - _str;
			}
		}

得到子串

得到pos位置向后len长度的子串。

		string substr(size_t pos = 0, size_t len = npos)
		{
			assert(pos < _size);
			size_t end = pos + len;
			if (len == npos || pos + len >= _size)//判断是否超出范围,并修正end
			{
				end = _size;
			}

			string str;
			str.reserve(end - pos);
			for (size_t i = pos; i < end; i++)
			{
				str += _str[i];
			}

			return str;
		}

操作符重载

+=操作符

直接写出重载的版本,分别是加单个字符,加字符串,直接复用push_back和append。

这里的返回值是传引用返回+=后的结果。

		1.尾部插入一字符
        string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
        2.追加字符串
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}

=操作符

类似拷贝构造的传统写法

		string& operator=(const string& s)
		{
			if (this != &s)//若等号两边是统一对象,则不用拷贝,减少开辟空间的开销
			{
				char* tmp = new char[s._capacity + 1];
				strcpy(tmp, s._str);
				delete[] _str;

				_str = tmp;
				_size = s._size;
				_capacity = s._capacity;
			}

			return *this;
		}

类似于拷贝构造的现代写法

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

			return *this;
		}

函数重载,当传非const类型的对象时,选择传值(而不是传引用!),则传入时生成一份拷贝,让这份拷贝直接与this对象进行交换。

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

			return *this;
		}

<<操作符

把字符一个一个传给out,这里用了范围for,底层是直接替换成了迭代器。

    	ostream& operator<<(ostream& out, const string& s)
    	{
	    	for (auto ch : s)
	    	{
	    		out << ch;
	    	}

	    	return out;
	    }

>>操作符

    istream& operator>>(istream& in, string& s)//不用const,因为要提取到s中,s可修改
	{
        s.clear();//先清除s中所有内容

        //char ch;
        //in >> ch;

        char ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			s += ch;
            //in >> ch;//不能用这种方式传字符,这种方式会跳过'\n'和' ',导致死循环
            ch.get();
		}
		return in;
	}

 问题:当输入很长的代码时,要多次扩容,开销增大。

改进:提前开空间,开多大合适?不确定。这里通过引入一个临时数组buff,当做一个缓冲区,来缓解多次扩容开空间的问题。

每装满一次buff数组就加到s中。

    istream& operator>>(istream& in, string& s)//不用const,要提取到s中
	{
		s.clear();
		char buff[128];
		char ch = in.get();
		int i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 127)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}

			ch = in.get();
		}

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

		return in;
	}

杂项

获取c字符串

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

获取字符串长度

		size_t size() const
		{
			return _size;
		}

  • 21
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橘子13

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值