模拟实现
成员变量
string类中需要三个成员变量分别记录元素个数、容量和内容。还需要一个size_t
类型npos-1表示整型的最大值。
private: size_t _size;//个数 size_t _capacity;//容量 char* _str;//字符数组 public: const static size_t npos; const size_t string::npos = -1;//npos代表的整型的最大值。
构造函数
//构造函数 string(const char* str = "") { _size = strlen(str); _capacity = _size; _str = new char[_capacity + 1]; memcpy(_str, str, _size + 1); }
这段代码是string类的构造函数。构造函数是在创建对象时自动调用的函数,用于初始化对象的数据成员。
这个构造函数接受一个const char*类型的参数str,默认值为一个空字符串。
在函数体内,首先使用strlen函数获取参数str的长度,并将其赋值给私有成员变量_size,表示字符串的个数。
接着,将_capacity的值初始化为_size,表示容量与个数相等。
然后,使用new运算符动态分配一个大小为_capacity + 1的字符数组,并将指针赋值给私有成员变量_str。
最后,使用memcpy函数将参数str中的字符串复制到新分配的字符数组中,并在最后一个位置设置为\0,表示字符串的结束。
因此,这个构造函数的作用是将一个C风格的字符串转换为string对象。
析构函数
//析构函数 ~string() { delete[] _str; _str = nullptr; _size = _capacity = 0; }
这段代码是string类的析构函数。析构函数是在对象被销毁时自动调用的函数,用于释放对象占用的资源。
在函数体内,首先使用delete[]运算符释放_str指向的动态分配的字符数组。
然后,将_str赋值为nullptr,表示指针不再指向任何内存地址。
最后,将_size和_capacity的值都设为0,表示对象已经被销毁。
因此,这个析构函数的作用是释放string对象占用的内存空间,并将对象的数据成员重置为初始值。
拷贝构造
//拷贝构造 string(const string& str) { _str = new char[str._capacity + 1]; memcpy(_str, str._str, str._size + 1); _size = str._size; _capacity = str._capacity; }
这段代码是string类的拷贝构造函数。拷贝构造函数是在创建一个新对象并用已有对象进行初始化时自动调用的函数,用于将一个对象的值复制到另一个对象中。
这个拷贝构造函数接受一个const string&类型的引用参数str,表示要被复制的对象。
在函数体内,首先使用new运算符动态分配一个大小为str._capacity + 1的字符数组,并将指针赋值给私有成员变量_str。
然后,使用memcpy函数将参数str中的字符串复制到新分配的字符数组中,并在最后一个位置设置为\0,表示字符串的结束。
接着,将_size和_capacity的值分别赋值为参数str对应的私有成员变量值,表示新对象的大小和容量与原对象相同。
因此,这个拷贝构造函数的作用是创建一个新的string对象,并将参数str中的数据成员值复制到新对象中。
c_str()
//返回C语言类型的字符串 const char* c_str() const { return _str; }
这段代码是string类的c_str函数,用于返回一个指向以空字符结尾的字符数组的指针,该字符数组包含了当前string对象中存储的字符串。
这个函数没有参数,返回值为const char*类型。
在函数体内,直接返回私有成员变量_str,该变量是一个指向字符数组的指针,指向当前string对象中存储的字符串。
由于返回值是一个指向常量字符的指针,因此该函数被声明为const类型成员函数,表示该函数不会修改对象的数据成员。
因此,这个函数的作用是返回当前string对象中存储的字符串的C语言类型表示。
size()
//返回长度 size_t size() const { return _size; }
这段代码是string类的size函数,用于返回当前string对象中存储的字符串的长度,即字符的个数。
这个函数没有参数,返回值为size_t类型。
在函数体内,直接返回私有成员变量_size,该变量表示当前string对象中存储的字符串的长度。
由于该函数不会修改对象的数据成员,因此被声明为const类型成员函数。
因此,这个函数的作用是返回当前string对象中存储的字符串的长度。
重载[]运算符
//重载[]运算符 char& operator[](size_t pos) { assert(pos < _size); return _str[pos]; } const char& operator[](size_t pos) const { assert(pos < _size); return _str[pos]; }
这段代码是string类对[]运算符进行重载的实现。通过重载[]运算符,可以使用类似数组下标的方式访问和修改string对象中的字符。
这个代码片段包含了两个重载版本,一个是非常量版本,另一个是常量版本。
在非常量版本中,重载的[]运算符接受一个size_t类型的参数pos,表示要访问或修改的字符的索引位置。
在函数体内,首先使用assert函数来检查索引位置是否越界,即是否小于字符串的长度_size。
然后,返回私有成员变量_str中索引位置为pos的字符的引用,以便可以修改该字符的值。
在常量版本中,重载的[]运算符也接受一个size_t类型的参数pos。
在函数体内,同样使用assert函数来检查索引位置是否越界。
然后,返回私有成员变量_str中索引位置为pos的字符的常量引用,以保证该字符的值不会被修改。
因此,这段代码的作用是重载string类的[]运算符,使得可以通过下标访问和修改字符串中的字符。
扩容函数
reserve()
//扩容函数 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; } }
这段代码是string类的reserve函数,用于增加当前string对象的容量,以便可以存储更多的字符。
这个函数接受一个size_t类型的参数n,表示要增加的容量大小。
在函数体内,首先判断参数n是否大于当前对象的容量_capacity。如果是,则需要进行扩容操作。
在扩容操作中,首先使用new运算符动态分配一个大小为n+1的字符数组,并将指针赋值给临时变量tmp。
然后,使用memcpy函数将当前对象中的字符串复制到新分配的字符数组中,这个函数比strcpy更快,因为不需要在找到\0之后继续复制。
接着,使用delete[]运算符释放当前对象中原来的字符数组。
最后,将指向新分配的字符数组的指针赋值给私有成员变量_str,表示当前对象已经扩容。同时,将私有成员变量_capacity的值更新为新的容量大小。
因此,这个函数的作用是增加当前string对象的容量,以便可以存储更多的字符。
resize()
void resize(size_t n, char ch = '\0') { if (n < _size) { _size = n; _str[_size] = '\0'; } else { reserve(n); for (size_t i = _size; i < n; i++) { _str[i] = ch; } _size = n; _str[_size] = '\0'; } }
这段代码是string类的resize函数,用于改变当前string对象中存储的字符串的长度。
这个函数接受一个size_t类型的参数n,表示要改变的字符串长度。同时,还接受一个可选的char类型参数ch,表示在扩展字符串时要填充的字符,默认值为\0。
在函数体内,首先判断参数n是否小于当前对象中存储的字符串的长度_size。如果是,则需要缩小字符串的长度。
在缩小字符串长度的操作中,首先将私有成员变量_size的值更新为n,表示当前对象中存储的字符串的长度已经缩小到了n。
然后,在新的字符串长度之后添加一个空字符\0,以便可以正确地表示新的字符串。
如果参数n大于等于当前对象中存储的字符串的长度_size,则需要扩展字符串的长度。
在扩展字符串长度的操作中,首先调用成员函数reserve(n)来确保当前对象中分配的内存空间足够存储新的字符串。
然后,在当前对象中存储的字符串的末尾添加字符ch,直到新字符串的长度达到了n。
最后,将私有成员变量_size的值更新为n,表示当前对象中存储的字符串的长度已经扩展到了n。同时,在新字符串长度之后添加一个空字符\0,以便可以正确地表示新的字符串。
因此,这个函数的作用是改变当前string对象中存储的字符串的长度,并根据需要在新字符串中添加或删除字符。
尾插
push_back()
//push_back() void push_back(char ch) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : 2 * _capacity); } _str[_size] = ch; ++_size; _str[_size] = '\0'; }
这段代码是string类的push_back函数,用于向当前string对象中添加一个字符。
这个函数接受一个char类型的参数ch,表示要添加的字符。
在函数体内,首先判断当前对象中存储的字符串的长度_size是否等于当前对象的容量_capacity。如果是,则需要进行扩容操作。
在扩容操作中,调用成员函数reserve(_capacity == 0 ? 4 : 2 * _capacity)来增加当前对象的容量。如果当前对象的容量为0,则增加容量到4;否则,将容量增加到原来的2倍。
然后,在私有成员变量_str中索引位置为_size的位置添加字符ch,表示将该字符添加到当前对象的末尾。
接着,将私有成员变量_size的值加1,表示当前对象中存储的字符串的长度已经增加了1个字符。
最后,在新字符串长度之后添加一个空字符\0,以便可以正确地表示新的字符串。
因此,这个函数的作用是向当前string对象中添加一个字符,并在需要时自动扩容以确保有足够的空间存储该字符。
append()
void append(const char* str) { size_t len = strlen(str); if (len + _size > _capacity) { reserve(len + _size); } memcpy(_str + _size, str, len + 1); _size += len; }
这段代码是string类的append函数,用于将一个以空字符结尾的C字符串附加到当前string对象中。
这个函数接受一个const char*类型的参数str,表示要附加的C字符串。
在函数体内,首先使用strlen函数计算出C字符串的长度,并将其存储在临时变量len中。
然后,判断将要附加的C字符串的长度加上当前对象中存储的字符串的长度是否大于等于当前对象的容量。如果是,则需要进行扩容操作。
在扩容操作中,调用成员函数reserve(len + _size)来增加当前对象的容量,以确保有足够的空间存储将要附加的C字符串。
接着,使用memcpy函数将C字符串复制到当前对象中存储的字符串的末尾。这个函数比strcpy更快,因为不需要在找到\0之后继续复制。
最后,将私有成员变量_size的值增加C字符串的长度len,表示当前对象中存储的字符串的长度已经增加了C字符串的长度。
因此,这个函数的作用是将一个以空字符结尾的C字符串附加到当前string对象中,并在需要时自动扩容以确保有足够的空间存储该字符串。
重载+=运算符
.
//重载+=运算符 string& operator+=(char ch) { push_back(ch); return *this; } string& operator+=(const char* str) { append(str); return *this; }
这里都是都是复用了上面的push_back()和append()
插入函数insert
插入字符
//在指定位置插入n个字符 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 (end >= pos && end != npos)//npos整型最大值 { _str[end + n] = _str[end]; --end; } for (size_t i = pos; i < pos + n; i++) { _str[i] = ch; } _size += n; }
这段代码是string类的insert函数,用于在当前string对象中的指定位置插入指定数量的字符。
这个函数接受三个参数:一个size_t类型的参数pos,表示要插入字符的位置;一个size_t类型的参数n,表示要插入的字符数量;一个char类型的参数ch,表示要插入的字符。
在函数体内,首先使用assert函数检查要插入的位置是否在当前对象中存储的字符串的范围内。如果不在范围内,则程序会终止。
然后,判断将要插入的字符数量加上当前对象中存储的字符串的长度是否大于等于当前对象的容量。如果是,则需要进行扩容操作。
在扩容操作中,调用成员函数reserve(_size + n)来增加当前对象的容量,以确保有足够的空间存储将要插入的字符。
接着,从当前对象中存储的字符串的末尾开始,将每个字符向后移动n个位置。这个操作是为了给将要插入的字符腾出空间。
然后,在指定位置插入n个字符。这个操作使用了一个for循环,从pos开始,循环n次,每次将要插入的字符添加到当前对象中存储的字符串中。
最后,将私有成员变量_size的值增加n,表示当前对象中存储的字符串的长度已经增加了n个字符。
需要注意的是,在while循环中使用了变量npos,这个变量是一个常量,表示无效的位置。在这个函数中,它被用来表示当前对象中存储的字符串已经被遍历完了。
因此,这个函数的作用是在当前string对象中指定位置插入指定数量的字符,并在需要时自动扩容以确保有足够的空间存储这些字符。
插入字符串
//在指定位置插入一个字符串 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 (end >= pos && end != npos)//npos表示整型最大值 { _str[end + len] = _str[end]; --end; } for (size_t i = pos; i < pos + len; i++) { _str[i] = str[i - pos]; } _size += len; }
这段代码是string类的insert函数,用于在当前string对象中的指定位置插入一个C风格字符串。
该函数首先检查要插入的位置是否在当前对象中存储的字符串的范围内,然后计算要插入的字符串长度。如果将要插入的字符数量加上当前对象中存储的字符串的长度大于等于当前对象的容量,则需要进行扩容操作。
在扩容操作中,调用成员函数reserve(_size + len)来增加当前对象的容量,以确保有足够的空间存储将要插入的字符串。
接着,从当前对象中存储的字符串的末尾开始,将每个字符向后移动len个位置。这个操作是为了给将要插入的字符串腾出空间。
然后,在指定位置插入字符串。这个操作使用了一个for循环,从pos开始,循环len次,每次将要插入的字符添加到当前对象中存储的字符串中。
最后,将私有成员变量_size的值增加len,表示当前对象中存储的字符串的长度已经增加了len个字符。
erase
//指定位置删除n个字符 void erase(size_t pos, size_t len = npos) { assert(pos <= _size); if (len == npos || pos + len >= _size) { _str[pos] = '\0'; _size = pos; _str[_size] = '\0'; } else { size_t end = pos + len; while (end <= _size) { _str[pos++] = _str[end++]; } _size -= len; } }
这段代码是string类的erase函数,用于从当前string对象中的指定位置开始删除指定数量的字符。
这个函数接受两个参数:一个size_t类型的参数pos,表示要删除字符的起始位置;一个size_t类型的参数len,表示要删除的字符数量,其默认值为npos,表示删除从起始位置到当前对象末尾的所有字符。
在函数体内,首先使用assert函数检查要删除的位置是否在当前对象中存储的字符串的范围内。如果不在范围内,则程序会终止。
然后,判断要删除的字符数量是否等于npos或者要删除的字符数量加上要删除的起始位置是否大于等于当前对象中存储的字符串的长度。如果是,则将当前对象中存储的字符串从指定位置开始到末尾的所有字符都删除,并将当前对象中存储的字符串的长度更新为指定位置。
否则,从指定位置开始,将要删除的字符向后移动len个位置。这个操作使用了一个while循环,从pos开始,循环到pos+len结束,每次将要删除的字符向后移动一个位置。
最后,将私有成员变量_size的值减去len,表示当前对象中存储的字符串的长度已经减少了len个字符。
需要注意的是,在if语句中,将pos位置处的字符设置为’\0’,是为了在删除操作后保证当前对象中存储的字符串以’\0’结尾。
查找函数find
查找字符
//在指定位置查找一个字符 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; }
这段代码是string类的find函数,用于在当前string对象中的指定位置开始查找一个字符。
这个函数接受两个参数:一个char类型的参数ch,表示要查找的字符;一个size_t类型的参数pos,表示从哪个位置开始查找,其默认值为0。
在函数体内,首先使用assert函数检查要查找的起始位置是否在当前对象中存储的字符串的范围内。如果不在范围内,则程序会终止。
然后,使用一个for循环从指定位置开始遍历当前对象中存储的字符串。如果遍历到的字符等于要查找的字符,则返回该字符在当前对象中存储的字符串中的位置。
如果整个字符串都被遍历完了,还没有找到要查找的字符,则返回常量npos,表示未找到。
需要注意的是,在这个函数中,变量npos表示整型最大值,被用来表示未找到要查找的字符。
查找字符串
//在指定位置查找一个字符串 size_t find(const char* str, size_t pos = 0) { assert(pos < _size); const char* ptr = strstr(_str + pos, str); if (ptr) { return ptr - _str; } else { return npos; } }
这段代码是string类的find函数,用于在当前string对象中的指定位置开始查找一个字符串。
这个函数接受两个参数:一个const char*类型的参数str,表示要查找的字符串;一个size_t类型的参数pos,表示从哪个位置开始查找,其默认值为0。
在函数体内,首先使用assert函数检查要查找的起始位置是否在当前对象中存储的字符串的范围内。如果不在范围内,则程序会终止。
然后,使用C标准库函数strstr在当前对象中存储的字符串中查找要查找的字符串。这个函数返回一个指向第一次出现要查找的字符串的指针,如果没找到则返回NULL。
如果找到了要查找的字符串,则返回该字符串在当前对象中存储的字符串中的位置。由于strstr函数返回的是指向要查找的字符串在当前对象中存储的字符串中的位置的指针,因此需要将这个指针减去当前对象中存储的字符串的首地址,才能得到该字符串在当前对象中存储的字符串中的位置。
如果整个字符串都被遍历完了,还没有找到要查找的字符串,则返回常量npos,表示未找到。
需要注意的是,在这个函数中,变量npos表示整型最大值,被用来表示未找到要查找的字符串。
substr()
//截取一段字符串 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; } string tmp; tmp.reserve(n); for (size_t i = pos; i < pos + n; i++) { tmp += _str[i]; } return tmp; }
这段代码是string类的substr函数,用于从当前string对象中的指定位置开始获取一个子字符串。
这个函数接受两个参数:一个size_t类型的参数pos,表示子字符串的起始位置,其默认值为0;一个size_t类型的参数len,表示子字符串的长度,其默认值为npos,表示获取从起始位置到当前对象末尾的所有字符。
在函数体内,首先使用assert函数检查要获取子字符串的起始位置是否在当前对象中存储的字符串的范围内。如果不在范围内,则程序会终止。
然后,判断要获取的子字符串的长度是否等于npos或者要获取的子字符串的长度加上要获取的子字符串的起始位置是否大于当前对象中存储的字符串的长度。如果是,则将要获取子字符串的长度设置为从指定位置开始到当前对象末尾的所有字符的长度。
接着,创建一个临时的string对象tmp,并使用reserve函数为这个对象分配足够的内存空间。这个内存空间大小为要获取子字符串的长度。
最后,使用一个for循环从指定位置开始,将要获取的子字符串中的每个字符添加到临时的string对象中。循环结束后,返回临时的string对象。
需要注意的是,在这个函数中,变量npos表示整型最大值,被用来表示要获取从起始位置到当前对象末尾的所有字符。
重载<运算符
bool operator<(const string& s) const { int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size); return ret == 0 ? _size < s._size : ret < 0; }
这段代码是string类的小于运算符重载函数,用于比较当前string对象和另一个string对象的大小关系。
这个函数接受一个const string&类型的参数s,表示要比较的另一个string对象。
在函数体内,使用C标准库函数memcmp比较当前对象中存储的字符串和另一个对象中存储的字符串的前缀部分,比较的长度为两个字符串中长度更短的那个。返回值为0表示两个字符串相等,返回值小于0表示当前对象中存储的字符串小于另一个对象中存储的字符串,返回值大于0表示当前对象中存储的字符串大于另一个对象中存储的字符串。
然后,使用三目运算符判断两个字符串是否相等。如果相等,则比较两个字符串的长度,返回长度更短的那个字符串小于另一个字符串。如果不相等,则返回比较结果。
需要注意的是,在这个函数中,使用了C标准库函数memcmp来进行字符串的比较,而不是使用循环遍历比较每个字符。这是因为memcmp函数可以利用CPU的特殊指令集进行优化,从而提高比较效率。
重载==运算符
bool operator==(const string& s) const { return _size == s._size && memcmp(_str, s._str, _size) == 0; }
这段代码是string类的等于运算符重载函数,用于比较当前string对象和另一个string对象是否相等。
这个函数接受一个const string&类型的参数s,表示要比较的另一个string对象。
在函数体内,首先比较当前对象中存储的字符串的长度和另一个对象中存储的字符串的长度是否相等,如果不相等,则直接返回false,表示两个对象不相等。
然后,使用C标准库函数memcmp比较当前对象中存储的字符串和另一个对象中存储的字符串是否相等。比较的长度为两个字符串中长度更短的那个。返回值为0表示两个字符串相等,返回值不为0表示两个字符串不相等。
最后,使用逻辑运算符&&将前面两个比较的结果合并起来,如果两个比较都为真,则返回true,表示两个对象相等;否则返回false,表示两个对象不相等。
重载<=、>、>=、!=运算符
bool operator<=(const string& s) const { return *this < s || *this == s; } bool operator>(const string& s) const { return !(*this <= s); } bool operator>=(const string& s) const { return !(*this < s); } bool operator!=(const string& s) const { return !(*this == s); }
上面这些代码也都是复用了'>'和'=='
的代码。
string中的swap函数
void swap(string& s) { std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); }
这段代码是string类的swap函数,用于交换当前string对象和另一个string对象的内容。
这个函数接受一个string&类型的参数s,表示要交换内容的另一个string对象。
在函数体内,使用C++标准库函数std::swap来交换当前对象中存储的字符串和另一个对象中存储的字符串、当前对象中存储的字符串的长度和另一个对象中存储的字符串的长度、当前对象中存储的字符串的容量和另一个对象中存储的字符串的容量。
重载=运算符
第一种重载方法
string& operator=(const string& s) { if (this != &s) { char* tmp = new char[s._capacity + 1]; memcpy(tmp, s._str, s._size+1); delete[] _str; _str = tmp; _size = s._size; _capacity = s._capacity; } return *this; }
第二种重载方法
string& operator=(const string& s) { if (this != &s) { string tmp(s); swap(tmp); } return *this; }
第三种重载方法
string& operator=(string tmp) { swap(tmp); return *this; }
迭代器
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类的迭代器相关函数,用于提供对string对象中存储的字符串字符的访问。
在这个代码中,首先使用typedef关键字定义了两个类型别名iterator和const_iterator,分别表示可变迭代器和不可变迭代器。可变迭代器可以用于修改string对象中存储的字符串,而不可变迭代器则不能。
然后,定义了两个函数begin()和end(),分别返回可变迭代器和不可变迭代器。这两个函数用于返回一个指向当前对象中存储的字符串的首字符的指针和一个指向当前对象中存储的字符串的尾字符的下一个字符的指针。
流插入
ostream& operator<<(ostream& out, const string& s) { for (auto ch : s) { out << ch; } return out; }
这段代码是string类的输出运算符重载函数,用于将当前string对象的内容输出到输出流中。
这个函数接受一个ostream&类型的参数out,表示要输出到的输出流,以及一个const string&类型的参数s,表示要输出的string对象。
在函数体内,使用C++11中的范围for循环遍历当前对象中存储的字符串中的每个字符。对于每个字符,使用输出运算符<<将其输出到输出流中。
最后,返回输出流的引用。
流提取
istream& operator>>(istream& in, string& s) { s.clear(); char ch = in.get(); // 处理前缓冲区前面的空格或者换行 while (ch == ' ' || ch == '\n') { ch = in.get(); } char buff[128]; int i = 0; while (ch != ' ' && ch != '\n') { buff[i++] = ch; if (i == 127) { buff[i] = '\0'; s += buff; i = 0; } ch = in.get(); } if (i != 0) { buff[i] = '\0'; s += buff; } return in; }
这段代码是一个重载了输入流操作符(>>)的函数,用于从输入流中读取字符串数据。:
1.首先,函数会清空字符串 s,以确保其为空。
2.接下来,使用 in.get() 函数读取输入流中的一个字符,并将其赋值给变量 ch。
3.在一个循环中,检查字符 ch 是否是空格或换行符。如果是,则继续调用 in.get() 读取下一个字符,直到遇到非空格和非换行符的字符为止,这样可以跳过输入流中前面的空格或换行。
4.创建一个字符数组 buff,起初为空。然后,在另一个循环中,将非空格和非换行符的字符存入 buff 中,并逐个增加索引 i。
5.检查索引 i 是否达到了上限(127),即是否已经存满了 buff 数组。如果是,将 buff 数组最后一个字符设置为字符串结束符(‘\0’),并将其内容添加到字符串 s 的末尾。然后,重置索引 i 为0。
6.继续执行循环,直到遇到空格或换行符。在每次循环迭代时,将非空格和非换行符的字符存入 buff 中,并逐个增加索引 i。
7.最后,检查索引 i 是否不为0,即是否有未添加到字符串 s 的字符。如果是,将 buff 数组最后一个字符设置为字符串结束符(‘\0’),并将其内容添加到字符串 s 的末尾。
8.返回输入流 in,以支持链式输入操作。
总体来说,该代码通过循环读取输入流中的字符,并根据空格和换行符的位置将字符拼接成一个字符串。这样,使用该函数可以便捷地从输入流中读取一个带有空格或换行符分隔的字符串。