STL中的string类的模拟实现【C++】

文章目录

  • 默认成员函数
    • 构造函数
    • 拷贝构造函数
  • 赋值运算符重载函数
  • 析构函数
  • begin
  • end
  • size
  • capacity
  • reserve
  • erase
  • resize
  • push_back
  • append
  • operator+=
  • insert
  • swap
  • substr
  • c_str
  • operator[ ]
  • find
  • clear
  • getline
  • >>运算符的重载
  • <<运算符的重载

默认成员函数

构造函数

构造函数设置为缺省参数,若不传入参数,则默认构造为空字符串。字符串的初始大小和容量均设置为传入C字符串的长度(不包括’\0’)

		//默认构造函数-全缺省
		string(const char * str ="")//常量字符串以\0为结束标志
		{
			_size = strlen(str);
			_capacity = strlen(str);//capacity存储有效字符,\0不是有效字符
				_str = new char [strlen(str) + 1];//strlen 计算字符不包含\0,但是需要多开一个空间给\0
				strcpy(_str, str);
		}

拷贝构造函数

在模拟实现拷贝构造函数前,我们应该首先了解深浅拷贝:

浅拷贝:拷贝出来的目标对象的指针和源对象的指针指向的内存空间是同一块空间。其中一个对象的改动会对另一个对象造成影响。
深拷贝:深拷贝是指源对象与拷贝对象互相独立。其中任何一个对象的改动不会对另外一个对象造成影响。

深拷贝写法一:

string(const string& s)
	:_str(new char[strlen(s._str) + 1]) //_str申请一块刚好可以容纳s._str的空间
	, _size(0)
	, _capacity(0)
{
	strcpy(_str, s._str);    //将s._str拷贝一份到_str
	_size = s._size;         //_size赋值
	_capacity = s._capacity; //_capacity赋值
}

先开辟一块足以容纳源对象字符串的空间,然后将源对象的字符串拷贝过去,接着把源对象的其他成员变量也赋值过去即可。因为拷贝对象的_str与源对象的_str指向的并不是同一块空间,所以拷贝出来的对象与源对象是互相独立的。

深拷贝写法二(推荐):

在这里插入图片描述

//第二种写法
string(const string& s)
	:_str(nullptr)
	, _size(0)
	, _capacity(0)
{
	string tmp(s._str); //调用构造函数,构造出一个C字符串为s._str的对象
	swap(tmp); //交换这两个对象
}

拷贝对象的_str与源对象的_str指向的也不是同一块空间,是互相独立的。

赋值运算符重载函数

与拷贝构造函数类似,赋值运算符重载函数的模拟实现也涉及深浅拷贝问题,我们同样需要采用深拷贝。下面也提供深拷贝的几种写法:

		//第一种写法
		//s1=s3
		string& operator=(const string& s)
		//	string& operator=( string *this ,const string& s)
		{
			if (this!= &s)//不是自己给自己赋值 
			{
				//开辟一块和s3一样大的空间tmp,将s3的内容拷贝到tmp中,释放s1,s1的指针指向tmp
				char* tmp = new char[s._capacity + 1];//+1 是给\0的
				memcpy(tmp, s._str, s._size + 1);
				delete[] this->_str;
				this->_str = tmp;
				this->_size = s._size;
				this->_capacity = s._capacity;
			}
			return *this;
		}

这种写法是通过采用“值传递”接收右值的方法,让编译器自动调用拷贝构造函数,然后我们再将拷贝出来的对象与左值进行交换即可。


		//第2种写法(推荐)
		//s1=s3
		string& operator=(string tmp) //编译器接收右值的时候自动调用拷贝构造函数
			//string& operator=( string *this ,string tmp)
		{
			//this->swap(tmp) 
			swap(tmp);//s1和tmp交换
			return *this;
		}

但是第二种写法无法避免自己给自己赋值,就算是自己给自己赋值这些操作也会进行,虽然操作之后对象中_str指向的字符串的内容不变,但是字符串存储的地址发生了改变,为了避免这种操作我们可以采用下面这种写法:


		//第三种写法
		//s1=s3
		string& operator=(const string& s)
			//	string& operator=( string *this ,const string& s)
		{
			if (this != &s)//不是自己给自己赋值 
			{
				string tmp(s);
				//this->swap(tmp);//this就是s1
				swap(tmp);//s1和tmp交换,tmp指向s1 ,并且出了作用域,tmp就销毁了 
			}
			return *this;
		}

析构函数

string类的析构函数需要我们进行编写,因为每个string对象中的成员_str都指向堆区的一块空间,当对象销毁时堆区对应的空间并不会自动销毁,为了避免内存泄漏,我们需要使用delete手动释放堆区的空间

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

begin

begin函数的作用就是返回字符串中第一个字符的地址

iterator begin()
{
	return _str; //返回字符串中第一个字符的地址
}
const_iterator begin()const
{
	return _str; //返回字符串中第一个字符的const地址
}

end

end函数的作用就是返回字符串中最后一个字符的后一个字符的地址(即’\0’的地址)

iterator end()
{
	return _str + _size; //返回字符串中最后一个字符的后一个字符的地址
}
const_iterator end()const
{
	return _str + _size; //返回字符串中最后一个字符的后一个字符的const地址
}

用迭代器遍历string的代码,其实就是用指针在遍历字符串

string s("hello world!!!");
string::iterator it = s.begin();
//auto it = s.begin();
while (it != s.end())
{
	cout << *it << " ";
	it++;
}
cout << endl;

范围for与迭代器,代码编译的时候,编译器会自动将范围for替换为迭代器的形式,也就是说范围for的底层就是迭代器,我们已经实现了string类的迭代器,自然也能用范围for对string进行遍历

string s("hello world!!!");
//编译器范围for将其替换为迭代器形式
for (auto e : s)
{
	cout << e << " ";
}
cout << endl;

size

size函数用于获取字符串当前的有效长度(不包括’\0’

//大小
size_t size()const
{
	return _size; //返回字符串当前的有效长度
}

capacity

capacity函数用于获取字符串当前的容量

//容量
size_t capacity()const
{
	return _capacity; //返回字符串当前的容量
}

reserve

1、当n大于对象当前的capacity时,将capacity扩大到n或大于n。
2、当n小于对象当前的capacity时,什么也不做。

void reserve(size_t n)//扩容
		{
			if (n > _capacity)
			{
				 char* tmp = new char [n + 1];//+1 是给\0开辟的空间
				 strcpy(tmp, _str);//将对象原本的C字符串拷贝过来(包括'\0')
				 delete[] _str;//释放对象原本的空间
				 _str = tmp;//将新开辟的空间交给_str
				 _capacity = n;
			}
		}

erase

在这里插入图片描述

		//版本一
		//void erase(size_t pos, size_t len = npos)
		//{
		   // assert(pos <= _size);
		   // //全部删完
		   // size_t n = _size - pos;//pos位置后面的字符
		   // if (len > n)
		   // {
		   //	 _str[pos] = '\0';//pos位置放\0
		   //	 _size = pos;
		   // }
		   // //删除一部分,n不完全删完
		   // else
		   // {
		   //	 //需要删除的部分数据 的后面几个覆盖需要删除的数据
		   //	 strcpy(_str + pos, _str + pos + len); //用需要保留的有效字符覆盖需要删除的有效字符
		   //	 _size -= len; //size更新
		   // }
		//}
		//版本二
		void erase(size_t pos, size_t len = npos)
		{
			assert(pos <= _size);
			//全部删完
			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;

			}
			//删除一部分,n不完全删完
			else
			{
				//需要删除的数据的 后几个数据 往前覆盖
				size_t end = pos + len;
				while (end <= _size)
				{
					_str[pos++] = _str[end++];
				}
				_size -= len;
			}
		}
		size_t find(char ch, size_t pos = 0)
		{
			assert(pos < _size);
			//遍历string 
			for (size_t i = 0; 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* ptr = strstr(_str + pos, str);
			if (ptr)//ptr!= nullptr
			{
				return ptr - _str;
			}
			return npos;
		}

resize

1、当n大于当前的size时,将size扩大到n,扩大的字符为ch,若ch未给出,则默认为’\0’。
2、当n小于当前的size时,将size缩小到n。
在这里插入图片描述

//改变大小
void resize(size_t n, char ch = '\0')
{
	if (n <= _size) //n<_size
	{
		_size = n; //将size调整为n
		_str[_size] = '\0'; //在size个字符后放上'\0'
	}
	else  //n>_size
	{
	   
		if (n > _capacity) //判断是否需要扩容
		{
			reserve(n); //扩容
		}
		//n>_size && n<_capacity
		for (size_t i = _size; i < n; i++) //将size扩大到n,扩大的字符为ch
		{
			_str[i] = ch;
		}
		_size = n; //size更新
		_str[_size] = '\0'; //字符串后面放上'\0'
	}
}

push_back

push_back函数就是在当前字符串尾插一个字符,尾插之前需要判断是否需要增容,若需要,则调用reserve函数进行增容,然后再尾插字符,注意尾插完字符后需要在该字符的后方设置上’\0’

void push_back(char ch )
		{
			//2倍扩容
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2); 
			}
			_str[_size] = ch;
			_size++;
			_str[_size] = '\0';//字符串后面放上'\0'
		}

append

append函数在当前字符串的后面追加一个字符串,追加前需要判断_size + len 和_capacity之间的关系 ,判断是否需要增容,然后再将待追加的字符串追加到对象的后方,因为待尾插的字符串后方自身带有’\0’,所以我们无需再在后方设置’\0’

void append(const char* str)
		{
			//至少扩容到_size+len
		size_t	len = strlen(str);
			if (_size + len > _capacity)//_size+len<=_capacity都不需要扩容
			{
				reserve(_size + len);
			}
			strcpy(_str + _size, str);
			_size += len;
		}

operator+=

//+=运算符重载
string& operator+=(char ch)
 //string & operator+= (string *this , const char * str) 
{
	push_back(ch); //尾插字符串
	return *this; //返回左值(支持连续+=)
}

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

insert

insert函数的作用是在字符串的任意位置插入字符或是字符串。
insert函数用于插入字符时,首先需要判断pos的合法性,若不合法则无法进行操作,紧接着还需判断当前对象能否容纳插入字符后的字符串,若不能则还需调用reserve函数进行扩容。插入字符的过程也是比较简单的,先将pos位置及其后面的字符统一向后挪动一位,给待插入的字符留出位置,然后将字符插入字符串即可。

	 void insert(size_t pos, size_t n, char ch)//插入字符
		 {
			 assert(pos <= _size);
			 //扩容 
			 if (n + _size > _capacity)
			 {
				 //至少扩容到_size+n
				 reserve(_size+n);
			 }
			 挪动数据 版本一
			 //int end = _size;
			 //while (end>=(int)pos)  //int 和 size_t 在比较中涉及到算术转换,int需要转换为unsigned int 
			 //{
				// _str[end + n] = _str[end];
				// end--;
			 //}
			 //挪动数据 版本二
			 size_t  end = _size;
			 while (end >= pos && end!=npos )  //end是size_t ,end会等于-1 ,加了end!=npos,end就不会等于-1了
			 {
				 _str[end + n] = _str[end];
				 end--;
			 }
			 //插入数据 
			 for (size_t i = 0; i < n; i++)
			 {
				 _str[pos+i] = ch;

			 }
			 _size += n;
		 }
		 
		 void insert(size_t pos, const char* str)//插入字符串
		 {
			 assert(pos <= _size);
			 //扩容
			 size_t len = strlen(str);
			 if (_size + len >= _capacity)
			 {
				 reserve(_size + len);
			 }
			 //挪动数据 
			 int end = _size;
			 while (end>=(int)pos)
			 {
				 _str[end + len] = _str[end];
				 --end;
			 }
			 //插入数据 
			 for (size_t i = 0; i < len; i++)
			 {
				 _str[pos + i] = str[i];
			 }
			 _size += len;
		 }

swap

swap函数用于交换两个对象的数据,直接调用库里的swap模板函数将对象的各个成员变量进行交换即可。但我们若是想在这里调用库里的swap模板函数,需要在swap函数之前加上“std::”,告诉编译器在c++标准库寻找swap函数,否则编译器编译时会认为你调用的是正在实现的swap函数(就近原则)。

//交换两个对象的数据
void swap(string& s)
{
	//调用库里的swap
	::swap(_str, s._str); //交换两个对象的C字符串
	::swap(_size, s._size); //交换两个对象的大小
	::swap(_capacity, s._capacity); //交换两个对象的容量
}

substr

在str中从pos位置开始,截取n个字符,然后将其返回
在这里插入图片描述

		string substr(size_t pos = 0, size_t len = npos)
		{
			assert(pos < _size);
			size_t n = len;
			//子串的长度大于源字符串,源字符串有多大,子串就有多大
			if (len == npos || pos + len > _size)
			{
				n = _size - pos;
			}
			//子串的长度小于源字符串,将源字符串和子串匹配的内容拷贝到tmp中
			string tmp;
			tmp.reserve(n);
			for (size_t i = pos; i < pos + n; ++i)
			{
				tmp += _str[i];
			}
			return tmp;
		}

c_str

c_str函数用于获取C形式的字符串,实现时直接返回对象的成员变量_str即可。
c_str将string对象转换为C类型字符串,以便与C语言的函数或者需要以C风格字符串作为参数的函数进行交互。

//返回C类型的字符串
	const char* c_str() const //将string转换为C类型的字符串,以便与C语言代码进行交互
		{
			return _str;
		}
void Test_string10()
{
	cxq::string  s1("hello world");
	s1 += '\0';
	s1 += "!!!!!!";//hello world\0!!!!!!\0
	cout << s1.c_str() << endl;//c形式的字符串遇到\0就终止,打印的结果就是hello world\0
	cout << s1 << endl;//打印_size个数的字符,而不是遇到\0就终止

}

如果用 cout << s1.c_str() << endl,c形式的字符串遇到第一个\0就终止,打印的结果就是hello world\0
但是用cout << s1 << endl,打印的是s1中的_size个数的字符,而不是遇到\0就终止

总结:
c的字符数组以\0为终止算长度
string不看\0,以_size为终止算长度

operator[ ]

[ ]运算符的重载是为了让string对象能像C字符串一样,通过[ ] +下标的方式获取字符串对应位置的字符

        char & operator[](size_t pos) //可读可写
			// char & operator[]( char *this ,size_t pos )
		 {
			 assert(pos <_size );
			 return _str[pos];//出了作用域,对象还在
		 }
		const  char& operator[](size_t pos) const //只能读
			 // char & operator[]( char *this ,size_t pos )
		 {
			 assert(pos < _size);
			 return _str[pos];//出了作用域,对象还在
		 }

find

find在字符串中查找一个字符或是字符串,即从字符串开头开始向后查找

      //正向查找第一个匹配的字符
		 size_t find(char ch, size_t pos = 0)
		 {
			 assert(pos < _size);
			 //遍历string 
			 for (size_t i = 0; i < _size; ++i)
			 {
				 if (_str[i] == ch)
				 {
					 return i;
				 }
			 }
			 //没有找到就返回npos
			 return npos;//npos是string类的一个静态成员变量,其值为整型最大值
		 }
		 //正向查找第一个匹配的字符串
		 size_t find(const char* str, size_t pos = 0)
		 {
			 assert(pos < _size);
			 const char* ptr = strstr(_str + pos, str);//strstr函数若是找到了目标字符串会返回字符串的起始位置,若是没有找到会返回一个空指针
			 if (ptr )//ptr!= nullptr
			 {
				 return ptr-_str;//计算目标字符串的起始位置和对象C字符串的起始位置的差值,进而得到目标字符串起始位置的下标
			 }
			 return npos;
		 }

clear

clear函数用于将对象中存储的字符串置空,实现时直接将对象的_size置空,然后在字符串后面放上’\0’即可

//清空字符串
void clear()
{
	_size = 0; //size置空
	_str[_size] = '\0'; //字符串后面放上'\0'
}

getline

getline函数用于读取一行含有空格的字符串。实现时于>>运算符的重载基本相同,只是当读取到’\n’的时候才停止读取字符

		//读取一行含有空格的字符串
		istream& getline(istream& in, string& s)
		{
			s.clear();
			//分割多个string用空格或者换行,getline可以读取空格
			char ch =in.get();//读取第一个字符
			while (ch == '\n')
			{
				s += ch;将读取到的字符尾插到字符串后面
				ch = in.get(); //继续读取字符
			}
		}

>>运算符的重载

重载>>运算符是为了让string对象能够像内置类型一样使用>>运算符直接输入。
输入前我们需要先将对象的C字符串置空,然后从标准输入流读取字符,直到读取到’ ‘或是’\n’便停止读取。

	istream& operator>> (istream& in, string& s)//流提取
	{
		//get函数,无论什么字符都能从流中读取,包括换行和空格
		char ch = in.get();//从输入流中读取一个字符,并将其赋值给变量ch


		流提取多个string用空格或者换行分割
		// 处理前缓冲区前面的空格或者换行
		while (ch != ' ' && ch != '\n')
		{
			s += ch;
			ch = in.get();
		}
		return in;
	}

<<运算符的重载

重载<<运算符是为了让string对象能够像内置类型一样使用<<运算符直接输出打印

	ostream& operator<< (ostream& out, const string& str) //流插入
	{
		/*	for (size_t i = 0; i < str.size(); ++i)
			{
				out << str[i];
			 }*/
		for (auto ch : str)
		{
			out << ch;
		}
		return out;
	}

说明:
为了防止库冲突,写在自己定义的命名空间内
整个模拟实现的代码都是写在.h文件下的, 不可以.h放声明, .cpp放定义(不可以声明和定义分离), 因为模板不支持分离编译

#define  _CRT_SECURE_NO_WARNINGS 1
#pragma once //防止头文件被多次包含
#include<assert.h>
#include<iostream>
#include<string>
namespace cxq
{
	class string
	{
	public:
		typedef char* iterator;//可读可写
		typedef const char* const_iterator;//只能读不能写

		iterator begin()
		{
			return  _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		const_iterator begin()  const
		{
			return _str;
		}
		const_iterator end() const
		{
			return _str + _size;
		}
		//默认构造函数-无参
		//string()
		//	:_size(0)
		//	,_capacity(0)
		//	,_str(new char [1]) // 如果是_str(nullptr)这种写法,会导致nullptr解引用
		//{
		//	_str[0] = '\0';//开一个空间放\0,
		//}
		//默认构造函数-全缺省
		string(const char* str = "")//常量字符串以\0为结束标志
		{
			_size = strlen(str);
			_capacity = strlen(str);//capacity存储有效字符,\0不是有效字符
			_str = new char[strlen(str) + 1];//strlen 计算字符不包含\0,但是需要多开一个空间给\0
		/*	strcpy(_str, str);*/
			memcpy(_str, str, _size + 1);
		}

		//string(const  char * str )
		//	:_size(strlen(str))
		//	,_capacity(strlen(str))//capacity存储有效字符,\0不是有效字符
		//	, _str(new char [_capacity +1] ) //strlen 计算字符不包含\0,但是需要多开一个空间给\0
		//{
		//	strcpy(_str, str);
		//}
		//拷贝构造
		string(const string& s)
		{
			_str= new char[s._capacity + 1]; //+1 给\0开辟空间
			 //拷贝
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}
		//第一种写法
		//string& operator=(const string& s)
			string& operator=( string *this ,const string& s)
		//	//s1=s3
		//	
		//{
		//	
		//	if (this!= &s)//不是自己给自己赋值 
		//	{
		//		//开辟一块和s3一样大的空间tmp,将s3的内容拷贝到tmp中,释放s1,s1的指针指向tmp
		//		char* tmp = new char[s._capacity + 1];//+1 是给\0的
		//		memcpy(tmp, s._str, s._size + 1);
		//		delete[] this->_str;
		//		this->_str = tmp;
		//		this->_size = s._size;
		//		this->_capacity = s._capacity;
		//	}
		//	return *this;
		//}

		第二种写法
		//string& operator=(const string& s)
		//	//	string& operator=( string *this ,const string& s)
		//		//s1=s3
		//{
		//	if (this != &s)//不是自己给自己赋值 
		//	{
		//		string tmp(s);
		//		//this->swap(tmp);//s1和tmp交换,tmp指向s1 ,并且出了作用域,tmp就销毁了 
		//		swap(tmp);//s1和tmp交换,tmp指向s1 ,并且出了作用域,tmp就销毁了 

		//	}
		//	return *this;
		//}


		//第三种写法(推荐)
		string& operator=(string tmp) //s1=s3
			//string& operator=( string *this ,string tmp)
		{
			//this->swap(tmp) 
			swap(tmp);//s1和tmp交换
			return *this;
		}
		~string()
		{
			delete[]_str;
			_str = nullptr;
			_size = _capacity = 0;
		}
		const char* c_str() const //将string转换为C风格的字符串,以便与C语言代码进行交互
		{
			return _str;
		}
		size_t size()const
		{
			return _size;
		}
		char& operator[](size_t pos)
			// char & operator[]( char *this ,size_t pos )
		{
			assert(pos < _size);
			return _str[pos];//出了作用域,对象还在
		}
		const  char& operator[](size_t pos) const
			// char & operator[]( char *this ,size_t pos )
		{
			assert(pos < _size);
			return _str[pos];//出了作用域,对象还在
		}
		void reserve(size_t n)//扩容
		{
			if (n > _capacity)
			{
				cout << "reserve()->" << n << endl;
				char* tmp = new char[n + 1];//+1 是给\0开辟的空间
				memcpy(tmp, _str, _size + 1);
				delete[] _str;
				_str = tmp;//无法理解
				_capacity = n;
			}
		}
		void resize(size_t n, char ch = '\0')
		{
			//n<= _size
			if (n <= _size)
			{
				_size = n;
				_str[_size] = '\0';
			}
			//n>_size
			else


			{
				//判断是否需要扩容
				if (n > _capacity)
				{
					reserve(n);

				}
				//n>_size && n<_capacity,不需要扩容
				//插入数据
				for (size_t i = _size; i < n; ++i)

				{

					_str[i] = ch;

				}
				_size = n;
				_str[_size] = '\0';
			}

		}
		void push_back(char ch)
		{
			//2倍扩容
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			_str[_size] = ch;
			_size++;
			_str[_size] = '\0';
		}
		void append(const char* str)
		{
			//至少扩容到_size+len
			size_t	len = strlen(str);
			if (_size + len > _capacity)//_size+len<=_capacity都不需要扩容
			{
				reserve(_size + len);
			}
			/*strcpy(_str + _size, str);*/
			memcpy(_str + _size, str, len + 1);
			_size += len;
		}
		string& operator+= (const char* str)
			//string & operator+= (string *this , const char * str) 
		{
			append(str);
			return *this;
		}
		string& operator+= (const char ch)
			//string & operator( string * this ,const char ch) 
		{
			push_back(ch);
			return *this;
		}
		void insert(size_t pos, size_t n, char ch)//插入字符
		{
			assert(pos <= _size);
			//扩容 
			if (n + _size > _capacity)
			{
				//至少扩容到_size+n
				reserve(_size + n);
			}
			挪动数据 版本一
			//int end = _size;
			//while (end>=(int)pos)  //int 和 size_t 在比较中涉及到算术转换,int需要转换为unsigned int 
			//{
			   // _str[end + n] = _str[end];
			   // end--;
			//}
			//挪动数据 版本二
			size_t  end = _size;
			while (end >= pos && end != npos)  //end是size_t ,end会等于-1 ,加了end!=npos,end就不会等于-1了
			{
				_str[end + n] = _str[end];
				end--;
			}
			//插入数据 
			for (size_t i = 0; i < n; i++)
			{
				_str[pos + i] = ch;

			}
			_size += n;
		}

		void insert(size_t pos, const char* str)//插入字符串
		{
			assert(pos <= _size);
			//扩容
			size_t len = strlen(str);
			if (_size + len >= _capacity)
			{
				reserve(_size + len);
			}
			//挪动数据 
			int end = _size;
			while (end >= (int)pos)
			{
				_str[end + len] = _str[end];
				--end;
			}
			//插入数据 
			for (size_t i = 0; i < len; i++)
			{
				_str[pos + i] = str[i];
			}
			_size += len;
		}
		//版本一
		//void erase(size_t pos, size_t len = npos)
		//{
		   // assert(pos <= _size);
		   // //全部删完
		   // size_t n = _size - pos;//pos位置后面的字符
		   // if (len > n)
		   // {
		   //	 _str[pos] = '\0';//pos位置放\0
		   //	 _size = pos;
		   // }
		   // //删除一部分,n不完全删完
		   // else
		   // {
		   //	 //需要删除的部分数据 的后面几个覆盖需要删除的数据
		   //	 strcpy(_str + pos, _str + pos + len); //用需要保留的有效字符覆盖需要删除的有效字符
		   //	 _size -= len; //size更新
		   // }
		//}
		//版本二
		void erase(size_t pos, size_t len = npos)
		{
			assert(pos <= _size);
			//全部删完
			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;

			}
			//删除一部分,n不完全删完
			else
			{
				//需要删除的数据的 后几个数据 往前覆盖
				size_t end = pos + len;
				while (end <= _size)
				{
					_str[pos++] = _str[end++];
				}
				_size -= len;
			}
		}
		size_t find(char ch, size_t pos = 0)
		{
			assert(pos < _size);
			//遍历string 
			for (size_t i = 0; 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* ptr = strstr(_str + pos, str);
			if (ptr)//ptr!= nullptr
			{
				return ptr - _str;
			}
			return npos;
		}
		string substr(size_t pos = 0, size_t len = npos)
			//在str中从pos位置开始,截取n个字符,然后将其返回
		{
			assert(pos < _size);
			size_t n = len;
			//子串的长度大于源字符串,源字符串有多大,子串就有多大
			if (len == npos || pos + len > _size)
			{
				n = _size - pos;
			}
			//子串的长度小于源字符串,将源字符串和子串匹配的内容拷贝到tmp中
			string tmp;
			tmp.reserve(n);
			for (size_t i = pos; i < pos + n; ++i)
			{
				tmp += _str[i];//拷贝数据
			}
			return tmp;
		}
		void swap(string & s )
			//void swap( void * this ,string& s)

		{
			//调用库里的swap
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		//bool operator<(const string& s)
		//	//bool operator<( string * this ,const string& s)
		//{
		//	size_t i1 = 0;
		//	size_t i2 = 0;
		//	while (i1 < _size &&i2 < s._size ) //i1和i2不能超过对应数组的size
		//	{
		//		if (_str[i1]> s._str[i2] )
		//		{
		//			return false;
		//		}
		//		else	if (_str[i1] < s._str[i2])
		//		{
		//			return true;
		//		}
		//		else//相等继续走
		//		{
		//			i1++;
		//			i2++;
		//		}
		//	
		//	}
		//	
		//	if (i1 == _size && i2 != s._size) //i1走完第一个数组了,i2没有走完
		//	{
		//		// "hello" "helloxx" true
		//		return true;
		//	}
		//	else
		//	{
		//		// "hello" "hello"   false
		//	// "helloxx" "hello" false
		//		return false;
		//	}
		//	// 如果不考虑代码可读性,可以直接写成return   i1 == _size && i2 != s._size 
		//}

		//第二种版本
		bool operator<(const string& s) const 
		{

			//分三种情况
			// "hello" "hello"   false
	// "helloxx" "hello" false
	// "hello" "helloxx" true

            
			int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
		

			
			return ret == 0 ? _size : s._size;
		}


		bool operator==(const string& s) const
		//	bool operator==( string  const *this , const string& s) const
			
		{
			//两个数组的大小相等 
			return _size == s._size && memcpy(_str, s._str, _size) ==0;
		}
		bool operator<=(const string& s) const
			//bool operator<=(string  const * this , const string& s) 

		{
			return *this < s  || *this ==s ;
		}
		bool operator>(const string& s) const
			//bool operator>( string   const * this const string& s) 
		{
			return !(*this <= s);
		}
		bool operator>=(const string& s) const
			//bool operator>=(string  const  * this ,const string& s) 
		{
			return !(*this < s);
		}
		bool operator!=(const string& s) const
			//bool operator!=( string const  *this , const string& s) 
		{
			return !(*this == s);
		}
		void clear()
		{
			_size = 0;
			_str[_size] = '\0';
		}
		//读取一行含有空格的字符串
		istream& getline(istream& in, string& s)
		{
			s.clear();
			//分割多个string用空格或者换行,getline可以读取空格
			char ch =in.get();//读取第一个字符
			while (ch == '\n')
			{
				s += ch;//将读取到的字符尾插到字符串后面
				ch = in.get(); //继续读取字符
			}
			return in;
		}
	private:
		size_t _size;
		size_t _capacity;
		char* _str;
	public:
		static size_t npos;//声明 
	};
	size_t string::npos = -1;//静态成员变量必须在类外面定义 
	ostream& operator<< (ostream& out, const string& str) //流插入
	{
		/*	for (size_t i = 0; i < str.size(); ++i)
			{
				out << str[i];
			 }*/
		for (auto ch : str)
		{
			out << ch;
		}
		return out;
	}
	//istream& operator>> (istream& in, string& s)//流提取
	//{
	//	s.clear();//清空上一次的字符
	//	//get函数,无论什么字符都能从流中读取,包括换行和空格
	//	char ch = in.get();//从输入流中读取一个字符,并将其赋值给变量ch


	//	//流提取多个string用空格或者换行分割
	//	// 处理前缓冲区前面的空格或者换行
	//	while (ch != ' ' && ch != '\n')
	//	{
	//		s += ch;
	//		ch = in.get();
	//	}
	//	return in;
	//}
	
	istream& operator>> (istream& in, string& s)//流提取
	{
		s.clear();//清空上一次的字符

		//补充;get函数,无论什么字符都能从流中读取,包括换行和空格

		char ch = in.get();//从输入流中读取一个字符,并将其赋值给变量ch
		char buff[128];
		int i = 0;

		//补充:流提取多个string用空格或者换行分割

		while (ch != ' ' && ch != '\n')	// 处理前缓冲区前面的空格或者换行
		{
			//将字符存到buff数组中,再将buff存到s中
			buff[i++] = ch;
			if (i == 127)
			{
				buff[i] = '\0';//最后一个位置放\0
				s += buff;//将buff存到s对象中
				i = 0;//重置
			}
			ch = in.get();//读取下一个ch

		}
		//不够128个字符,开辟的空间有剩余
		if (i != 0)
		{
			buff[i] = '\0';//在有效字符后加\0
			//再将buff存到s中
			s += buff;
		}
		return in;
	}
};





如果你觉得这篇文章对你有帮助,不妨动动手指给点赞收藏加转发,给鄃鳕一个大大的关注
你们的每一次支持都将转化为我前进的动力!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

现代控制理论

B站学习视频https://space.bilibili.com/230105574/channel/seriesdetail?sid1569601 一.引入状态-空间表达 &#xff08;本质上是使用一组向量的线性组合来表示整个系统任意物理量&#xff0c;也就是一个特征分解的过程&#xff09; 现代控制理论的基础是 状态-空间表达方…

IDEA以服务列表的形式展示

IDEA以服务列表的形式展示 要是没有要显示的服务列表的话就右键将启动的全部添加进去。正常是懒加载的形式&#xff0c;正常启动了就会添加进去。

Toyota Programming Contest 2023#4(AtCoder Beginner Contest 311)(A-G)

Contest Duration: 2023-07-22(Sat) 20:00 - 2023-07-22(Sat) 21:40 (local time) (100 minutes) 头文件和宏 #include<iostream> #include<string> #include<vector> using namespace std; #define int long long #define fer(i,a,b) for(int ia;i<b;i…

【时间复杂度】

旋转数组 题目 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 /* 解题思路&#xff1a;使用三次逆转法&#xff0c;让数组旋转k次 1. 先整体逆转 // 1,2,3,4,5,6,7 // 7 6 5 4 3 2 1 2. 逆转子数组[0, k - 1] // 5 6 7 4 3…

Pytorch个人学习记录总结 03

目录 Transeforms的使用 常见的transforms Transeforms的使用 torchvision中的transeforms&#xff0c;主要是对图像进行变换&#xff08;预处理&#xff09;。from torchvision import transforms transeforms中常用的就是以下几种方法&#xff1a;&#xff08;Alt7可唤出…

多源BFS-- 矩阵距离

关于多源BFS&#xff0c;基本上就是单源BFS的简单升级了一下&#xff0c;比如在queue中队头开始时只有一个&#xff0c;我们通过这一个队头去推导其他的东西。而多源最短路就是队头一开始有1-n个可能的数&#xff0c;一个一个去BFS。 题目思路&#xff1a; 这个题就直接把所有的…

0成本搭建自己的云数据库

第一步&#xff0c;租免费的云服务器 www.aliyun.com 阿里云的&#xff0c;可以免费租三个月 进入主页后选择云服务器ESC 选择这款&#xff0c;点击试用就行 第二步&#xff0c;配置服务器 在配置服务器系统的时候选择centos&#xff0c;省事&#xff0c;别选ubuntu&#x…

[Spring] 三级缓存解决循环依赖详解

什么是循环依赖 注册一个bean对象的过程&#xff1a; Spring扫描class得到BeanDefinition – 根据得到的BeanDefinition去生成bean – 现根据class推断构造方法 – 根据推断出来的构造方法&#xff0c;反射&#xff0c;得到一个对象 – 填充初始对象中的属性(依赖注入) – 如果…

服务器中了360后缀勒索病毒,360后缀勒索病毒介绍解密数据恢复

360后缀勒索病毒&#xff0c;是BeijingCrypt勒索家族中的一种勒索软件病毒&#xff0c;这种恶意软件一旦攻击了企业的服务器就会利用自身独特的加密技术来全盘扫描系统文件&#xff0c;并对用户的全部文件进行加密&#xff0c;并要求用户支付赎金以解锁文件。近期&#xff0c;我…

C# 数据结构】Heap 堆

【C# 数据结构】Heap 堆 先看看C#中有那些常用的结构堆的介绍完全二叉树最大堆 Heap对类进行排序实现 IComparable<T> 接口 对CompareTo的一点解释 参考资料 先看看C#中有那些常用的结构 作为 数据结构系类文章 的开篇文章&#xff0c;我们先了解一下C# 有哪些常用的数据…

CNNdebug尝试

这算是啥问题&#xff1f;&#xff1f; 接着根据群里大佬提供的指示&#xff0c;将train和validate中的nums_work改成0即可 此处因为数据已经打乱了&#xff0c;所以在这里就不用打乱数据&#xff0c;把shuffle True修改成为False 后面查看指定目录下&#xff0c;竟然没有这个…

性能测试工具 Jmeter 引入 jar 包踩过的坑

目录 前言&#xff1a; Jmeter 中调用自己编写 jar 中的类出错 错误日志&#xff1a; 出现以上错误的原因&#xff1a; 解决方法&#xff1a; 前言&#xff1a; JMeter 是一种开源的性能测试工具&#xff0c;可以帮助我们快速地进行网站、应用程序等的性能测试和压力测试…

20230720在ubuntu22.04系统下载+解密+合并ts切片的步骤(STEP-BY-STEP版本)

20230720在ubuntu22.04系统下载解密合并ts切片的步骤&#xff08;STEP-BY-STEP版本&#xff09; 2023/7/20 23:06 https://app1ce7glfm1187.h5.xiaoeknow.com/v2/course/alive/l_64af6130e4b03e4b54da1681?type2&app_idapp1cE7gLFM1187&pro_idterm_645c69388953e_Nhew…

C# List 详解七

目录 42.Sort() 43.ToArray() 44.ToString() 45.TrimExcess() 46.TrueForAll(Predicate) C# List 详解一 1.Add(T)&#xff0c;2.AddRange(IEnumerable)&#xff0c;3.AsReadOnly()&#xff0c;4.BinarySearch(T)&#xff0c; C# List 详解二 5.Cl…

TEE GP(Global Platform)认证规范

TEE之GP(Global Platform)认证汇总 一、GP认证规范库 二、TEE GP认证规范文档 如果需要TEE对应的GP认证规范文档&#xff0c;请按照下方选择框选择TEE&#xff0c;然后Search&#xff0c;共查询到31个相关规范文档。 参考&#xff1a; GlobalPlatform Certification - Global…

[回馈]ASP.NET Core MVC开发实战之商城系统(一)

经过一段时间的准备&#xff0c;新的一期【ASP.NET Core MVC开发实战之商城系统】已经开始&#xff0c;今天着重讲解布局设计&#xff0c;环境搭建&#xff0c;系统配置&#xff0c;及首页商品类型&#xff0c;banner条&#xff0c;友情链接等功能的开发。 首页布局设计 首页是…

工程安全监测无线振弦采集仪在建筑物中的应用

工程安全监测无线振弦采集仪在建筑物中的应用 工程安全监测无线振弦采集仪是一种用于建筑物结构安全监测的设备&#xff0c;它采用了无线传输技术&#xff0c;具有实时性强、数据精度高等优点&#xff0c;被广泛应用于建筑物结构的实时监测和预警。下面将从设备的特点、应用场…

(原创)自定义DialogFragment以及解决其内存泄漏问题

前言 日常开发中&#xff0c;dialog是常见的功能&#xff0c;我们时常需要弹出来一些弹框提示用户 今天就定义了一个方便的dialog基类BaseSimpleDialogFragment&#xff0c; 支持快速地显示一个dialog 主要功能有&#xff1a; initAnimation&#xff1a;设置入场和出场动画 ge…

【C进阶】指针进阶(1)_二次复习版

目录 1. 字符指针 1.1常量字符串的修改 加上const解决问题 打印常量字符串 1.2数组存放的字符串 1.3例题:数组创建与常量池的区别 2. 指针数组 2.1字符指针数组 2.2整型指针数组 2.3使用3个一维数组,模拟实现一个二维数组 2.4例题: 3.数组指针 3.1 数组指针的定义…

同步网盘使用中的五大突出优势

同步网盘是一种流行的云存储解决方案&#xff0c;它可以将您本地计算机上的文件与云端存储空间同步&#xff0c;以保证文件的备份和访问。那么&#xff0c;同步网盘使用中的突出优势是什么呢&#xff1f;下面就为您详细介绍。 一、数据备份 同步网盘最大的优势之一就是可以自动…