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

news2024/11/28 10:39:29

文章目录

  • 默认成员函数
    • 构造函数
    • 拷贝构造函数
  • 赋值运算符重载函数
  • 析构函数
  • 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;
	}
};





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

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

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

相关文章

^(按位异或)操作符详解

因为未知&#xff0c;所以全力以赴 目录 例1.实现两个数的交换 例2.找出单身狗 1.简单版 2.进阶版 大家好&#xff0c;我是纪宁。这篇博客介绍^操作符及使用案例。 位操作符是对操作数的二进制补码进行操作。^就是位操作符的一种&#xff0c;叫按位异或操作符。计算结果是…

【期末课程设计】学生成绩管理系统

因其独特&#xff0c;因其始终如一 文章目录 一、学生成绩管理系统介绍 二、学生成绩管理系统设计思路 三、源代码 1. test.c 2. Student Management System.c 3.Stu_System.c 4.Teacher.c 5.Student Management System.h 前言&#xff1a; 学生成绩管理系统含教师…

C语言每日一题:1.证明尼克彻斯定理。

思路1&#xff1a; 0.输入一个值作为n 1.假设输入的n4计算4^364. 2.因为他们都是连续的奇数可以把它写成另一种方法&#xff0c;每一个数之间相差2。 4^313(132)(134)(136); 3.64-(246)52–>52/4等于13 4.这样的话我们就找到了连续奇数的第一个数字。 //具体代码&#xff1a…

Selenium 修改 HTTP 请求头三种方式

目录 前言&#xff1a; 什么是 HTTP 请求头 需要更改 HTTP 请求请求头 Selenium 修改请求头 Java HTTP 请求框架 代码实战 使用反向代理 使用 Firefox 扩展 下载火狐浏览器扩展 加载火狐扩展 设置扩展首选项 设置所需的功能 完整自动化用例 前言&#xff1a; Sele…

USB转串行通信芯片FT2232

1 FT2232主要特性 1&#xff09;2232表示支持2个RS232。 2&#xff09;FT2232D最高支持full-speed&#xff0c;所以时钟是12MHz&#xff1b;而FT2232H最高支持high-speed&#xff0c;所以时钟是60MHz。 3&#xff09;FT2232的Port A和Port B会在Windows设备管理器的“通用串行总…

九、正则表达式详解:掌握强大的文本处理工具(一)

文章目录 &#x1f340;引言&#x1f340;正则表达式的基本语法&#x1f340;常用操作符&#x1f340;实例应用&#x1f340;具体操作演示 &#x1f340;引言 正则表达式(Regular Expression)是一种强大的文本处理工具&#xff0c;常用于搜索、匹配和替换操作。它使用一种特定的…

jenkins Transferred 0 file(s)问题

每次构建都都是成功&#xff0c;但是就没有就是没有传输过去文件 Transferred 0 file(s) 因为jenkins是容器启动&#xff0c;会考虑有没有可能是从容器ssh传输呢&#xff1f; &#xff08;如果不是容器启动得&#xff0c;就把源文件地址改成 相对路径就行&#xff09; 我就在容…

17.matlab数据分析多项式的积分(matlab程序)

1.简述 Matlab中对多项式进行积分 &#xff08;1&#xff09;多项式的微分操作由polyder函数实现 &#xff08;2&#xff09;MATLAB中没有专门的对多项式积分函数&#xff0c;但可以用[p./length(p):1:-1]的方法完成积分&#xff0c;k为常数 2.代码 clc; clear all; p1[2 6 8];…

​ jgliu的​博客推荐 rapidIO/DDR/SPI/I2C

在博客园中发现一位博主的文章质量比较高 作者&#xff1a; jgliu 这里贴几篇感兴趣的文章地址 1.rapidIO从基础到原理到实现都有很详细的介绍&#xff0c;虽然该高速接口在FPGA中用的较多&#xff0c;ASIC不常用&#xff0c;但通过rapidIO的学习 可以加深对高速接口的理解-…

二十四章:SEgmentation TRansformer (SETR)——以Transformer的序列到序列的视角重新思考语义分割问题

0.摘要 最近的语义分割方法采用了全卷积网络&#xff08;FCN&#xff09;和编码器解码器架构。编码器逐渐降低空间分辨率&#xff0c;并学习具有更大感受野的抽象/语义视觉概念。由于上下文建模对于分割是至关重要的&#xff0c;最新的研究工作将重点放在增加感受野上&#xff…

hadoop的分区学习

自定义分区实现&#xff1a; 抽象类 要自定义分区规则&#xff0c;就必须继承并且重写。 设置分区数量 driver类&#xff1a; job.setNumReduceTasks(3);job.setPartitionerClass(FlowPartitioner.class);分区方法类&#xff0c;直接用编号代替就好了 public class FlowParti…

二叉树详解

这里写目录标题 前言树型结构(了解)树常见的概念树的表示形式&#xff08;了解&#xff09;树的应用 二叉树概念两种特殊的二叉树二叉树的性质(重要)二叉树的存储二叉树的基本操作 前言 本篇博客讲述了以下几个知识点 树的基本概念二叉树概念及特性二叉树的基本操作 树型结构…

OpenCv之特征检测

目录 一、基本概念 二、harris角点检测 三、SIFT算法 四、Shi-Tomasi角点检测 一、基本概念 特征检测指的是使用计算机提取图像信息&#xff0c;决定每个图像的点是否属于一个图像特征。特征检测的结果是把图像上的点分为不同的子集&#xff0c;这些子集往往属于孤立的点、…

FPGA简单双端口RAM——IP核

文章目录 前言一、双端口 RAM1、简单双端口与真双端口2、简单双端口RAM框图 二、 IP核配置1、RAM双端口 IP 核配置2、PLL IP 核配置 三、源码1、ram_wr(写模块)2、ram_rd(读模块)3、ip_2port_ram(顶层文件) 四、仿真1、仿真文件2、波形仿真 五、SignalTap II在线验证六、总结七…

百度知道上云与架构演进

作者 | 百度知道研发组 导读 百度知道作为上线十多年的老产品线&#xff0c;业务场景多、架构老旧、代码风格不统一&#xff0c;同时业务迭代较快&#xff0c;整体承载流量大&#xff0c;稳定性要求高&#xff0c;给业务全面上云带来不小的挑战。本文基于实践&#xff0c;介绍知…

Nginx配置访问密码

使用得场景 因为想将skywalking对外提供访问&#xff0c;但是skywalking又没有认证功能&#xff0c;所以使用nginx来做。 安装htpasswd 因为需要使用到htpasswd&#xff0c;htpasswd是Apache服务器中生成用户认证的一个工具&#xff0c;如果未安装&#xff0c;则使用如下命令…

Linux底层

一. arm基础知识 基础&#xff1a;c语言 具有一定硬件基础 特点---》前后联系 arm目标&#xff1a; 看懂简单的汇编代码 会看电路图、芯片手册 学会如何用软件控制硬件思想 解决问题的办法 谈谈对嵌入式的理解&#xff1f; 以计算应用为中心&#xff0c;软硬件可裁剪的…

Vue 双重v-for渲染表单,再复制表单编辑之深拷贝

文章目录 前言问题背景实现拷贝表单如何实现深拷贝Object.assignJSON实现的深拷贝递归实现解决循环引用的递归实现require(lodash).cloneDeep() 前言 在做复杂的动态表单&#xff0c;实现业务动态变动&#xff0c;比如有一条需要动态添加的el-form-item中包含了多个输入框&…

Tomcat服务器下载安装及配置教程(IDEA中使用Tomcat)

目录 友情提醒第一章、Tomcat下载与安装1.1&#xff09;Tomcat介绍1.2&#xff09;官网下载 第二章、Tomcat配置环境变量2.1&#xff09;windows环境变量配置2.2&#xff09;验证Tomcat配置是否成功2.3&#xff09;报错解决 第三章、IDEA整合Tomcat3.1&#xff09;打开IDEA开发…

【OAuth2】OAuth2概述及使用GitHub登录第三方网站

【OAuth2】OAuth2概述及使用GitHub登录第三方网站 文章目录 【OAuth2】OAuth2概述及使用GitHub登录第三方网站0. 导言1. OAuth2 简介2. OAuth2 认证授权总体流程3. OAuth2 标准接口4. OAuth2 四种授权模式4.1 授权码模式4.2 简化模式4.3 密码模式4.4 客户端模式 5. GitHub授权登…