模拟实现string(传统写法与现代写法)

1. 简易string(无增删查改)

开始必须重新定义一个命名空间,把我们自定义的string放进命名空间里。

假如不考虑增删查改,我们先可以做一个简单的string,几乎下意识的就写出了这样的构造函数

string(char* str)
			:_str(str)
		{}

这是非常不合理的,假如外面传入的是一个常量字符串。

string s("1234");

假如要修改常量字符串的话,会直接中断。
那我们就开一个和他同样大小的空间,再把字符拷贝进去。使用new开辟后面还可以动态增长。

namespace zjn
{
	class string
	{
	public:
		string(char* str)
			:_str(new char[strlen( str)+1])
		{
			strcpy(_str, str);
		}
		~string()
		{
			delete[] _str;
		}

	private:
		char* _str;
	};
}

有时我们也会定义无参的string。那么这样写可以吗,我们配合一个接口c_str进行验证

         string()
			:_str(nullptr)
		{
			;
		}
const char* c_str()
{
         return _str;
}
int main()
{
    zjn::string s1("1234");
	zjn::string s2;
	cout << s1.c_str() << endl;
	cout << s2.c_str ()<< endl;
	return 0;
	}

cout是自动识别类型,是因为他重载了各种类型的函数,打印的原理无非就是遇到‘\0’停止,可是在s2由于我们构造的时候使用nullptr构造的,所以cout会解引用进行访问,就会崩溃。(析构函数时delet[]所包含的free(NULL)并不会报错
在这里插入图片描述
所以正确的做法应该是

         string()
			:_str(new char[1])
		{
			_str[0] = '\0';
		}

其实只要写成默认构造函数就好了。这样就不用写两个构造函数

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

1.1 深浅拷贝

在学习拷贝构造的时候,我们讲到一个stack对象的拷贝,自动生成的构造函数只是浅拷贝,两个指针指向的是同一块区域,所以执行析构函数的时候会将同一块资源释放两次。
在这里插入图片描述
在这里插入图片描述
所以默认生成的满足不了需求,必须自己写一个拷贝构造函数来实现深拷贝。
在这里插入图片描述

在这里插入图片描述

#include<iostream>
#include<assert.h>
#pragma warning(disable:4996)
using namespace std;

namespace zjn
{
	class string
	{
	public:
		
		string(char* str="")
			:_str(new char[strlen( str)+1])
		{
			strcpy(_str, str);
		}
		~string()
		{
			delete[] _str;
			_str = nullptr;
		}
		const char* c_str()
		{
			return _str;
		}
		string(const string& s)
			:_str(new char[strlen(s._str)+1])
		{
			strcpy(_str,s._str);
		}
		
		char&  operator[](size_t i)
		{
			assert(i < _size);
			return _str[i];
		}
		//释放原空间,然后深拷贝
		string& operator=(const string& s)
		{
			if (this!=&s)
			{
				delete[] _str;
				//不能直接strcpy因为空间可能不够
				_str = new char[strlen(s._str) + 1];
				strcpy(_str, s._str);
			}
			return *this;
		}
	private:
		char* _str;
	};
}
int main()
{
	zjn::string s1("1234");
	zjn::string s2;
	zjn::string s3(s1);
	s3[1] = 'x';
	cout << s1.c_str() << endl;
	cout << s2.c_str ()<< endl;
	return 0;
}

2. string的传统写法

一个真正的string是要支持增删查改的,所以必须要有size和capcacity。
size和capacity是内置类型几乎不需要注意什么地方

2.1 四个默认成员函数

		/*string(char* str)
			:_str(new char[strlen(str)+1])
			,_size(strlen( str))
			, _capacity(_size)
		{}*/
		string(char* str="")
		{
			_str=new char[strlen(str)+1];
			_size = strlen(str);
			_capacity = _size;
			strcpy(_str, str);
		}
		string(const string& s)
		{
			_size = s._size;
			_capacity = s._capacity;
			_str = new char[strlen(s._str) + 1];
			strcpy(_str, s._str);
		}
		~string()
		{
			delete[] _str;
			_str = nullptr;
		}
		string& operator=(const string& s)
		{
			if (this != &s){
			   delete[] str;
              _str = new char[strlen(s._str) + 1];
			  _capacity = s._capacity;
			  _size = s._size;
			}
			return *this;
		}
  • 默认构造函数:当默认构造函数选择为为参数列表声明时,最好不要写有依赖关系的,因为实际初始化的顺序只和声明的顺序有关。所以我们直接选择写在函数体内
  • 拷贝构造函数:需要深拷贝实现,即重新开一段空间,把数据拷贝过来
  • 重载赋值函数:需要注意自己给自己赋值的情况,释放原空间进行深拷贝,假如是自己给自己赋值的话,原空间释放,指针指向新开的一段空间,strlen时遇到’\0’停止,那段空间是随机值。

3. string的现代写法

3.1 构造函数与析构函数

		string(const char* str="")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_size + 1];
			strcpy(_str, str);
		}
         ~string()
		{
			delete[] _str;
			_str = nullptr;
			_capacity = _size=0;
		}
  • 构造函数与析构函数没有什么差别。

3.2 拷贝构造

一开始是这么写的

		string(const string& s)
		{.
		    //局部对象拥有相同的str,用于和this._str进行交换
			string temp(s._str);
		   std::swap(_str,temp._str);
		   std::swap(_size,temp._size);
		   std::swap(_capacity,temp._capacity);
		   
		}

很明显是有错误的,因为_str没有初始化是一个野指针。当与temp._str进行交换时,_str指向temp._str那块空间。temp._str指向那块随机空间,当出了函数体,调用析构函数,清理资源_str,free掉temp._str,就会报错,即free了一个野指针。

        string(const string& s)
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{
		   string temp(s._str);
		   std::swap(_str,temp._str);
		   std::swap(_size,temp._size);
		   std::swap(_capacity,temp._capacity);
		}

那么我们将它初始化成nullptr就好了。

3.3 赋值重载

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

在这里插入图片描述
交换了之后,出了函数体,调用析构,刚好也会清理掉不要的"worldx"那块资源。

其实还有一种更简洁的写法

      //参数不能加const因为我们会修改s,不能传引用因为我们修改了s,传引用外面的也会修改
      
		string& operator=( string s)
		{
		    std::swap(_str, s._str);
		    std::swap(_size, s._size);
		    std::swap(_capacity, s._capacity);
			return *this;
		}

s就代替了之前的temp对象,且s也是一个局部对象,也会调用析构函数。
可以看到存在了大量的swap代码复用,我们可以将swap写成一个函数。然后对所有代码进行修改。


		string(const char* str="")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_size + 1];
			strcpy(_str, str);
		}
		
		// std中的swap并不好
		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size,s._size);
			std::swap(_capacity, s._capacity);
		}

		//拷贝构造
		//string s1(s2);
		string(const string& s)
			:_str(nullptr)
			, _capacity(0)
			, _size(0)
		{
			string temp(s._str);
			swap(temp);
		
		}
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_capacity = _size=0;
		}
		string& operator=( string s)
		{
		    swap(s);
			return *this;
		}

这就是一个有资源管理的string的现代写法。

3.4 std中的swap与string中的swap

那么为什么不直接调用库中的swap交换两个对象,而是用库中的swap一个个交换属性,最后将他们封装成一个专门的swap呢?
swap(s1,s2);
s1.swap(s2);
这两种差异是特别大的。
std中的swap是一个由函数模板推演而来的模板函数。其中经历了一次拷贝构造,两次赋值。代价太大,效率会大打折扣。
在这里插入图片描述

4. 修改

         //[]重载
		char& operator[](size_t i)
		{
			assert(i < _size);
			return _str[i];
		}
		//[]重载(const对象调用)
		const char& operator[](size_t i)const
		{
			assert(i < _size);
			return _str[i];
		}
		//迭代器
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str+_size;
		}
		//迭代器(const对象)
		typedef const char* const_iterator;
		const_iterator begin()const
		{
			return _str;
		}
		const_iterator end()const
		{
			return _str + _size;
		}
	    //范围for
	   for (auto& e : s)
	    {
		e += 1;
	    }
  • 重载[]:需要注意,i需要小于size,size作为下表的话是’\0’的位置。
  • string 的迭代器实际就是一个指针。typedef为iterator是为了和其它接口保持一致,假如你是一个链表就不能继续使用指针了,因为指针指向那个节点,解引用之后是一个结构体,并不是节点的值。所以在链表中迭代器实际是这样定义的。
struct ListIterator
	{
		operator*();
		operator++();
		Node* node;
	};

重载他的*和++来达到目的。

  • 范围for:会自动转换为迭代器,也就是说自动调用begin(),与end()。

5. 增加

          void resize(size_t n, char ch='\0')
			{
			
			//_size变小,capacity不变,直接在n处放'\0'
			if (n<_size)
			{
			_str[n] ='\0';
			_size = n;
			}
			else
			{
				if (n>_capacity)
				{
					reserve(n);
				}
				//1 2 3 4 
				for (size_t i = _size; i < n; i++)
				{
					_str[i] = ch;
					
				}
				_str[n] = '\0';
				_size = n;
				
			}
			}
         //增加就可能会引起扩容
         void reserve(int  n)
		    {
			if (n > _capacity)
			{
				//需要temp不然直接delete的话字符串的内容会丢失
				char* temp = new char[n + 1];
				strcpy(temp, _str);
				delete[] _str;
				_str = temp;
				_capacity = n;
			}
		}
		//字符尾插
		string& push_back(char ch)
		{
			if (_size == _capacity)
			{
				reserve(2 * _capacity);
			}
			_str[_size] = ch;
			_size++;
			_str[_size] = '\0';
			return *this;
		}
		//字符串拼接
		void append(const char* str)
		{
			int len = strlen(str);
			if (len + _size> _capacity)
			{
				reserve(len + _size);
			}
			//忘记strcpy,参数忘记+_size
			strcpy(_str+_size, str);
			_size += len;
	
		}
         //字符+=
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		//字符串+=
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
			
		}
		//string对象+=
		string& operator+=(const string& s)
		{
			append(s._str);
			return *this;
		}
		//插入字符
		string& insert(size_t pos, char ch)
		{
			assert(pos <= _size);
			if (_size == _capacity)
			{
                size_t newcapacity = _capacity == 0 ? 8 : 2 * _capacity;
				reserve(newcapacity);
			}
			/*	逻辑没有问题,但是当pos等于0时,要把第一位往前挪,end--就会变成-1,才能由于类型为size_t他就会变为整形最大值。
			    假如把end变为int类型也避免不了,因为pos为size_t,身为int类型的end会类型提升为size_t,依旧是整形最大值。
				那把end变成int类型,再把pos显示的转为int(pos不要直接改成int与库不相符),这样是可以的。
			    size_t end = _size;
				while(end>=pos)
				{
					_str[end + 1] = _str[end];
					end--;
				}*/
			//虽然_size是'\0',但是_size+1不会越界因为假如空间不够前面会扩容,数组是有这么大空间的,只不过没有存字符。
			//这样当pos等于0时,end为1字符已经全部挪走,end--为0,此时不会再进入循环,也就不会变成-1了。
         		size_t end = _size + 1;
				while (end > pos)
				{
					_str[end] = _str[end - 1];
					end--;
				}
				_str[pos] = ch;
				_size++;
				return *this;
              
		}
		//插入字符串
		string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			int len = strlen(str);
			if (_size + strlen(str) > _capacity)
			{
				reserve(_size+len);
			}
			int end = _size + len;
			//画图分析
			//避免类型提升
			while (end  >= (int)pos+len)
			{
				_str[end] = _str[end - len];
				end--;
			}
			strncpy(_str + pos, str, len);
			_size += len;
			return *this;
			
		}

		
  • reserve:只要增加就可能会有扩容的场景。
  • push_back:追加一个字符
  • append:拼接一个字符串。
  • +=:分别有字符,字符串,string三种函数
  • insert:push_back,append,+=可以复用这个插入函数,但是要注意类型提升问题。

6. 删除

//删除,和库类似,我们给一个npos缺省值
		void erase(size_t pos, size_t len = npos)
		{
			//从pos起,删除len个字符。
			assert(pos < _size);
			//len为npos的时候会溢出
			//if (pos+len>_size)
			//
			//假如要求删的超过字符串,a b c d,要求从c删除100个,就把d置成'\0'就行了。
			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				//直接拷过去,然后覆盖,'\0'也会拷贝过去
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}

		}
  • erase,注意溢出,直接使用strcpy覆盖。

7. 查找

        //查找字符,缺省默认从第一个位置开始
		size_t find(char ch, size_t pos = 0)
		{
			for (size_t i = 0; i < _size; i++)
			{
				if (ch == _str[i])
				{
					return i;
				}
			}
			return npos;
		}
		//查找子串
		size_t find(const char* sub, size_t pos = 0)
		{
			//在_str+pos这个字符串里找sub
			const char* ret = strstr(_str + pos, sub);
			if (nullptr == ret)
			{
				return npos;
			}
			else
			{
				return ret - _str;
			}
		}

  • find: 一个查找字符,一个查找子串,缺省值都是从第一个位置开始查找

8. 重载的全局函数

//大于重载
	bool operator>(const string& s1, const string& s2)
	{
		size_t l1=0, l2 = 0;
		while (l1 < s1.size() && l2 < s2.size())
		{
			if (s1[l1] == s1[l2])
			{
				l1++;
				l2++;
			}
			else
			{
				if (s1[l1]>s2[l2])
				{
					return true;
				}
				else
				{
					return false;
				}
			}
		}
		//假如有一个长一个短
		if (l1 < s1.size())
		{
			return true;
		}
		else if (l2 < s2.size())
		{
			return false;
		}
		else
		{
			return false;
		}
	}
	//==重载
	bool operator==(const string& s1, const string& s2)
	{
		size_t l1 = 0, l2 = 0;
		while (l1 < s1.size() && l2 < s2.size())
		{
			if (s1[l1] == s1[l2])
			{
				l1++;
				l2++;
			}
			else
			{
				return false;
			}
		}
		//假如有一个长一个短
		if (l1 < s1.size())
		{
			return false;
		}
		else if (l2 < s2.size())
		{
			return false;
		}
		else
		{
			return true;
		}

	}
	//输出
	std::ostream& operator<<(std::ostream& out, const string& s)
	{
		for (size_t i = 0; i < s.size(); i++)
		{
			out << s[i] ;
		}
		return out;

	}
	//输入
	std::istream& operator>>(std::istream& in,  string& s)
	{
		
		char ch;
		while (1)
		{
			//这样不行,默认空格为下一个,不能123这样输,只能1 2 3
			//in >> ch;
			in.get(ch);
			if ( ch==' '||ch=='\n')
			{
				break;
			}
			else
			{
				s += ch;
			}
		}
		return in;
	}

最终代码

#include<iostream>
#include<assert.h>

#pragma warning(disable:4996)


namespace zjn
{
	class string
	{
	public:
		//传统写法
		构造
		//string(const char* str = "")
		//{
		//	_size = strlen(str);
		//	_capacity = _size;
		//	_str = new char[_capacity + 1];
		//	strcpy(_str, str);
		//}
		析构
		//~string()
		//{
		//	delete[] _str;
		//	_str = nullptr;
		//	_capacity = _size = 0;
		//}
		拷贝构造
		//string(const string& s)
		//{
		//	_str = new char[strlen(s._str) + 1];
		//	strcpy(_str, s._str);
		//	_capacity = s._capacity;
		//	_size = s._size;
		//}
		赋值重载
		//string& operator=(const string& s)
		//{
		//	if (this != &s)
		//	{
		//		delete[] _str;
		//		_str = new char[strlen(s._str) + 1];
		//		strcpy(_str, s._str);
		//		_size = s._size;
		//		_capacity = s._capacity;
		//	}
		//	return *this;
		//}
		//构造
		string(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_size + 1];
			strcpy(_str, str);
		}

		 //std中的swap并不好
		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

		//拷贝构造
		//string s1(s2);
		string(const string& s)
			:_str(nullptr)
			, _capacity(0)
			, _size(0)
		{
			string temp(s._str);
			swap(temp);

		}
		//析构
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_capacity = _size = 0;
		}
		//赋值
		string& operator=(string s)
		{
			swap(s);
			return *this;
		}
		size_t size()const
		{
			return _size;
		}
		//重载[]
		//返回值忘记引用了
		
		char& operator[](size_t i)
		{
			assert(i < _size);
			return _str[i];
		}
		const char& operator[](size_t i)const
		{
			assert(i < _size);
			return _str[i];
		}
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		typedef const char* const_iterator;
		const_iterator begin()const
		{
			return _str;
		}
		const_iterator end()const
		{
			return _str + _size;
		}
		//增
		//扩容+初始化。
		//当前n<capacity的话,不改变capacity,只改变size。n>capacity,改变size改变capacity。
			void resize(size_t n, char ch='\0')
			{
			
				//_size变小,capacity不变,直接在n处放'\0'
			if (n<_size)
			{
			_str[n] ='\0';
			_size = n;
			}
			else
			{
				if (n>_capacity)
				{
					reserve(n);
				}
				//1 2 3 4 
				for (size_t i = _size; i < n; i++)
				{
					_str[i] = ch;
					
				}
				_str[n] = '\0';
				_size = n;
				
			}
			}
		//扩容
		void reserve(size_t  n)
		{
			if (n > _capacity)
			{
				//需要temp不然直接delete的话字符串的内容会丢失
				char* temp = new char[n + 1];
				strcpy(temp, _str);
				delete[] _str;
				_str = temp;
				_capacity = n;
			}
		}
		//字符尾插
		void push_back(char ch)
		{
			/*if (_size == _capacity)
			{
			reserve(2 * _capacity);
			}
			_str[_size] = ch;
			_size++;
			_str[_size] = '\0';*/
			insert(_size, ch);

		}
		void append(const char* str)
		{
			//int len = strlen(str);
			//if (len + _size> _capacity)
			//{
			//	reserve(len + _size);
			//}
			忘记strcpy,参数忘记+_size
			//strcpy(_str+_size, str);
			//_size += len;
			insert(_size, str);

		}
		//字符+=
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		//字符串+=,参数忘记const
		string& operator+=(const char* str)
		{
			append(str);
			return *this;

		}
		//string对象+=
	    string& operator+=(const string& s)
		{
			append(s._str);
			return *this;
		}
		//插入字符
		void insert(size_t pos, char ch)
		{
			assert(pos <= _size);
			if (_size == _capacity)
			{
			    //假如capacity为0一开始开8个对象类型(char)空间
				size_t newcapacity = _capacity == 0 ? 8 : 2 * _capacity;
				reserve(newcapacity);
			}
			/*	逻辑没有问题,但是当pos等于0时,要把第一位往前挪,end--就会变成-1,才能由于类型为size_t他就会变为整形最大值。
				假如把end变为int类型也避免不了,因为pos为size_t,身为int类型的end会类型提升为size_t,依旧是整形最大值。
				那把end变成int类型,再把pos显示的转为int(pos不要直接改成int与库不相符),这样是可以的。
				size_t end = _size;
				while(end>=pos)
				{
				_str[end + 1] = _str[end];
				end--;
				}*/
			//虽然_size是'\0',但是_size+1不会越界因为假如空间不够前面会扩容,数组是有这么大空间的,只不过没有存字符。
			//这样当pos等于0时,end为1字符已经全部挪走,end--为0,此时不会再进入循环,也就不会变成-1了。
			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				end--;
			}
			_str[pos] = ch;
			_size++;
			

		}
		//插入字符串
		void insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			int len = strlen(str);
			if (_size + strlen(str) > _capacity)
			{
				reserve(_size + len);
			}
			int end = _size + len;
			//画图分析
			//避免整形提升
			while (end >= (int)pos + len)
			{
				_str[end] = _str[end - len];
				end--;
			}
			strncpy(_str + pos, str, len);
			_size += len;
			

		}
		//删除,和库类似,我们给一个npos缺省值
		void erase(size_t pos, size_t len = npos)
		{
			//从pos起,删除len个字符。
			assert(pos < _size);
			//len为npos的时候会溢出
			//if (pos+len>_size)
			//
			//假如要求删的超过字符串,a b c d,要求从c删除100个,就把d置成'\0'就行了。
			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				//直接拷过去,然后覆盖,'\0'也会拷贝过去
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}

		}
		//查找字符,缺省默认从第一个位置开始
		size_t find(char ch, size_t pos = 0)
		{
			for (size_t i = 0; i < _size; i++)
			{
				if (ch == _str[i])
				{
					return i;
				}
			}
			return npos;
		}
		//查找子串
		size_t find(const char* sub, size_t pos = 0)
		{
			//在_str+pos这个字符串里找sub
			const char* ret = strstr(_str + pos, sub);
			if (nullptr == ret)
			{
				return npos;
			}
			else
			{
				return ret - _str;
			}
		}


	private:
		char* _str;
		size_t _capacity;
		size_t _size;
		//声明
		static const size_t npos;
	};
	//定义初始化
	const size_t string::npos = -1;
	//大于重载
	bool operator>(const string& s1, const string& s2)
	{
		size_t l1=0, l2 = 0;
		while (l1 < s1.size() && l2 < s2.size())
		{
			if (s1[l1] == s1[l2])
			{
				l1++;
				l2++;
			}
			else
			{
				if (s1[l1]>s2[l2])
				{
					return true;
				}
				else
				{
					return false;
				}
			}
		}
		//假如有一个长一个短
		if (l1 < s1.size())
		{
			return true;
		}
		else if (l2 < s2.size())
		{
			return false;
		}
		else
		{
			return false;
		}
	}
	//==重载
	bool operator==(const string& s1, const string& s2)
	{
		size_t l1 = 0, l2 = 0;
		while (l1 < s1.size() && l2 < s2.size())
		{
			if (s1[l1] == s1[l2])
			{
				l1++;
				l2++;
			}
			else
			{
				return false;
			}
		}
		//假如有一个长一个短
		if (l1 < s1.size())
		{
			return false;
		}
		else if (l2 < s2.size())
		{
			return false;
		}
		else
		{
			return true;
		}

	}
		//大于等于
	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);
	}
	//输出
	std::ostream& operator<<(std::ostream& out, const string& s)
	{
		for (size_t i = 0; i < s.size(); i++)
		{
			out << s[i] ;
		}
		return out;

	}
	//输入
	std::istream& operator>>(std::istream& in,  string& s)
	{
	     //防止对一个原本就有数据的对象输入
		s.resize(0);
		char ch;
		while (1)
		{
			//这样不行,默认空格为下一个,不能123这样输,只能1 2 3
			//in >> ch;
			in.get(ch);
			if ( ch==' '||ch=='\n')
			{
				break;
			}
			else
			{
				s += ch;
			}
		}
		return in;
	}
	
}
  • 8
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

楠c

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

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

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

打赏作者

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

抵扣说明:

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

余额充值