STL之string模拟实现(内含图解)

🐎前言

首先说明一下,string是c++中的类,他有很多接口函数,string类和所有的类一样,有public的函数,也有private的成员,当我们调用string的子函数时, 这里我先把private的成员给出来,所有的函数都是为了让成员使用而创造的。
下面介绍string的成员
_str:一个字符串
_capacity:用来记录容量
_size:用来记录字符串的字符个数
npos:这是一个数字,代表string的最大值,大概有四亿九千万。在一些函数中如果不给参数,就用npos作为缺省值。

	private:
 			char* _str;
 //这里_str是char*类型的,因为_str的目的是存储字符串,用指针存储,并且存储的是字符串所以用char
 			size_t _capacity;
 			size_t _size;

			const static size_t npos;
 	
 	

为了更好的帮助大家理解,我画了一张图。
框里的hello world是_str。
在这里插入图片描述

请添加图片描述

🐎构造函数

构造函数的作用是给成员初始化。
给一个空字符作为缺省值,不传参时,构造的就是空字符串,传入参数时,就用传入的字符串。


直接上代码!

//构造函数
 12		//构造函数的目的是初始化
       //,把size置成字符串的长度的个数
13		//初始化把capacity置成和size一样的
        //缺省值是空字符串
14			string(const char* str = "")
15				: _size(strlen(str))
16				,_capacity(_size)
17			{
18				_str = new char [_size+1];//给_str开辟空间
19				strcpy(_str, str);//拷贝过来,把外面的str传给里面的_str
20			}
21	

🐎 析构函数

清理内存的一些资源。

//析构函数
 49	 			~string()
 50	 			{
 51	 				_size = _capacity=0;//size和capacity置成0
 52	 				delete[]_str;//释放空间
 53	 				_str = nullptr;//置成null,防止出现野指针
 54	 	
 55	 			}

🐎拷贝构造函数

写拷贝构造函数之前,你需要了解深浅拷贝。这里我简单介绍一下
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,可能就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,但是其他的对象不知道该资源已经被释放了,以为资源还有效,所以他们会继续对这个资源进行访问。这时就发生了访问违规。为了解决浅拷贝的问题,深拷贝就出现了。


深拷贝:如果一个类中涉及到资源的管理,其拷贝的构造函数,赋值运算符重载以及析构函数必须要显式给出。(就是要手动写,不能用编译器自动生成的)。一般这种情况都是按照深拷贝方式提供。

上代码!

//s2(s1)拷贝构造函数
 		//s2=s1
 	 		string(const string& s)
 					: _size(strlen(s._str))
 					, _capacity(_size)
   			{
 	 				_str = new char[_capacity + 1];
 					strcpy(_str, s._str);
 	 			}
 	 	

以下是图解。

🐎operator=赋值运算符重载

画张图理解这个函数是干什么用
通

过图片可以看出,s2原本的值是‘xy’,通过调用函数,s2变成了和s1相同的值。

		//operator=
 30	 			string& operator=(const string& s)
 31	 			{
 32	 				if (this != &s)//防止自己给自己赋值
 33	 					//这里我的想法是写成this!=&s._str
 34	 					//这个想法是是错误的,this是一个类,s._str是类的对象,根本无法比较
 35	 				{
 36	 					char* tmp = new char[s._capacity+1];
 37	 					strcpy(tmp, s._str);
 38	 					delete[]_str;
 39	 					_str =tmp;
 40	 					_size = s._size;
 41	 					_capacity = s._capacity;
 42	 				}
 43	 				return *this;
 44	 			}
 47	

以下是图解,大家可以根据图更好理解代码实现。
在这里插入图片描述

🐎返回首元素地址(c_str)

我们直接返回首元素的地址即可(注意这里返回指针,其实返回的是他的首元素的地址)。

		//返回首元素的地址
 48			//这里为什么加const,因为他可能被其他的函数调用,并且这个函数不会改变值,
 49			//所以加上const防止有的函数不能调用它
 50			const char* c_str()const
 51			{
 52				return _str;
 53			}
 54	

🐎operator[]

这里传过来一个pos,就是下标,我们返回下标对应的值即可。
实现它是为了我们可以像访问数组一样访问字符串。

		//operator[]
 55		   char& operator[](size_t pos)
 56			{
 57			   assert(pos < _size); //这两种写法都是可以的,断言pos不会超过字符串的长度即可
 58			 //  assert(pos < strlen(_str));
 59			   return _str[pos];
 60			}
 61	

🐎size

我们直接返回	_size即可,不包括\0

这里可能有的同学会问,为什么要实现一个函数size,直接返回成员_size不行吗?
注意成员_size是private类型的,我们不能直接访问私有类型,所以实现一个公有的函数间接访问_size.

	   //size
 62		   size_t size()
 63		   {
 64			   return _size;
 65		   }
 66	

🐎capacity

我们直接返回	_size即可,不包括\0

这里和size一样,我们通过函数间接访问私有的成员。

	   //cpaacity
 67		   size_t capacity() const
 68		   {
 69			   return _capacity;
 70		   }
 71	

🐎operator+=

这是插入字符函数,可以插入一个或者多个字符,这两个函数复用了push_back和append,这两个函数我将会在后面实现。

	   //operator +=  
 72		   //插入一个字符
 73		  string& operator+=(const char ch)
 74		   {
 75			  push_back(ch);
 76			  return *this;
 77			  //判断空间够不够
 78		   }
 79		  //operator +=  
 80	      //插入多个字符
 81		  string& operator+=(const char* str)
 82		  {
 83			  append(str);
 84			  return *this;
 86		  }
 87	

🐎扩容函数(reserve)

调整容量大小到n,我们先new一个n+1的新空间,然后把原来的数据拷贝到新空间上,再释放原来的空间,再交换两个指针的指向,再将_capacity置成n即可。

	  //reserve  扩容函数
 88		  string& reserve(size_t n)
 89		  {
 90			  if(n>_capacity)
 91				  {
 92					 char* tmp = new char[n+1];
 93					 strcpy(tmp, _str);
 94					 delete[]_str;
 95					 _str = tmp;
 					 _capacity=n;
 96				  }
 97	
 98		  }
 99	

下面来看看图解。
在这里插入图片描述

🐎resize

resize会出现两种情况

  1. 给定的size的值n小于_size,这种情况数据会被截断,只保留前面的n个
  2. 给定的size的值n大于_size,这种情况又可以分成两种情况
    2.1 _size<n<_capacity
    这种情况需要改变__capacity的值
    2.2 n>_capacity
    这种情况需要增容,扩大_capacity
	  //改变size,如果新的size的比capacity还大,就补成\0
 100		  //如果新的size比size小,就截短即可
 101		  void resize(size_t n, char ch ='\0')
 102		  {
 103			  if (n < _size)
 104			  {
 105				  _size = n;
 106				  _str[_size] = '\0';
 107			  }
 108			  else//   n>=_size
 109			  {
 110				  if (n > _capacity)
 111				  {
 112					  reserve(n);
 113				  }
 114				  for (size_t i = 0; i < n; i++)
 115				  {
 116					  _str[i] = ch;
 117				  }
 118				  _size = n;
 119				  _str[_size] = '\0';
 120			  }
 121	
 122		  }
 123	

下面看看图解


在这里插入图片描述

🐎push_back

push_back就是在一个字符串尾部拼接上字符,我们首先要考虑空间是否足够,若不够需要扩容,可以利用reserve函数,扩大2倍,然后再插入数据,最后在末尾填上\0

 这个函数也可以通过复用insert函数实现(insert函数我将在后面实现)

	  //push back 插入一个字符
 125		  void push_back(char ch)
 126		  {
 127			  //先判断空间够不够
 128			  //如果不够,就进行增容
 129			  if (_capacity == _size)
 130			  {
 131				  reserve(_capacity == 0 ? 4 : _capacity * 2);
 132				  
 133			  }
 134			  _str[_size] = ch;
 135			  ++_size;
 136			  _str[_size] = '\0';
 137	
 138	
 139			  //也可以复用insert函数,insert函数我将在后面实现
 140			  // 这样可以让函数更加健壮,复用也会让函数更加简洁
 141			  // insert(_size,ch);
 142		  }
 143	

🐎append

append其实和push_back类似,只不过append是拼接上多个字符,也就是一个字符串。这里的问题就出现在字符串,因为我们不知道字符串的长度是多少,就不知道要扩容到多少,是扩容到2倍还是3倍?这里要看的是传入的字符串的个数,我们的空间至少要让开到_size+len(字符串的长度),让空间能够满足最低的情况,让所有字符都有地方住。开好空间后,利用strcpy把字符串里面的数据拷贝过来即可。

	  //append  插入多个字符
 145		  void append(const char* str)
 146		  {
 			//这里我把字符串的长度和原来的长度加在一起作为len
 			//,也就是合起来的字符串的长度是len
 147			  size_t len = strlen(str)+_size;
 148			  if (len > _capacity)
 149			  {
 150				  reserve(len + 1);
 151			  }
 152			  strcpy(_str + len, str);
 153			  _size = len;
 154	
 155			  //也可以复用insert函数,insert函数我将在后面实现
 156	          // 这样可以让函数更加健壮,复用也会让函数更加简洁
 157	          // insert(_size,str);
 158		  }
 159	

🐎insert

insert分为两种情况,插入一个字符或者插入多个字符

	  //insert  插入一个字符
 161		  string& insert(size_t pos, char ch)
 162		  {
 163			  assert(pos < _capacity);
 164			  if (_capacity == _size)
 165			  {
 166				  reserve(_capacity == 0 ? 4 : _capacity * 2);
 167			  }
 168	
 169			  size_t end = _size+1;
 170			  while(pos<end)
 171			  {
 172				  _str[end ] = _str[end-1];
 173				  --end;
 174			  }
 175			  _str[pos] = ch;
 176			  _size++;
 177			  return *this;
 178	
 179		  }
 180	
//insert  插入多个字符
 182		  string& insert(size_t pos, const char* str)
 183		  {
 184			  assert(pos < _capacity);
 185			  size_t len = strlen(str);
 186			  if (_size+len> _capacity)
 187			  {
 188				  reserve(_size+len);
 189			  }
 190			  size_t end = _size + len;
 191	
 192			  //往后挪动n个字符
 193				while(end>pos+len-1)
 194			  {
 195				  _str[end] = _str[end -len];
 196				  --end;
 197			  }
 198				strncpy(_str + len, str, len);
 199				_size += len;
 200	
 201				return *this;
 202		  }
 203	
 204	

以下是插入多个字符的图解,插入单个字符的也是同样的道理。
在这里插入图片描述

🐎earse

我们要删除pos位置开始的len个字符
分成两种情况

  1. pos位置之后的所有位置都被删除,则需要把\0置于pos位置,_size=pos即可
  2. pos位置之后的字符只删除一部分,删除的部分形成的空位需要让后面的字符补上。

在这里插入图片描述

	  string& earse(size_t pos, size_t len=npos)
 228		  {
 229			  assert(pos < _capacity);
 230			  //从pos开始,直接全部删完,删到最后
 231			  if (len == npos || pos + len >= _capacity)
 232			  {
 233				  reserve(pos);
 234				  _str[pos] = '\0';
 235			  }
 236			  //只删一部分
 237			  else
 238			  {
 239				  size_t begin = pos + len;
 240				  while(begin<=_size)
 241				  {
 242					  _str[pos] = _str[pos + len];
 243					  begin++;
 244					  //pos++;我认为这里是pos也可以,把后面的字符往前覆盖就行
 245				  }
 246				  _size -= len;
 247			  }
 248			  return *this;
 249		  }
 250	

下面看一下图解
在这里插入图片描述

🐎find

利用的是strstr函数查找字符串,找到了就返回字符串的第一个字符的地址,没有找到返回npos
分成两种情况:

  1. 找一个字符
    1.1 对于这种情况,直接返回pos即可
  2. 找一个字符串
    2.2 对于这种情况,找到字符串后,我们需要返回第一个字符的下标,通过指针差值确定目标字符串的位置。
	  //find
 252		  //找一个字符,从0开始找,如果能找到就返回他的下标,如果找不到就返回npos
 253		  size_t find(const char ch,size_t pos=0)
 254		  {
 255			  for (; pos < _size; ++pos)
 256			  {
 257				  if (_str[pos] == ch)
 258					  return pos;
 259			  }
 260			  return npos;
 261		  }
 262	
//find
 263		  //找一个字符串,从0开始找,如果能找到就返回他的下标,如果找不到就返回npos
 264		  size_t find(const char* str, size_t pos = 0)
 265		  {
 266			  const char* p = strstr(_str + pos, str);
 267			  //strstr找一个字符串中某个子串是否存在,如果存在就返回这个子串的地址,不存在就返回null
 268			  //定义:strstr(str1,str2) 函数用于判断字符串str2是否是str1的子串
 269			  //如果是,则该函数返回str2在str1中首次出现的地址;否则,返回NULL。 
 270			  if (p == nullptr)
 271				  return npos;
 272			  else
 273				  return p-_str;
 274			  //为什么要减去_str
 275			  //答:因为这个函数最后要返回的是这个字符串的首元素的下标
 276			  // 直接返回p是一个地址,而指针减指针就是字符个数
 277			  // 之前有一个求字符串的长度的题   就用到了指针减指针
 278		  }
 279	

🐎比较大小函数

这里我把所有的比较类函数放在一起实现,他们的核心思想都是一样的

  1. strcmp函数比较字符串的大小,他把字符转换成对应的ASCLL码值,然后比较大小。如果前面的字符大则返回一个正数,如果相等就返回0,如果后面的字符大就返回一个负数。
  2. 这里复用了c_str函数,极大的提高了程序的健壮性,如果需要修改某处,只要修改<和=即可,因为其他的都是复用出来的。
bool operator<(const string& s1, const string& s2)
 321	{
 322		//strcmp比较字符串的大小,如果s1大就返回1,s1小就返回-1,相等就返回0
 323		return strcmp(s1.c_str(),s2.c_str())<0;
 324	}
 325	
 326	bool operator==(const string& s1, const string& s2)
 327	{
 328		return strcmp(s1.c_str(), s2.c_str()) == 0;
 329	}
 330	
 331	bool operator<=(const string& s1, const string& s2)
 332	{
 333		return s1 < s2 || s1==s2 ;
 334	}
 335	
 336	bool operator>(const string& s1, const string& s2)
 337	{
 338		return !(s1 <= s2) ;
 339	}
 340	
 341	bool operator>=(const string& s1, const string& s2)
 342	{
 343		return !(s1<s2);
 344	}
 345	
 346	bool operator!=(const string& s1, const string& s2)
 347	{
 348		return !( s1 == s2);
 349	}
 350	
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值