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

这篇具有很好参考价值的文章主要介绍了STL中的string类的模拟实现【C++】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

默认成员函数

构造函数

构造函数设置为缺省参数,若不传入参数,则默认构造为空字符串。字符串的初始大小和容量均设置为传入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指向的并不是同一块空间,所以拷贝出来的对象与源对象是互相独立的。

深拷贝写法二(推荐):

STL中的string类的模拟实现【C++】,c++,java,开发语言

//第二种写法
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

STL中的string类的模拟实现【C++】,c++,java,开发语言

		//版本一
		//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。
STL中的string类的模拟实现【C++】,c++,java,开发语言

//改变大小
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个字符,然后将其返回
STL中的string类的模拟实现【C++】,c++,java,开发语言

		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;
	}
};





完整代码

#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);
		}

		//string(const  char * str )
		//	:_size(strlen(str))
		//	,_capacity(strlen(str))//capacity存储有效字符,\0不是有效字符
		//	, _str(new char [_capacity +1] ) //strlen 计算字符不包含\0,但是需要多开一个空间给\0
		//{
		//	strcpy(_str, str);
		//}
		//

		~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)
			{
				 char* tmp = new char [n + 1];//+1 是给\0开辟的空间
				 strcpy(tmp, _str);
				 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);
			_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)
		 {
			 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;
		 }
		
	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;
	}
};





如果你觉得这篇文章对你有帮助,不妨动动手指给点赞收藏加转发,给鄃鳕一个大大的关注
你们的每一次支持都将转化为我前进的动力!!!
文章来源地址https://www.toymoban.com/news/detail-606913.html

到了这里,关于STL中的string类的模拟实现【C++】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++】String类的模拟实现。

    🎉博客主页:小智_x0___0x_ 🎉欢迎关注:👍点赞🙌收藏✍️留言 🎉系列专栏:C++初阶 🎉代码仓库:小智的代码仓库 string类中需要三个成员变量分别记录元素个数、容量和内容。还需要一个 size_t 类型npos-1表示整型的最大值。 这段代码是 string 类的构造函数。构造函数是在

    2024年02月13日
    浏览(42)
  • 【C++】string类的模拟实现

    前言:在上一篇中我们讲到了string类的使用方法,今天我们将进一步的去学习string类,去底层看看它顺带模拟实现部分的内容。 💖 博主CSDN主页:卫卫卫的个人主页 💞 👉 专栏分类:高质量C++学习 👈 💯代码仓库:卫卫周大胖的学习日记💫 💪关注博主和博主一起学习!一起努

    2024年03月21日
    浏览(43)
  • C++ 之 string类的模拟实现

    这学习我有三不学 昨天不学,因为昨天是个过去 明天不学,因为明天还是个未知数 今天不学,因为我们要活在当下,我就是玩嘿嘿~ –❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀-正文开始-❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–

    2024年04月27日
    浏览(31)
  • 【C++初阶】9. string类的模拟实现

    string类的完整实现放这里啦!快来看看吧 string类的作用就是将字符串类型实现更多功能,运算符重载,增删改查等等操作,所以其成员就包含char*的字符串 在之前的学习过程中,我们了解到类中存在的六个默认函数,其中就包含默认构造函数,那么对于string类是否需要用户自

    2024年02月09日
    浏览(35)
  • 【C++初阶】学习string类的模拟实现

    前面已经学习了string类的用法,这篇文章将更深入的学习string类,了解string类的底层是怎么实现的。当然,这里只是模拟一些常用的,不常用的可以看文档学习。 我们一共创建两个文件,一个是test.cpp文件,用于测试;另一个是string.h文件,用于声明和定义要模拟的string类。

    2024年02月03日
    浏览(53)
  • 【c++】string类的使用及模拟实现

    我们先了解一下什么是OOP思想 OOP思想,即面向对象编程(Object-Oriented Programming)的核心思想,主要包括“抽象”、“封装”、“继承”和“多态”四个方面。 抽象:抽象是忽略一个主题中与当前目标无关的那些方面,以便充分地注意与当前目标有关的方面。抽象并不打算了

    2024年04月11日
    浏览(35)
  • 【C++】——string类的介绍及模拟实现

    C语言中,字符串是以’\\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。所以我们今天来学习C++标准库中的string类。

    2024年02月07日
    浏览(56)
  • C++ STL string类模拟实现

    目录 string类成员变量 一.构造函数 二.析构函数 三.拷贝构造 四.size(),capacity() 五.operator [ ] 六. operator =  七.字符串比较  八.reserve() 九.push_back(),append() 十.operator+=  十一.insert()  十二.迭代器  十二.erase() 十三.swap()  十四.find() 十五.流提取,流输出 十六

    2024年02月14日
    浏览(52)
  • C++ [STL之string模拟实现]

    本文已收录至《C++语言》专栏! 作者:ARMCSKGT 前面我们介绍了STL容器string的部分接口使用,有了string使我们对字符串的操作如鱼得水,其实string不止于使用方便,其实现也有许多我们值得学习的地方,本节将为您介绍string常用接口的代码实现! 本文接口的实现借助于C++官方

    2024年02月05日
    浏览(50)
  • 【C++初阶】第八站:string类的模拟实现

    目录 string类的模拟实现 经典的string类问题 浅拷贝 深拷贝 写时拷贝(了解) 构造函数 string的全缺省的构造函数: string的拷贝构造函数 传统写法 现代写法 string的赋值重载函数 传统写法 现代写法 string的无参构造函数: 遍历函数 operator[ ] 迭代器 迭代器的底层实现begin和end:

    2024年04月28日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包