zl程序教程

您现在的位置是:首页 >  其他

当前栏目

C++ STL:string类的模拟实现

2023-04-18 14:21:37 时间

目录

前置说明

一. 构造函数和析构函数的模拟实现

1.1 构造函数

1.2 析构函数

二. string类对象容量及成员相关的函数

2.1 获取字符串有效字符数、容量及_str成员变量获取相关函数

2.2 扩容及变长相关函数

2.3 字符串清空和判空函数

三. 运算符重载函数

3.1 赋值运算符重载函数

3.2 成员访问操作符[]重载函数

3.3 +=运算符重载函数 —— 尾插字符串或字符

四. 增删查改相关函数

4.1 插入字符串(字符)相关函数

4.2 子字符串删除函数

4.3 子串查找函数

五. 迭代器相关函数

六. 关于string的全局函数

6.1 字符串比较相关函数

6.2 字符串输出和输入相关函数

6.3 字符串与数值之间的相互转换

附录:string类模拟实现完整版


前置说明

模拟实现的string类定义了四个成员函数,分别为(1)char* str -- 指向字符串的指针、(2)size_t size -- 字符串中的字符数 、(3)size_t capacity -- 字符串最大可容纳的字符数量、(4)size_t _npos -- size_t类型的-1

一. 构造函数和析构函数的模拟实现

1.1 构造函数

这里实现两种形式的构造函数,一种是通过字符串来构造,第二种是通过一个string类进行拷贝构造。注意:定义拷贝构造函数要进行深拷贝而不是浅拷贝(值拷贝),否则,会使两个string类对象中的_str指向同一块内存区域,在调用析构函数时对同一块空间连续两次释放,从而导致程序崩溃。

图1.1 采用浅拷贝(左)和深拷贝(右)的内存指向情况示意图
        string(const char* str = "")   //通过字符串构造和默认构造
			: _size(strlen(str))
			, _capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		//explicit string(const string& str)  //拷贝构造
		//一般写法 -- 正常完成深拷贝
		//string(const string& str)  //拷贝构造
		//	: _size(str._size)
		//	, _capacity(str._capacity)
		//{
		//	_str = new char[_capacity + 1];
		//	strcpy(_str, str._str);
		//}

		//拷贝构造复用写法 -- 临时对象
		string(const string& str)
			: _str(nullptr)
			, _size(str._size)
			, _capacity(str._capacity)
		{
			if (this != &str)  //排除自己给自己拷贝的情况
			{
				string tmp(str._str);
				std::swap(_str, tmp._str);
			}
		}

1.2 析构函数

析构函数要完成的工作为:释放string类对象的成员函数_str指向的内存空间。

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

二. string类对象容量及成员相关的函数

2.1 获取字符串有效字符数、容量及_str成员变量获取相关函数

  • size函数和length函数:获取字符串长度(末尾''不包含在内)。
  • capacity函数:获取当前string类对象的容量。
  • c_str函数:获取string对象的成员变量_str。
        size_t size()  const //有效字符个数
		{
			return _size;
		}

		size_t length() const
		{
			return _size;
		}

		size_t capacity()  //获取容量
		{
			return _capacity;
		}

		const char* c_str() const  //获取字符串类对象变量_str
		{
			return _str;
		}

2.2 扩容及变长相关函数

这里主要涉及两个函数:

  • reserve:将string类对象的字符容量扩大到n,如果给定的n小于原来的_capacity,则容量不发生改变。
  • resize:将字符串的长度变为n,并将新增的空间的内容初始化为ch。

reserve函数进行的工作:

  1. 检查n是否大于_capacity,如果否,函数不进行任何工作。
  2. 如果n大于_capacity,那么开辟一块能容纳n+1个字符的内存空间,将类对象原来的字符串复制到新开辟的这块空间。
  3. 释放原来的字符串内存空间,_str指向新开辟的这块内存空间。
  4. 更新容量_capacity。

resize函数进行的工作:

  1. 如果n小于或等于_size,则将下标为n位置处的元素改为''。
  2. 如果n大于_size且小于等于_capacity,那么则将下标从_size开始到n-1的元素初始化为ch,然后末尾补''。
  3. 如果n大于_capacity,那么则要先扩容到n,然后将下标位置>=_size的元素全部初始化为ch,然后末尾补''。
        void reserve(size_t n)  //容量指定函数
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);

				delete[] _str;
				std::swap(_str, tmp);

				_capacity = n;
			}
		}

		void resize(size_t n, char ch = '')  //字符串长度变长或缩短函数
		{
			if (n <= _size)  //容量变更后比原有效字符数小
			{
				_size = n;
				_str[n] = '';
			}
			else  //容量变更后比原有效字符数大
			{
				if (n > _capacity)  //容量不足,扩容
				{
					reserve(n);
				}

				//将_size开始向后的内容全部变为ch
				memset(_str + _size, ch, n - _size);
				_size = n;
				_str[n] = '';
			}
		}

2.3 字符串清空和判空函数

  • clear:将字符串中的有效字符数清零。
  • empty:判断当前字符串是否为空。
        void clear()  //清空字符串
		{
			_str[0] = '';
			_size = 0;
		}

		bool empty()  //判断当前字符串是否为空
		{
			return _size == 0;
		}

三. 运算符重载函数

3.1 赋值运算符重载函数

赋值运算符重载函数完成的功能是将一个string类对象的值赋给另一个类对象,要完成string对象间的赋值有两种方法:

  1. 一般方法:规规矩矩进行深拷贝
  2. 通过临时对象赋值:创建一个与赋值对象的字符串内容相同的临时类对象,交换被赋值对象和临时对象的_str成员值,这样在临时对象生命周期结束自动调用析构函数时,会释放被赋值对象_str原来指向的空间。
		//一般写法 -- 正常进行深拷贝
		string& operator=(const string& str)  //赋值运算符重载
		{
			//开辟一块新的内存空间用于存储被复制的字符串
			char* tmp = new char[str._capacity + 1];
			strcpy(tmp, str._str);

			//释放原内存空间,同时更新字符串内容
			delete[] _str;
			_str = tmp;

			//更新数据量和容量
			_size = str._size;
			_capacity = str._capacity;

			return *this;
		}

		//复用写法 -- 创建临时对象,调用构造函数
		string& operator=(const string& str)
		{
			if (this != &str)  //排除自赋值情况
			{
				string tmp(str);  //创建临时对象

				swap(_str, tmp._str);  //交换指针指向
				_size = str._size;
				_capacity = str._capacity;
			}

			return *this;
		}

3.2 成员访问操作符[]重载函数

函数功能为获取下标位置为pos处的字符串,如果pos越界,则断言报错。

		char& operator[](size_t pos)  //下标访问操作符
		{
			assert(pos < _size);
			return this->_str[pos];
		}

		const char& operator[](size_t pos) const  //针对const对象的下标访问操作符
		{
			assert(pos < _size);
			return this->_str[pos];
		}

3.3 +=运算符重载函数 —— 尾插字符串或字符

函数所进行的工作包括:

  1. 检查当前对象容量是否充足,如果容量不足,就扩容。
  2. 将字符或字符串尾插到原字符串的后面。
		string& operator+=(const char* str)  //重载+=操作符:字符串尾插
		{
			size_t len = strlen(str);   //尾插字符串长度
			if (_size + len > _capacity)
			{
				//如果空间不足就扩容
				reserve(_size + len);
			}

			strcpy(_str + _size, str);
			_size += len;

			return *this;
		}

		string& operator+=(const string& str)
		{
			(*this) += str._str;  //获取str的成员_str,复用字符串尾插
			return *this;
		}

		string& operator+=(char ch)  //重载+=操作符:尾插字符
		{
			if (_size + 1 > _capacity)  //容量不足就开空间
			{
				reserve(_capacity == 0 ? 4 : 2 * _capacity);
			}

			_str[_size++] = ch;
			_str[_size] = '';

			return *this;
		}

四. 增删查改相关函数

4.1 插入字符串(字符)相关函数

  • push_back:尾插字符函数。
  • append:尾插字符串函数。
  • insert:在pos位置插入字符串函数。

push_back函数和append函数与+=完成一样的操作,直接复用+=即可。insert函数则需要将从pos位置开始往后的元素向后移动len个单位,len为插入字符串的长度,然后在pos位置插入新字符串。

图4.1 insert函数实现的功能图解
		void push_back(char ch)  //尾插字符
		{
			(*this) += ch;
		}
		 
		void append(const char* str)  //尾插字符串
		{
			(*this) += str;
		}

		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;  //找到ch就返回下标
				}
			}
			
			//找不到就返回npos
			return _npos;
		}

		string& 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 + len;
			//按从后往前的顺序,让pos位置开始往后的字符后移len个单位
			while (end >= pos + len)
			{
				_str[end] = _str[end - len];
				--end;
			}
			memcpy(_str + pos, str, len);  //pos位置插入字符串

			_size += len;  //更新有效字符数

			return *this;
		}

		string& insert(size_t pos, const string& str)
		{
			assert(pos <= _size);
			return insert(pos, str._str);
		}

4.2 子字符串删除函数

erase函数:从pos位置开始删除len个函数。

  1. 检查pos是否越界,如果越界,则断言报错。
  2. 将从下标位置pos+len开始的元素向前移动len个单位(包含末尾的''),如果len>_size-pos成立,那么说明要将pos往后的元素全部删除,只需将pos位置处的元素改为''即可。
  3. 更新_size的值。
		string& 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 + len;
			//按从后往前的顺序,让pos位置开始往后的字符后移len个单位
			while (end >= pos + len)
			{
				_str[end] = _str[end - len];
				--end;
			}
			memcpy(_str + pos, str, len);  //pos位置插入字符串

			_size += len;  //更新有效字符数

			return *this;
		}


		string& insert(size_t pos, const string& str)
		{
			assert(pos <= _size);
			return insert(pos, str._str);
		}

4.3 子串查找函数

find函数:从pos位置处开始查找子串,如果找到,就返回子串第一个元素的下标,如果找不到,就返回_npos。

		size_t find(const char* str, size_t pos = 0)  //查找一个字符串
		{
			assert(pos < _size);
			char* ptr = strstr(_str + pos, str);  //获取子字符串首字符地址

			if (ptr != nullptr)
			{
				return ptr - _str;
			}
			else
			{
				return _npos;
			}
		}

		size_t find(const string& str, size_t pos = 0)  //在string类对象中查找子字符串
		{
			assert(pos < _size);

			return find(str._str, pos);  //复用
		}

五. 迭代器相关函数

普通对象迭代器和const属性对象迭代器本质上都为char*/const char*:

  • typedef char* iteratior
  • typedef const char* const_iterator
  • begin:获取首字符元素地址。
  • end:获取字符串末尾''的地址。
		typedef char* iterator;
		typedef const char* const_iterator;

		iterator begin()  //头指针
		{
			return _str;
		}

		iterator end()  //尾指针
		{
			return _str + _size;
		}

		const_iterator begin() const  //const对象头指针
		{
			return _str;
		}

		const_iterator end() const  //const对象尾指针
		{
			return _str + _size;
		}

六. 关于string的全局函数

6.1 字符串比较相关函数

关于比较,总共有六种形式:>、==、>=、<、<=、!=,我们只需要模拟实现其中的两个,然后其余4个复用已经模拟实现完成的两个即可。

	bool operator<(const string& s1, const string& s2)  //比较s1是否小于s2
	{
		size_t i1 = 0, i2 = 0;
		size_t len1 = s1.size();  //字符串s1长度
		size_t len2 = s2.size();  //字符串s2长度

		while (i1 < len1 && i2 < len2)
		{
			if (s1[i1] < s2[i2])
			{
				return true;
			}
			else if (s1[i1] == s2[i2])
			{
				++i1;
				++i2;
			}
			else
			{
				return false;
			}
		}

		//如果s1长度小于s2,就成立,否则不成立
		return i2 < len2;
	}

	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 false;
			}
			else
			{
				++i1;
				++i2;
			}
		}

		return i1 == s1.size() && i2 == s2.size();
	}

	bool operator<=(const string& s1, const string& s2)
	{
		return (s1 == s2) || (s1 < s2);
	}

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

6.2 字符串输出和输入相关函数

  • ostream& operator<<(ostream& out, const string& s) -- 流插入操作符重载函数
  • istream& operator>>(istream& in, string& s) -- 流输出操作符重载函数

注意重载流插入操作符时,不能使用out << s.c_str,因为这样遇到''就会截止输出,从而不一定输出s.size()个字符。

	ostream& operator<<(ostream& out, const string& s)
	{
		//写法1:范围for循环
		/*for (auto ch : s)   
		{
			out << ch;
		}*/

		//写法2:普通for循环
		for (size_t i = 0; i < s.size(); ++i)
		{
			out << s[i];
		}

		//out << s.c_str();  //不能这么写,因为此时遇到停止读取,而不是读s.size()个字符

		return out;
	}

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

		while (ch != ' ' && ch != '
')
		{
			s += ch;
			ch = in.get();
		}

		return in;
	}

6.3 字符串与数值之间的相互转换

  • stoi:字符串转换为int型数据。
  • stod:字符串转换为double型数据。
	int stoi(const string& s)
	{
		size_t len = s.size();  //字符串长度
		long long flag = 1;  //正负号标识符
		size_t i = 0;

		long long retVal = 0;

		//排除空格
		while (i < len && s[i] == ' ')
		{
			++i;
		}

		//检查正负号
		if (i < len && s[i] == '+')
		{
			++i;
		}
		else if (i < len && s[i] == '-')
		{
			++i;
			flag = -flag;
		}

		//排除0
		while (i < len && s[i] == '0')
		{
			++i;
		}

		//计算结果
		while (i < len && (s[i] <= '9' && s[i] >= '0'))
		{
			retVal = retVal * 10 + (s[i] - '0') * flag;

			//检查是否越界
			if (retVal >= INT_MAX)
			{
				return INT_MAX;
			}
			else if (retVal <= INT_MIN)
			{
				return INT_MIN;
			}

			++i;
		}

		return (int)retVal;
	}

	double stod(const string& s)
	{
		size_t i = 0;
		size_t len = s.size();  //字符串长度

		//最终返回值 = 整数部分 + 小数部分
		double integerRet = 0;  //返回值的整数部分
		double decimalRet = 0;  //返回值的小数部分

		int flag = 1;   //正负号标识符

		while (i < len && s[i] == ' ')  //排除空格
		{
			++i;
		}

		if (i < len && s[i] == '+')  //确定返回值是正数还是负数
		{
			++i;
		}
		else if (i < len && s[i] == '-')
		{
			flag = -flag;
			++i;
		}

		//计算整数部分
		while (i < len && (s[i] >= '0' && s[i] <= '9'))
		{
			integerRet = integerRet * 10 + (s[i] - '0') * flag;
			++i;
		}

		//计算小数部分
		if (i < len && s[i] == '.')
		{
			double factorial = 0.1;  //系数
			++i;  //跳过小数点

			while (i < len && (s[i] >= '0' && s[i] <= '9'))
			{
				decimalRet += (s[i] - '0') * factorial * flag;
				factorial *= 0.1;
				++i;
			}
		}

		return integerRet + decimalRet;
	}

附录:string类模拟实现完整版

#include<iostream>
#include<assert.h>
#include<string.h>
#include<stdbool.h>
#include<limits>

using namespace std;

namespace zhang
{
	class string
	{
	public:
		//1.创建和销毁相关
		string(const char* str = "")   //通过字符串构造和默认构造
			: _size(strlen(str))
			, _capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		//explicit string(const string& str)  //拷贝构造
		//一般写法 -- 正常完成深拷贝
		//string(const string& str)  //拷贝构造
		//	: _size(str._size)
		//	, _capacity(_size)
		//{
		//	_str = new char[_capacity + 1];
		//	strcpy(_str, str._str);
		//}

		//拷贝构造复用写法 -- 临时对象
		string(const string& str)
			: _str(nullptr)
			, _size(str._size)
			, _capacity(str._capacity)
		{
			if (this != &str)  //排除自己给自己拷贝的情况
			{
				string tmp(str._str);
				std::swap(_str, tmp._str);
			}
		}

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

		//2.类对象成员和容量相关
		size_t size()  const //有效字符个数
		{
			return _size;
		}

		size_t length() const
		{
			return _size;
		}

		size_t capacity()  //获取容量
		{
			return _capacity;
		}

		const char* c_str() const  //获取字符串类对象变量_str
		{
			return _str;
		}

		void reserve(size_t n)  //容量指定函数
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);

				delete[] _str;
				std::swap(_str, tmp);

				_capacity = n;
			}
		}

		void resize(size_t n, char ch = '')  //容量变更函数
		{
			if (n <= _size)  //容量变更后比原有效字符数小
			{
				_size = n;
				_str[n] = '';
			}
			else  //容量变更后比原有效字符数大
			{
				if (n > _capacity)  //容量不足,扩容
				{
					reserve(n);
				}

				//将_size开始向后的内容全部变为ch
				memset(_str + _size, ch, n - _size);
				_size = n;
				_str[n] = '';
			}
		}

		void clear()  //清空字符串
		{
			_str[0] = '';
			_size = 0;
		}

		bool empty()  //判断当前字符串是否为空
		{
			return _size == 0;
		}

		//3.运算符重载相关
		//一般写法 -- 正常进行深拷贝
		//string& operator=(const string& str)  //赋值运算符重载
		//{
		//	//开辟一块新的内存空间用于存储被复制的字符串
		//	char* tmp = new char[str._capacity + 1];
		//	strcpy(tmp, str._str);

		//	//释放原内存空间,同时更新字符串内容
		//	delete[] _str;
		//	_str = tmp;

		//	//更新数据量和容量
		//	_size = str._size;
		//	_capacity = str._capacity;

		//	return *this;
		//}

		//复用写法 -- 调用构造函数
		string& operator=(const string& str)
		{
			if (this != &str)  //排除自赋值情况
			{
				string tmp(str);  //创建临时对象

				swap(_str, tmp._str);  //交换指针指向
				_size = str._size;
				_capacity = str._capacity;
			}

			return *this;
		}

		char& operator[](size_t pos)  //下标访问操作符
		{
			assert(pos < _size);
			return this->_str[pos];
		}

		const char& operator[](size_t pos) const  //针对const对象的下标访问操作符
		{
			assert(pos < _size);
			return this->_str[pos];
		}

		string& operator+=(const char* str)  //重载+=操作符:字符串尾插
		{
			size_t len = strlen(str);   //尾插字符串长度
			if (_size + len > _capacity)
			{
				//如果空间不足就扩容
				reserve(_size + len);
				_capacity = _size + len;
			}

			strcpy(_str + _size, str);
			_size += len;

			return *this;
		}

		string& operator+=(const string& str)
		{
			(*this) += str._str;
			return *this;
		}

		string& operator+=(char ch)  //重载+=操作符:尾插字符
		{
			if (_size + 1 > _capacity)  //容量不足就开空间
			{
				reserve(_capacity == 0 ? 4 : 2 * _capacity);
			}

			_str[_size++] = ch;
			_str[_size] = '';

			return *this;
		}

		//4.迭代器
		typedef char* iterator;
		typedef const char* const_iterator;

		iterator begin()  //头指针
		{
			return _str;
		}

		iterator end()  //尾指针
		{
			return _str + _size;
		}

		const_iterator begin() const  //const对象头指针
		{
			return _str;
		}

		const_iterator end() const  //const对象尾指针
		{
			return _str + _size;
		}

		//5.增删查改
		void push_back(char ch)  //尾插字符
		{
			(*this) += ch;
		}
		 
		void append(const char* str)  //尾插字符串
		{
			(*this) += str;
		}

		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;  //找到ch就返回下标
				}
			}
			
			//找不到就返回npos
			return _npos;
		}

		size_t find(const char* str, size_t pos = 0)  //查找一个字符串
		{
			assert(pos < _size);
			char* ptr = strstr(_str + pos, str);  //获取子字符串首字符地址

			if (ptr != nullptr)
			{
				return ptr - _str;
			}
			else
			{
				return _npos;
			}
		}

		size_t find(const string& str, size_t pos = 0)  //在string类对象中查找子字符串
		{
			assert(pos < _size);

			return find(str._str, pos);  //复用
		}

		string& 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 + len;
			//按从后往前的顺序,让pos位置开始往后的字符后移len个单位
			while (end >= pos + len)
			{
				_str[end] = _str[end - len];
				--end;
			}
			memcpy(_str + pos, str, len);  //pos位置插入字符串

			_size += len;  //更新有效字符数

			return *this;
		}

		string& insert(size_t pos, const string& str)
		{
			assert(pos <= _size);
			return insert(pos, str._str);
		}

		string& erase(size_t pos = 0, size_t len = _npos)  //子串删除函数
		{
			assert(pos < _size);

			if (pos + len > _size)  //pos后面的所有字符都会被删除
			{
				_str[pos] = '';
				_size = pos;
			}
			else  //pos向后的字符没有被删完
			{
				for (size_t i = pos; i <= _size; ++i)
				{
					_str[i] = _str[i + len];
				}
				_size -= len;
			}

			return *this;
		}

	private:
		char* _str;  //指向存储字符串内存的指针
		size_t _size;  //有效字符数
		size_t _capacity;  //当前类对象容量(最多几个有效字符)
		static size_t _npos;
	};

	size_t string::_npos = -1;

	bool operator<(const string& s1, const string& s2)  //比较s1是否小于s2
	{
		size_t i1 = 0, i2 = 0;
		size_t len1 = s1.size();  //字符串s1长度
		size_t len2 = s2.size();  //字符串s2长度

		while (i1 < len1 && i2 < len2)
		{
			if (s1[i1] < s2[i2])
			{
				return true;
			}
			else if (s1[i1] == s2[i2])
			{
				++i1;
				++i2;
			}
			else
			{
				return false;
			}
		}

		//如果s1长度小于s2,就成立,否则不成立
		return i2 < len2;
	}

	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 false;
			}
			else
			{
				++i1;
				++i2;
			}
		}

		return i1 == s1.size() && i2 == s2.size();
	}

	bool operator<=(const string& s1, const string& s2)
	{
		return (s1 == s2) || (s1 < s2);
	}

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

	ostream& operator<<(ostream& out, const string& s)
	{
		//写法1:范围for循环
		/*for (auto ch : s)   
		{
			out << ch;
		}*/

		//写法2:普通for循环
		for (size_t i = 0; i < s.size(); ++i)
		{
			out << s[i];
		}

		//out << s.c_str();  //不能这么写,因为此时遇到停止读取,而不是读s.size()个字符

		return out;
	}

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

		while (ch != ' ' && ch != '
')
		{
			s += ch;
			ch = in.get();
		}

		return in;
	}

	int stoi(const string& s)
	{
		size_t len = s.size();  //字符串长度
		long long flag = 1;  //正负号标识符
		size_t i = 0;

		long long retVal = 0;

		//排除空格
		while (i < len && s[i] == ' ')
		{
			++i;
		}

		//检查正负号
		if (i < len && s[i] == '+')
		{
			++i;
		}
		else if (i < len && s[i] == '-')
		{
			++i;
			flag = -flag;
		}

		//排除0
		while (i < len && s[i] == '0')
		{
			++i;
		}

		//计算结果
		while (i < len && (s[i] <= '9' && s[i] >= '0'))
		{
			retVal = retVal * 10 + (s[i] - '0') * flag;

			//检查是否越界
			if (retVal >= INT_MAX)
			{
				return INT_MAX;
			}
			else if (retVal <= INT_MIN)
			{
				return INT_MIN;
			}

			++i;
		}

		return (int)retVal;
	}

	double stod(const string& s)
	{
		size_t i = 0;
		size_t len = s.size();  //字符串长度

		//最终返回值 = 整数部分 + 小数部分
		double integerRet = 0;  //返回值的整数部分
		double decimalRet = 0;  //返回值的小数部分

		int flag = 1;   //正负号标识符

		while (i < len && s[i] == ' ')  //排除空格
		{
			++i;
		}

		if (i < len && s[i] == '+')  //确定返回值是正数还是负数
		{
			++i;
		}
		else if (i < len && s[i] == '-')
		{
			flag = -flag;
			++i;
		}

		//计算整数部分
		while (i < len && (s[i] >= '0' && s[i] <= '9'))
		{
			integerRet = integerRet * 10 + (s[i] - '0') * flag;
			++i;
		}

		//计算小数部分
		if (i < len && s[i] == '.')
		{
			double factorial = 0.1;  //系数
			++i;  //跳过小数点

			while (i < len && (s[i] >= '0' && s[i] <= '9'))
			{
				decimalRet += (s[i] - '0') * factorial * flag;
				factorial *= 0.1;
				++i;
			}
		}

		return integerRet + decimalRet;
	}
};