引言:
在 C++ 中,标准库提供了强大的 std::string 类,它封装了字符串的大部分操作,使得使用字符串变得非常方便。尝试自己实现 string 类,深入理解字符串的底层实现机制,不仅可以帮助我们更好地使用 std::string,还能帮助我们深入理解动态内存管理、掌握深拷贝和深赋值的概念、提升对字符串操作的理解。
一、类的设计与接口
1、命名空间的使用
使用
namespace bit避免与标准库string类冲突
2、类的成员变量
char* _str:动态分配的字符数组,用于存储字符串
size_t _size:当前字符串的长度
size_t _capacity:分配的内存容量
3、默认成员函数(构造函数、析构函数、拷贝构造函数、赋值重载函数)
构造函数:
默认构造函数:初始化为空字符串
带参构造函数:接受
const char*,动态分配内存并复制内容
析构函数:释放动态分配的内存
拷贝构造函数:实现深拷贝
赋值重载:实现深赋值,防止自赋值
4、遍历:下标+[]、迭代器支持
重载[ ]以提供下标+[]形式的遍历
提供
iterator和const_iterator支持迭代器
5、其他接口(增删查、常用运算符重载)
reverse:预留内存
push_back、append、+=:添加字符或字符串
insert、erase:插入和删除操作
find:查找字符或子串
substr:获取子字符串
c_str、size、clear:辅助功能
常用运算符重载
二、 实现细节
1、文件创建
创建string.h文件和string.cpp文件实现声明与定义的分离,同时使用命名空间,避免与标准库中的string类冲突
string.h和string.cpp内容的大致结构
//string.h内容
#include<iostream>
#include<assert.h>
using namespace std;
//使用命名空间封装避免和库里的冲突
namespace bit
{
class string
{
public:
//各类接口声明...
private:
char* _str;
size_t _size;
size_t _capacity;
}
从上面的声明可以看出,string类的底层是一个和顺序表极其类似的结构,或者说字符串本质上是一种特殊的顺序表,其中的元素是字符
三个成员变量:char* _str size_t _size size_t _capacity
char* _str :指向字符数组的指针,存储实际的字符串数据,是一个动态分配的内存区域,用于存储字符串的内容。通过它可以动态分配内存,可根据需要调整大小。但是需要手动管理内存分配和释放,例如在构造函数、析构函数和拷贝构造函数中进行操作
size_t _size:记录当前字符串的实际长度(不包括末尾的空字符\0),它也是动态的,随着字符串内容的改变而改变。用于快速获取字符串长度,避免每次调用长度函数进行计算
size_t _capacity:记录分配给_str的内存容量(以字符为单位),_capapcity表示分配的内存可以容纳的字符总数(包括末尾的\0)。它用于优化内存分配,避免频繁的内存重新分配,以及判断是否需要重新分配更大的内存
//string.cpp
#include"string.h"
namespace bit
{
//接口的具体实现
}
我们将实现较为复杂的接口,在上面的.cpp文件中实现,而较短、较简单的函数则在.h文件直接实现定义
2、具体实现
2.1 默认成员函数实现
先来实现默认成员函数:构造函数、析构函数、拷贝构造函数、赋值运算符重载
在string.h文件中加上三个函数的声明:
//构造函数
string(const char* str = "");
//析构函数
~string();
//拷贝构造函数
string(const string& s);
//赋值运算符重载
string& operator=(const string& s);
在string.cpp文件中对四个函数进行具体实现:
1 构造函数:
声明:
//构造函数
string(const char* str = "");
解释:构造函数是用来初始化对象的,对于构造函数来说,一般需要实现两类:带参构造、无参构造,因为初始化对象时,有时会给初始值有时不会,所以在声明时我们给出了缺省值"",也就是const char* str = "",这样如果用户没有给初始值,会自动初始化为"",也就是空
实现:
//未优化(普通)版,使用初始化列表
string(const char*str)
:_str(new char[strlen(str)+1])
:_size(strlen(str))
:_capacity(strlen(str))
{}
在写构造函数时,初始化成员变量除了使用函数体内赋值,还可以使用初始化列表的方式,我们尽量使用初始化列表的方式,因为那些你不在初始化列表初始化的成员也会走初始化列表;但是上方的写法不好,因为strlen是一个O(N)的函数,上面一下就使用了三个O(N),这会对函数有着极大的效率影响,所以将其优化成了下面的版本⬇
//构造函数
string::string(const char* str)
:_size(strlen(str))
{
_capacity = _size;
_str = new char[_size + 1];//+1给\0预留位置
strcpy(_str, str);
}
解释:为了改善strlen带来的效率问题,我们使用了初始化列表,只调用strlen一次,获取string的长度,将其初始化赋值给_size,再在函数体内利用_size的值初始化_capacity和_str,在给_str开辟空间时,记得多初始化一个空间给\0留位置,最后用strcpy函数将值拷贝进_str的空间
2 析构函数
声明:
//析构函数
~string();
实现:
string::~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
因为我们定义的结构是有动态分配内存的,所以在结束时也需要析构函数来释放空间
3 拷贝构造函数
声明:
//拷贝构造函数
string(const string& s);
注意:拷贝构造函数的第一个参数必须是该类型的引用
实现:
string::string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
我们需要先拷贝一块和被拷贝者同样大小的空间即_str = new char[s._capacity + 1],再拷贝相同的内容,这样拷贝后的_str指向的是一个独立的内存空间,而不是两者指向同一块空间,即深拷贝
不能是浅拷贝,必须完成深拷贝,也就是拷贝完后拷贝方和被拷贝方指向的是两个不同的空间,需要重新开辟一块同样大小的空间,再将内容拷贝给新的空间


4 赋值运算符重载
声明:
//赋值运算符重载
string& operator=(const string& s);
注意:返回值返回的是引用
实现:
string& string::operator=(const string& s)
{
//避免自己给自己赋值的情况
if (this != &s)
{
//释放原来的空间
delete[]_str;
//开辟同样大小的空间
_str = new char[s._capacity + 1];//多开一个预留给\0
strcpy(_str, s._str);
}
return *this;
}
因为赋值时会先释放自己原来的空间,如果出现自己给自己赋值的情况,因为自己原来的空间被释放了,那就没办法获取到值给自己赋值,所以会先判断是否是给自己赋值
和拷贝构造一样,赋值运算符也必须是深赋值,即两方指向两块不同的空间
2.2 遍历的实现
1 下标+[ ]形式的遍历
因为函数内容较短,我们能就直接实现在声明文件(string.h)中
//[]的普通版本,返回的是引用
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];
}
注意:有时候只需要遍历不需要修改值时就要使用const
先判断访问的下标是否合适,再通过指针解引用的操作返回该下标查找的值
2 迭代器形式的遍历
先使用using语法定义类型别名,
-
iterator:指向char的指针,可以用于读写操作 -
const_iterator:指向const char的指针,只能用于读取操作
//需要先定义迭代器类型
using iterator = char*;//迭代器
using const_iterator = const char*;//const迭代器
//用原身指针返回迭代器
//返回开始位置
iterator begin()
{
return _str;
}
//返回最后一个位置
iterator end()
{
return _str + _size;
}
//const版本
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
注意const版本的写法为:const_iterator begin() const ,因为指向的内容不可以修改,不是迭代器不能修改,迭代器要++,所以不是这样声明const iterator begin() const
2.3 增删查改的实现
1 增(插入)
声明:
//插入部分
void reserve(size_t n);//预留内存函数
void push_back(char ch);
void append(const char* str);
string& operator+=(char ch);
string& operator+=(const char* str);
void insert(size_t pos, char ch);
void insert(size_t pos, const char* str);
实现:
reserve:
插入时会涉及空间不够的问题,也就是需要扩容,这里我们通过实现reserve函数来进行扩容,reserve函数的作用就是通过传递空间大小参数n来让其开设n大小的空间,如果n大于现有的空间大小,就进行扩容,然后将原来空间的值拷贝到新的空间,再将原来的空间进行释放,将新的空间赋值给对象
//借助reserve来扩容
void string::reserve(size_t n)
{
if (n > _capacity)
{
//记住:开空间一定要多开一个
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
push_back 和 append:
push_back用来插入单个字符,append用来插入长的字符串,注意两者只能尾插
//插入单个字符
void string::push_back(char ch)
{
//空间满了就扩容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;//从尾部插入ch
_size++;//长度++
}
//插入字符串
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t newCapacity = 2 * _capacity;
// 扩2倍不够,则需要多少扩多少
if (newCapacity < _size + len)
newCapacity = _size + len;
reserve(newCapacity);
}
//拷贝
strcpy(_str + _size, str);//在末尾将字符串的内容拷贝进来
_size += len;
}
重载+=运算符:
在实现+=时,可以直接复用push_back 和 append,同样+=也有插入字符和字符串两种情况
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
insert:实现任意位置的插入
insert插入字符:
定义end指向\0的下一个位置,在end走到pos(待插入的位置)之前,将end-1位置上的数据移动到end的位置上,最后在pos位置插入需要插入的字符ch

//pos位置插入一个字符
void string::insert(size_t pos, char ch)
{
//判断插入位置是符合法
assert(pos <= _size);
//空间不够插入就扩容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
//让end指向\0的下一个位置
size_t end = _size + 1;
while (end>pos)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = ch;
_size++;
}
insert插入字符串:
定义end到插入字符串后,字符串最末尾的位置

void string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t newCapacity = 2 * _capacity;
// 扩2倍不够,则需要多少扩多少
if (newCapacity < _size + len)
newCapacity = _size + len;
reserve(newCapacity);
}
//定义到+上插入字符串后最末尾的位置
size_t end = _size + len;
while (end > pos+len-1)
{
_str[end] = _str[end-len];
--end;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size+=len;
}
2 删
声明:
erase:在pos位置删除len个字符
void erase(size_t pos, size_t len=npos);
注:len给了一个缺省参数npos,它在C++的std::string类里是一个特殊的常量,用来表示“无效位置”或者“最大可能位置”,这里使用npos作为缺省参数表示,如果用户没有键入第二个参数,就将从pos位置开始往后的所有内容全部删除
所以在我们自定义的类中还应该加上npos的声明定义:
//string.h
public:
static const size_t npos;
//string.cpp
const size_t string::npos=-1;
实现:
会有两种情况:
①删除的部分大于整个字符串的长度

②只删除字符串内的一部分

void string::erase(size_t pos, size_t len)
{
assert(pos < _size);
//删除部分大于原来的长度
if (len >= _size - pos)
{
_str[pos] = '\0';
_size = pos;
}else
{ //只删除一部分
size_t end = pos + len;
while (end<=_size)
{
//end的值从后往前挪
_str[end - len] = _str[end];
++end;
}
_size -= len;
}
}
3 查
声明:
find:在pos位置查找想要的内容
size_t find(char ch, size_t pos = 0);//从pos位置查找字符
size_t find(const char* str, size_t pos=0);//从pos位置查找子串
pos都默认给了缺省值0,当用户没有键入第二个参数时,默认从开头的位置开始找
实现:
//查找字符
size_t string::find(char ch, size_t pos)
{
assert(pos < _size);
//要从pos位置遍历找
for (size_t i = pos; i < _size; i++)
{
if (ch == _str[i])
{
return i;
}
}
return npos;
}
//查找子串
size_t string::find(const char* str, size_t pos)
{
assert(pos < _size);
//返回str在_str+pos第一次出现的位置
const char* ptr=strstr(_str+pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
//返回索引值
return ptr - _str;
}
}
4 改
因为“改”通过拷贝构造函数和赋值运算符重载或者遍历改值就可以,直接看前面的实现即可
2.4 运算符重载
声明:
bool operator==(const string& lhs, const string& rhs);
bool operator!= (const string& lhs, const string& rhs);
bool operator> (const string& lhs, const string& rhs);
bool operator< (const string& lhs, const string& rhs);
bool operator>= (const string& lhs, const string& rhs);
bool operator<= (const string& lhs, const string& rhs);
注:上面的运算符重载都定义为全局函数,非成员函数,因为如果设计为成员函数会固定第一个参数的类型
在实现之前我们需要先实现一个辅助功能c_str,它的功能是返回C格式的字符串,作用是提供了一种将C++的std::string对象转换为C语言风格字符串的机制,从而使得std::string可以与C语言库函数无缝兼容,也即是让C++的string对象可以使用C语言库中的函数
const char* c_str() const
{
return _str;
}
实现:
bool operator== (const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) == 0;
}
bool operator!= (const string& lhs, const string& rhs)
{
return !(lhs == rhs);
}
bool operator> (const string& lhs, const string& rhs)
{
return !(lhs <= rhs);
}
bool operator< (const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) < 0;
}
bool operator>= (const string& lhs, const string& rhs)
{
return !(lhs < rhs);
}
bool operator<= (const string& lhs, const string& rhs)
{
return lhs < rhs || lhs == rhs;
}
在函数实现中我们使用了C语言的strcmp函数,所以需要使用c_str将其转换为C语言格式的字符串,同时我们又使用了复用的方式,将已实现的函数进行复用
2.4 其他接口+辅助功能
1 substr
功能:从字符串中提取一个子字符串,从pos位置提取长度为len的子字符串
声明:
string substr(size_t pos, size_t len=npos);
实现:
string string::substr(size_t pos, size_t len)
{
//查找的子串长度大于可查找(剩余)的部分
//则直接取到结尾
if (len > (_size - pos))
{
//更新子串长度
len = _size - pos;
}
bit::string sub;
sub.reserve(len);
for (size_t i = 0; i < len; i++)
{
sub += _str[pos + i];
}
return sub;
}
2 流插入和流提取重载
为了我们实现的string类可以在屏幕上进行输入和打印,还必须实现流插入和流提取符号的重载
声明
ostream& operator<<(ostream& os, const string& str);
istream& operator>>(istream& is, string& str);
is和os对象都必须传引用,不能传值,两者区别在于ostream要加const
实现:
ostream& operator<<(ostream& os, const string& str)
{
for (size_t i = 0; i < str.size(); i++)
{
os << str[i];
}
return os;
}
流提取:
重载的输入流提取运算符>>,用于从输入流istream中读取一个字符串,并将其存储到自定义的string对象str中
-
输入:
-
istream& is:输入流对象,通常是一个std::cin或文件输入流 -
string& str:引用到一个std::string对象,用于存储读取到的字符串
-
-
输出:
-
返回值是
istream&,即输入流的引用,这使得可以链式调用提取运算符
-
在实现流提取前,需要先实现另一个辅助功能clear,作用是将内容清空
void clear()
{
_str[0] = '\0';
_size = 0;
}
istream& operator>>(istream& is, string& str)
{
//把数据清掉,不出现提取的结果是+在原来字符尾巴后面的情况
str.clear();
//一次开够,空间浪费,整一个buff
int i = 0;
char buff[256];
char ch;
ch = is.get();//为了可以读取空格
while (ch != ' ' && ch != '\n')//遇到空格或者换行就停止提取
{
//放到buff,减少扩容,即用即销毁
buff[i++] = ch;
if (i == 255)
{
buff[i] = '\0';
str += buff;
i = 0;
}
}
if (i > 0)
{
buff[i] = '\0';
str += buff;
}
return is;
}
实现逻辑:
在读取新字符串之前,先使用str.clear();清空str的内容,确保不会将新读取的内容追加到旧内容的末尾。再定义一个缓冲区buff,大小为256个字符,用于临时存储读取的字符。使用i作为缓冲区的索引,记录当前缓冲区中存储的字符数量。
使用is.get()从输入流中逐字符读取,而不是is >> ch。is.get()会读取所有字符,包括空格和换行符,而is >> ch会跳过空格。循环读取字符,直到遇到空格或换行符为止。每次读取的字符存储到缓冲区buff中。如果缓冲区满了(i == 255),将缓冲区的内容追加到目标字符串str中,并重置缓冲区索引i。如果循环结束时缓冲区中还有未处理的字符(i > 0),将这些字符追加到目标字符串str中。
使用将缓冲区的内容追加到目标字符串str中的方式可以一次性扩容,减少了扩容带来的效率问题
三、完整展示
string.h文件:
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
//使用命名空间封装避免和库里的冲突
namespace bit
{
class string
{
public:
//typedef char* iterator;
using iterator = char*;//迭代器
using const_iterator = const char*;//const迭代器
//构造函数:不能初始化为空和0,防止空指针访问的问题
//带参构造函数,最好是有全缺省的省去无参构造函数
string(const char* str = "");//直接这样初始化就行,常量字符串默认给\0
//析构函数
~string();
//拷贝构造函数
string(const string& s);
//赋值运算符重载
string& operator=(const string& s);
//插入部分
void reserve(size_t n);
void push_back(char ch);
void append(const char* str);
string& operator+=(char ch);
string& operator+=(const char* str);
void insert(size_t pos, char ch);
void insert(size_t pos, const char* str);
void erase(size_t pos, size_t len=npos);
//查找
size_t find(char ch, size_t pos = 0);
size_t find(const char* str, size_t pos=0);
//遍历部分
//[]的普通版本,返回的是引用
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];
}
//用原身指针返回迭代器
//返回开始位置
iterator begin()
{
return _str;
}
//返回最后一个位置
iterator end()
{
return _str + _size;
}
//指向的内容不可以修改,不是迭代器不能修改,迭代器要++
//所以不是这样声明const iterator begin() const
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
//返回大小
size_t size() const
{
return _size;
}
//短的就直接写在声明里
const char* c_str() const
{
return _str;
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
string substr(size_t pos, size_t len=npos);
private:
char* _str;
size_t _size;
size_t _capacity;
public:
static const size_t npos;
};
//全局函数
bool operator==(const string& lhs, const string& rhs);
bool operator!= (const string& lhs, const string& rhs);
bool operator> (const string& lhs, const string& rhs);
bool operator< (const string& lhs, const string& rhs);
bool operator>= (const string& lhs, const string& rhs);
bool operator<= (const string& lhs, const string& rhs);
//流插入和流提取
ostream& operator<<(ostream& os, const string& str);
istream& operator>>(istream& is, string& str);
}
string.cpp文件
#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
namespace bit
{
const size_t string::npos=-1;
//有参,全缺省,缺省只能在声明写
string::string(const char* str)
:_size(strlen(str))
{
_capacity = _size;
_str = new char[_size + 1];//+1给\0预留位置
strcpy(_str, str);
}
string::~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
//拷贝构造
string::string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
// s1=s2=s3
// s1=s3,s就是s3
string& string::operator=(const string& s)
{
//避免自己给自己赋值的情况
if (this != &s)
{
//释放原来的空间
delete[]_str;
//开辟同样大小的空间
_str = new char[s._capacity + 1];//多开一个预留给\0
strcpy(_str, s._str);
}
return *this;
}
//借助reserve来扩容
void string::reserve(size_t n)
{
if (n > _capacity)
{
//记住:开空间一定要多开一个
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void string::push_back(char ch)
{
//满了
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
//insert写好,push_back是可以复用的,相当于不用写push_back
//insert(_size, ch);
}
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t newCapacity = 2 * _capacity;
// 扩2倍不够,则需要多少扩多少
if (newCapacity < _size + len)
newCapacity = _size + len;
reserve(newCapacity);
}
//拷贝
strcpy(_str + _size, str);
_size += len;
}
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
//pos位置插入一个字符
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
size_t end = _size + 1;
while (end>pos)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = ch;
_size++;
}
//插入字符串
void string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t newCapacity = 2 * _capacity;
// 扩2倍不够,则需要多少扩多少
if (newCapacity < _size + len)
newCapacity = _size + len;
reserve(newCapacity);
}
size_t end = _size + len;
while (end > pos+len-1)
{
_str[end] = _str[end-len];
--end;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size+=len;
}
//从pos位置删除len个字符
void string::erase(size_t pos, size_t len)
{
assert(pos < _size);
if (len >= _size - pos)
{
_str[pos] = '\0';
_size = pos;
}else{ //只删除一部分
size_t end = pos + len;
while (end<=_size)
{
//end的值从后往前挪
_str[end - len] = _str[end];
++end;
}
_size -= len;
}
}
//查找字符
size_t string::find(char ch, size_t pos)
{
assert(pos < _size);
//要从pos位置开始找
for (size_t i = pos; i < _size; i++)
{
if (ch == _str[i])
{
return i;
}
}
return npos;
}
//查找子串
size_t string::find(const char* str, size_t pos)
{
assert(pos < _size);
//返回str在_str+pos第一次出现的位置
const char* ptr=strstr(_str+pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
string string::substr(size_t pos, size_t len)
{
//查找的子串长度大于可查找(剩余)的部分
//则直接取到结尾
if (len > (_size - pos))
{
//更新子串长度
len = _size - pos;
}
bit::string sub;
sub.reserve(len);
for (size_t i = 0; i < len; i++)
{
sub += _str[pos + i];
}
//cout << sub.c_str() << endl;
return sub;
}
////////////////////////////////////////////////////////////////////
bool operator== (const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) == 0;
}
bool operator!= (const string& lhs, const string& rhs)
{
return !(lhs == rhs);
}
bool operator> (const string& lhs, const string& rhs)
{
return !(lhs <= rhs);
}
bool operator< (const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) < 0;
}
bool operator>= (const string& lhs, const string& rhs)
{
return !(lhs < rhs);
}
bool operator<= (const string& lhs, const string& rhs)
{
return lhs < rhs || lhs == rhs;
}
ostream& operator<<(ostream& os, const string& str)
{
//可以自定义打出来的格式
//os<<'"';
//os << "xx\"xx";
for (size_t i = 0; i < str.size(); i++)
{
os << str[i];
}
//os << '"';
return os;
}
istream& operator>>(istream& is, string& str)
{
//把数据清掉
//不出现提取的结果是+在原来字符尾巴后面的情况
str.clear();
//一次开够,空间浪费
//str.reserve(1024);
//整一个buff
int i = 0;
char buff[256];
char ch;
//is >> ch;//默认读到空格就丢弃,就会拿不到字符串的空格
ch = is.get();//这样就会读取到空格
while (ch != ' ' && ch != '\n')//遇到空格或者换行就停止提取
{
/*str += ch;
ch = is.get();*/
//放到buff,减少扩容,即用即销毁
buff[i++] = ch;
if (i == 255)
{
buff[i] = '\0';
str += buff;
i = 0;
}
}
if (i > 0)
{
buff[i] = '\0';
str += buff;
}
return is;
}
450

被折叠的 条评论
为什么被折叠?



