👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:C++航路
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨
一、准备工作
为了方便管理代码,分两个文件来写:
Test.cpp
- 测试代码逻辑string.h
- 模拟实现string
二、string的结构
我们知道,string
是一个管理字符数组的类,底层其实就是一个支持动态增长的字符数组,就像数据结构学的动态顺序表。
namespace wj
{
class string
{
public:
private:
char* _str; // 指向动态开辟数组
size_t _size; // 字符个数(不包含'\0')
size_t _capacity; // 容量(不包含'\0')
};
}
string
类的成员变量有三个
- 字符指针
_str
指向开辟的动态数组 -
_size
标识有效数据个数(不包含'\0'
), -
_capacity
记录容量的大小(一般也不包含'\0'
)
还需要注意的是:我们新命名了命名空间域wj
,就是避免和库中的string
产生冲突。
三、模拟实现常见初始化操作
3.1 用C字符串构造
namespace wj
{
class string
{
public:
// 用C字符串构造
string(const char* s)
:_str(new char[strlen(s) + 1]) // +1是为'\0'
, _size(strlen(s))
, _capacity(_size)
{
// 拷贝数据
memcpy(_str, s, _size + 1);
// memcpy以拷贝字节个数
// +1是为了拷贝'\0'
}
private:
char* _str; // 动态字符数组
size_t _size; // 字符个数
size_t _capacity; // 容量
};
}
以上代码有个易错点:要注意初始化列表的顺序,是按照成员变量的顺序来赋值的!
3.2 c_str
为了验证代码的正确性,需要打印出结果。由于自己模拟实现的string
,还没有实现重载流插入<<
,所以不能直接打印string
对象,而流插入<<
是可以打印内置类型的。因此string
是有提供转为内置类型的接口c_str
:
const char* c_str() const
{
return _str;
}
为什么会在函数后加个const
呢?在往期博客我们讲过:只要成员函数内部不修改成员变量,都应该加上const
接下来来测试代码:
3.3 无参构造
无参构造的结果就是空字符,默认是有
'\0'
的
string()
:_str(new char[1])
, _size(0)
,_capacity(_size)
{
_str[0] = '\0';
}
【测试结果】
3.4 拷贝构造
自定义类型的拷贝必须先调用拷贝构造函数。但如果不手动编写,编译器会默认生成一个浅拷贝/值拷贝的拷贝构造函数,即将所有成员变量逐一拷贝到新对象中(浅拷贝),这种拷贝方式对于内置类型或者全是自定义类型的成员变量来说是没有问题的。但是,如果类中有动态分配内存的指针变量,则需要手动编写深拷贝的拷贝构造函数。
// string s1("hello world");
// string s2(s1); // 拷贝构造
// 这里的str是s1的别名,其实就是s1
string(const string& str)
{
// 1. 开一个和s1一样大的空间
_str = new char[str._capacity + 1];
// 2. 将s1的数据拷贝给s2
memcpy(_str, str._str, str._size + 1);
_size = str._size;
_capacity = str._capacity;
}
注意:以上代码其实隐藏了一个this
指针,这个this
指针其实就是s2
,因此以上代码本质就是:
string(const string& str)
{
this->_str = new char[str._capacity + 1];
memcpy(this->_str, str._str, str._size + 1);
this->_size = str._size;
this->_capacity = str._capacity;
}
但是高手是不会把this
写出来的
【测试结果】
四、析构函数
由于成员变量含有动态内存开辟的空间,因此要手动写出析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
五、模拟实现常见遍历操作
5.1 下标访问[] + size接口
size_t size() const
{
return _size;
}
// 可读可写
// 可以引用返回:对象不在此函数域(在堆上),出了此作用域不会被销毁
char& operator[](size_t pos)
{
// 断言,防止越界
assert(pos >= 0 && pos < _size);
return _str[pos];
}
// 可读不可写
const char& operator[](size_t pos) const
{
assert(pos >= 0 && pos < _size);
return _str[pos];
}
【测试结果】
5.2 迭代器
- 迭代器
iterator
本质上就是一个容器的内嵌类型(内置类型),可以是内部类(自定义类型)、也可以是typedef
的类型。而string
的迭代器本质就是一个char*
的指针,因此直接typedef
即可。- 但是要注意:
typedef
要写在public
段中,因为迭代器本身就是要给类外用的。
// 可读可写
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;
}
【测试结果】
在往期博客我们将过,范围for
的底层就是迭代器,因此也可以使用范围for
:
六、尾插
6.1 push_back - 尾插一个字符 + reserve - 扩容
要尾插字符之前,首先需要考虑当前有效字符个数_size
是否大于当前容量_capacity
,如果大于,则需要扩容。因此,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;
}
}
void push_back(char ch)
{
// 可能存在扩容
if (_size == _capacity)
{
// 假设默认2倍扩容
// 如果是空串,扩了2倍容量还是0,因此要考虑容量为0的情况
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
// 插入
_str[_size] = ch;
_size++;
// 别忘了在末尾加'\0'
_str[_size] = '\0';
}
【测试结果】
6.2 append - 尾插字符串
string& append(const char* s)
{
// 可能存在扩容
size_t n = strlen(s);
if (_size + n > _capacity)
{
// 至少扩容_size+n
reserve(_size + n);
}
memcpy(_str + _size, s, n + 1);
_size += n;
return *this;
}
【测试结果】
6.3 运算符重载+= – push_back和append升级版
它既可以尾插一个字符,还可以尾插字符串。因此,直接复用push_back
和append
即可
// +=字符串
string& operator+=(const char* s)
{
append(s);
return *this;
}
// +=字符
string& operator+=(char c)
{
push_back(c);
return *this;
}
【测试结果】
七、插入insert
string
的插入接口设计有点冗余,我们直接实现最常见的即可
- . 在
pos
位置插入n
个字符
【思路】
- 首先要判断下标的合法性。
- 其次还要判断插入的字符加上原有的字符是否超过当前容量,超过就扩容。
- 然后就是挪动数据和插入数据。注意挪动数据一定要从最后一个字符
'\0'
开始挪动n
次;不能从pos
位置开始挪,否则后面的内容就被覆盖了。以下是动图展示
// pos - 下标
// n - 插入的字符个数
// x - 插入的字符
void insert(size_t pos, size_t n, char x)
{
// 判断下标pos的合法性
assert(pos >= 0 && pos <= _size);
// 可能存在扩容
if (_size + n > _capacity)
{
reserve(_size + n);
}
// 挪动数据
// 从'\0'位置上开始挪动
size_t end = _size;
while (end >= pos)
{
// 一个字符挪动n次
_str[end + n] = _str[end];
end--;
}
// 插入数据
for (size_t i = 0; i < n; i++)
{
_str[pos + i] = x;
}
_size += n;
}
通过思路分析,不难可以写出以上代码。但是以上代码有一个bug
,当头插时,程序就崩溃了(如下图)
通过走读代码我们发现,pos
和end
的类型是都是无符号类型size_t
。因此end
最后自减到-1
,由于类型是size_t
,而无符号的-1
是一个相当大的数,循环条件成立就会一直死循环下去。
因此这里有两种方法:
第一种:将pos
和end
的类型全部改为int
void insert(int pos, size_t n, char x)
{
// 判断下标pos的合法性
assert(pos >= 0 && pos <= _size);
// 可能存在扩容
if (_size + n > _capacity)
{
reserve(_size + n);
}
// 挪动数据
int end = _size;
while (end >= pos)
{
_str[end + n] = _str[end];
end--;
}
// 插入数据
for (int i = 0; i < n; i++)
{
_str[pos + i] = x;
}
_size += n;
}
以上这种方法虽然可以,但是和库里提供的类型是有所差别的,因此还是有些不好。
第二种:既然size_t
类型的end
自减到-1
就会死循环,那么加个end != -1
不就完事了。恰好,string
库里提供了公共静态成员常量npos
,这个常量使用值-1
定义。
所以,最终代码如下:
namespace wj
{
class string
{
public:
void insert(size_t pos, size_t n, char x)
{
// 判断下标pos的合法性
assert(pos >= 0 && pos <= _size);
// 可能存在扩容
if (_size + n > _capacity)
{
reserve(_size + n);
}
// 挪动数据
size_t end = _size;
while (end != npos && end >= pos)
{
// 挪动n次
_str[end + n] = _str[end];
end--;
}
// 插入数据
for (size_t i = 0; i < n; i++)
{
_str[pos + i] = x;
}
_size += n;
}
private:
char* _str; // 动态字符数组
size_t _size; // 字符个数
size_t _capacity; // 容量
static const size_t npos;
};
const size_t string::npos = -1;
}
需要注意的是:静态成员需要在类外定义
【测试结果】
- 在
pos
位置插入字符串
思路和以上类似,这里就不过多赘述了
void insert(size_t pos, const char* str)
{
// 判断下标pos的合法性
assert(pos <= _size);
// 可能存在扩容
size_t length = strlen(str);
if (_size + length > _capacity)
{
reserve(_size + length);
}
// 挪动数据
size_t end = _size;
while (end >= pos && end != npos)
{
_str[end + length] = _str[end];
end--;
}
// 插入数据
for (size_t i = 0; i < length; i++)
{
_str[pos + i] = str[i];
}
_size += length;
}
【测试结果】
八、删除操作erase
【思路】
- 首先要检查下标的合法性
- 删除要分情况讨论:
第一种:当前下标往后的字符全都需要删除
第二种:删除的字符是符合范围内的
string& erase(size_t pos = 0, size_t len = npos)
{
// 检查下标合法性
assert(pos >= 0 && pos < _size);
// pos位置(包括pos)后面全部删除的情况
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
// 删一部分的情况
else
{
size_t end = pos + len;
while (end <= _size)
{
_str[pos] = _str[end];
pos++;
end++;
}
_size -= len;
}
return *this;
}
【测试结果】
九、查找操作find
- 查找字符
// 从下标pos开始查找字符,如果实参不传第二个参数,默认从下标为0开始查找
size_t find(char x, size_t pos = 0) const
{
// 检查查找下标的合法性
assert(pos >= 0 && pos < _size);
// 遍历查找即可
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == x)
{
// 找到返回下标
return i;
}
}
// 没找到默认返回npos,其实就是-1
return npos;
}
【测试结果】
- 查找字符串
查找子串可以有很多方法,最简便就是使用C语言中的strstr
函数
size_t find(const char* str, size_t pos = 0) const
{
// 检查查找下标的合法性
assert(pos >= 0 && pos < _size);
//strstr(const char * str1, const char * str2) - 从str1中查找str2
const char* ptr = strstr(_str + pos, str);
// 如果ptr为空说明没找到
if (ptr == nullptr)
{
// 找不到返回npos
return npos;
}
// 否则找到了
else
{
return ptr - _str;
}
}
【测试结果】
十、截取substr
// pos - 截取的下标
// len - 截取的长度
// npos - 截取的最大长度
string substr(size_t pos = 0, size_t len = npos) const
{
// 判断下标的合法性
assert(pos >= 0 && pos < _size);
// 分两种情况,
// 1. 可能需要截到尾
// 2. 可能需要截取一部分
size_t n = len;
// 截取到尾的情况
if (len == npos || pos + len >= _size)
{
// 左闭右开
n = _size - pos;
}
// 截取一部分情况
else
{
string tmp;
tmp.reserve(n);
for (size_t i = pos; i < pos + n; i++)
{
// 将截取的字符全部放去tmp
tmp += _str[i];
}
}
return tmp;
}
十一、改变字符串的有效个数resize
三种情况:
- 当
n
小于size
,相当于删除数据,保留n
个字符- 当
n
等于size
,则保留原数据- 当
n
大于size
,则会扩容,同时后面会补充n - size
个字符(不指定默认是'\0'
)
void resize(size_t n, char ch = '\0')
{
// 相当于删除数据,只保留前n个字符
if (n < _size)
{
_size = n;
_str[_size] = '\0';
}
else // 否则相当于插入数据
{
// 可能有扩容情况,n比_capacity大才会扩容,n=_capacity什么也不发生
reserve(n);
for (size_t i = _size; i < n; i++) // 填数据
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
}
十二、流插入<<
成员函数默认第一个形参都是对象的地址,也就是隐藏的this
指针。由于cout
抢占了对象的第一个位置,因此不能当做成员函数,就只能写在类外。这里和以往实现日期类不同的是不需要在类中定义成友元(没有访问类的私有成员)
// 注意:必须引用返回,不然会报错。因为ostream这个类做了一个防拷贝
ostream& operator<<(ostream& out, const string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
总结流插入和c_str
的差别
-
c_str
返回的是C形式的字符串,遇到'\0'
就不打印 - 流插入打印是不管
'\0'
,有多少字符就打印多少字符
十三、流提取>>
istream& operator>>(istream& in, string& s)
{
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;// += 自己就会扩容
ch = in.get();
}
return in;
}
注意:不能用in
(本质上就是cin
)直接读取,因为当我们输入完字符后,都是以换行结束,但in
默认读取到空格或者换行就不会往下读了(只有getline
可以)。istream
类里有get
接口可以读取空格和换行
但以上代码还是不够完善,当多次对一个对象输入的情况,要对之前形成一次覆盖,可以对比库里的string
自己实现的上一次输入的数据没有清理干净。因此要清理上次的内容
istream& operator>>(istream& in, string& s)
{
// 清理
s.clear();
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;// += 自己就会扩容
ch = in.get();
}
return in;
}
【测试结果】
还有一个问题,当一开始输入连续空格或者换行时,可以对比自己实现的和库里的:
因此要过滤前面的空格或者换行
istream& operator>>(istream& in, string& s)
{
// 清理
s.clear();
char ch = in.get();
// 处理前缓冲区前面的空格或者换行
while (ch == ' ' || ch == '\n')
{
ch = in.get();
}
while (ch != ' ' && ch != '\n')
{
s += ch;// += 自己就会扩容
ch = in.get();
}
return in;
}
【测试结果】
但是以上代码的性能不够好,当输入好几个字符时,由于+=
就会不断进行扩容,就会有损耗。
优化思路:提前开128个空间的字符数组,减少扩容的消耗
istream &operator>>(istream &in, string &s)
{
s.clear();
char ch = in.get();
// 处理前缓冲区前面的空格或者换行
while (ch == ' ' || ch == '\n')
{
ch = in.get();
}
// 开128个空间的字符数组
char buff[128];
int i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
// 当i为127,就要给'\0'预留一个空间
if (i == 127)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
// 处理循环结束后如果i不为0,
if (i)
{
buff[i] = '\0';
s += buff;
}
return in;
}
十四、清空操作clear
直接将第一个字符改成'\0'
即可
void clear()
{
_str[0] = '\0';
_size = 0;
}
十五、比较操作
15.1 <
// s1 < s2
bool operator<(const string& s) const
{
// 以短的字符串为基础来比较
int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
// 如果ret == 0
// 说明以短的字符串为基础比较的字符对应位置都是相等
// 则长的字符串大
if (ret == 0)
return _size < s._size;
// 否则
// ret < 0说明s1 < s2为真
// ret>0,则s1>s2为假
else // ret != 0
return ret < 0;
}
15.2 ==
bool operator==(const string& s) const
{
return _size == s._size
&& memcmp(_str, s._str, _size) == 0;
}
当写完<
和==
,剩下的代码就可以复用了。
15.3 <=
bool operator<=(const string& s) const
{
return *this < s || *this == s;
}
15.4 >
bool operator>(const string& s) const
{
return !(*this <= s);
}
15.5 >=
bool operator>=(const string& s) const
{
return !(*this < s);
}
15.6 !=
bool operator!=(const string& s) const
{
return !(*this == s);
}
十六、交换操作swap
// 16. swap
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_capacity, s._capacity);
std::swap(_size, s._size);
}
十七、赋值运算符重载=
默认不写是以值的方式逐字节拷贝(浅拷贝/值拷贝),因此内置类型成员变量是直接赋值的,而自定义类型成员会去调用它的默认构造函数。但要注意动态开辟的成员变量。如果不写深拷贝,两个对象会同时指向动态开辟的空间,就会导致析构两次的问题。
法一:传统写法:
// s1 = s2
// s1是隐藏的this指针
// 方法:
// 首先开一个和s2同样大的空间并且把s2的数据拷贝
// 然后再释放掉s1指向的空间,最后再让s1指向新拷贝的那个空间
string& operator=(const string& s)
{
if (this != &s)
{
// 首先开一个和s2同样大的空间并且把s2的数据拷贝
char* tmp = new char[s._capacity + 1];
memcpy(tmp, s._str, s._size + 1);
// 然后再释放掉s1指向的空间
delete[] _str;
// 最后再让s1指向新拷贝的那个空间
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
法二:现代写法文章来源:https://www.toymoban.com/news/detail-654062.html
// 方法:
// 用s2拷贝构造tmp的对象,然后再让tmp和s1交换
string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s);
swap(tmp);
}
return *this;
}
法二延伸:文章来源地址https://www.toymoban.com/news/detail-654062.html
//s1 = s2
// 传参时,s2调用拷贝构造给tmp(深拷贝),然后再和s1交换
// 交换的是深拷贝的对象
string& operator=(string tmp)
{
swap(tmp);
return *this;
}
十八、源码string.h
#pragma once
#include <assert.h>
namespace wj
{
class string
{
public:
string(const char* str)
:_str(new char[strlen(str) + 1])
, _size(strlen(str))
, _capacity(_size)
{
memcpy(_str, str, _size + 1);
}
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
const char* c_str() const
{
return _str;
}
string()
:_str(new char[1])
, _size(0)
, _capacity(0)
{
_str[0] = '\0';
}
size_t size() const
{
return _size;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
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;
}
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;
}
}
void push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
}
void append(const char* s)
{
size_t length = strlen(s);
if (_size + length > _capacity)
{
reserve(_size + length);
}
memcpy(_str + _size, s, length + 1);
_size += length;
}
string& operator+=(char x)
{
push_back(x);
return *this;
}
string& operator+=(const char* s)
{
append(s);
return *this;
}
string& insert(size_t pos, int n, char x) // 函数重载
{
assert(pos <= _size);
if (_size + n > _capacity)
{
reserve(_size + n);
}
size_t end = _size;
while (end >= pos && end != npos)
{
_str[end + n] = _str[end];
end--;
}
for (size_t i = 0; i < n; i++)
{
_str[pos + i] = x;
}
_size += n;
return *this;
}
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t length = strlen(str);
if (_size + length > _capacity)
{
reserve(_size + length);
}
size_t end = _size;
while (end >= pos && end != npos)
{
_str[end + length] = _str[end];
end--;
}
for (size_t i = 0; i < length; i++)
{
_str[pos + i] = str[i];
}
_size += length;
return *this;
}
string& erase(size_t pos, size_t len = npos)
{
assert(pos <= _size);
if (len == npos || len + pos >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
size_t end = pos + len;
while (end <= _size)
{
_str[pos] = _str[end];
pos++;
end++;
}
_size -= len;
}
return *this;
}
size_t find(char x, size_t pos = 0)
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == x)
{
return i;
}
}
return npos;
}
size_t find(const char* str, size_t pos = 0)
{
assert(pos < _size);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _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 < pos + n; i++)
{
tmp += _str[i];
}
return tmp;
}
string(const string& s)
{
// 深拷贝
_str = new char[s._capacity + 1];
memcpy(_str, s._str, s.size() + 1);
_size = s._size;
_capacity = s._capacity;
}
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';
}
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
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;
}
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);
}
bool operator!=(const string& s) const
{
return !(*this == s);
}
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_capacity, s._capacity);
std::swap(_size, s._size);
}
// 法一(传统写法):
/*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;
}*/
// 法二的延伸
//s1 = s2
// 传参时,s2调用拷贝构造给tmp(深拷贝),然后再和s1交换
/*
string& operator=(string tmp)
{
swap(tmp);
return *this;
}
*/
private:
char* _str;
size_t _size;
size_t _capacity;
static size_t npos;
};
size_t string::npos = -1;
ostream& operator<<(ostream& out, const string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch = in.get();
while (ch == ' ' || ch == '\n')
{
ch = in.get();
}
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
// 优化版
/*
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;
// 当i为127,就要给'\0'预留一个空间
if (i == 127)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
// 处理循环结束后如果i不为0,
if (i)
{
buff[i] = '\0';
s += buff;
}
return in;
}
*/
}
到了这里,关于【C++初阶】模拟实现string的常见操作的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!