模拟实现C++中string类的常用方法

前言

首先,你需要了解和使用过string类,才知道我说的是什么,这里并不实例string类如何使用,因为之前我出过c++基础 STL 第二篇:(string容器),如果有需要可以看看

1 实现一个简易版的string 类

一个简易版的string类:只完成基本的构造析构赋值函数的重载,正确的分配内存即可,也就是说没有增删查改的功能;


1.1 简易版string 类的成员

分析一个类,实现一个类,首先的确认类的成员变量有什么;string类本质是字符串,所以里面会有一个成员变量:
并且该成员变量通常是私有的,为的不让外界访问到本类的数据成员;

class string 
{
private:
char* _str;
}

1.2 简易版string 类的构造函数

对于构造函数,我们调用的方式是有很多种:
我们实现以下基本两种:

string s1("hello world"); //const char* 参数构造
string s2; //默认构造

对应的构造函数,我们可以合并以上两种调用方式,通过缺省参数的方式,可以达到这个效果:

string (const char* s="") //给默认参数一个/0,也就是空(注意双引号是没有空格)
{
	_str = new[strlen(s)+1] //开辟空间
	strcpy(_str,s); //拷贝数据过来
}

我们知道构造函数,就是完成初始化的工作,哪初始化谁呢,就是我们string的成员数据_str;
对于这个char*指针类型的成员数据,我们需要为它动态开辟一个新的内存去管理它;


1.3 简易版string 类的析构函数

既然有构造函数,那么自然有析构函数,自然而然析构函数为的就是清理string类里面成员变量_str开辟的资源,也就是_str的动态内存;
析构函数,只要string类的对象声明周期结束之前就会自动调用,所以我们不需要主动调用析构函数;

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

析构函数干的是就是清理对象资源即可;


1.3 简易版string 类的拷贝构造函数

对于拷贝构造函数:本意就是用一个对象去初始化一个新的对象;所以我们会有这样的调用方式

string s1("hello world");
string s2(s1) ; //调用拷贝构造函数,为的是初始化我们的s2;使得s2和s1有一样的值;

基于以上的方式,我们可以很容易写出这样的拷贝构造:(这是会崩溃的)

string(const string& s):_str(s._str)
{}

以上的拷贝构造,是我们一直谈的浅拷贝,浅拷贝带来的问题我已经谈过了,我们不允许string的拷贝构造函数发生的是浅拷贝;因为它会导致调用者使用完 s1对象和s2对象时候,自动析构时候会发生两次析构同一份内存;这就会导致程序崩溃;


所以我们有一种常见的深拷贝的方式:为的是处理浅拷贝,调用者使用时候,会发生两次析构的问题;

string(const sting& s):_str(new char[strlen(s._str)+1])
{
	strcpy(_str,s._str);
}

这种方式深拷贝,是让s2指向了一份新的内存,并且把s1的数据拷贝到了s2指向新的内存空间中;从而达到了s2和s1指向不同空间数据,析构不会析构两次的问题;


1.4 简易版的string 类的拷贝构造函数(新写法)

什么是新的写法呢?首先我们得知道,我们原来上面的拷贝构造,就是安安稳稳的完成自己的功能的拷贝构造,这样不是不行,只是缺少点复用的方式;
也就是说,我们是否可以同一个另一种写法,通过已有的函数,去复用,用来实现拷贝构造函数呢?
那肯定是有的:
(这个版本有个bug,下面会分析,修复)

string(const string& s)
{
	string temp(s._str); //先调用拷贝构造一个临时对象
	std::swap(_str,temp._str); //再交换this对象和temp对象的_str的指向
}

上面的拷贝构造什么意思呢?简单的说就是复用了构造函数,和std::swap函数达到的目的;
画个图分析分析:
在这里插入图片描述
这样我们就达到了一个目的:给this对象的_str成功的拷贝到了数据:
但是这里还有一个问题:就是this对象的_str刚开始是指向不明空间的(也就是没有初始化_str),这样再调用std::swap交换时没什么问题的;出了拷贝构造函数时候,由于temp对象需要被析构,但是由于调用std::swap,temp对象的_str就指向了原来this对象的_str,而它是随机值,没被初始化,这样就会导致一个问题:temp对象析构会析构一个未初始化的空间数据,从而崩溃;


我可以测试以下
在这里插入图片描述


解决这个问题我们只要先给this的_str初始化nullptr就可以;

string(const string& s):_str(nullptr) //这个初始化一定要有,不然析构temp时候,会崩溃
{
	string temp(s._str); //先调用拷贝构造一个临时对象
	std::swap(_str,temp._str); //再交换this对象和temp对象的_str的指向
}

1.5 简易版string 类的赋值运算符重载

对于运算符重载,我们实现一个这样的调用方式的:

string s1("hello world");
string s2 ;
s2 = s1; //调用赋值运算符重载

首先分析这个函数的功能,也是为了复制一份s1的数据到s2中,那么同时也要避免浅拷贝的问题,所以我们有一种写法:就是深拷贝的赋值运算重载

string& operator=(const sting& s)
{
	char* temp = new char[strlen(s._str)+1];
	strcpy(temp,s._str);
	delete _str;
	_str = temp;
	return *this;
}

这个代码的意思是:开辟一个临时空间,存储s对象的内容,再释放this对象_str的指针,同时再让this的_str指向temp;


这样就完成了该函数的功能,但是还有一个小缺陷:就是假如调用者是 s1 = s1的方式调用,也就是自己调用自己呢?那么上诉的操作就很多余了,其实自己调用自己,我们只要直接返回*this即可,所以为了避免这种情况自己调用自己,我们修改代码,控制这种情况:

string& operator=(const sting& s)
{
	if(this == &s)
	{
		return *this;
	}
	char* temp = new char[strlen(s._str)+1];
	strcpy(temp,s._str);
	delete _str;
	_str = temp;
	return *this;
}

1.6 简易版string 类的赋值运算符重载(新写法)

既然拷贝构造可以复用构造函数,那么我们复制运算符重载一样可以复用:但是它复用的是拷贝构造去实现

string& operator=(const string& s)
{
	if(this == &s)
		return *this;
		
	string temp(s);
	std::swap(_str,temp_str);
	return *this;
}

这种方式非常简单明了,完成了深拷贝的赋值运算符重载;也就是复用了拷贝构造函数;


还有一种更加简洁的复制运算符重载:

string& operator(string s)
{
	if(this == &s) return *this;
	std::swap(_str,s._str);
	return *this;
}

这个版本的赋值运算符重载,直接在传参时候,构造出来s对象,用构造出来的s对象来直接初始化this对象;极其简洁


1.7 简易版的string 类的总代码

//这是一个没有增删查改的string 模拟实现

namespace xjh
{
	class string
	{
	public:
		string(const char* s  = "") 
		{
			_str = new char[strlen(s) + 1];
			strcpy(_str, s);
		}
			string& operator=(const string& s)
		{
			if (this == &s)
			{
				return *this;
			}
			char* temp = new char[strlen(s._str) + 1];
			strcpy(temp, s._str);
			delete[] _str;

			_str = temp;
			return *this;
		}
		string(const string& s)
		{
		_str = new char[strlen(s._str) + 1];
		strcpy(_str, s._str);
		}	
		~string()
		{
			if (_str == nullptr) return;

			delete[] _str;
			_str = nullptr;
		}
	private:
		char* _str;	
	};
}

2.含有增删查改的string模拟实现

在这里我们除了实现基本的构造,拷贝,复制,析构,等意外,我们还希望实现常用的增删查改等功能;
实现的常用的成员函数有:

void push_back(char ch); //尾插
void append(const char* str); //追加
void reserve(size_t n); //扩容为大小n
void resize(size_t n, char ch = '\0'); //扩容并初始化
string& insert(size_t n, char ch); //在n位置插入字符
string& insert(size_t n, const char* s);//
string& erase(size_t pos = 0, size_t len = npos);//删除长度为len的字符
void clear();//清理资源
size_t find(const char* str, size_t pos = 0);//查找str,从pos位置开始
size_t find(char ch);//查找字符 ch
const char* c_str() const;//获得字符串
size_t size() const;//实际容量大小,不包含字符串
//迭代器
const_iterator begin() const;//
iterator begin();//
iterator end();//
const_iterator end() const;//
//运算符重载
string& operator+=(const char* str);//
string& operator+=(char ch);//
bool operator<(const string& s1, const string& s2);
bool operator==(const string& s1, const string& s2);
bool operator<=(const string& s1, const string& s2);
bool operator>(const string& s1, const string& s2);
bool operator>=(const string& s1, const string& s2);
bool operator!=(const string& s1, const string& s2);
ostream& operator<<(ostream& out, const string& s2);
istream& operator>>(istream& in,  string& s2);

2.1 基本的数据成员

要设计一个可以增删查改的string类,我们就需要一些数据成员,去保存数据的大小和容量;

class string
{
private:
	char* _str;
	size_t _size; //计算字符实际大小,不包含\0
	size_t _capacity;//计算可用容量大小,不包含\0
	static const size_t npos; //这个用来find查找使用的
};
const size_t string::npos = -1;

上面还有一个npos变量,可以暂时不用管,待会会讲到它的作用;


2.2 构造函数,和析构函数的实现

没什么好说的,只不过是比简易版的类,多了两个成员变量的初始化;完成即可;

string(const char* str = "") :_size(strlen(str)),
			_capacity(_size)
		{
			_str = (new char[_capacity + 1]);
			strcpy(_str, str);
		}

析构函数也是:和简易版的多出两个成员变量的清理而已;

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

2.3 拷贝构造函数,赋值运算符的实现

这个比简易类的多出也就是两个成员函数的拷贝而已;
拷贝构造:

string(const string& str) :_size(str._size), _capacity(str._capacity)			
		{
			_str = (new char[str._capacity+1]);
			strcpy(_str, str._str);
		}

赋值运算符:

string& operator=(const string& str)
	{
		if (this != &str)
		{
			_size = str._size;
			_capacity = str._capacity;
			//先申请临时空间,再释放原来的空间
			char* temp = new char[str._capacity + 1];
			strcpy(temp, str._str);
			delete[] _str;
			_str = temp;
		}
		return *this;
		}

2.4 拷贝构造,赋值运算符函数(新写法)

拷贝构造:复用构造函数

string(const string& str) //这里要赋值为空,要不然
		:_str(nullptr),_size(0),_capacity(0)//当交换结束后,调用析构函数,会释放非法空间,会崩溃
		{
			string temp(str._str); //先调用构造函数
			std::swap(_str,temp._str); //再交换数据
			std::swap(_size,temp._size);
			std::swap(_capacity,temp._capacity);
		}

赋值运算符:复用拷贝构造

string& operator=(const string& str)
{
	if(this != &str)
	{
	string temp(str);
	std::swap(_str,str._str);
	std::swap(_size,str._size);
	std::wap(_capacity,str._capacity);
	}
	return *this;
}

2.5 扩容的相关函数

我们知道
reserve函数,和 resize函数的区别在于,前者空间变化,不初始化,后者空间变化,会初始化;
并且resize:当传参的参数都比原来字符串大小还要小,就会截断超过参数的字符串;
而对于reserve来说:当传参的参数都比原来字符串大小还要小,就不做任何事情


对于 reserve函数:
只要传入的参数n,大于原来的字符串容量,那么就扩容,并且还会拷贝原来的数据到扩容的空间中;
也就是说,即使扩容的容量比原来的字符串空间还要大,也不会删掉原来的字符串

void reserve(size_t n)
{
	if(n>_capacity)
	{
		char* temp = new char[n+1];
		strcpy(temp,_str);
		delete[] _str;
		_str = temp;
		_capacity = n; //这个别忘记赋值了
	}
}

对于resize函数:
要考虑三种情况:
1 重置的空间,比原字符串小;
处理方式,只要把n位置的字符修改为\0即可,这样就代表该字符串到\0结束,相当于截断大于n位置的字符串了;
![在这里插入图片描述](https://img-blog.csdnimg.cn/14be7e46759145afa83a646c0c8d2515.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5ZGL5Z
2 重置的空间,比原字符串大,但没有超过容量;
处理方式用\0初始化;
在这里插入图片描述
3 重置的空间,比原字符串大,但没有超过容量;
先扩容,再初始化


void resize(size_t n,char ch = '\0')
{
	if(n >=_size)//重置的空间,比原字符串小
	{
		_size = n;
		_str[_size] = '\0';		
	}
	else //重置的空间,比原字符串大
	{
		//重置的空间,比原字符串大,但超过容量了;
		if(n > _capacity) reserve(n);
		//初始化,扩容的空间
		memset(_str+_size,ch,n-_size);
		_size = n;
		_str[_size] = '\0';
	}
}

2.6 尾插,追加,任意位置插入,+=运算符的重载的实现

对于尾插,我们实现的是尾插入一个字符;
涉及到插入,首先第一考虑容量的够不够,不够首先扩容;


尾插函数的实现:

void push_back(char ch)
{
	//满了我们以2倍容量方式扩容
	if(_size == _capacity)
		reserve(_capacity == 0 ? 4:_capcacity*2);
	_str[_size] = ch;
	++_size;
	_str[_size] = '\0';
}

对于追加,也就是往尾部追加字符串:
那么我们也要考虑:追加的字符串,是否大于原字符串剩余的容量,大于那么必须得扩容;
如何判断追加得字符串是否大于源字符串剩余得容量呢?
我们可以通过计算出追加字符串的大小,然后再加上原字符串的大小,再和原字符串得容量比较即可判断;


void append(const char* s)
{
	size_t len  = strlen(s);
	//追加的字符串,大于原字符串剩余的容量
	if(_size+len > _capacity)
		reserve(_size+len);
	//再将s追加到字符串后面
	stcpy(_str+_size,s);
	_size = _size+len;		
}

有了上面的操作,我们就可以复用它们去实现+=运算符重载了,因为这两个运算符本质也是追加:

//插入一个字符,+=的实现方式
string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
//追加一个字符串,+=的实现方式
string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}

任意位置插入一个字符,或者一个字符串:
重载这两个版本:
任意位置插入字符:
在这里插入图片描述


string& insert(size_t n ,char ch)
{
	assert(n<=_capacity); //插入的位置必须小于容量
	if( n== _capacity)
		reserve(_capacity == 0 ? 4 :_capacity*2);
	
	//先移动,腾出一个字符的位置
	size_t end = _size+1;
	while(end > n)
	{
		_str[end] =_str[end+1];
		--end;
	}
	_str[n] = ch;
	_size++;
	
	return*this;
}

任意位置插入一串字符串:

string& insert(size_t n, const char* s)
{
	assert(n<=_capacity);
	size_t len = strlen(s);
	//扩容
	if(len+_size > _capacity)
		reserve(len+_size);
	
	size_t end = _size+len; 
	while(end > n+len)
	{
		_str[end] = _str[end-len];
		end--;
	}
	//插入数据
	strncpy(_str+n,s,n);
	_size+=len;
	return*this;
}

2.7 迭代器,大小,获取字符串,方括号重载[ ]

不多说,迭代器就是可以先认为就是一个指针,这是一个简易版的迭代器,也就是说,只是char*用迭代器来替代完成了,基本的用法;
实际上真正的迭代器是要分装一个类来实现的,但是呃呃,简单通行嘛。我们就简单的实现一个别名的方式就好;

typedef char* iterator;
typedef const char* const_iterator;

	const_iterator begin() const
	{
		return _str;
	}
	const_iterator end() const
	{
		return _str + _size;
	}
	iterator begin()
	{
		return _str;
	}
	iterator end()
	{
		return _str + _size;
	}

大小,获取字符串,方括号重载[ ]:其实分装为函数,来获取成员方法嘛

const char* c_str() const
{
	return _str;
}
		
size_t size() const
{
	return _size;
}

char& operator[](size_t pos)
{
	assert(pos < _size); //越界检查
	return _str[pos];
}

const char& operator[](size_t pos) const
{
	assert(pos < _size); //越界检查
	return _str[pos];
}		
		

2.8 关系运算符,流插入和流提取的重载

关系运算符实现,关键实现两个:< 和 ==,然后其他的关系运算符复用这两个即可;
注意:string类中 关系运算符实现,是全局函数的实现方式;

bool operator<(const string& s1, const string& s2) 
{
	size_t i1 = 0, i2 = 0;
	while (i1 < s1.size() && i2 < s2.size())
	{
		if (s1[i1] < s2[i2])
		{
			return true;
		}
		else if (s1[i1] > s2[i2])
		{
			return false;
		}
		else //相等继续走
		{
			++i1;
			++i2;
		}
	}
		//退出循环有三种可能
		//"abc" "abc" flase
		//"abc" "abcd" true
		//"abcd" "abc" flase
		return i2 < s2.size() ? true : false;
		/*
			还可以直接调用C库函数,完成opreator<的函数
			return strcmp(s1.c_str(),s2.c_str()) < 0;
		*/		
	}
bool operator==(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) == 0;
	}
	bool operator<=(const string& s1, const string& s2)
	{
		return s1 < s2 || s1 == s2;
	}
	bool operator>(const string& s1, const string& s2)
	{
		return !(s1 <= s2);
	}
	bool operator>=(const string& s1, const string& s2)
	{
		return !(s1 < s2);
	}
	bool operator!=(const string& s1, const string& s2)
	{
		return !(s1 == s2);
	}

	ostream& operator<<(ostream& out, const string& s2)
	{
		for (auto ch : s2)
		{
			out << ch;
		}
		return out;
		/*
		out<<s2.c_str();//这种方式输出遇到\0就停止了,无法输出全部字符;
		return out;
		*/
	}
		
	istream& operator>>(istream& in,  string& s2)
	{
		s2.clear();
		char ch = in.get();

		while (ch != ' ' && ch != '\n')
		{
			s2 += ch;
			ch = in.get();
		}
		return in;
	}

3.9 查找函数的实现

查找函数也有两个版本:一个查找字符,一个查找子串;
注意:我们查到到了,就返回对应下标,没找到,那么久返回npos,其实该值就是-1,但是由于我们返回类型为
size_t,所以-1,会被转换位4亿几千万那个超级大的数;
这个数的意义就是,表示我一个字符串,都查到4亿多的位置,还没找到,那么就是表示没找到该数了;

size_t find(char ch)
{
	for (size_t i = 0; i < _size; ++i)
	{
		if (ch == _str[i])
		{
			return i;
		}
		return npos;
	}
}
size_t find(const char* str, size_t pos = 0)
{
	const char* pstr = strstr(_str+pos, str);
	if (nullptr == str)
	{
		return npos;
	}
	else
	{
		return pstr - _str;
	}
}

2.10 删除和清理

//len = npos表示默认删除完整个子串
string& erase(size_t pos = 0, size_t len = npos)
{
	assert(pos < _size);
	if (len == npos || pos + len >= _size)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		strcpy(_str + pos, _str + pos + len);
		_size -= len;
	}
	return *this;
}

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

2.11 string 类的全部实现代码

#pragma once
#include<algorithm>
#include<assert.h>
#include<iostream>
namespace xjh
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		const_iterator begin() const
		{
			return _str;
		}
		const_iterator end() const
		{
			return _str + _size;
		}
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		/*
		string(const char* str ) :_size(strlen(str)),
			_capacity(_size)
		{
			_str = (new char[_capacity + 1]);
			strcpy(_str, str);
		}
		*/
		//这个解决上个代码的问题:默认构造的问题
		string(const char* str = "") :_size(strlen(str)),
			_capacity(_size)
		{
			_str = (new char[_capacity + 1]);
			strcpy(_str, str);
		}
		
			//传统的写法,仅仅是完成了深拷贝
			//拷贝构造			
		string(const string& str) :_size(str._size), _capacity(str._capacity)
			
		{
			_str = (new char[str._capacity+1]);
			strcpy(_str, str._str);
		}
		/*
		//现代写法的:拷贝构造
		//s2(s1)
		string(const string& str) //这里要赋值为空,要不然
		:_str(nullptr),_size(0),_capacity(0)//当交换结束后,调用析构函数,会释放非法空间,会崩溃
		{
			string temp(str._str); //先调用构造函数
			std::swap(_str,temp._str); //再交换数据
			std::swap(_size,temp._size);
			std::swap(_capacity,temp._capacity);
		}
		*/
					
		/*
			//s1 = s3;
			//这种方式:自己给自己赋值会出问题
			//s3 = s3;
		string& operator=(const string& str)
		{
			delete[] _str;

			_str = new char[strlen(str._str) + 1];
			strcpy(_str, str._str);

			return *this;
		}
		*/

		/*
			 //解决了上一个问题:自己给自己赋值的问题
			 //但是还有问题:假如new失败了呢?你还把_str释放了,
			 //这样就会抛异常出问题
		string& operator=(const string& str)
		{
			if (this != &str)
			{
				delete[] _str;
				_str = new char[strlen(str._str) + 1];
				strcpy(_str, str._str);
			}

			return *this;
		}
		*/

		//解决了上一个问题:new失败时候,并且还把自己的空间释放的问题
		string& operator=(const string& str)
		{
			if (this != &str)
			{
				_size = str._size;
				_capacity = str._capacity;
				//先申请临时空间,再释放原来的空间
				char* temp = new char[str._capacity + 1];
				strcpy(temp, str._str);
				delete[] _str;
				_str = temp;
			}
			return *this;
		}
		/*
			//现代写法:=赋值运算符
			// s2 = s1
			string& operator=(const string& str)
			{
				if(this != &str)
				{
					string temp(str);
					std::swap(_str,str._str);
					std::swap(_size,str._size);
					std::swap(_capacity,str._capacity);
				}
				return *this;
			}

			// 更加简洁的写法,直接再传参时候,拷贝构造了str
			string& operator=( string str)
			{
				std::swap(_str,str_str);
				std::swap(_size,str._size);
				std::swap(_capacity,str._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[](size_t pos)
		{
			assert(pos < _size); //越界检查
			return _str[pos];
		}
		const char& operator[](size_t pos) const
		{
			assert(pos < _size); //越界检查

			return _str[pos];
		}
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* temp = new char[n + 1];
				strcpy(temp, _str);
				delete[] _str;
				_str = temp;
				_capacity = n;
			}
		}
		void push_back(char ch)
		{
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4:_capacity * 2);
			}
			_str[_size] = ch;
			_size++;
			_str[_size] = '\0';
		}
		void append(const char* str)
		{
			size_t len = strlen(str);

			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			strcpy(_str + _size, str);
			_size += len;
		}
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}
		void resize(size_t n, char ch = '\0')
		{
			if (n <= _size)
			{
				_size = n;
				_str[_size] = '\0';
			}
			else
			{
				if (n > _capacity)
				{
					reserve(n);
				}
				memset(_str + _size, ch, n - _size);
				_size = n;
				_str[_size] = '\0';
			}
		}
		size_t find(char ch)
		{
			for (size_t i = 0; i < _size; ++i)
			{
				if (ch == _str[i])
				{
					return i;
				}
				return npos;
			}
		}
		size_t find(const char* str, size_t pos = 0)
		{
			const char* pstr = strstr(_str+pos, str);

			if (nullptr == str)
			{
				return npos;
			}
			else
			{
				return pstr - _str;
			}
		}
		string& insert(size_t n, char ch)
		{
			assert(n <= _capacity);

			if (_size == _capacity)
			{
				reserve(_capacity == 4 ? 0 : _capacity * 2);
			}
			/*
				//假如这里没有int强制类型转换,那么就会头插时候发生越界
				//size_t类型要注意--时候会发生越界的问题
			size_t end = _size;
			while (end >= (int)n)
			{
				_str[end + 1] = _str[end];
				end--;
			}*/
		
			size_t end = _size+1;
			while (end >n)
			{
			_str[end ] = _str[end-1];
			end--;
			}
			_str[n] = ch;
			++_size;

			return *this;
		}
		string& insert(size_t n, const char* s)
		{
			assert(n <= _capacity);
			size_t len = strlen(s);
			if (_size+len > _capacity)
			{
				reserve(_size+len);
			}

			size_t end = _size + len;
			while (end >n+len)
			{
				_str[end] = _str[end - len];
				end--;
			}
			strncpy(_str+n, s, n);
			_size += len;

			return *this;
		}
		string& erase(size_t pos = 0, size_t len = npos)
		{
			assert(pos < _size);

			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
			return *this;
		}
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
		static const size_t npos;
	};
	const size_t string::npos = -1;
	bool operator<(const string& s1, const string& s2) 
	{
		size_t i1 = 0, i2 = 0;

		while (i1 < s1.size() && i2 < s2.size())
		{
			if (s1[i1] < s2[i2])
			{
				return true;
			}
			else if (s1[i1] > s2[i2])
			{
				return false;
			}
			else //相等继续走
			{
				++i1;
				++i2;
			}
		}
		//退出循环有三种可能
		//"abc" "abc" flase
		//"abc" "abcd" true
		//"abcd" "abc" flase

		return i2 < s2.size() ? true : false;
		/*
			还可以直接调用C库函数,完成opreator<的函数
			return strcmp(s1.c_str(),s2.c_str()) < 0;
		*/		
	}
	bool operator==(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) == 0;
	}
	bool operator<=(const string& s1, const string& s2)
	{
		return s1 < s2 || s1 == s2;
	}
	bool operator>(const string& s1, const string& s2)
	{
		return !(s1 <= s2);
	}
	bool operator>=(const string& s1, const string& s2)
	{
		return !(s1 < s2);
	}
	bool operator!=(const string& s1, const string& s2)
	{
		return !(s1 == s2);
	}

	ostream& operator<<(ostream& out, const string& s2)
	{
		for (auto ch : s2)
		{
			out << ch;
		}
		return out;
		/*
		out<<s2.c_str();//这种方式输出遇到\0就停止了,无法输出全部字符;
		return out;
		*/
	}
	
	istream& operator>>(istream& in,  string& s2)
	{
		s2.clear();
		char ch = in.get();

		while (ch != ' ' && ch != '\n')
		{
			s2 += ch;
			ch = in.get();
		}
		return in;
	}

}

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

呋喃吖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值