前面我们知道了string的结构比较复杂,这里我们实现简单一点的
string类的成员变量有
char* _str
是指向堆中字符串的指针
_size
表示有效字符的长度
_capacity
表示空间容量
这里的实现方式类似于顺序表
我们还需定义一个静态成员npos
同时,我们也要把自己实现string类放在一个命名空间my_string
中
此时,就有了一个初步的一个框架:
namespace my_string
{
class string
{
public:
//成员函数
private:
char* _str;
size_t _size;
size_t _capacity;
public:
const static size_t npos;
};
const size_t string::npos = -1;
}
现在我们有三个成员变量,就可以实现3个函数:c_str()
,size()
和capacity()
const char* c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
这三个函数不会修改成员函数,所以可以用const修饰this指针
这里我们只实现无参构造和用字符串构造
完全可以把这两种写成一个含有缺省参数的构造函数
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
memmove(_str, str, _size + 1);
}
_size的值就是str的长度,_capacity的值和_size相同
然后new出_capacity+1大小的空间
之所以要开辟_capacity大小的空间,是因为_size表示有效字符长度,_capacity表示可容纳有效字符的容量,都不包含’
\0'
,但是在存储上还是需要存储'\0‘
的,所以要在底层多开辟一个字符空间
然后这里最主要的一处就是memmove(_str, str, _size + 1)
把str中的值拷贝到新空间中,这是深拷贝
如果写成
_str = str
就成为浅拷贝了,后面析构时就会发生一块空间析构2次,报错
析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
string里的迭代器其实就是指针,我们可以把char*
,typedef
成iterator
,const迭代器同理
typedef char* iterator;
typedef const char* const_iterator;
然后实现begin()
和end()
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin()const
{
return _str;
}
const_iterator end()const
{
return _str + _size;
}
因为范围for的底层也是调用迭代器,所以此时实现了迭代器之后,我们的string也支持范围for了
[]
运算符的重载需要重载2个函数,一个函数是即可读也可写,一个是只可以读
这里我们可以用assert
断言判断一下pos
是否合法
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 capacity)
{
if (capacity > _capacity)
{
char* tmp = new char[capacity + 1];
memmove(tmp, _str,_size+1);
delete[] _str;
_str = tmp;
_capacity = capacity;
tmp = nullptr;
}
}
这里需要注意的一点是,把
_str
中的数据拷贝到tmp
中时,需要使用memcpy
或者memmove
函数,虽然_str
和tmp
是指向字符串的,本应使用strcpy
但是strcpy
会拷贝直到'\0'
,如果字符串中间有字符'\0'
的话,数据挪动只挪动到'\0'
,会有数据缺失
所以必须要用memcpy
或mrmmove
,以_size+1为终止条件
所以要注意,在模拟实现string中,要用memcpy
或memmove
替代所有strcpy
void resize(size_t n, char ch= '\0')
{
if (n < _size)
{
_size = n;
_str[_size] = '\0';
}
else
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
}
push_back
尾插一个字符
需要先判断容量是否满,如果满了要扩容,我们这里指定的扩容策略是:二倍扩容。并且如果当前容量为0,就设置容量为4
接着就在_str[_size]处放入字符c
,再在它后面加上\0
void push_back(char c)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = c;
_size++;
_str[_size] = '\0';
}
下面是2个append
函数,一种参数是string对象,一种是字符串
首先判断是否需要扩容,接着就深拷贝数据过去
void append(const string& str)
{
size_t len = str.size();
if (size() + len > capacity())
{
reserve(size() + len);
}
// strcpy(_str+size(),str._str);
memmove(_str + _size, str._str,len+1);
_size += len;
}
void append(const char* c)
{
size_t len = strlen(c);
if (len + size() > capacity())
{
reserve(size() + len);
}
/*strcpy(_str + size(), c);*/
memmove(_str + _size, c,len+1);
_size += len;
}
operator+=
函数
operator+=函数可以复用前面的push_back
函数和append
函数
string& operator+=(const string& str)
{
append(str);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
string& operator+=(char c)
{
push_back(c);
return *this;
}
先实现插入n
个字符ch
的insrt
函数
首先还是判断是否需要扩容
assert(pos <= size());
if (n + size() > capacity())
{
reserve(n + size());
}
接着需要挪动数据,把pos包括pos后面的数据向后挪动n
个距离
size_t end = size();
while (pos <= end)
{
_str[end + n] = _str[end];
end--;
}
这里其实有个错误,在判断条件中需要加上end != npos
因为end的类型为size_t
无符号整形
假如pos的值为0时,pos==end==0
时循环继续,end减去1后变为整形的最大值,仍然大于pos,死循环
所以才要加上判断end != npos
size_t end = size();
while (pos <= end && end != npos)
{
_str[end + n] = _str[end];
end--;
}
接着就是插入n
个字符ch
了
for (int i = 0; i < n; i++)
{
_str[pos + i] = ch;
}
_size += n;
完整代码:
void insert(size_t pos, size_t n, char ch)
{
assert(pos <= size());
if (n + size() > capacity())
{
reserve(n + size());
}
size_t end = size();
//这里判断条件多了end != npos,是因为end的类型是size_t,无符号整形
//假如pos的值为0时,pos==end==0时循环继续,end减去1后变为整形的最大值,仍然大于pos,死循环
//所以才要加上判断end != npos
while (pos <= end && end != npos)
{
_str[end + n] = _str[end];
end--;
}
for (int i = 0; i < n; i++)
{
_str[pos + i] = ch;
}
_size += n;
}
然后我们就可以根据上面的写法,完成插入字符串和插入一个string对象的insert
函数
void insert(size_t pos, const string& str)
{
assert(pos <= size());
int len = str.size();
if (len + size() > capacity())
{
reserve(len + size());
}
size_t end = size();
while (pos <= end && end != npos)
{
_str[end + len] = _str[end];
end--;
}
for (int i = 0; i < len; i++)
{
_str[pos + i] = str._str[i];
}
_size += len;
}
void insert(size_t pos, const char* str)
{
assert(pos <= size());
int len = strlen(str);
if (len + size() > capacity())
{
reserve(len + size());
}
size_t end = size();
while (pos <= end && end != npos)
{
_str[end + len] = _str[end];
end--;
}
const char* cur = str;
int i = 0;
while (*cur != '\0')
{
_str[pos + i] = *cur;
cur++;
i++;
}
_size += len;
}
这里实现查找一个字符和查找一个字符串的find函数
查找一个字符很简单,遍历一遍,看有没有和要查找一样的字符的,如果找到,返回下标,如果没找到,就返回npos
size_t find(char ch,size_t pos = 0)
{
assert(pos < size());
for (int i = pos; i < size(); i++)
{
if (_str[i] == ch)
return i;
}
return npos;
}
查找一个字符串:
我们使用C中的strstr
查找字符串,如果找到了,会返回指向找到的字符串首个元素的指针,否则返回NULL
找到时,需要返回字符位置,这里可以使用指向同一块空间上的2个指针相减,就可以得到中间的元素个数,也就是字符位置的下标
size_t find(const char* str, size_t pos = 0)
{
assert(pos < size());
char* rp = strstr(_str + pos, str);
if (rp == NULL)
{
return npos;
}
else
{
return rp - _str;
}
}
void erase(size_t pos, size_t n = npos)
如果n
的值为npos
或者pos+n>size()
,就表示删除pos
位置(包括pos)后所有字符
我们就直接把_str[pos]
值改为'\0'
,接着改变_size
的值为pos
就可以
剩下的情况就是正常删除就可以了
void erase(size_t pos, size_t n = npos)
{
assert(pos <= size());
if (n == npos || pos + n > size())
{
_str[pos] = '\0';
_size = pos;
}
else
{
int cur = pos + n;
while (cur != size() + 1)
{
_str[pos] = _str[cur];
pos++;
cur++;
}
_size -= n;
}
}
string substr(size_t pos = 0,size_t len =npos) const
如果len==npos
或pos+len>size()
就表示取从pos
位置后所有字符
string substr(size_t pos = 0,size_t len =npos) const
{
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 < n + pos; i++)
{
tmp += _str[i];
}
return tmp;
}
赋值重载有2中写法,一种是传统写法,一种是现代写法
传统写法很简单,就是实现一个深拷贝就可以了
string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
memmove(tmp, s._str, s._size + 1);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
现代写法的思想:
a = b;是一个赋值重载的调用,以往在赋值重载中需要写将b的_size,_capacity拷贝给a
最主要的时,需要先delete掉a._str在堆上的空间,再开辟一个和b._str一样的空间给a
那么在函数已经拷贝出了一个临时对象tmp,既然这个临时对象在函数结束后会自动销毁,不如交换this和tmp的_str各个成员变量的值
这样在函数结束后,tmp自动被销毁,tmp._str指向的空间被销毁,这块空间就是之前this->_str指向的空间
我们不必再去手动销毁这段空间了
string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s);
std::swap(_str, tmp._str);
std::swap(_size, tmp._size);
std::swap(_capacity, tmp._capacity);
}
return *this;
}
我们也可以把三个std::swap
函数封装起来写成一个函数
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s);
std::swap(_str, tmp._str);
std::swap(_size, tmp._size);
std::swap(_capacity, tmp._capacity);
}
return *this;
}
下面还有一种更简单的写法:
上面的那个写法还需创建一个tmp临时对象,这里直接用一个非引用类型的形参
这个形参在函数结束后也就自行调用析构函数被清理了
string& operator=(string tmp)
{
swap(tmp);
return *this;
}
拷贝构造也有传统写法和现代写法
//拷贝构造函数传统写法
string(const string& s)
{
_str = new char[s._capacity + 1];
//strcpy(_str, s._str);
memmove(_str, s._str, s._size + 1);
_size = s._size;
_capacity = s._capacity;
}
拷贝构造函数现代写法
这种写法的拷贝构造需要写初始化列表,对this的成员进行初始化
因为对于拷贝构造对于成员的默认初始化,在不同的平台,不同版本的编译器是不同的,有可能会进行初始化,也有可能不会初始化
如果没有进行初始化,和tmp swap完之后tmp的成员都是随机值
然后tmp调用析构函数时,销毁一块随机空间,会报错
string(const string& s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(s._str);
swap(tmp);
}
而且对于现代写法,遇到中间有’\0’的字符串,只会拷贝到’\0’以前,所以对于拷贝构造,传统写法更好一点
字符串是根据每个字符的ASCII来判断大小
我们逐个字符判断大小
while (i1 < _size && i2 < s._size)
{
if (_str[i1] < s._str[i2])
{
return true;
}
else if (_str[i1] > s._str[i2])
{
return false;
}
else
{
i1++;
i2++;
}
}
然后如果知道某一字符串遍历完了还没有判断出哪个字符串小,我们就需要判断字符串长度来判断,字符串长的就大
比如:“hello”就比"helloworld"小
bool operator<(const string& s)const
{
size_t i1 = 0;
size_t i2 = 0;
while (i1 < _size && i2 < s._size)
{
if (_str[i1] < s._str[i2])
{
return true;
}
else if (_str[i1] > s._str[i2])
{
return false;
}
else
{
i1++;
i2++;
}
}
if (_size < s._size)
{
return true;
}
else
{
return false;
}
}
operator==:
bool operator==(const string& s)const
{
return _size == s._size && memcmp(_str, s._str, _size)==0;
}
接着就可以复用这2个函数,完成其余比较大小的运算符重载
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 || *this == s;
}
流提取,流插入我们要写在类外
std::ostream& operator<<(std::ostream& out, const my_string::string& str)
{
for (auto e : str)
{
out << e;
}
return out;
//C的字符数组,以\0算长度
//string不看\0,以_size为终止长度
}
void clear() //在流提取重载中清理缓冲区使用
{
_str[0] = '\0';
_size = 0;
}
std::istream& operator>>(std::istream& in, my_string::string& str)
{
str.clear();//清除掉同一个string对象中缓冲区中的内容
//使用in.get()是因为,get()函数可以读取任何字符包括:' '和'\n'
//而使用in>>ch读到' '或 '\n'时会卡住(阻塞)
char ch = in.get();
//下面这么写是处理前缓冲区前面的空格或者换行
//保证输入:"空格空格空格空格hello" 的时候可以正常读取到"hello"
//以及输入"换行换行换行换行换行hello" 时可以正常读取到"hello"
while (ch == ' ' || ch == '\n')
{
ch = in.get();
}
//设置一个char类型的数组,是因为如果没读取一个字符就+=到str中,会频繁开空间,有消耗
//在栈上开辟一个数组消耗小,所以先把读取的字符放到buff里,等buff"满"的时候,=再把这个数组+=到str中
char buff[128];
int i = 0;
//每读取到一个字符,如果不是空格或换行,就+=到string里,继续读取
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
//如果buff"满"的时候,要在最后加上'\0',把buff加到str里
//此时还需继续读取,就把i赋为0,让buff再从头存储
if (i == 127)
{
buff[i] = '\0';
str += buff;
i = 0;
}
//str += ch;
ch = in.get();
}
//如果读取的字符个数没有超过128,同时也读取到了字符,就在当前i下标处赋'\0'
//把buff加到str中
if (i != 0)
{
buff[i] = '\0';
str += buff;
}
return in;
}
#pragma once
#include
#include
namespace my_string
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
//范围for的底层其实就是使用迭代器,所以这里实现了迭代器之后,就自然而然地支持范围for循环了
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
//strcpy(_str, str);
memmove(_str, str, _size + 1);
}
//拷贝构造函数传统写法
string(const string& s)
{
_str = new char[s._capacity + 1];
//strcpy(_str, s._str);
memmove(_str, s._str, s._size + 1);
_size = s._size;
_capacity = s._capacity;
}
//拷贝构造函数现代写法
//这种写法的拷贝构造需要写初始化列表,对this的成员进行初始化
//因为对于拷贝构造对于成员的默认初始化,在不同的平台,不同版本的编译器是不同的,有可能会进行初始化,也有可能不会初始化
//如果没有进行初始化,和tmp swap完之后tmp的成员都是随机值
//然后tmp调用析构函数时,销毁一块随机空间,会报错
/*string(const string& s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(s._str);
swap(tmp);
}*/
//而且对于现代写法,遇到中间有'\0'的字符串,只会拷贝到'\0'以前
//所以对于拷贝构造,传统写法更好一点
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
const char* c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
char& operator[](size_t pos)//读写
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos)const//只读
{
assert(pos < _size);
return _str[pos];
}
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin()const
{
return _str;
}
const_iterator end()const
{
return _str + _size;
}
void reserve(size_t capacity)
{
if (capacity > _capacity)
{
char* tmp = new char[capacity + 1];
//strcpy(tmp, _str);
memmove(tmp, _str,_size+1);
delete[] _str;
_str = tmp;
_capacity = capacity;
tmp = nullptr;
}
}
void resize(size_t n, char ch= '\0')
{
if (n < _size)
{
_size = n;
_str[_size] = '\0';
}
else
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
}
void push_back(char c)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = c;
_size++;
_str[_size] = '\0';
}
void append(const string& str)
{
size_t len = str.size();
if (size() + len > capacity())
{
reserve(size() + len);
}
// strcpy(_str+size(),str._str);
memmove(_str + _size, str._str,len+1);
_size += len;
}
void append(const char* c)
{
size_t len = strlen(c);
if (len + size() > capacity())
{
reserve(size() + len);
}
/*strcpy(_str + size(), c);*/
memmove(_str + _size, c,len+1);
_size += len;
}
string& operator+=(const string& str)
{
append(str);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
string& operator+=(char c)
{
push_back(c);
return *this;
}
void insert(size_t pos, const string& str)
{
assert(pos <= size());
int len = str.size();
if (len + size() > capacity())
{
reserve(len + size());
}
size_t end = size();
while (pos <= end && end != npos)
{
_str[end + len] = _str[end];
end--;
}
for (int i = 0; i < len; i++)
{
_str[pos + i] = str._str[i];
}
_size += len;
}
void insert(size_t pos, const char* str)
{
assert(pos <= size());
int len = strlen(str);
if (len + size() > capacity())
{
reserve(len + size());
}
size_t end = size();
while (pos <= end && end != npos)
{
_str[end + len] = _str[end];
end--;
}
const char* cur = str;
int i = 0;
while (*cur != '\0')
{
_str[pos + i] = *cur;
cur++;
i++;
}
_size += len;
}
void insert(size_t pos, size_t n, char ch)
{
assert(pos <= size());
if (n + size() > capacity())
{
reserve(n + size());
}
size_t end = size();
//这里判断条件多了end != npos,是因为end的类型是size_t,无符号整形
//假如pos的值为0时,pos==end==0时循环继续,end减去1后变为整形的最大值,仍然大于pos,死循环
//所以才要加上判断end != npos
while (pos <= end && end != npos)
{
_str[end + n] = _str[end];
end--;
}
for (int i = 0; i < n; i++)
{
_str[pos + i] = ch;
}
_size += n;
}
void erase(size_t pos, size_t n = npos)
{
assert(pos <= size());
if (n == npos || pos + n > size())
{
_str[pos] = '\0';
_size = pos;
}
else
{
int cur = pos + n;
while (cur != size() + 1)
{
_str[pos] = _str[cur];
pos++;
cur++;
}
_size -= n;
}
}
size_t find(char ch,size_t pos = 0)
{
assert(pos < size());
for (int i = pos; i < size(); i++)
{
if (_str[i] == ch)
return i;
}
return npos;
}
size_t find(const char* str, size_t pos = 0)
{
assert(pos < size());
char* rp = strstr(_str + pos, str);
if (rp == NULL)
{
return npos;
}
else
{
return rp - _str;
}
}
string substr(size_t pos = 0,size_t len =npos) const
{
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 < n + pos; i++)
{
tmp += _str[i];
}
return tmp;
}
/*string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
memmove(tmp, s._str, s._size + 1);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}*/
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
//赋值重载现代写法
// a = b;是一个赋值重载的调用,以往在赋值重载中需要写将b的_size,_capacity拷贝给a
// 最主要的时,需要先delete掉a._str在堆上的空间,再开辟一个和b._str一样的空间给a
// 那么在函数已经拷贝出了一个临时对象tmp,既然这个临时对象在函数结束后会自动销毁,不如交换this和tmp的_str各个成员变量的值
// 这样在函数结束后,tmp自动被销毁,tmp._str指向的空间被销毁,这块空间就是之前this->_str指向的空间
// 我们不必再去手动销毁这段空间了
//
//string& operator=(const string& s)
//{
// if (this != &s)
// {
// string tmp(s);
// std::swap(_str, tmp._str);
// std::swap(_size, tmp._size);
// std::swap(_capacity, tmp._capacity);
// //swap(tmp);
// }
// return *this;
//}
//这种写法更加简介
//上面的那个写法还需创建一个tmp临时对象,这里直接用一个非引用类型的形参
//这个形参在函数结束后也就自行调用析构函数被清理了
string& operator=(string tmp)
{
swap(tmp);
return *this;
}
void clear() //在流提取重载中清理缓冲区使用
{
_str[0] = '\0';
_size = 0;
}
bool operator<(const string& s)const
{
size_t i1 = 0;
size_t i2 = 0;
while (i1 < _size && i2 < s._size)
{
if (_str[i1] < s._str[i2])
{
return true;
}
else if (_str[i1] > s._str[i2])
{
return false;
}
else
{
i1++;
i2++;
}
}
if (_size < s._size)
{
return true;
}
else
{
return false;
}
}
bool operator==(const string& s)const
{
return _size == s._size && memcmp(_str, s._str, _size)==0;
}
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 || *this == s;
}
private:
char* _str;
size_t _size;
size_t _capacity;
public:
const static size_t npos;
};
const size_t string::npos = -1;
std::ostream& operator<<(std::ostream& out, const my_string::string& str)
{
for (auto e : str)
{
out << e;
}
return out;
//C的字符数组,以\0算长度
//string不看\0,以_size为终止长度
}
std::istream& operator>>(std::istream& in, my_string::string& str)
{
str.clear();//清除掉同一个string对象中缓冲区中的内容
//使用in.get()是因为,get()函数可以读取任何字符包括:' '和'\n'
//而使用in>>ch读到' '或 '\n'时会卡住(阻塞)
char ch = in.get();
//下面这么写是处理前缓冲区前面的空格或者换行
//保证输入:"空格空格空格空格hello" 的时候可以正常读取到"hello"
//以及输入"换行换行换行换行换行hello" 时可以正常读取到"hello"
while (ch == ' ' || ch == '\n')
{
ch = in.get();
}
//设置一个char类型的数组,是因为如果没读取一个字符就+=到str中,会频繁开空间,有消耗
//在栈上开辟一个数组消耗小,所以先把读取的字符放到buff里,等buff"满"的时候,=再把这个数组+=到str中
char buff[128];
int i = 0;
//每读取到一个字符,如果不是空格或换行,就+=到string里,继续读取
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
//如果buff"满"的时候,要在最后加上'\0',把buff加到str里
//此时还需继续读取,就把i赋为0,让buff再从头存储
if (i == 127)
{
buff[i] = '\0';
str += buff;
i = 0;
}
//str += ch;
ch = in.get();
}
//如果读取的字符个数没有超过128,同时也读取到了字符,就在当前i下标处赋'\0'
//把buff加到str中
if (i != 0)
{
buff[i] = '\0';
str += buff;
}
return in;
}
};