深入篇【C++】手搓模拟实现string类(详细剖析常见的各接口):【400行代码实现】

news2024/9/23 7:32:30

深入篇【C++】手搓模拟实现string类(包含常见的各接口):【400行代码实现】

  • 【string类模拟实现完整代码】
  • Ⅰ.构造/析构
      • 1.string()
      • 2.operator=
      • 3.~string()
  • Ⅱ.访问遍历
      • 1.operator[]
      • 2.iterator
      • 3.范围for
  • Ⅲ.增操作
      • 1.push_back()
      • 2.append()
      • 3.operator+=
      • 4.insert()
  • Ⅳ.删操作
      • 1.erase()
      • 2.clear()
  • Ⅴ.查操作
      • 1.size()
      • 2.find()
      • 3.c_str()
  • Ⅵ.改操作
      • 1.substr()
      • 2.reserve()
      • 3.resize()
  • Ⅶ.比较操作
      • 1.operator<
      • 2.operator==
      • 3.其他比较
  • Ⅷ.流插入/提取
      • 1.operator<<
      • 2.operator>>

【string类模拟实现完整代码】

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <assert.h>
#include <string.h>

namespace tao
{
	
	class string
	{
 	  public:
		  typedef char* iterator;//普通迭代器
		  typedef  const char* const_iterator; //const迭代器

		  //无参构造---不代表没有数据,是有一个\0的,但没有大小,因为\0不计入大小计算中
		 /* string()
		  {
			  _size = _capacity = 0;
			  _str = new char[1];
			  _str[0] = '\0';
		  }*/
		  string(const char* str="")//可以与无参构造一起复用。全缺省//常量字符串后面默认有\0
		  {
			  _size = strlen(str);
			  _capacity = _size;
			  //_str=str
			  //不能直接将str赋给_str,因为str是const修饰的,库里规定的,不可以被修改,而_str不是const类型的
			  //而需要开辟一块跟str一样大的空间,然后拷贝给_str
			  _str = new char[_capacity + 1];
			  memcpy(_str, str,_size+1);
	       }
		  string (const string& s)//深拷贝
		  {
			  _str = new char[s._capacity+1];
			  memcpy(_str, s._str,s.size()+1);
			  _size = s._size;
			  _capacity = s._capacity;
		  }
		  //s1=s3
		  string& operator=(const string& s)
		  {
			  if (*this != s)
			  {
				  char* tmp = new char[s._capacity + 1];
				  memcpy(tmp, s.c_str(), s._size);
				  delete[] _str;
				  _str = tmp;
				  _size = s.size();
				  _capacity = s._capacity;
				
			  }
			  return *this;
		  }
		  ~string()
		  {
			  delete[] _str;
			  _str = nullptr;
			  _size = _capacity = 0;
		  }
		 const char* c_str() const
	      {
			  return _str;
		  }
		  //要实现遍历,首先需向大小
		 size_t size() const //一般只读,不给修改
		 {
			 return _size;
		 }
		 char& operator[](int pos)//可以引用返回,因为出了函数值还在
		 {
			 assert(pos < _size);
			 return _str[pos];
		 }
		 //有两种重载类型,一种是上面的另一种是const修饰的对象,只读,不给修改的
		 const char& operator[](int pos) const
		 {
			 assert(pos < _size);
			 return _str[pos];
		 }
		 //通过迭代器进行遍历,迭代器是一种类型,是string类里的一种类型,可以是内部类,也可以是自定义的。
		 //我们在这里自定义一个iterator。
		 iterator begin()//begin返回的是指向开头位置的迭代器
		 {
			 return _str;
		 }
		 iterator end()//end返回的是指向最后一个字符的下一个位置
		 {
			 return _str + _size;
		 }
		 const_iterator begin()const
		 {
			 return _str;
		 }
		 const_iterator end()const
		 {
			 return _str + _size;
		 }

		 void reserve(size_t n)
		 {
			 if (n > _capacity)
			 {
				 char* temp = new char[n + 1];
				 memcpy(temp, _str,_size+1);
				 delete[] _str;
				_str = temp;
				_capacity = n;
			 }
		 }
//增
		 void push_back(char ch)//尾插首秀按需要考虑是否需要扩容--->扩容最好用reserve来扩容
		 {
			 if (_size >= _capacity)
			 {
				 //可以直接扩容2倍,但要注意一种情况,当为空串时
				 reserve(_capacity == 0 ? 4 : 2 * _capacity);
				
			 }
			 _str[_size++] = ch;
			 _str[_size] = '\0';
		 }
		 void append(const char * str)
		 {
			 size_t len = strlen(str);
			 if (_size + len > _capacity)
			 {
				 //这个不可以直接2倍扩容,因为可能2倍扩容后的容量还不够
				 //至少需要扩容到_size+len大小
				 reserve(_size + len);
				 memcpy(_str + _size, str,len+1);
				 _size += len;
			 }
		 }
		 string& operator+=(char ch)
		 {
			 push_back(ch);
			 return *this;
		 }
		 string& operator+=(const char* str)
		 {
			 append(str);
			 return *this;
		 }
		 void insert(size_t pos, size_t n, char ch)
		 {
			 //第一步检查pos的合法性
			 assert(pos <= _size);
			 //检查是否需要扩容---》直接用reserve扩容
			 if (_size+n > _capacity)
			 {
				 reserve(_size + n);
			 }
			 //第三步挪动数据
			 size_t end = _size;
			 //这里有一个坑,当pos位置为0时,也就是头插时会出问题,因为while循环的调试是end>=pos
			 //也就是end需要小于0时才可以停下来,当end=0时,进入循环里,end--,后不会变成-1,因为end是size_t,会变成很大是数
			 //所以有问题,解决方法是再加上一个条件,那就是end>=pos&&end!=npos时当满足这两个条件时
			 while (end >= pos&&end!=npos)
			 {
				 _str[end + n] = _str[end];
				 end--;
			 }
			 for (int i = 0; i < n; i++)
			 {
				 _str[pos + i] = ch;
			 }
			  _size += n;

		 }

		 void insert(size_t pos, const char* str)
		 {
			 //第一步检查pos的合法性
			 assert(pos <= _size);
			 //检查是否需要扩容---》直接用reserve扩容
			 size_t len = strlen(str);
			 if (_size + len > _capacity)
			 {
				 reserve(_size + len);
			 }
			 //挪动数据
			 size_t end = _size;
			 while (end >= pos && end != npos)
			 {
				 _str[end + len] = _str[end];
				 end--;
			 }
			 for (int i = 0; i < len; i++)
			 {
				 _str[pos + i] = str[i];
			 }
			 _size += len;
		 }

//删
		 void erase(size_t pos, size_t len=npos)
		 {
			 assert(pos <= _size);
			 if (len == npos || pos + len > _size)//删除完
			 {
				 _str[pos] = '\0';
				 _size = pos;
				 _str[_size] = '\0';
			 }
			 else
			 {
				 size_t end = pos + len;
				 while (end <= _size)
				 {
					 _str[pos++] = _str[end++];
					
				 }
				 _size -= len;
			 }
			
		 }
		 void clear()
		 {
			 _str[0] = '\0';
			 _size = 0;
		 }
//查/改
		 size_t find(char ch, size_t pos = 0)
		 {
			 assert(pos <= _size);
			 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);
			 const char* tmp = strstr(_str+pos, str);//返回的是指向str位置的指针
			 if (tmp == nullptr)
			 {
				 return npos;
			 }
			 return tmp - _str;
		 }
		 string substr(size_t pos,size_t len=npos)
		 {
			 assert(pos <= _size);
			 size_t n = len;
			 if (len == npos || pos + len > _size)
			 {
				 n = _size - pos;
			 }
			 string tmp;
			 for (size_t i = pos; i < n + pos; i++)
			 {
				 tmp += _str[i];
			 }
			 return tmp;
		 }
		 void resize(size_t n,char ch='\0')
		 {
			 if (n < _size)
				 _size = n;
			 else
			 {
				 reserve(n);//不管n是否大于capacity都给他扩容到n即可
				 for (size_t i = _size; i < n; i++)
				 {
					 _str[i] = ch;
				 }
				 _size = n;
				 _str[_size] = '\0';
			 }
		 }
//比较大小
		 bool operator<(const string& s)
		 {
			 int i1 = 0;
			 int i2 = 0;
			 while (i1 < _size && i2 < s.size())
			 {
				 if (_str[i1] < _str[i2])
				 {
					 return true;
				 }
				 else if (_str[i1] > _str[i2])
				 {
					 return false;
				 }
				 else
				 {
					 ++i1;
					 ++i2;
				 }
			 }
			 //"tao"  "taox"
			 //taox   tao
			 //tao tao
			 if (i1 == _size && i2 != s.size())
			 {
				 return true;
			 }
			 else
			 {
				 return false;
			 }
		 }
		 bool operator==(const string& s)
		 {
			 return _size == s.size() && memcmp(_str, s.c_str(), _size);
		 }
		 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);
		 }
		 bool operator!=(const string& s)
		 {
			 return !(*this == s);
		 }
	  private:
		  char* _str;
		  size_t _size;
		  size_t _capacity;
		  public:

		  size_t static npos;
	};

	size_t string::npos = -1;

	/* ostream& operator<<(ostream& out, const string& s)
	{

		for (auto ch : s)
		{
			out << ch;
		}
		return out;

	}*/
	 
	istream& operator>>(istream& in, string& s)
	{
		//每次进入流提取之前都要把之前的缓存清理掉。
		s.clear();
		char ch;
		ch = in.get();//in流提取,会跳过空格和换行
		//但是这样很麻烦,需要不断的扩容,从小扩到大。
		// 
		//还要清理字符之前的空格和换行
		while (ch == ' ' || ch == '\n')
		{
			ch = in.get();
		}
		//所有又采取一种方法,把ch提取的字符县附近一个数组里面
		//这样扩容就不会频繁的扩容,一段一段的扩容,累加一定量再放进去
		char buf[128];
		int i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buf[i++] = ch;
			
			if (i == 127)
			{
				buf[i] = '\0';
				s += buf;
				i = 0;
			}
			ch = in.get();
		}
		if (i != 0)
		{
			buf[i] = '\0';
			s += buf;
		}
		return in;
	}

};


//c的字符数组以\0为终止长度
//string不看\0,以size为终止长度

Ⅰ.构造/析构

//成员变量
  private:
		  char* _str;
		  size_t _size;
		  size_t _capacity;

1.string()

1.无参构造和带参构造可以一起复用。
2.构造初始化 不可以这样写_str=str。
不能直接将str赋给_str,因为str是const修饰的,库里规定的,不可以被修改,而_str不是const类型的,而需要开辟一块跟str一样大的空间,然后拷贝给_str。这才是正确的做法。
3.拷贝构造采取的是深度拷贝,一般分成三步。
①首先需要给_str开辟一块跟要拷贝对象一样大的空间。
②将要拷贝的对象的值拷贝给要创建的对象
③初始化对象的大小与容量。

  //无参-----不代表没有数据,是有一个\0的,但没有大小,因为\0不计入大小计算中
		 /* string()
		  {
			  _size = _capacity = 0;
			  _str = new char[1];
			  _str[0] = '\0';
		  }*/
//带参构造----可以与无参构造一起复用。全缺省//常量字符串后面默认有\0
		  string(const char* str="")
		  {
			  _size = strlen(str);
			  _capacity = _size;
			  _str = new char[_capacity + 1];
			  memcpy(_str, str,_size+1);
	       }
//拷贝构造	       
	       string (const string& s)//深度拷贝
		  {
			  _str = new char[s._capacity+1];
			  memcpy(_str, s._str,s.size()+1);
			  _size = s._size;
			  _capacity = s._capacity;
		  }

2.operator=

赋值运算符重载的做法跟拷贝类似,但有一步不同。那就是需要释放_str对象的空间,因为拷贝,原对象是没有空间的,所有不需要释放。
①首先开辟一块跟赋值对象一样大小的空间,由temp指向。
②然后将赋值对象的值拷贝到tmp里。
③将被赋值对_str象的空间释放。
④最后将tmp赋给_str.
⑤将_str对象的大小和容量都与s对象一致。

  //s1=s3
		  string& operator=(const string& s)
		  {
			  if (*this != s)
			  {
				  char* tmp = new char[s._capacity + 1];
				  memcpy(tmp, s.c_str(), s._size);
				  delete[] _str;
				  _str = tmp;
				  _size = s.size();
				  _capacity = s._capacity;
				
			  }
			  return *this;
		  }

3.~string()

析构,要与new[]对应用delete[]释放空间。
然后将指针置空,大小容量置0

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

Ⅱ.访问遍历

1.遍历,首先需要知道对象的大小,这里直接用了,因为size的写法在下面,可以去下面看。
2.而遍历的方法有多种,其中有下标+[]遍历,利用迭代器遍历,或者范围for遍历。
3.下标+[pos]方法首先需要判断pos位置是否合法。
4.下面给了两个重载,一个是用于普通对象遍历,一个用于const修饰的对象遍历。也就是一个可读可写,另一个只读不能写。

1.operator[]

//可以引用返回,因为出了函数值还在
          char& operator[](int pos)
		 {
			 assert(pos < _size);
			 return _str[pos];
		 }
 //有两种重载类型,一种是上面的另一种是const修饰的对象,只读,不给修改的
		 const char& operator[](int pos) const
		 {
			 assert(pos < _size);
			 return _str[pos];
		 }

2.iterator

1.iterator其实在string里来说本质上可以看成一个指针类型。
2.而想要定义一个新的类型,要么使用内部类,或者自己typedef定义一个,这里iterator是自己定义。
3.定义完iterator类型后,就可以写begin()和end()了。begin返回的是指向开头位置的迭代器,end返回的是指向最后一个字符的下一个位置。

//我们在这里自定义一个iterator。
  typedef char* iterator;//普通迭代器
  typedef  const char* const_iterator; //const迭代器
 //通过迭代器进行遍历,迭代器是一种类型,是string类里的一种类型,可以是内部类,也可以是自定义的。

		 iterator begin()//begin返回的是指向开头位置的迭代器
		 {
			 return _str;
		 }
		 iterator end()//end返回的是指向最后一个字符的下一个位置
		 {
			 return _str + _size;
		 }
 // const修饰的对象进行遍历
		 const_iterator begin()const 
		 {
			 return _str;
		 }
		 const_iterator end()const
		 {
			 return _str + _size;
		 }

3.范围for

范围for其实底层就是迭代器。
如果迭代器写正确了,那么范围for就可以用了。

这里可以演示一下范围for如何使用。

    tao::string::iterator it = s1.begin();
	while (cit != s1.end())
	{
	
		cout << (*cit);
		cit++;
	}

	for (auto ch : s1)//范围for底层其实就是迭代器
	{
		cout << ch << endl;
	}

Ⅲ.增操作

1.在尾插之前我们需要干什么呢?首先需要考虑是否需要扩容。
2.怎么扩容呢?我们可以直接利用reserve来扩容,这里直接使用reserve()。
3.当实际数据的大小超过容量大小时,我们就需要进行扩容。扩多大呢?一般是扩2倍。
4.但要考虑一种情况那就是一开始容量为0,那扩容2倍后还是0,所以需要讨论一下。
5.扩完容,就可以将字符插入到尾部了,插完后,size需要++,要将最后一位放入’\0’。因为本来str最后一位就是’\0’,现在被覆盖了,就需要手动添加上了。

1.push_back()

	 void push_back(char ch)//尾插首秀按需要考虑是否需要扩容--->扩容最好用reserve来扩容
		 {
			 if (_size >= _capacity)
			 {
				 //可以直接扩容2倍,但要注意一种情况,当为空串时,就不能用2倍乘了,直接赋给4即可。
				 reserve(_capacity == 0 ? 4 : 2 * _capacity);
			 }
			 _str[_size++] = ch;
			 _str[_size] = '\0';
		 }

2.append()

1.跟尾插一个字符一样,现在要尾插一个字符串,第一步仍然需要考虑是否要扩容。
2.扩容还是用我们的reserve来扩容,那扩容多大呢?因为尾插的是一个字符串(字符串长度为len),如果要扩2倍的话也有可能扩容后还是不够,所以至少要扩容到size+len个长度。
3.扩容完,就可以将要尾插的字符串直接用memcpy拷贝过去,然后需要将大小控制一致。

void append(const char * str)
		 {
			 size_t len = strlen(str);
			 if (_size + len > _capacity)
			 {
				 //这个不可以直接2倍扩容,因为可能2倍扩容后的容量还不够
				 //至少需要扩容到_size+len大小
				 reserve(_size + len);
			 }
			 	 memcpy(_str + _size, str,len+1);
				 _size += len;
		 }

3.operator+=

1.其实尾插一个字符或者字符串最喜欢的不是push_back和append,最好用的是+=。
2.+=运算符重载其实就是直接复用这两个函数即可。

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

4.insert()

1.insert成员函数,这里写了两个,一个是用来插入字符的,一个用来插入字符串。
2.功能:在某个位置pos插入n个字符/在某个位置插入一个字符串。
①首先第一步需要检查pos位置的合法性。
②第二步检查是否需要扩容,直接用reserve()扩容即可。
③第三步挪动数据,从后往前挪动。要插入n个字符,从后面开始的位置上每个字符就需要挪动n个位置,这样pos位置上才可以留出n个位置。
④将n个字符插入到留出的位置上去。大小需要控制一致。
3.注意当pos为0时的坑,需要增加额外条件来判断,end>=pos&&end!=npos.。npos是一个静态变量,需要在类里声明,类外定义喔。

void insert(size_t pos, size_t n, char ch)
		 {
			 //第一步检查pos的合法性
			 assert(pos <= _size);
			 //检查是否需要扩容---》直接用reserve扩容
			 if (_size+n > _capacity)
			 {
				 reserve(_size + n);
			 }
			 //第三步挪动数据
			 size_t end = _size;
			 //这里有一个坑,当pos位置为0时,也就是头插时会出问题,因为while循环的调试是end>=pos
			 //也就是end需要小于0时才可以停下来,当end=0时,进入循环里,end--,后不会变成-1,因为end是size_t,会变成很大是数
			 //所以有问题,解决方法是再加上一个条件,那就是end>=pos&&end!=npos时当满足这两个条件时
			 while (end >= pos&&end!=npos)
			 {
				 _str[end + n] = _str[end];
				 end--;
			 }
			 for (int i = 0; i < n; i++)
			 {
				 _str[pos + i] = ch;
			 }
			  _size += n;

		 }

		 void insert(size_t pos, const char* str)
		 {
			 //第一步检查pos的合法性
			 assert(pos <= _size);
			 //检查是否需要扩容---》直接用reserve扩容
			 size_t len = strlen(str);
			 if (_size + len > _capacity)
			 {
				 reserve(_size + len);
			 }
			 //挪动数据
			 size_t end = _size;
			 while (end >= pos && end != npos)
			 {
				 _str[end + len] = _str[end];
				 end--;
			 }
			 for (int i = 0; i < len; i++)
			 {
				 _str[pos + i] = str[i];
			 }
			 _size += len;
		 }

Ⅳ.删操作

1.erase()

1.erase()删除某个位置len个字符,len给了缺省值npos,也就是不写长度时,默认从pos位置一直删除到尾。
2.所以会出现两种情况:删除后面的全部字符/删除后面的部分字符。这取决于len是否给值和len是否大于size-pos
3.当没有给len值是使用缺省值,那将会删除完后面的,当len大于size-pos时,也会将后面的删除完。将后面删除完并不需要真的将后面的数据全部删除,只需要将pos位置改成’\0’即可,并将大小修改即可。
4.而pos位置后面不完全删除,就需要挪动数据覆盖了。从后往前覆盖。最后需要将大小减去len长度。

void erase(size_t pos, size_t len=npos)
		 {
			 assert(pos <= _size);
			 if (len == npos || pos + len > _size)//删除完
			 {
				 _str[pos] = '\0';
				 _size = pos;
				 _str[_size] = '\0';
			 }
			 else
			 {
				 size_t end = pos + len;
				 while (end <= _size)
				 {
					 _str[pos++] = _str[end++];
					
				 }
				 _size -= len;
			 }
			
		 }

2.clear()

1.删除数据并不需要真的删除,只需要将第一个位置上修改成’\0’即可。
2.大小修改成0.

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

Ⅴ.查操作

1.size()

	  //要实现遍历,首先需向大小
		 size_t size() const //一般只读,不给修改
		 {
			 return _size;
		 }

2.find()

1.find()可以从某个位置开始查找某个字符,返回改字符的位置。或者查找某个字符串,返回该字符串的位置。
2.从某个位置开始查找字符:
①首先需要判断pos位置是否合法。
②直接利用遍历从pos位置开始查找该字符ch
③如果找到直接返回该下标,如果没有找到则返回npos。
3.从某个位置开始查找字符串。
①首先需要判断pos位置合法性
②可以直接利用string.h库函数strstr来查找字符串,找到智慧返回指向该字符起始位置的指针。
③指针-指针等于长度,所以tmp减去起始位置就是tmp的位置。

size_t find(char ch, size_t pos = 0)
		 {
			 assert(pos <= _size);
			 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);
			 const char* tmp = strstr(_str+pos, str);//返回的是指向str位置的指针
			 if (tmp == nullptr)
			 {
				 return npos;
			 }
			 return tmp - _str;
		 }

3.c_str()

返回C格式的字符串

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

Ⅵ.改操作

1.substr()

1.substr()截取部分字符串,从pos位置上截取长度为len的字符串。len给了缺省值npos,说明如果不给定长度,则默认从pos位置一直截取到尾。所以这里需要讨论一下,有两种情况:从pos位置往后全部截取/从pos位置往后部分截取。
2.当len不给定长度时,则全部截取,当len大于size-pos时,则全部截取。如果给定长度,并且长度小于size-pos时则部分截取。
3.定义一个新的string对象,将pos位置后面len长度的字符串尾插到对象上。

string substr(size_t pos,size_t len=npos)
		 {
		 //pos位置合法性
			 assert(pos <= _size);
			 size_t n = len;//需要讨论一下len的长度是否是缺省值或者大于size-pos
			 if (len == npos || pos + len > _size)
			 {
				 n = _size - pos;//以上两种情况都是从pos位置截取完,所以只要让n=size-pos就可以截取完。不然n的长度就是给定的len长度。
			 }
			 string tmp;
			 for (size_t i = pos; i < n + pos; i++)
			 {
				 tmp += _str[i];
			 }
			 return tmp;
		 }

2.reserve()

1.reserve()预留空间。主要用来扩容。
2.当实际数据大小大于容量时就要进行扩容。
3.扩容逻辑也很简单:其实就是异地扩容,重新开出一块空间。
①:扩容n个大小,那就开辟n个大小的空间,不过这里需要n+1,这一个位置是留给’\0’的。
②开完空间后,就可以将原空间数据拷贝过来
③释放原空间。
④将开辟的空间再赋给_str.最后容量需要保持一致。

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

3.resize()

1.resize(n,ch=‘\0’)修改数据的大小为n。多出的数据用ch来填充。
当ch不给定时,默认用0填充,当ch给定时用字符ch填充。
2.当n大于原来的size时,size要改变,而且capacity也要改变,也就是要扩容。当n小于size时,size需要改变,但capcaity不用改变。
3.扩容完将字符ch填充到原size位置后面。最后size大小需要保持一致,最后一位需要手动写上’\0’

void resize(size_t n,char ch='\0')
		 {
			 if (n < _size)
				 _size = n;
			 else
			 {
				 reserve(n);//不管n是否大于capacity都给他扩容到n即可
				 //因为当n小于capacity时reserve也不会改变capacity.
				 
				 for (size_t i = _size; i < n; i++)
				 {
					 _str[i] = ch;
				 }
				 _size = n;
				 _str[_size] = '\0';
			 }
		 }

Ⅶ.比较操作

1.operator<

1.string对象比较跟字符串比较是一样的,这里最好不要直接用strcpy来比较,有很多坑,这里最好自己手动一个一个比较。每个位置进行一一比较。当相同时就一起再往后走,当小于时就返回。
2.因为两个字符串对象大小不一定相等,比较大小肯定是要按照长度小的来比,因为如果按照长度长的比较那么就越界了。
3.要考虑下三种情况 tao tao 或者 taoxx tao 或者 tao taoxx。前面都一样。后面只需要判断一下如果第二个字符串长的话那么一定第一个字符串小于第二个字符串。其他两种情况都是flase。

 bool operator<(const string& s)
		 {
			 int i1 = 0;
			 int i2 = 0;
			 while (i1 < _size && i2 < s.size())
			 {
				 if (_str[i1] < _str[i2])
				 {
					 return true;
				 }
				 else if (_str[i1] > _str[i2])
				 {
					 return false;
				 }
				 else
				 {
					 ++i1;
					 ++i2;
				 }
			 }
			 //"tao"  "taox"
			 //taox   tao
			 //tao tao
			 if (i1 == _size && i2 != s.size())
			 {
				 return true;
			 }
			 else
			 {
				 return false;
			 }
		 }

2.operator==

1.两个相同的字符串肯定长度一样长,并且比较大小都一样。

 bool operator==(const string& s)
		 {
			 return _size == s.size() && memcmp(_str, s.c_str(), _size);
		 }

3.其他比较

1.写了前面两个,后面的比较都可以复用前面的两个。

 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);
		 }
		 bool operator!=(const string& s)
		 {
			 return !(*this == s);
		 }

Ⅷ.流插入/提取

1.operator<<

1.流插入cout,利用运算符<<重载,要注意这个函数不能写成成员函数,因为this指针会抢占左操作数,而左操作数应该是流插入cout。所以必须写在类外。不能写在类里。
2.而写在类外的话,想要访问类里的私有成员就得需要使用友元,而这里可以不需要使用友元就可以访问私有成员,那就是一个一个字符打印就可以访问了。

 ostream& operator<<(ostream& out, const string& s)
	{

		for (auto ch : s)
		{
			out << ch;
		}
		return out;

	}

2.operator>>

1.跟流插入操作符一样,流提取也不能写在类里,要写在类外。
2.流插入需要考虑很多方面:
①cin和scanf当遇到空格或换行都会停止读取。
②cin.get()函数可以读取不管是换行还是空格。我们这里使用get。
③读取字符之前,要清理字符之前的空格和换行,这样才可以读取到。
④每次读取之前都需要将缓冲区内容清空,不然下一次读取就会将上一次的内容也读取下来。
3.这里因为如果每次都读取一个字符会很麻烦,因为会不断的扩容,如果提取的字符很长,就会从小到大扩容。所以这里采取的是将提取的字符放入一个数组里,当提取部分或者全部提取之后再放进去。这样就可以减少扩容次数了。注意最后一位要放入’\0’.

istream& operator>>(istream& in, string& s)
	{
		//每次进入流提取之前都要把之前的缓存清理掉。
		s.clear();
		char ch;
		ch = in.get();//in流提取,会跳过空格和换行
		//但是这样很麻烦,需要不断的扩容,从小扩到大。
		// 
		//还要清理字符之前的空格和换行
		while (ch == ' ' || ch == '\n')
		{
			ch = in.get();
		}
		//所有又采取一种方法,把ch提取的字符县附近一个数组里面
		//这样扩容就不会频繁的扩容,一段一段的扩容,累加一定量再放进去
		char buf[128];
		int i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buf[i++] = ch;
			
			if (i == 127)
			{
				buf[i] = '\0';
				s += buf;
				i = 0;
			}
			ch = in.get();
		}
		if (i != 0)
		{
			buf[i] = '\0';
			s += buf;
		}
		return in;
	}

。。。。。。。请添加图片描述请添加图片描述请添加图片描述。。。。。。。。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/739607.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Harbor未授权创建管理员

人处在幸福与不幸交织的矛盾之中&#xff0c;反而使内心有一种更为深刻的痛苦&#xff0c;看来近在眼前的幸福而实际上又远得相当渺茫&#xff0c;海市蜃楼。放不得抓不住。 漏洞描述 近日&#xff0c;镜像仓库Harbor爆出任意管理员注册漏洞&#xff0c;攻击者在请求中构造特…

eNSP-VLAN多端口成员模式+DHCP

VLAN多端口成员模式DHCP 文章目录 VLAN多端口成员模式DHCP一、题目要求二、题目分析三、拓扑结构四、基本配置五、测试验证1.网段测试2.访问测试 一、题目要求 1、PC1和pc3所在接口为Access接口&#xff0c;PC2/4/5/6处于同一网段&#xff0c;其中PC2可以访问PC4/5/6&#xff…

Webkit内核探究——Webkit CSS实现

文章目录 前言1、CSS是什么2、CSS实现模型3、CSS默认样式表4、CSS解析5、CSS如何作用于Render Tree 前言 CSS在Webkit中的实现属于相对独立的一个模块&#xff0c;注意这里说的是相对。 CSS在Webkit中的作用自然是不言而喻的&#xff0c;在Web早期&#xff0c;文档的结构和样…

【运维工程师学习五】数据库

【运维工程师学习五】数据库 1、常用的关系型数据库2、C/S结构3、MariaDB图形客户端4、安装MariaDB5、启动MariaDB及验证启动是否成功6、验证启动——端口7、验证启动——进程8、MariaDB配置文件路径主配置文件解读&#xff1a; 9、MariaDB的配置选项10、MariaDB客户端连接1、在…

Windows下 Oracle 12c 安装保姆级图文详解

Windows下 Oracle 12c 安装步骤如下&#xff1a; 1、将压缩包“winx64_12c_database_1of2.zip“和“winx64_12c_database_2of2.zip”解压到同一目录“database”目录。 2、双击“database”目录下的“setup.exe"&#xff0c;软件会加载并初步校验系统是否可以达到了数据…

华为云出品《深入理解高并发编程:Java线程池核心技术》电子书发布

系统拆解线程池核心源码的开源小册 透过源码看清线程池背后的设计和思路 详细解析AQS并发工具类 点击下方链接进入官网&#xff0c;右上角搜索框搜索“《深入理解高并发编程&#xff1a;Java线程池核心技术》” 即可获取下载。 https://auth.huaweicloud.com/authui/login…

01-线性表 (数据结构和算法)

要点&#xff1a; 程序 数据结构 算法 一、数据结构的概述 程序 数据结构 算法 数据结构&#xff1a;计算机存储、组织数据的方式 算法&#xff1a;处理数据的方式 1.1 基本概念和术语 1、数据 数据&#xff08;data&#xff09;&#xff1a;所有能够输入到计算机中…

【Method】稀疏与压缩感知 | 图像稀疏性及压缩感知方法白话讲解

【Method】稀疏与压缩感知 | 图像稀疏性及压缩感知方法白话讲解 文章目录 【Method】稀疏与压缩感知 | 图像稀疏性及压缩感知方法白话讲解1. 为什么图像是可压缩的&#xff1a;图像空间的广阔2. 什么是Sparsity&#xff1f;3.压缩感知&#xff1a;简介4.压缩感知&#xff1a;数…

matlab学习指南(3):最全MATLAB工具箱Toolbox下载地址大汇总

&#x1f305;*&#x1f539;** φ(゜▽゜*)♪ **&#x1f539;*&#x1f305; 欢迎来到馒头侠的博客&#xff0c;该类目主要讲数学建模的知识&#xff0c;大家一起学习&#xff0c;联系最后的横幅&#xff01; 喜欢的朋友可以关注下&#xff0c;私信下次更新不迷路&#xff0…

火车头采集器AI伪原创【php源码】

本文介绍火车头采集器AI伪原创&#xff0c;对于新媒体从业者来说&#xff0c;会写文章是最基本的职业技能&#xff0c;而伪原创是我们经常使用的技能。今天我要讲的是SEO标兵如何在伪原创上创作文章。 首先&#xff0c;原创性永远是最好的&#xff0c;更受读者欢迎。伪原创的出…

Microsoft Dynamics 365:VS2019引用BC发布的SOAP服务

1、搜索网页服务 2、点击新建 3、选择对象类型&#xff1a;页面、单元、查询&#xff0c;输入ID&#xff0c;勾选即可发布服务 4、复制SOAP URL到浏览器里看看是否可以访问&#xff0c;这样就OK的 5、 右键添加服务引用 6、选择高级 7、添加web引用 8、服务地址粘贴进去查找服…

云原生监控——VictoriaMetrics

1.简介 VictoriaMetrics是一个快速高效且可扩展的监控解决方案和时序数据库&#xff0c;可以作为Prometheus的长期远端存储&#xff0c;具备的特性有&#xff1a; 支持prometheus查询api&#xff0c;同时实现了一个metricsql 查询语言支持全局查询视图&#xff0c;支持多prom…

uniapp引用leaflet地图实现方案

最近在做uniapp实现的移动端app&#xff0c;其中一些模块需要gis地图&#xff0c;在最开始的时候我尝试了使用uniapp官方自带的map组件&#xff0c;但是非常不好用。 后来又引用了mars2d来实现&#xff0c;但是发现这种引用方式会出现一个bug&#xff0c;在浏览器当中使用的时候…

Blueprint —— 入门笔记

蓝图比C性能较慢&#xff1b; 蓝图起作用需在场景中创建实例&#xff1b; 在Event Graph内 按住右键&#xff0c;平移界面&#xff1b;滚动滚轮&#xff0c;缩放界面&#xff1b;按住左键节点&#xff0c;移动节点&#xff1b;右击&#xff0c;显示节点对话框&#xff1b;按住…

外部存储器接口(EMIF)

1 接口信号与控制寄存器 EMIF(External Memory Interface)外部存储器接口为DSP芯片与众多外部设备之间提供一种连接方式&#xff0c;EMIF最常见的用途就是同时连接FLASH和SDRAM。EMIF性能优良&#xff0c;跟外部SDRAM和异步器件连接时&#xff0c;具有很大的方便性和灵活性。根…

ModaHub魔搭社区:常用的相似性度量——浮点向量相似性度量和二进制向量相似性度量

目录 常用的相似性度量 浮点向量相似性度量 二进制向量相似性度量 总结 常用的相似性度量 如果没有相似性度量——计算两个向量之间距离的方法,再好的向量数据库也没有用。因为存在许多度量,我们在这里只讨论最常用的子集。 浮点向量相似性度量 最常见的浮点向量相似…

【数据分析 - 基础入门之pandas篇①】- pandas介绍

文章目录 前言一、pandas介绍二、pandas优势2.1 强大的数据结构支撑2.2 优点 三、pandas学习路线结语相关导读 前言 一、pandas介绍 pandas 是 Python 的 核心数据分析支持库 &#xff0c;提供了快速、灵活、明确的数据结构&#xff0c;旨在简单、直观地处理关系型、标记型数据…

【JUC进阶】11. BlockingQueue

目录 1、前言 2、BlockingQueue 2.1、ArrayBlockingQueue 2.1.1、take() 2.1.2、put() 2.2、LinkedBlockingQueue 2.3、PriorityBlockingQueue 2.4、SynchronousQueue 3、简单使用 3.1、创建ArrayBlockingQueue 3.2、Demo 1、前言 对于并发程序而言&#xff0c;高性…

python: FileHelper

# encoding: utf-8 # 版权所有 2023 涂聚文有限公司 # 许可信息查看&#xff1a; # 描述&#xff1a; # Author : geovindu,Geovin Du 涂聚文. # IDE : PyCharm 2023.1 python 311 # Datetime : 2023/7/9 19:12 # User : geovindu # Product : PyCharm # Proj…

QT事件处理

设计一个闹钟&#xff0c;定时播报内容。 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include <QTimerEvent> #include <QDateTime> #include <QMessageBox> #include <QTextToSpeech> #include <QDebug> namespa…