前言
本期分享C++string类的模拟实现(参考SGI 30实现),不套类模版,降低学习成本, 进一步加深理解。
属性介绍
名称 | 具体成员 |
---|---|
动态字符数组 | char* _s |
存储有效数据的容量 | size_t capacity |
有效数据的个数 | size_t size |
npos | static const size_t npos |
方法介绍
要实现的接口大概以下:
- 迭代器
- begin
- end
- 基本成员函数
- constructor(构造)
- destructor(析构)
- copy constructor(拷贝构造)
- assignment operator overload(赋值运算符重载)
- capacity
- capacity
- size
- reserve
- resize
- empty
- modify
- push_back
- pop_back
- append
- insert
- erase
- find
- operator +=
- clear
- swap
- c_str
- access
- operator []
- 非成员函数重载
- operator >>
- operator <<
实现
声明
避免和库的冲突,我们需要实现在自己的命名空间内:
//string.hpp
#include <iostream>
using std:: cin;
using std:: cout;
using std:: endl;
using std:: ostream;
using std:: istream;
using std:: swap;
namespace bacon
{
class string
{
friend ostream& operator<<(ostream& out, const string& s);
friend istream& operator>>(istream& in, bacon::string& s);
public:
typedef char* iterator;
//迭代器
iterator begin() const;//闭
iterator end() const;//开
//构造
//用char*构造
string(const char* s = "");
//拷贝构造
string(const string& s);
//析构
~string();
//赋值重载
//string& operator=(const string& s);//传统写法
string& operator=(string s);//现代写法
//capacity
size_t size() const;
size_t capacity() const;
void reserve(size_t n);
void resize(size_t n, char c = '\0');
bool empty() const;
//modify
void push_back(char ch);
void pop_back();
void append(const char* s);
string& insert(size_t pos, char ch);
string& insert(size_t pos, const char* s);
string& erase(size_t pos, size_t n = npos);
size_t find(const char& ch, size_t pos = 0) const;
size_t find(const char* s, size_t pos = 0) const;
string& operator+=(char ch);
string& operator+=(const char* s);
string& operator+=(const string& s);
void clear();
void swap(string& s);
const char* c_str() const;
//access
char& operator[](size_t i);
const char& operator[](size_t i) const;
private:
char* _s;
size_t _size;//有效数据个数,_s[size]就是\0的位置
size_t _capacity;//存放有效数据的空间大小,开辟时多开一个预留给\0
static const size_t npos = -1;
};
}
-
构造函数要给缺省值,来覆盖空string的情况
-
“\0”:字符串,有俩\0
-
“”:字符串,有一个\0
-
-
类外实现,使用类内的东西需要指定类域,如类型、函数名等。
-
缺省参数仅声明时给即可。
【接口需要实现const还是非const版本,或是二者都要?】
从功能上分辨:
- 功能只需要读:const
- 功能只需要写:非const
- 功能读写都需要:cosnt + 非const
基本成员函数
//---------------迭代器---------------
string::iterator string::begin() const
{
return _s;
}
string::iterator string::end() const
{
return _s + _size;
}
//---------------构造---------------
string::string(const char* s)
{
//开空间
_size = strlen(s);
_capacity = _size;
_s = new char[_capacity + 1];//有效数据空间 + 预留\0空间
//赋值
strcpy(_s, s);
}
//---------------析构---------------
string::~string()
{
delete[] _s;
_s = nullptr;
_size = _capacity = 0;
}
//---------------拷贝构造---------------
//传统写法
//string::string(const string& s)
//{
// _s = new char[s._capacity + 1];
// _capacity = s._capacity;
//
// strcpy(_s, s._s);
// _size = s._size;
//}
//现代写法:复用构造
string::string(const string& s)
:_s(nullptr),
_size(0),
_capacity(0)
{
string tmp(s._s);//构造(开了自己的空间)
swap(tmp);
}
//---------------赋值重载---------------
//传统写法
//string& string::operator=(const string& s)
//{
// if(this != &s)
// {
// char* tmp = new char[s._capacity + 1];
// strcpy(tmp, s._s);
//
// delete[] _s;
// _s = tmp;
// _size = s._size;
// _capacity = s._capacity;
// }
// return *this;
//}
//现代写法
string& string::operator=(string s)//s是实参的一份临时拷贝,给已存在实参初始化:调用拷贝构造
{
swap(s);
return *this;
}
成员_s是动态字符数组,由于后续需要释放,不能浅拷贝(直接按字节拷贝),否则指向同一块空间,析构时先后释放两次,出错。
这也代表涉及拷贝的 拷贝构造 和 赋值重载 都需要实现深拷贝(开辟自己的空间)。
【拷贝构造的传统写法和现代写法】
开空间最好不要用C的内存函数,毕竟我们在玩自定义类型,虽然当前的char*可以用,但容易造成“自定义类型忘记初始化”的问题,且往后实现别的也不行了,new才是真理。
传统写法:不复用
string::string(const string& s)
{
_s = new char[_capacity + 1];
_size = s._size;
_capacity = s._capacity;
strcpy(_s, s._s);
}
现代写法:复用构造
string::string(const string& s)
:_s(nullptr),
_size(0),
_capacity(0)
{
string tmp(s._s);//构造
swap(tmp);
}
- 必须将_s置空,否则tmp析构野指针出错
- 复用构造,tmp深拷贝:开辟空间,拷贝内容
- 交换成员,_s坐享其成,啥也不用干就拿到空间和数据,tmp苦不堪言,不仅要去开空间拷贝,还要帮_s释放空间
【为什么要自己实现swap?】
void string::swap(string& s)
{
std::swap(_s, s._s);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
template <class T> void swap ( T& a, T& b )
{
T c(a); a=b; b=c;
}
前者巧妙交换成员,后者粗暴交换对象, 多了三次深拷贝。
【赋值运算符重载的传统写法和现代写法】
传统写法:不复用
string& string::operator=(const string& s)
{
if(this != &s)
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp, s._s);
delete[] _s;
_s = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
现代写法:复用拷贝构造
string& string::operator=(string s)//s是实参的一份临时拷贝,调用拷贝构造
{
swap(s);
return *this;
}
- 复用拷贝构造:传值传参,调用拷贝构造产生临时对象,s有自己的空间
- 交换的思路和上面一样,_s坐享其成,tmp苦不堪言
现代写法的思路:找工具人。
拷贝构造的工具人是自己构造的,赋值重载的工具人是路边拉来的。
但是我们测试的时候还得调试来看,不方便,我们实现一下打印相关的接口:
//---------------capacity---------------
size_t string::size() const
{
return _size;
}
//---------------access---------------
char& string::operator[](size_t i)//可修改
{
assert(i < _size);
return _s[i];
}
const char& string::operator[](size_t i) const//不可修改
{
assert(i < _size);
return _s[i];
}
//---------------流插入运算符重载---------------
ostream& operator<<(ostream& out, const bacon::string& s)
{
for(size_t i = 0 ; i<s.size(); i++)
{
out << s[i];
}
return out;
}
#include "string.h"
using namespace::bacon;
void t1()
{
string s1("bacon's string");//构造
string s2(s1);//拷贝构造
string s3 = s1;//赋值重载
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
//迭代器 + []重载遍历
bacon::string::iterator it1 = s1.begin();
while(it1 != s1.end())
{
++(*it1);
++it1;
}
cout << s1 << endl;
//范围for遍历
for(auto& e : s1)
{
--e;
}
cout << s1 << endl;
}//析构
int main(int argc, const char * argv[])
{
t1();
return 0;
}
bacon's string
bacon's string
bacon's string
cbdpo(t!tusjoh
bacon's string
可以看到,有了begin和end方法,范围for也能用了——但当我们将begin的名字改成Begin
说begin未定义,那肯定就是调用了begin,底层实际上很像宏或实例化。
capacity
size_t string::size() const
{
return _size;
}
size_t string::capacity() const
{
return _capacity;
}
void string::reserve(size_t n)
{
if(n > _capacity)
{
char* tmp = new char[n + 1];//预留\0
_capacity = n;
strcpy(tmp, _s);
delete[] _s;
_s = tmp;
}
}
void string::resize(size_t n, char c)
{
//n < size:删除数据
//n > size:多出的部分填充c(若需要,扩容)
if(n > _size)
{
if(n > _capacity)
reserve(n + 1);
for(size_t i = _size; i < n; ++i)
_s[i] = c;
}
_size = n;
_s[_size] = '\0';
}
bool string::empty() const
{
return _size == 0;
}
- reserve
- 不缩容:开销大。**C++中已经不再用C的内存函数,否则容易导致“自定义类型未初始化”的问题。没有realloc这种函数,我们每次扩容都是异地扩——开空间,拷贝,销毁。
- 当知道将来要用多大空间,可以提前reserve,免得频繁异地扩。如s1将来要用100个char的空间,s1.reserve(100);
- n < size:删除数据
n > size:多出的部分填充c(若需要,扩容)
modify
几个轻松愉快的接口:
void string::push_back(char ch)
{
if(_size == _capacity)
reserve(_capacity == 0 ? 4 : 2*_capacity);
_s[_size++] = ch;
_s[_size] = '\0';
}
void string::pop_back()
{
assert(!empty());
_s[--_size] = '\0';
}
void string::append(const char* s)
{
size_t len = strlen(s);
if(_size + len > _capacity)
reserve(_size + len + 1);//字符串长度不定,二倍扩容不一定够(手动计算)
strcpy(_s + _size, s);//覆盖原\0,strcpy会把src的\0一起拷到dst
_size += len;
}
老伙计insert
插入字符
若我们想
在"helloworld"的5下标位置,插入’ ‘,需要先挪动腾出位置,再插入’ ’
string& string::insert(size_t pos, char ch)
{
assert(pos <= _size);//pos == _size:尾插
if(_size == _capacity)
reserve(_capacity == 0 ? 4 : 2*_capacity);
//挪动数据,腾出1个空间(需挪\0)
//[pos, _size] ==> [pos + 1, _size + 1]
size_t end = _size;
while(end >= pos)
{
//[pos+1, _size+1] = [pos, _size]
_s[end + 1] = _s[end];
--end;
}
_s[pos] = ch;
++_size;
return *this;
}
上面的实现有没有什么问题?
…
…
有的,比如要头插的时候:
当pos == 0,对于
while(end >= pos)
和
_s[end + 1] = _s[end];
期望的最后一次挪动:
end = 0;
_s[1] = _s[0];
但此次挪动后,size_t的end自减始终>=0,无法跳出循环。说白了就是,
碰到了小于0的位置,使得size_t影响逻辑。
怎么解决呢?
【1.硬干:强制类型转换】
string& string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if(_size == _capacity)
reserve(_capacity == 0 ? 4 : 2*_capacity);
//方案1:end改成int类型,但还是不能直接解决
//int会向size_t提升(二者比较了,精度低==>精度高),pos也要强转成int
int end = _size;
while(end >= (int)pos)
{
//挪动数据
//[pos+1, _size+1] = [pos, _size] 腾出1个位置
_s[end + 1] = _s[end];
--end;
}
_s[pos] = ch;
++_size;
return *this;
}
当pos == 0,对于
while(end >= (int)pos)
和
_s[end + 1] = _s[end];
期望的最后一次挪动:
end = 0;
_s[1] = _s[0];
此次挪动后,int的end自减<0,跳出循环,符合效果。
但不符合原意了:作为下标的end和pos不会小于0,其类型就应该是size_t。
【2.巧妙避开:不碰<0的位置】
string& string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if(_size == _capacity)
reserve(_capacity == 0 ? 4 : 2*_capacity);
//方案2:end不设计成拿数据的位置,而设计成放数据的位置,避免访问<0的位置
size_t end = _size + 1;
while(end > pos)
{
//挪动数据
//[pos, _size+1] = [pos, _size] 腾出1个位置
_s[end] = _s[end - 1];
--end;
}
_s[pos] = ch;
++_size;
return *this;
}
当pos == 0,对于
while(end > pos)
和
_s[end] = _s[end - 1];
期望的最后一次挪动:
end = 1;
_s[1] = _s[0];
此次挪动后,size_t的end自减==0,跳出循环。
这样一来,成功挪动, 又没触碰到危险的地方。
插入字符串
string& string::insert(size_t pos, const char* s)
{
assert(pos <= _size);
reserve(_capacity == 0 ? 4 : 2*_capacity);
size_t len = strlen(s);
size_t end = _size + len;
while(end >= pos+len)//最后一次需要_s[pos+len] = _s[pos]
{
//挪动数据,腾出len个空间(需挪\0)
//[pos+len, _size+len] = [pos, _size] len个位置
_s[end] = _s[end - len];
--end;
}
memcpy(_s + pos, s, len);//用strcpy会拷\0,不是想要的效果
_size += len;
return *this;
}
但这样还是有坑:但pos==0 && len == 0,对空string插入空串,–end又是“size_t无法小于0打破循环”的问题。
同样避开这个场景:
while(end > pos + len - 1)
{
_s[end] = _s[end - len];
--end;
}
对于这种控制数组下标的问题,有一个办法:
需求 + 挪动方式 + 始末下标
比如插入字符串,
-
需求:pos到pos+len的位置需要腾出来给s,也就代表[pos, _size]要移动到[pos+len, _size+len]
-
挪动方式:总体向后移动,从后开始,给一个end作为放数据的位置,则end-len为拿数据的位置
-
始末下标:依照挪动方式可以确定,
end作为放数据的位置要从_size+len开始,最后一次需要_s[pos+len] = _s[pos],即
end∈[_size + len, pos + len]
简单说,
-
[pos, _size]要移动到[pos+len, _size+len]
-
end为放数据的位置,end-len为拿数据的位置
-
end∈[_size + len, pos + len]
代码对应以上三点实现
写出while ==> 写出挪动方式 ==> 控制下标
就完事。
对于下标控制的越界问题:
把各个下标能取到的奇怪值都代一下
这里“对空string插入空串”就是代了pos和len都是0的情况。
老伙计erase
string& string::erase(size_t pos, size_t n)
{
assert(pos < _size);
if(n == npos || pos + n >= _size)
{
_s[pos] = '\0';
_size = pos;
}
else
{
//[1, 2, 3, 4, 5] pos = 1, n = 2
//[1, 4, 5]
//[pos+n, _size] ==> [pos, _size-n]
// size_t begin = pos;
// while(begin <= _size - n)
// {
// _s[begin] = _s[begin + n];
// ++begin;
// }
strcpy(_s + pos, _s + pos + n);
_size -= n;
}
return *this;
}
- 如果后面都要删,直接放\0
- 否则老实挪动覆盖
size_t string::find(const char& ch, size_t pos) const
{
assert(pos < _size);
for(size_t i = 0; i < _size; ++i)
{
if(_s[i] == ch)
return i;
}
return -1;
}
size_t string::find(const char* s, size_t pos) const
{
assert(pos < _size);
const char* ret = strstr(_s + pos, s);
if(nullptr == ret)
return -1;
else
return ret - _s;//元素个数/下标
}
void string::clear()
{
_s[0] = '\0';
_size = 0;
}
void string::swap(string& s)
{
std::swap(_s, s._s);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
const char* string::c_str() const
{
return _s;
}
operator overload
//---------------operator overload---------------
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* s)
{
append(s);
return *this;
}
string& string::operator+=(const string& s)
{
append(s.c_str());
return *this;
}
access
//---------------access---------------
char& string::operator[](size_t i)//可修改
{
assert(i < _size);
return _s[i];
}
const char& string::operator[](size_t i) const//不可修改
{
assert(i < _size);
return _s[i];
}
非成员函数运算符重载
//---------------非成员函数运算符重载---------------
ostream& operator<<(ostream& out, const bacon::string& s)
{
for(size_t i = 0 ; i<s.size(); i++)
{
out << s[i];
}
return out;
}
istream& operator>>(istream& in, bacon::string& s)
{
s.clear();
//缓冲区
char buf[128] = {'\0'};
size_t cnt = 0;
char ch = in.get();
while(ch != ' ' && ch != '\n' && ch != '\t') //遇到空白字符停下
{
//缓冲区满了就追加
if(cnt == 127) //考虑\0
{
s += buf;
cnt = 0;
}
buf[cnt++] = ch;
ch = in.get();
}
//缓冲区内若还有救追加
if(cnt > 0)
{
buf[cnt] = '\0';
s += buf;
}
return in;
}
整体代码
//
// string.h
// string_3
//
// Created by Bacon on 2022/12/6.
//
#ifndef string_h
#define string_h
#include <iostream>
using std:: cin;
using std:: cout;
using std:: endl;
using std:: ostream;
using std:: istream;
using std:: swap;
namespace bacon
{
class string
{
friend ostream& operator<<(ostream& out, const string& s);
friend istream& operator>>(istream& in, bacon::string& s);
public:
typedef char* iterator;
//迭代器
iterator begin() const;
iterator end() const;
//构造
//用char*构造
string(const char* s = "");
//拷贝构造
string(const string& s);
//析构
~string();
//赋值重载
// string& operator=(const string& s);//传统写法
string& operator=(string s);//现代写法
//capacity
size_t size() const;
size_t capacity() const;
void reserve(size_t n);
void resize(size_t n, char c = '\0');
bool empty() const;
//modify
void push_back(char ch);
void pop_back();
void append(const char* s);
string& insert(size_t pos, char ch);
string& insert(size_t pos, const char* s);
string& erase(size_t pos, size_t n = npos);
size_t find(const char& ch, size_t pos = 0) const;
size_t find(const char* s, size_t pos = 0) const;
string& operator+=(char ch);
string& operator+=(const char* s);
string& operator+=(const string& s);
void clear();
void swap(string& s);
const char* c_str() const;
//access
char& operator[](size_t i);
const char& operator[](size_t i) const;
private:
char* _s;
size_t _size;//size就是\0的位置
size_t _capacity;
static const size_t npos = -1;
};
//类外实现,使用类内的东西需要指定类域,如类型、函数名等。
//---------------迭代器---------------
string::iterator string::begin() const
{
return _s;
}
string::iterator string::end() const
{
return _s + _size;
}
//---------------构造---------------
string::string(const char* s)
{
//开空间
_size = strlen(s);
_capacity = _size;
_s = new char[_capacity + 1];//预留\0空间
//赋值
strcpy(_s, s);
}
//---------------析构---------------
string::~string()
{
delete[] _s;
_s = nullptr;
_size = _capacity = 0;
}
//---------------拷贝构造---------------
//传统写法
//string::string(const string& s)
//{
// _s = new char[s._capacity + 1];
// _size = s._size;
// _capacity = s._capacity;
//
// strcpy(_s, s._s);
//}
//现代写法:复用构造
string::string(const string& s)
:_s(nullptr),
_size(0),
_capacity(0)
{
string tmp(s._s);//构造
swap(tmp);
}
//---------------赋值重载---------------
//传统写法
//string& string::operator=(const string& s)
//{
// if(this != &s)
// {
// char* tmp = new char[s._capacity + 1];
// strcpy(tmp, s._s);
//
// delete[] _s;
// _s = tmp;
// _size = s._size;
// _capacity = s._capacity;
// }
// return *this;
//}
//现代写法
string& string::operator=(string s)//s是实参的一份临时拷贝,调用拷贝构造
{
swap(s);
return *this;
}
//---------------capacity---------------
size_t string::size() const
{
return _size;
}
size_t string::capacity() const
{
return _capacity;
}
void string::reserve(size_t n)
{
if(n > _capacity)
{
char* tmp = new char[n + 1];//预留\0
_capacity = n;
strcpy(tmp, _s);
delete[] _s;
_s = tmp;
}
}
void string::resize(size_t n, char c)
{
//n < size:删除数据
//n > size:多出的部分填充c(若需要,扩容)
if(n > _size)
{
if(n > _capacity)
reserve(n + 1);
for(size_t i = _size; i < n; ++i)
_s[i] = c;
}
_size = n;
_s[_size] = '\0';
}
bool string::empty() const
{
return _size == 0;
}
//---------------modify---------------
void string::push_back(char ch)
{
if(_size == _capacity)
reserve(_capacity == 0 ? 4 : 2*_capacity);
_s[_size++] = ch;
_s[_size] = '\0';
}
void string::pop_back()
{
assert(!empty());
_s[--_size] = '\0';
}
void string::append(const char* s)
{
size_t len = strlen(s);
if(_size + len > _capacity)
reserve(_size + len + 1);//字符串长度不定,二倍扩容不一定够(手动计算)
strcpy(_s + _size, s);//覆盖原\0,strcpy会把src的\0一起拷到dst
_size += len;
}
//string& string::insert(size_t pos, char ch)
//{
// assert(pos <= _size);//pos == _size:尾插
// if(_size == _capacity)
// reserve(_capacity == 0 ? 4 : 2*_capacity);
//
// //挪动数据,腾出1个空间(需挪\0)
// //[pos, _size] ==> [pos + 1, _size + 1]
// size_t end = _size;
// while(end >= pos)
// {
// _s[end + 1] = _s[end];
// --end;
// }
//
// _s[pos] = ch;
// ++_size;
//
// return *this;
//}
当pos == 0:end始终>=0,死循环
string& string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if(_size == _capacity)
reserve(_capacity == 0 ? 4 : 2*_capacity);
//[pos, _size] ==> [pos + 1, _size + 1]
//方案1:end改成intl类型,但是还是不能直接解决,int会向size_t提升(二者比较了,精度低==>精度高)
//pos也要强转成int,但这样不符合原意了
// int end = _size;
while(end >= pos)
// while(end >= (int)pos)
// {
_s[0] = _s[-1];
// _s[end + 1] = _s[end];
// --end;
// }
//方案2:end不设计成拿数据的位置,而设计成放数据的位置
size_t end = _size + 1;
while(end > pos)
{
_s[end] = _s[end - 1];
--end;
}
_s[pos] = ch;
++_size;
return *this;
}
string& string::insert(size_t pos, const char* s)
{
assert(pos <= _size);
reserve(_capacity == 0 ? 4 : 2*_capacity);
size_t len = strlen(s);
//挪动数据,腾出len个空间(需挪\0)
size_t end = _size + len;
// while(end > pos + len - 1)
while(end > pos + len - 1)
{
_s[end] = _s[end - len];
--end;
}
memcpy(_s + pos, s, len);//用strcpy会拷\0,不是想要的效果
_size += len;
return *this;
}
string& string::erase(size_t pos, size_t n)
{
assert(pos < _size);
if(n == npos || pos + n >= _size)
{
_s[pos] = '\0';
_size = pos;
}
else
{
//[1, 2, 3, 4, 5] pos = 1, n = 2
//[1, 4, 5]
//[pos+n, _size] ==> [pos, _size-n]
// size_t begin = pos;
// while(begin <= _size - n)
// {
// _s[begin] = _s[begin + n];
// ++begin;
// }
strcpy(_s + pos, _s + pos + n);
_size -= n;
}
return *this;
}
size_t string::find(const char& ch, size_t pos) const
{
assert(pos < _size);
for(size_t i = 0; i < _size; ++i)
{
if(_s[i] == ch)
return i;
}
return -1;
}
size_t string::find(const char* s, size_t pos) const
{
assert(pos < _size);
const char* ret = strstr(_s + pos, s);
if(nullptr == ret)
return -1;
else
return ret - _s;//元素个数/下标
}
void string::clear()
{
_s[0] = '\0';
_size = 0;
}
void string::swap(string& s)
{
std::swap(_s, s._s);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
const char* string::c_str() const
{
return _s;
}
//---------------operator overload---------------
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* s)
{
append(s);
return *this;
}
string& string::operator+=(const string& s)
{
append(s.c_str());
return *this;
}
//---------------access---------------
char& string::operator[](size_t i)//可修改
{
assert(i < _size);
return _s[i];
}
const char& string::operator[](size_t i) const//不可修改
{
assert(i < _size);
return _s[i];
}
//---------------非成员函数运算符重载---------------
ostream& operator<<(ostream& out, const bacon::string& s)
{
for(size_t i = 0 ; i<s.size(); i++)
{
out << s[i];
}
return out;
}
istream& operator>>(istream& in, bacon::string& s)
{
s.clear();
//缓冲区
char buf[128] = {'\0'};
size_t cnt = 0;
char ch = in.get();
while(ch != ' ' && ch != '\n' && ch != '\t') //遇到空白字符停下
{
//缓冲区满了就追加
if(cnt == 127) //考虑\0
{
s += buf;
cnt = 0;
}
buf[cnt++] = ch;
ch = in.get();
}
//缓冲区内若还有救追加
if(cnt > 0)
{
buf[cnt] = '\0';
s += buf;
}
return in;
}
}
#endif /* string_h */
今天的分享就到这里啦,不足之处望请斧正!
这里是培根的blog,期待与你共同进步
下期见~