string类的模拟实现(三)

注意点:初始化列表的顺序不能改变,因为变量是按照 初始化列表 的顺序来进行初始化的。

之前在string类(二)中提到简单的功能,这期是在这些功能之上,基础代码:

cpp 复制代码
//传统写法的string类
 
#include <iostream>
#include <string>
#include <assert.h>
#include <stdlib.h>
 
using namespace std;
 
class String
{
public:
	String(const char* str = "") //默认构造函数,没有传参就是空字符串
	{
		//构造String类对象时,如果传递nullptr指针,可以认为程序非法
		if (nullptr == str)
		{
			assert(false);
			return;
		}
 
		_str = new char[strlen(str) + 1];//开辟对应的空间,加一是给\0保留位置
		strcpy(_str, str); //_str复制str
	}
 
	String(const String& s)                           //计算 strlen(s._str) + 1得到需要分配的内存大小
		:_str(new char[strlen(s._str) + 1])           //使用 new运算符在堆上分配相应大小的字符数组
	{                                                 //new表达式返回指向分配内存的指针
		strcpy(_str, s._str);                         //这个指针值​​直接​​用于初始化 _str成员变量
	}

 
    //传统写法
	String& operator=(const String& s) //赋值运算符重载  str1 = str2;  -> str1.operator=(str2)
	{
		if (this != &s)                //检查自我赋值,this指向调用该成员函数的对象(不是指参数)
		{
			char* pStr = new char[strlen(s._str) + 1];
			strcpy(pStr, s._str);      //strcpy函数将源字符串 s._str的内容复制到新分配的内存 pStr中
			delete[] _str;
			_str = pStr;
		}
		return *this;
	}
 
    //现代写法
	String& operator=(String s)
	{
		swap(_str, s._str);
		return *this;
	}

 
	~String()                //析构函数,不为空就释放空间
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}
 
private:
	char* _str;
};

一些简单功能的实现:

cpp 复制代码
		// 返回c语言形式的字符串
		const char* c_str() const
		{
			return _str;
		}
 
		// 返回有效的字符个数
		size_t Size() const
		{
			return _size;
		}

访问string的下标:

operator [] 需要注意两个接口,一个接口是可读可写的函数,也就是针对的是非const 对象;另一个是只可读的 const 对象:【至于为什么有两个接口?在string类(二)讲过】

可读可写(非const对象):

cpp 复制代码
		char& operator[](size_t pos)
		{
			assert(pos < _size);
 
			return _str[pos];
		}

只可读(const对象): //const 成员函数 (重点介绍)

cpp 复制代码
		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
 
			return _str[pos];
		}

上述的只可读接口函数 当中,这是一个const成员函数,函数声明末尾的 const关键字修饰的是​​隐含的 this指针​, 实际上const 将this指针从String* const 变为**const String* const,**修。因为const对象只能调用const成员函数.

普通版本:

cpp 复制代码
const String s("hello"); // const 对象
char c = s[0];           // 错误!可读可写版本的 operator[]

提供的const版本:

cpp 复制代码
const String s("hello"); // const 对象
char c = s[0];           // 正确!调用 const 版本的 operator[]

String* const 变为**const String* const,**这两种指针类型虽然看起来相似,但有着根本性的区别,顺带复习const。

  • String* const:const pointer to String(指向 String 的常量指针)
  • const String* const :const pointer to const String(指向 const String 的常量指针)

详细对比:

  1. String* const(指向非常量 String 的常量指针,const只修饰ptr指针)
cpp 复制代码
String str1("Hello");
String str2("World");

String* const ptr = &str1; 

// ptr = &str2;  // 错误!不能改变指针的指向(因为ptr是常量)

// 通过 ptr 访问的特性:
ptr->append("!");    // 正确!可以修改所指对象的内容
cout << ptr->c_str(); // 正确!可以读取所指对象的内容

指针++不能改变朝向++ ,但是可以改变++指针指向的内容++。

  1. const String* const(指向常量 String 的常量指针,string也被const修饰)
cpp 复制代码
const String str1("Hello");
String str2("World");

const String* const ptr = &str1; 
// ptr 本身的特性:
// ptr = &str2;  // 错误!不能改变指针的指向

// 通过 ptr 访问的特性:
// ptr->append("!");    // 错误!不能修改所指对象的内容
cout << ptr->c_str();   // 正确!可以读取所指对象的内容

指针的指向和内容++都不可被修改++。

简单复习:

在 this 指针上下文中的意义 :

普通成员函数中的 this

cpp 复制代码
class String {
public:
    void modify() {
        // this 的类型是 String* const
        // 可以修改成员变量:this->_str = new_value;
    }
};

const 成员函数中的 this

cpp 复制代码
class String {
public:
    void inspect() const {
        // this 的类型是 const String* const
        // 不能修改成员变量:this->_str = new_value; // 错误!
    }
};

对于字符串的增,实现两个函数,一个是 push_back() 一个是 append() ,push_back() 是尾差一个字符 ,append() 是尾差一个字符串

当然,增,可能会有一个扩容的问题:对于 push_back() 我们可以直接用 _capacity 的 2 倍的形式扩容;append() 尾差的是字符串,我们不敢直接 扩容2倍。所以,我们这里使用 strlen(str) 计算要插入的字符串的有效字符 + _size 原本字符串数组当中的有效字符作为 扩容的条件和 扩容的大小。

在官方的string类当中,有一个reserve() 扩容函数 ,在这这里我们就直接实现这个函数,那么在上述的 push_back() 一个是 append() 函数中我们就使用这个函数来进行扩容:

实现reserve() :

cpp 复制代码
		// 扩容函数
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n; 
			}
		}

扩容之后就可以实现上述两个函数了:

cpp 复制代码
		// 增
		void push_back(char ch)
		{
			// 如果有效字符个数超过了 容量
			if (_size >= _capacity)
			{
				// 2倍扩容
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			_str[_size] = ch;
			_size++;
			_str[_size] = '\0';
		}
 
		void append(const char* str)
		{
			size_t len = strlen(str);
			if (len + _size > _capacity)
			{
				// 扩容
				reserve(len + _size);
			}
			strcpy(_str + _size, str);
			_size += len;
		}

当然上述两个函数都不是我们最常用的,最常用的是 operator += 这个运算符重载函数,当然这个函数的底层和上述差不多,只不过在使用的时候更加方便:

他同样有两个接口,一个是 尾插字符,一个是尾差字符串:

cpp 复制代码
		// += 尾差字符
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
 
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}

对于插入,还有就是在 指定位置(pos)位置插入字符或字符串(insert())函数的实现:

cpp 复制代码
		// 指定位置插入一个或多个字符
		void insert(size_t pos, size_t n, char ch)
		{
			assert(pos <= _size);
 
			if (_size + n >= _capacity)
			{
				// 扩容
				reserve(_size + n);
			}
 
			//往后挪动数据
			size_t end = _size;
			while (pos <= end && end != npos)
			{
				_str[end + n] = _str[end];
				--end;
			}
 
			// 覆盖值
			for (size_t i = 0; i < n; i++)
			{
				_str[pos + i] = ch;
			}
 
			_size += n;
		}
cpp 复制代码
		// 指定位置插入一个字符串
		void insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			size_t len = strlen(str);
 
			if (_size + len >= _capacity)
			{
				// 扩容
				reserve(_size + len);
			}
 
			//往后挪动数据
			size_t end = _size;
			while (pos <= end && end != npos)
			{
				_str[end + len] = _str[end];
				--end;
			}
 
			// 覆盖值
			for (size_t i = 0; i < len; i++)
			{
				_str[pos + i] = *(str + i);
			}
 
			_size += len;
		}

在实现的时候遇到的问题,如下图:

end是int类型的,pos是size_t类型的,按照上述的调试,本次循环应该退出循环,但是实际上是进入了循环;

其原因是因为,在c语言的语法当中规定,在一个运算符的两边,如果左右两边的操作数的类型不相同,就会发生整形提升,通常是小类型像大类型发生转换,比如如果是 int类型和 double类型,那么int类型会转换为 double类型。

所以,看似上述end到了-1,按道理应该退出循环,但是int类型的 end 发生的整形提升,提升到了 size_t 无符号整形,所以,了解类型的值域的循环的小伙伴就知道,此时end就不会是-1,从无符号整形的视角来看,是全1,就是整形的最大值。自然就不会跳出循环。

解决上述问题的方式有很多,可以强制类型转换:

或者像官方string当中一样设置一个 npoe :

如上设置之后,就可以在while循环当中多增加一个条件,当 end 走到 -1 的之后就停止:

删:

erase函数,从pos位置删除一个或多个字符:

cpp 复制代码
		// 从pos位置删除一个或多个字符
		void erase(size_t pos, size_t len = npos)
		{
			assert(pos <= _size);
 
			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				size_t end = pos + len;
				while (end <= _size)
				{
					_str[pos++] = _str[end++];
				}
				_size -= len;
			}
		}

clear()函数,删除字符串当中所有的有效字符:

cpp 复制代码
		// 清除所有有效字符
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}

find()函数,在pos位置查找一个字符:

cpp 复制代码
		// 从pos位置往后查找一个字符
		size_t find(char ch, size_t pos = 0)
		{
			for (size_t i = pos; i <= _size; i++)
			{
				if (_str[i] == ch)
				{
					return i;
				}
			}
 
			return npos;
		}

find()函数,从pos位置开始查找一个字符串:

cpp 复制代码
		// 从pos位置开始查找一个字符串
		size_t find(const char* str, size_t pos = 0)
		{
			assert(pos < _size);
			const char* ptr = strstr(_str, str);
			if (ptr)
			{
				return ptr - _str;
			}
			else
			{
				return npos;
			}
		}

substr()函数,从字符串数组的pos位置开始取出len个字符大小的字符串:

cpp 复制代码
		// 从pos位置开始,从字符串数组当中取出len个字符的字符串,返回string类
		string substr(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
 
			size_t n = len;
			if (len == npos || len + pos >= _size)
			{
				n = _size - pos;
			}
 
			string tmp;
			tmp.reserve(n);
			for (int i = pos; i < pos + n; i++)
			{
				tmp += _str[i];
			}
 
			return tmp;
		}

resize的实现:

resize可以扩容也可以缩容,会初始化,默认 '\0'。

cpp 复制代码
		// resize 删除或扩容添加函数
		void resize(size_t n, char ch = '\0')
		{
			if (n < _size)
			{
				_size = n;
				_str[_size] = '\0';
			}
			else
			{
				// 先检测 扩容
				reserve(n);
 
				for (int i = _size; i < n; i++)
				{
					_str[i] = ch;
				}
				_size = n;
				_str[_size] = '\0';
			}
		}

operator<<(流插入的实现)

基本实现方式:

cpp 复制代码
// 正确的流插入操作符实现
std::ostream& operator<<(std::ostream& out, const std::string& str)
{
    // 方式一:使用下标运算符访问
    for (size_t i = 0; i < str.size(); i++)
    {
        out << str[i];
    }
    
    // 方式二:使用范围for循环(更现代、简洁)
    for (auto ch : str)
    {
        out << ch;
    }
    
    return out;
}

此处的 打印和 使用 str 来打印,两者其实有差别的,

str 打印的是一个字符串,是一个内置类型,打印这个字符串是以 '\0' 作为终止符的,而流输入是插入多少就打印多少,所以遇见 '\0' 是不会停止的,会出现问题。

所以推荐使用memcpy();

cpp 复制代码
// 使用 memcpy 的正确方式
std::string::string(const std::string& other)
{
    _size = other._size;
    _capacity = other._capacity;
    _str = new char[_capacity + 1];
    
    // 使用 memcpy 而不是 strcpy
    memcpy(_str, other._str, _size + 1); // 拷贝所有字节,包括中间的 \0
}

operator>>(流提取的实现)

有问题的实现:

cpp 复制代码
// 有问题的初始实现
std::istream& operator>>(std::istream& in, std::string& str)
{
    char ch;
    in >> ch; // 这里使用 >> 会跳过空格和换行
    while (ch != ' ' && ch != '\n')
    {
        str += ch;
        in >> ch; // 同样会跳过空格和换行
    }
    return in;
}

in >> ch会跳过空白字符(空格、制表符、换行等),因此循环永远不会遇到空格或换行,导致无法检测到终止条件,死循环。

而且,我们使用 流提取来对string类对象当中的字符串数组进行写入数据的话,我们希望的是覆盖,而不是像 operator+= 函数一样尾插,那我们可以考虑先用 clear() 函数对字符串进行清除。

解决方法,在 istream 这个流提取当中有一个接口 get(),它默认每次只读取一个字符,不管这个字符是 空格 还是 换行。

代码如下:

cpp 复制代码
	istream& operator>> (istream& in, string& str)
	{
        str.clear();
		char ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			str += ch;
			ch = in.get();
		}
 
		return in;
	}

这算是基础的正确实现。

改进点​​:

  1. 使用 in.get()而不是 in >> ch,因为前者会读取所有字符(包括空白字符)
  2. 先调用 str.clear()清空字符串,确保是覆盖而非追加

基础实现虽然正确,但性能上有缺陷:

  • 每次添加一个字符可能导致频繁的内存重新分配
  • 对于长字符串,性能较差

性能优化实现:

开辟一个临时数组,这个数组的大小可以自己规定,规定大小为 128 个字符;相当于是把我们输入的字符串,以一组127个有效字符,分割成很多组,当一组的字符填满之后,在对str对象当中的字符串数组进行填写,这样就避免了 在 operator+= 当中很多次的扩容操作。

还需要优化的是,上述我们的实现的流提取,遇到换行或者空格就会停止,那么如果我们在输入有效字符之前,有空格或者换行,那么就会直接停止,但是在官方的string当中的流提取是会把前面的空格和换行清除的,所以我们这加一个循环,把有效字符之前的空格和换行给删除了。

最终代码实现:

cpp 复制代码
istream& operator>> (istream& in, string& str)
	{
		str.clear();
		char ch = in.get();
 
		while (ch == ' ' || ch == '\0')
		{
			ch = in.get();
		}
 
		char Buff[128];
		int i = 0;
 
		while (ch != ' ' && ch != '\n')
		{
			Buff[i++] = ch;
 
			if (i == 127)
			{
				Buff[i] = '\0';
				str += Buff;
				// 重置i
				i = 0;
			}
			ch = in.get();
		}
 
		// 如果此时 i 不是0,说明Buff 当中还有字符没有 += 完
		if (i != 0)
		{
			Buff[i] = '\0';
			str += Buff;
		}
 
		return in;
	}

比较大小operator<

可以直接使用 C 当中strcmp() 这个库函数来实现,但是还是会出现和上述一样的问题,如果有效字符当中有 '\0' ,那么就会出现问题,所以我们应该使用 memcmp()。

但是,memcmp()还是有问题,如下两种情况,两个字符串字符个数不相等,还是比较麻烦的:

所以还是要自己来实现,其实自己实现也不难:

  • 两个字符串一起走,如果当前哪一个字符串的字符的ascll值大,那么他就大,反之;如果当前两个字符串的字符相等,那么就继续往后走。
  • 之后到最后,有两种情况,一种是两个字符串字符相等,字符个数也相等,那么这两个字符串就相等;另一种就是上述说的两种情况,短的字符串走完了,长的字符串没有走完,那么长的字符串就是大的那一个字符串;

代码实现:

cpp 复制代码
		int operator< (const string& str)
		{
			//return strcmp(_str, str._str) < 0;
 
			size_t i1 = 0;
			size_t i2 = 0;
 
			while (i1 < _size && i2 < str._size)
			{
				if (_str[i1] < str._str[i2])
				{
					return true;
				}
				else if (_str[i1] > str._str[i2])
				{
					return false;
				}
				else
				{
					++i1;
					++i2;
				}
			}
 
			/*if (i1 == _size && i2 != str._size)
			{
				return true;
			}
			else
			{
				return false;
			}*/
			// 或者
			
			// return _size < str._size;
 
			// 或者
 
			return i1 == _size && i2 != str._size;
		}

复用 memcmp()函数实现的代码如下:

cpp 复制代码
		int operator< (const string& str)
		{
			int Mybool = memcmp(_str, str._str, _size < str._size ? _size : str._size);
 
			return Mybool == 0 ? _size < str._size : Mybool < 0;
		}

operator== <= > >= !=

写好一个之后,后面的就简单了,可以直接复用:

cpp 复制代码
		bool operator== (const string& str) const
		{
			return _size == str._size &&
				memcmp(_str, str._str, _size) == 0;
		}
 
		bool operator<= (const string& str) const
		{
			return *this < str || *this == str;
		}
 
		bool operator> (const string& str) const
		{
			return !(*this <= str);
		}
 
		bool operator>= (const string& str) const
		{
			return !(*this < str);
		}
 
		bool operator!= (const string& str) const
		{
			return !(*this == s);
		}

operator=

文献参考:C++-string类的模拟实现-CSDN博客 //来源博主:chihiro

赋值拷贝有两种,一种是浅拷贝,一种是深拷贝:

来看下面这个代码:

cpp 复制代码
string& operator= (const string& str)
		{
			if (this != &str)
			{
				string tmp(str);
 
				std::swap(_str, tmp._str);
				std::swap(_size, tmp._size);
				std::swap(_capacity, tmp._capacity);
			}
			return *this;
		}

如上,新创建一个和 str 一样的对象(调用了拷贝构造函数)-tmp,然后把tmp和 this对象的数组指针和 所以成员交换一下,然后就可以实现深拷贝了,这是一个很妙的写法,看下图:

如上所示,tmp新开辟了一块空间,s1想着,反正你tmp 的生命周期 就在这个函数当中,出了这个函数,tmp就需要调用析构函数,释放空间,那么s1就把他的空间给tmp,tmp就把新开辟的,和s3赋值好的空间给s1,然后tmp在最后释放空间的时候就释放的是s1原本的空间,相当于是tmp为s1把原空间给释放掉了。

注意:在赋值操作符重载函数当中不能像如下一样写:

cpp 复制代码
  string& operator= (const string& str)
    {
        string tmp(str);
        std::swap(tmp , *this);
 
        return *this;
    }

像上述一样写会造成递归死循环。

这时候,swap()这个函数在两个参数都是对象的时候,它其中调用的就是 operator= 赋值操作符重载函数,如下所示:

那像上述代码就会在swap 和 operator= 之间来回跳 ,造成递归式的死循环

所以在像上述一样使用swap 的时候,还是需要自己实现swap()函数:

就如上述string类的swap如下所示实现:

cpp 复制代码
class string
{
`````````````
 
		void swap(string& str)
		{
			std::swap(_str, str._str);
			std::swap(_size, str._size);
			std::swap(_capacity, str._capacity);
		}
 
````````````
};

根据上述实现的swap()函数,上述的 operator= 函数可以如下优化:

cpp 复制代码
		string& operator= (string& str)
		{
			swap(str);
 
			return *this;
		}

上述相当于把两个string对象的 所有成员 都给交换了。

cpp 复制代码
		string& operator= (string str)
		{
			swap(str);
 
			return *this;
		}

而上述才是和之前一样,是传值拷贝,需要创建临时对象,也就是这个str就是局部变量,局部变量出了这个函数作用域就结束了生命周期,相当于是 str 帮s1 (*this)把s1原本的空间释放掉了。

基于上述方法,对拷贝构造函数的优化

基于上述的优化,我们可以优化拷贝构造函数,我们也可以在拷贝构造函数使用和上述一样的方式来,让编译器来帮我们释放空间:

cpp 复制代码
		string(const string& s)
		{
			string tmp(s._str);
			// 让tmp来帮s对象释放掉原来的空间
			swap(tmp);
		}

上述是对拷贝构造函数的优化,但是上述还是有问题:

上述代码在一般的编译器当中就会报错,编译器对于类当中的内置类型,一般来说是不会自动做初始化的,如果你在一些环境当中看见初始化的,都属于是这个编译器的优化,但是不是所以的编译器都会这样做,所以我们不敢依靠编译器来自动初始化,我们需要手动初始化。

上述的 this 对象没有手动初始化,对于其中的内置类型(_size 和 _capacity )来说,是随机值,那么tmp交换的是一个(_size 和 _capacity )是随机值的对象,在这个函数结束之后tmp调用 析构函数 delete 释放空间的时候就会出现问题。

所以我们应该这样写:

cpp 复制代码
		string(const string& s)
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{
			string tmp(s._str);
			// 让tmp来帮s对象释放掉原来的空间
			swap(tmp);
		}

需要注意的是,上述是直接用s._str 来构造的tmp对象,那么对于 下述情况就会出现问题:

cpp 复制代码
"hello\0worle"

对于上述这个字符串,他只能拷贝 hello。所以如果是有上述情况使用的话,建议使用之前使用的拷贝构造函数来实现。

string类的完整代码:

cpp 复制代码
#pragma once
#include<assert.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>

using namespace std;

namespace string_begin
{
	class string
	{

	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		iterator begin() const
		{
			return _str;
		}

		iterator end() const
		{
			return _str + size();
		}
		// 构造函数
		//string()
		//	:_str(new char[1]),
		//	_size(0),
		//	_capacity(0)
		//{
		//	_str[0] = '\0';
		//}

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

		//string(const char* str = '\0') // 错误写法
		//string(const char* str = nullptr) // 错误写法
		//string(const char* str = "\0") // 可以但是没有必要下面更好
		string(const char* str = "")
			: _str(new char[strlen(str) + 1]),
			_size(strlen(str)),
			_capacity(strlen(str))
		{
			memcpy(_str, str, _size + 1);
		}

		// 拷贝构造函数(深拷贝)
		//string(const string& s)
		//{
		//	_str = new char[s._capacity + 1];
		//	//strcpy(_str, s._str);
		//	memcpy(_str, s._str, s._size + 1);
		//	_capacity = s._capacity;
		//	_size = s._size;
		//}

		string(const string& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			string tmp(s._str);
			// 让tmp来帮s对象释放掉原来的空间
			swap(tmp);
		}

		// 析构函数
		~string()
		{
			delete _str;
			_str = nullptr;
			_size = _capacity = 0;
		}

		// 返回c语言形式的字符串
		const char* c_str() const
		{
			return _str;
		}

		// 返回有效的字符个数
		size_t size() const
		{
			return _size;
		}

		// 下标+引用返回的运算符重载函数
		// 要提供两个版本,一个是非const对象的,一个是const对象的
		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* tmp = new char[n + 1];
				memcpy(tmp, _str, _size + 1);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}

		// 增
		void push_back(char ch)
		{
			// 如果有效字符个数超过了 容量
			if (_size >= _capacity)
			{
				// 2倍扩容
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			_str[_size] = ch;
			_size++;
			_str[_size] = '\0';
		}

		void append(const char* str)
		{
			size_t len = strlen(str);
			if (len + _size > _capacity)
			{
				// 扩容
				reserve(len + _size);
			}
			//strcpy(_str + _size, str);
			memcpy(_str + _size, str, len + 1);
			_size += len;
		}

		// += 尾插字符
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}

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

		// 指定位置插入一个或多个字符
		void insert(size_t pos, size_t n, char ch)
		{
			assert(pos <= _size);

			if (_size + n >= _capacity)
			{
				// 扩容
				reserve(_size + n);
			}

			//往后挪动数据
			size_t end = _size;
			while (pos <= end && end != npos)
			{
				_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);
			}

			//往后挪动数据
			size_t end = _size;
			while (pos <= end && end != npos)
			{
				_str[end + len] = _str[end];
				--end;
			}

			// 覆盖值
			for (size_t i = 0; i < len; i++)
			{
				_str[pos + i] = *(str + i);
			}

			_size += len;
		}

		// 从pos位置删除一个或多个字符
		void erase(size_t pos, size_t len = npos)
		{
			assert(pos <= _size);

			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				size_t end = pos + len;
				while (end <= _size)
				{
					_str[pos++] = _str[end++];
				}
				_size -= len;
			}
		}

		// 从pos位置往后查找一个字符
		size_t find(char ch, size_t pos = 0)
		{
			assert(pos <= _size);
			for (size_t i = pos; i <= _size; i++)
			{
				if (_str[i] == ch)
				{
					return i;
				}
			}

			return npos;
		}

		// 从pos位置开始查找一个字符串
		size_t find(const char* str, size_t pos = 0)
		{
			assert(pos <= _size);
			const char* ptr = strstr(_str, str);
			if (ptr)
			{
				return ptr - _str;
			}
			else
			{
				return npos;
			}
		}

		// 从pos位置开始,从字符串数组当中取出len个字符的字符串,返回string类
		string substr(size_t pos, size_t len = npos)
		{
			assert(pos < _size);

			size_t n = len;
			if (len == npos || len + pos >= _size)
			{
				n = _size - pos;
			}

			string tmp;
			tmp.reserve(n);
			for (int i = pos; i < pos + n; i++)
			{
				tmp += _str[i];
			}

			return tmp;
		}

		// resize 删除或扩容添加函数
		void resize(size_t n, char ch = '\0')
		{
			if (n < _size)
			{
				_size = n;
				_str[_size] = '\0';
			}
			else
			{
				// 先检测 扩容
				reserve(n);

				for (int i = _size; i < n; i++)
				{
					_str[i] = ch;
				}
				_size = n;
				_str[_size] = '\0';
			}
		}

		// 清除所有有效字符
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}

		// 比较string大小
		//int operator< (const string& str) const
		//{
		//	//return strcmp(_str, str._str) < 0;

		//	size_t i1 = 0;
		//	size_t i2 = 0;

		//	while (i1 < _size && i2 < str._size)
		//	{
		//		if (_str[i1] < str._str[i2])
		//		{
		//			return true;
		//		}
		//		else if (_str[i1] > str._str[i2])
		//		{
		//			return false;
		//		}
		//		else
		//		{
		//			++i1;
		//			++i2;
		//		}
		//	}

		//	/*if (i1 == _size && i2 != str._size)
		//	{
		//		return true;
		//	}
		//	else
		//	{
		//		return false;
		//	}*/
		//	// 或者
		//	
		//	// return _size < str._size;

		//	// 或者

		//	return i1 == _size && i2 != str._size;
		//}

		// < 当中复用 memcmp
		bool operator< (const string& str) const
		{
			int Mybool = memcmp(_str, str._str, _size < str._size ? _size : str._size);

			return Mybool == 0 ? _size < str._size : Mybool < 0;
		}

		bool operator== (const string& str) const
		{
			return _size == str._size &&
				memcmp(_str, str._str, _size) == 0;
		}

		bool operator<= (const string& str) const
		{
			return *this < str || *this == str;
		}

		bool operator> (const string& str) const
		{
			return !(*this <= str);
		}

		bool operator>= (const string& str) const
		{
			return !(*this < str);
		}

		bool operator!= (const string& str) const
		{
			return !(*this == str);
		}

		void swap(string& str)
		{
			std::swap(_str, str._str);
			std::swap(_size, str._size);
			std::swap(_capacity, str._capacity);
		}

		// 复制操作符重载函数
		//string& operator= (string& str)
		//{
		//	if (this != &str)
		//	{
		//		char* tmp = new char[str._capacity + 1];
		//		memcpy(tmp, str._str , str._size + 1);
		//		delete[] _str;
		//		_str = tmp;

		//		_size = str._size;
		//		_capacity = str._capacity;
		//	}

		//	return *this;
		//}

		//string& operator= (const string& str)
		//{
		//	if (this != &str)
		//	{
		//		string tmp(str);

		//		//std::swap(_str, tmp._str);
		//		//std::swap(_size, tmp._size);
		//		//std::swap(_capacity, tmp._capacity);
		//		swap(tmp);

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

		string& operator= (string str)
		{
			swap(str);

			return *this;
		}

	protected:
		char* _str;
		size_t _size;
		size_t _capacity;

		size_t static npos;
	};
	size_t string::npos = -1;

	ostream& operator<< (ostream& out, string& str)
	{
		//// 方式一
		//for (int i = 0; i < str.size(); i++)
		//{
		//	out << str[i];
		//}

		// 方式二
		for (auto ch : str)
		{
			out << ch;
		}

		return out;
	}

	istream& operator>> (istream& in, string& str)
	{
		str.clear();
		char ch = in.get();

		while (ch == ' ' || ch == '\0')
		{
			ch = in.get();
		}

		char Buff[128];
		int i = 0;

		while (ch != ' ' && ch != '\n')
		{
			Buff[i++] = ch;

			if (i == 127)
			{
				Buff[i] = '\0';
				str += Buff;
				// 重置i
				i = 0;
			}
			ch = in.get();
		}

		// 如果此时 i 不是0,说明Buff 当中还有字符没有 += 完
		if (i != 0)
		{
			Buff[i] = '\0';
			str += Buff;
		}

		return in;
	}
}