C++模拟实现string的示例代码

2023-02-27,,

目录
  • 一、std::swap和std::string::swap的区别
  • 二、string的默认构造函数
    • 1、构造函数
    • 2、拷贝构造
    • 3、赋值运算符重载
    • 4、析构函数
  • 三、string中的小接口
    • 四、遍历接口的实现
      • 1、对operator[]进行重载
      • 2、迭代器
    • 五、reserve和resize
      • 六、插入删除查找相关接口
        • 1、push_back、append、+=
        • 2、insert和earse
        • 3、find
      • 七、流插入和流提取
        • 八、模拟实现的string整体代码

          一、std::swap和std::string::swap的区别

          如果用std::swap交换两个string对象,将会发生1次构造和2次赋值,也就是三次深拷贝;而使用std::string::swap仅交换成员,代价较小。

          二、string的默认构造函数

          1、构造函数

          string(const char* s = "")
          {
              _size = strlen(s);//_size和_capacity均不包含'\0'
              _capacity = _size;
              _arr = new char[_size + 1];
              memcpy(_arr, s, _size + 1);
          }
          

          构造函数用缺省值,能够满足空串的构造。

          这里设计_size和_capacity均不包含'\0'。_arr的空间多new一个,用于储存'\0'。

          再将形参的内存拷贝至_arr中,即可完成构造。

          2、拷贝构造

          写法1:老老实实的根据string对象的私有变量进行拷贝构造。

          string(const string& s)
          {
              _size = s._size;//_size和_capacity均不包含'\0'
              _capacity = s._capacity;
              _arr = new char[_capacity + 1];
              memcpy(_arr, s._arr, _capacity + 1);
          }
          

          写法2:通过构造一个临时对象,将这个临时对象的私有变量全部和*this的私有变量交换。

          注意拷贝构造需要先将_arr初始化为nullptr,防止后续tmp拿到随机地址。(tmp销毁将调用析构函数,对一块随机地址的空间进行析构程序将会崩溃)

          void swap(string& s)
          {
              std::swap(_arr, s._arr);
              std::swap(_size, s._size);
              std::swap(_capacity, s._capacity);
          }
          string(const string& s)
              :_arr(nullptr)//防止交换后tmp._arr为随机值,析构出错
          {
              string tmp(s.c_str());//构造
              swap(tmp);
          }
          

          3、赋值运算符重载

          写法1:同样的老实人写法。这种写法要防止自己给自己赋值!

          string& operator=(const string& s)
          {
              if (this != &s)//防止自己给自己赋值
              {
                  _size = s._size;
                  _capacity = s._capacity;
                  char* tmp = new char[_capacity + 1];
                  delete[] _arr;
                  _arr = tmp;
                  memcpy(_arr, s._arr, _capacity + 1);
              }
              return *this;
          }
          

          写法2:通过构造临时变量tmp,完成赋值。这种写法无需担心自己给自己赋值的情况,并且_arr无需初始化为nullptr。 

          void swap(string& s)
          {
              std::swap(_arr, s._arr);
              std::swap(_size, s._size);
              std::swap(_capacity, s._capacity);
          }
          string& operator=(const string& s)
          {
              string tmp(s.c_str());//构造
              swap(tmp);
              return *this;
          }
          

          4、析构函数

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

          三、string中的小接口

          //string的size()接口
          size_t size()const//右const修饰*this,这样const和非const对象均可调用
          {
              return _size;
          }
          //string的c_str()接口
          const char* c_str()const
          {
              return _arr;
          }
          //string的capacity()接口
          size_t capacity()const
          {
              return _capacity;
          }
          //string的clear()接口
          void clear()
          {
              _arr[0] = '\0';
              _size = 0;
          }
          //string的判空
          bool empty()const
          {
              return _size == 0 ? false : true;
          }

          如果函数形参不发生改变的,无脑加const修饰。

          只有指针和引用会有const权限问题。

          四、遍历接口的实现

          1、对operator[]进行重载

          char& operator[](size_t pos)//普通对象,可读可写
          {
              assert(pos < _size);
              return _arr[pos];
          }
          const char& operator[](size_t pos)const//const对象,仅读
          {
              assert(pos < _size);
              return _arr[pos];
          }
          

          让字符串进行下标式的访问,需要重载两个operator[]函数,正常对象去调可读可写,const对象调用只读。

          2、迭代器

          typedef char* iterator;
          iterator begin()
          {
              return _arr;
          }
          iterator end()//end指向字符串的'\0'
          {
              return _arr + _size;
          }
          

          string的迭代器是字符指针,写完迭代器就可以用迭代器实现访问、修改了。

          范围for的底层也是一个迭代器,但是范围for底层只认begin()和end(),如果和自己实现的迭代器接口名称对不上,那么范围for将无法使用。

          五、reserve和resize

          //sring的reserve接口, 如果预开空间小于现有空间,将不会改变容量。
          void reserve(size_t n = 0)
          {
              if (n + 1 > _capacity)
              {
                  char* tmp = new char[n + 1];
                  memset(tmp, '\0', n + 1);
                  memcpy(tmp, _arr, _size);
                  delete[] _arr;
                  _arr = tmp;
                  _capacity = n;
              }
          }
          //sring的resize接口
          void resize(size_t n, char c)
          {
              //判断n的大小
              if (n > _capacity)
              {
                  reserve(n);
                  memset(_arr + _size, c, n - _size);
                  _size = n;
              }
              else
              {
                  _arr[n] = '\0';
                  _size = n;
              }
          }

          reserve是扩容,可以用于预开空间,防止频繁的空间申请。申请一块n+1大小的空间,将该空间全部初始化'\0',再将_arr中的数据拷贝至tmp中,释放_arr,_arr指向tmp。

          在resize中需要考虑_size扩容和缩容的问题。

          六、插入删除查找相关接口

          1、push_back、append、+=

          string& push_back(const char c)
          {
              //判断容量
              if (_size == _capacity)
              {
                  size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;//防止出现空串的情况
                  reserve(newCapacity);
              }
              _arr[_size++] = c;
              return *this;
          }
          string& append(const char* s)
          {
              //判断容量
              size_t len = strlen(s);
              if (_size + len > _capacity)
              {
                  reserve(_size + len);
              }
              strcpy(_arr + _size, s);
              _size += len;
              return *this;
          }
          string& operator+=(const char c)
          {
              push_back(c);
              return *this;
          }
          string& operator+=(const char* s)
          {
              append(s);
              return *this;
          }

          写push_back要考虑到原对象为空串的情况(即_capacity为0)。

          +=可以复用push_back和append。

          2、insert和earse

          string& insert(size_t pos, char c)
          {
              assert(pos < _size);
              //判断容量
              if (_size == _capacity)
              {
                  reserve(_capacity + 1);
              }
              //挪动数据
              for (size_t i = _size; i > pos; --i)
              {
                  _arr[i] = _arr[i - 1];
              }
              _arr[pos] = c;
              ++_size;
              return *this;
          }
          string& insert(size_t pos, const char* s)
          {
              size_t len = strlen(s);
              //判断容量
              if (len + _size > _capacity)
              {
                  reserve(len + _size);
              }
              //挪动数据
              for (size_t i = _size + len; i > pos + len - 1; --i)
              {
                  _arr[i] = _arr[i - len];
              }
              memcpy(_arr + pos, s, len);
              _size += len;
              return *this;
          }
          string& earse(size_t pos, size_t len = npos)
          {
              assert(pos < _size);
              //先判断删到底的情况
              if (len == npos || pos + len >= _size)
              {
                  _arr[pos] = '\0';
                  _size = pos;
              }
              else
              {
                  memcpy(_arr + pos, _arr + pos + len, _size - pos - len);
                  _size -= len;
              }
              return *this;
          }

          insert接口在挪动数据时,从最后一个元素的后一个(后len个)位置开始覆盖,可以保证不出现size_t 类型越界的情况。

          earse接口,需要分类讨论字符串是否删到底。

          注意,这个pos是const static成员,C++语法中,只有指针和整型的const static成员是可以在类中进行初始化的。

          3、find

          size_t find(const char c, size_t pos = 0)const
          {
              assert(pos < _size);
              for (size_t i = pos; i < _size; ++i)
              {
                  if (_arr[i] == c)
                  {
                      return i;
                  }
              }
              return npos;
          }
          size_t find(const char* s, size_t pos = 0)const
          {
              assert(pos < _size);
              const char* p = strstr(_arr, s);
              if (p != nullptr)
              {
                  return _arr - p;
              }
              return npos;
          }

          从指定位置找字符或字符串,找到了,返回第一个匹配字符/子串的下标。

          七、流插入和流提取

          //流插入和流提取的重载时为了自定义类型的输入输出
          inline ostream& operator<<(ostream& out, const string& s)//这里访问的到私有,所以可以不用写成友元函数
          {
              for (size_t i = 0; i < s.size(); ++i)//流插入按照_size打印,c_str找到'\0'结束打印
              {                                    //比如我在字符串中间插入一个'\0',打印结果不一样
                  out << s[i];
              }
              return out;
          }
          inline istream& operator>>(istream& in, string& s)
          {
              s.clear();//用之前先清空s
              //in >> c;//流提取不会识别空格和换行
              char c = in.get();
              char buff[128] = { '\0' };//防止频繁扩容
              size_t i = 0;
              while (c != ' ' && c != '\n')
              {
                  if (i == 127)
                  {
                      s += buff;
                      i = 0;
                  }
                  buff[i++] = c;
                  c = in.get();
              }
              if (i > 0)
              {
                  buff[i] = '\0';
                  s += buff;
              }
              return in;
          }

          因为string提供了访问私有的接口,所以流插入和流提取可以不用重载成string类的友元函数。

          对于流提取,如果频繁的尾插,会造成频繁扩容。而且C++的扩容和C语言的扩容不一样,C++使用new不能原地扩容,只能异地扩容,异地扩容就会导致新空间的开辟、数据的拷贝、旧空间释放。为了防止频繁扩容,我们可以创建一个可以存储128字节的数组,在这个数组中操作,这个数组满了就尾插至对象s中。

          为什么不能用getline,而是要一个字符一个字符尾插呢?因为流提取遇到空格和'\n'会结束提取,剩余数据暂存缓冲区,如果是getline的话,遇到空格是不会停止读取的。

          八、模拟实现的string整体代码

          #pragma once
          #define _CRT_SECURE_NO_WARNINGS 1
          #include <iostream>
          #include <assert.h>
          using std::cout;
          using std::cin;
          using std::endl;
          using std::ostream;
          using std::istream;
          namespace jly
          {
          	class string
          	{
          	public:
          		void swap(string& s)
          		{
          			std::swap(_arr, s._arr);
          			std::swap(_size, s._size);
          			std::swap(_capacity, s._capacity);
          		}
          		//构造函数
          		string(const char* s = "")
          		{
          			_size = strlen(s);//_size和_capacity均不包含'\0'
          			_capacity = _size;
          			_arr = new char[_size + 1];
          			memcpy(_arr, s, _size + 1);
          		}
          		//拷贝构造
          		//写法1
          		//string(const string& s)
          		//{
          		//	_size = s._size;//_size和_capacity均不包含'\0'
          		//	_capacity = s._capacity;
          		//	_arr = new char[_capacity + 1];
          		//	memcpy(_arr, s._arr, _capacity + 1);
          		//}
          		//写法2
          		string(const string& s)
          			:_arr(nullptr)//防止交换后tmp._arr为随机值,析构出错
          		{
          			string tmp(s.c_str());//构造
          			swap(tmp);
          		}
          		//赋值运算符重载
          		//写法1
          		//string& operator=(const string& s)
          		//{
          		//	if (this != &s)//防止自己给自己赋值
          		//	{ 
          		//		_size = s._size;
          		//		_capacity = s._capacity;
          		//		char* tmp = new char[_capacity + 1];
          		//		delete[] _arr;
          		//		_arr = tmp;
          		//		memcpy(_arr, s._arr, _capacity + 1);
          		//	}
          		//	return *this;
          		//}
          		//写法2
          		string& operator=(const string& s)
          		{
          			string tmp(s.c_str());//构造
          			swap(tmp);
          			return *this;
          		}
          		//析构函数
          		~string()
          		{
          			_size = _capacity = 0;
          			delete[] _arr;
          			_arr = nullptr;
          		}
          		//string的size()接口
          		size_t size()const//右const修饰*this,这样const和非const对象均可调用
          		{
          			return _size;
          		}
          		//string的c_str()接口
          		const char* c_str()const
          		{
          			return _arr;
          		}
          		//string的capacity()接口
          		size_t capacity()const
          		{
          			return _capacity;
          		}
          		//string的clear()接口
          		void clear()
          		{
          			_arr[0] = '\0';
          			_size = 0;
          		}
          		//string的判空
          		bool empty()const
          		{
          			return _size == 0 ? false : true;
          		}
          		//对operator[]进行重载
          		char& operator[](size_t pos)//普通对象,可读可写
          		{
          			assert(pos < _size);
          			return _arr[pos];
          		}
          		const char& operator[](size_t pos)const//const对象,仅读
          		{
          			assert(pos < _size);
          			return _arr[pos];
          		}
          		//迭代器
          		typedef char* iterator;
          		iterator begin()const
          		{
          			return _arr;
          		}
          		iterator end()const//end指向字符串的'\0'
          		{
          			return _arr + _size ;
          		}
          		//string的reserve接口,如果预开空间小于现有空间,将不会改变容量。
          		void reserve(size_t n=0)
          		{
          			if (n + 1 > _capacity)
          			{
          				char* tmp = new char[n + 1];
          				memset(tmp, '\0', n + 1);
          				memcpy(tmp, _arr, _size);
          				delete[] _arr;
          				_arr = tmp;
          				_capacity = n;
          			}
          		}
          		//string的resize接口
          		void resize(size_t n, char c='\0')
          		{
          			//判断n的大小
          			if (n > _capacity)
          			{
          				reserve(n);
          				memset(_arr + _size,c,n-_size);
          				_size = n;
          			}
          			else
          			{
          				_arr[n] = '\0';
          				_size = n;
          			}
          		}
          		//插入删除查找相关接口
          		string& push_back(const char c)
          		{
          			//判断容量
          			if (_size == _capacity)
          			{
          				size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;//防止出现空串的情况
          				reserve(newCapacity);
          			}
          			_arr[_size++] = c;
          			return *this;
          		}
          		string& append(const char* s)
          		{
          			//判断容量
          			size_t len = strlen(s);
          			if (_size + len > _capacity)
          			{
          				reserve(_size + len);
          			}
          			strcpy(_arr+_size,s);
          			_size += len;
          			return *this;
          		}
          		string& operator+=(const char c)
          		{
          			push_back(c);
          			return *this;
          		}
          		string& operator+=(const char* s)
          		{
          			append(s);
          			return *this;
          		}
          		string& insert(size_t pos, char c)
          		{
          			assert(pos < _size);
          			//判断容量
          			if (_size == _capacity)
          			{
          				reserve(_capacity + 1);
          			}
          			//挪动数据
          			for (size_t i = _size; i > pos; --i)
          			{
          				_arr[i] = _arr[i - 1];
          			}
          			_arr[pos] = c;
          			++_size;
          			return *this;
          		}
          		string& insert(size_t pos, const char* s)
          		{
          			size_t len = strlen(s);
          			//判断容量
          			if (len + _size > _capacity)
          			{
          				reserve(len + _size);
          			}
          			//挪动数据
          			for (size_t i = _size + len; i > pos + len - 1; --i)
          			{
          				_arr[i] = _arr[i - len];
          			}
          			memcpy(_arr + pos, s, len);
          			_size += len;
          			return *this;
          		}
          		string& earse(size_t pos, size_t len = npos)
          		{
          			assert(pos<_size);
          			//先判断删到底的情况
          			if (len == npos || pos + len >= _size)
          			{
          				_arr[pos] = '\0';
          				_size = pos;
          			}
          			else
          			{
          				memcpy(_arr + pos, _arr + pos + len,_size-pos-len);
          				_size -= len;
          			}
          			return *this;
          		}
          		size_t find(const char c, size_t pos = 0)const
          		{
          			assert(pos < _size);
          			for (size_t i = pos; i < _size; ++i)
          			{
          				if (_arr[i] == c)
          				{
          					return i;
          				}
          			}
          			return npos;
          		}
          		size_t find(const char* s, size_t pos = 0)const
          		{
          			assert(pos < _size);
          			const char* p = strstr(_arr, s);
          			if (p != nullptr)
          			{
          				return _arr - p;
          			}
          			return npos;
          		}
          	private:
          		char* _arr;
          		size_t _size;
          		size_t _capacity;
          		const static size_t npos = -1;//只有const static整型、指针成员变量可以在类中定义,其他类型不行
          	};
          	//流插入和流提取的重载时为了自定义类型的输入输出
          	inline ostream& operator<<(ostream& out, const string& s)//这里访问得到私有,所以可以不用写成友元函数
          	{
          		for (size_t i = 0; i < s.size(); ++i)//流插入按照_size打印,c_str找到'\0'结束打印
          		{                                    //比如我在字符串中间插入一个'\0',打印结果不一样
          			out << s[i];
          		}
          		return out;
          	}
          	inline istream& operator>>(istream& in, string& s)
          	{
          		s.clear();//用之前先清空s
          		//in >> c;//流提取不会识别空格和换行
          		char c=in.get();
          		char buff[128] = { '\0' };//防止频繁扩容
          		size_t i = 0;
          		while (c != ' ' && c != '\n')
          		{
          			if (i == 127)
          			{
          				s += buff;
          				i = 0;
          			}
          			buff[i++] = c;
          			c = in.get();
          		}
          		if (i > 0)
          		{
          			buff[i] = '\0';
          			s += buff;
          		}
          		return in;
          	}
          	//测试函数
          	void test1()
          	{
          	
          	}
          }

          以上就是C++模拟实现string的示例代码的详细内容,更多关于C++实现string的资料请关注其它相关文章!

          原文地址:https://blog.csdn.net/gfdxx/article/details/127893255