本篇中,将会详细的介绍 Cpp 中 string 的使用,以及 string 类常用函数的模拟实现。对于 string 的内置函数来说,存在很多很冗余的用法,很多函数都有很多种用法,本篇将会讲解常用内置函数的常用用法,模拟函数的时候,也只是模拟常用的用法代码。本篇实现的 string ,仅仅只是通过一种很简单的方式实现的,对于 vs 、g++ 下的 string 相比都是小巫见大巫。
本篇的函数讲解顺序按照 cplusplus 官网的顺序,网站:string - C++ Reference (cplusplus.com)
目录如下:
目录
1. iterator —— 迭代器
首先我们第一个要讲解的就是我们的迭代器,在string中存在许多的迭代器,但是我们常用的就是:
因此,我们主要围绕这四个迭代器来实现,先讲解其功能,对于 begin 和 end 来说:
其主要的作用为:begin:返回 string 串中的第一个元素的位置,end:返回 string 串中的最后一个元素的位置。对于 rbegin 和 rend 来说则是相反的,rbegin 返回逆向串的第一个字符,也就返回正向串的最后一个字符,rend 返回逆向串的最后一个字符,也就是返回正向串的第一个元素,所以从功能上来说,begin == rend,rbegin == end,其使用如下:
如图所示,一个为正向打印,一个为逆向打印,根据以上实现的功能,模拟出的代码为:
namespace MyString { class string { public: typedef char* iterator; typedef const char* const_iterator; typedef char* reverse_iterator; typedef const char* const_reverse_iterator; iterator begin() { return _str; } const_iterator begin() const { return _str; } iterator end() { return _str + _size; } const_iterator end() const { return _str + _size; } reverse_iterator rbegin() { return _str + _size; } const_reverse_iterator rbegin() const { return _str + _size; } reverse_iterator rend() { return _str; } const_reverse_iterator rend() const { return _str; } private: char* _str; size_t _size; size_t _capacity; }; }
对于如上的代码,我们同样的函数还需要写出两个版本,一个普通版本,一个由 const 修饰的版本,因为对于由 const 修饰的 string 并不能调用普通版本的函数,所以我们需要在写一个由 const 修饰的版本。另外,实现一个属于自己的类,需要使用命名空间封装,否则会与库中自带的 string 相矛盾。
当然,对于以上的实现,我们更多是基于功能实现的,对于不同的编译器的实现方式存在不同,而且对于不同的类的迭代器实现方式也不同,我们实现出的函数是以简单的方式实现的。
2. capacity —— 容量
接下来我们将实现与容量相关的类函数,仍然存在很多函数,但是我们只需要掌握使用以下由红框框住的代码即可:
首先讲解的是最为简单的两个类函数,分别是 size 和 capacity,只需要返回串的尺寸和容量(当前可以容纳多少字符的量),因为 _size 和 _capacity 被 private 封装,不能直接访问,所以我们需要使用函数来获取,在函数中,我们只需要返回 _size 和 _capacity 的值即可。其实 length 和 size 的功能是相同的,返回的都是 _size ,但是最常用的还是 size 函数。
接下来需要掌握的为 clear 和 empty 函数,clear 的功能为:将串的内容清楚,使串变成空串,所以具体实现,我们只需要将串中第 0 个位置的字符置为 '\0' 即可;对于 empty 函数,功能为,判断当前串是否为空串,实现起来仍然轻松,只需要返回 _size 是否 等于 0 即可。
接下的重点就为 resize 和 reserve 函数了,对于这两个函数的解释如下:
首先是 reserve 函数,该函数的作用为:将字符串的容量改为 n ,若当前容量已经大于 n 则不需要修改,因为不会将其缩容,只有当 n 大于当前容量时,才会扩容。
对于 resize 函数,功能为:将字符串的长度修改为长度 n ,但是像比于 reserve 函数,resize 函数可以将其长度缩小,当 n 小于 _size 的时候,就减小长度,大于 _size 的时候就增长长度。并且由图中可以看到,该函数存在两个重载函数,其中一个为,n 大于 _size 之后,在后面追加字符 c。我们在实现的时候,将其改为一个函数,具体的实现如下:
namespace MyString { class string { public: const size_t size() const { return _size; } const size_t capacity() const { return _capacity; } bool empty() const { return _size == 0; } void clear() { _str[0] = '\0'; _size = 0; } // 对数据进行扩容 void reserve(size_t n) { // 仅仅 n 大于 capacity 的时候,我们才进行扩容 if (n > _capacity) { // 每一次分配看见,需要多分配一个,给 '\0'腾一个空间 char* tmp = new char[n + 1]; // 将原字符串拷贝后删除 strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } } // 将resize函数改为半缺省函数,不提供字符c的时候,自动在后面补'\0' void resize(size_t n, char c = '\0') { if (n <= _size) { _str[n] = '\0'; _size = n; } else { reserve(n); for (int i = _size; i < n; i++) _str[i] = c; _size = n; _str[_size] = '\0'; } } private: char* _str; size_t _size; size_t _capacity; }; }
3. Element access —— 访问成员
接下来我们将完成的函数为可以访问到串中元素的函数,如下:
上图中有四个函数,但是我们我们经常使用用的函数为第一个运算符重载函数。使用该函数可以向访问数组内容一样访问串中的元素,并且还可以对串中的元素进行修改,对于其实现如下:
// []运算符重载 const char& operator[](size_t pos) const { // 判断获取的位置是否合法 assert(pos < _size); return _str[pos]; } char& operator[](size_t pos) { assert(pos < _size); return _str[pos]; }
对于以上函数,我们仍然重载了两个,一个普通版,一个被 const 修饰的版本。
4. modify —— 修改串内容
接下来的内容轮到了 string 的重头戏,也就是对 string 中的内容进行修改,具体将要掌握的函数如下:
在讲解这些函数之前,我们先介绍一个常数 npos = -1,对于该值,若我们将其赋值给一个无符号整数,结果类型转化,该无符号整数将会变为最大的整型数。
我们先介绍 push_back 函数,其功能为:在串的后面加上压入一个字符元素,实现起来也相对简单,唯一需要注意的是,我们需要判断是否需要扩容,实现如下:
void push_back(char c) { // 需要先判断 if (_size == _capacity) reserve(_capacity == 0 ? 4 : 2 * _capacity); _str[_size++] = c; _str[_size] = '\0'; }
接下来介绍 append 函数,append 函数有很多重载函数,其中我们最常用为在后面追加字符串,其中需要注意仍为:判断函数是否需要扩容。具体实现如下:
// 追加字符串 void append(const char* str) { // 判断是否需要扩容 int len = strlen(str); if (len > _capacity - _size) reserve(len + _size + 1); // 使用strcpy 库函数进行拷贝 strcpy(_str + _size, str); _size += len; }
接着是我们 swap 函数,对于 swap 函数而言,我们不需要太多的拷贝,我们只需要将指针切换可以了,将 _str 的指针与目标串的指针交换,然后交换 _size 和 _capacity 的值,代码如下:
void swap(string& str) { // 使用 std 中的 swap,若不指定,就会选择就近的 swap std::swap(str._str, _str); std::swap(str._size, _size); std::swap(str._capacity, _capacity); }
接着是我们运算符重载函数 operator+= ,对于这个函数而言,也是一个常用的函数,常用的重载有在后面 += 一个字符或者一个串,我们只需要在函数中套用 push_back 与 append 函数即可,如下:
string& operator+=(const char* str) { append(str); return *this; } string& operator+=(char c) { push_back(c); return *this; }
然后介绍一个相对较难实现的函数:insert 函数,该函数的功能为在指定位置插入一个串或者一个一个字符,通常的使用中,我们并不建议使用该函数,因为需要移动串中的元素,效率较为低下,实现的代码如下:
string& insert(size_t pos, char c) { assert(pos <= _size); // 开始循环后移,判断是否需要扩容 if (_size == _capacity) reserve(2 * _capacity); int end = _size; // 需要强制类型转换,防止出现类型转化,进入无限循环 while (end >= (int)pos) { _str[end + 1] = _str[end]; end--; } _str[pos] = c; _size++; return *this; } string& insert(size_t pos, const char* str) { assert(pos <= _size); size_t len = strlen(str); if (len > _capacity - _size) reserve(_size + len + 1); // 进行循环拷贝 int end = len + _size; while (end >= (int)pos + len) { _str[end] = _str[end - len]; end--; } strncpy(_str + pos, str, len); //for (int i = 0; i < len; i++) // _str[i + pos] = str[i]; return *this; }
最后就是我们 erase 函数,该函数的功能为删除从 pos 位置开始的 len 个元素,同时还是一个全缺省函数,若不指定 pos 和 len,那么将会删除所有的元素,我们在删除的时候,也需要判断从 pos 位置开始的 len 个元素是否会大于等于 _size ,若小于,则需要移动覆盖元素,实现如下:
string& erase(size_t pos = 0, size_t len = npos) { assert(pos < _size); if (pos + len >= _size) { _str[pos] = '\0'; return *this; } // 往前覆盖 int begin = pos + len; while (begin <= _size) { _str[begin - len] = _str[begin]; begin++; } return *this; }
5. string operations —— string 的操作
接下来将要讲解的是 string 中的一些其他操作,如下:
其中红框框住的这些是我们需要模拟实现的,至于其他的函数,要求掌握的程度不高,需要使用的时候,查看文档即可。
首先要介绍的是 c_str 函数,对于这个函数而言,其作用为在 string 中得到一个像在 C语言中的一个字符串,设计这个函数的目的之一就是为了兼容 C 语言。对于该函数的实现还是较为的简单,只需要返回 string 中字符串的首地址即可,对于函数的修饰,直接使用 const 修饰,因为传过去的值不能被修改,如下:
const char* c_str() const { return _str; }
接下来将要实现的函数为 find 和 rfind 函数,这两个函数都作用为在 string 中查找从 pos 位置查找字符或者查找字符串,返回查找到的第一个位置,find 和 rfind 的区别为 find 为从前面开始查找, rfind 为从后面开始查找,这两个函数仅仅为查找,所以不需要修改 string 的值,需要使用 const 修饰。实现原理如下:
size_t find(const char* str, size_t pos = 0) const { // 普通写法 //if (pos >= _size) // return npos; //size_t len = strlen(str); // //for (int i = pos; i < _size; i++) { // int j = 0; // for (; j < len; j++) { // if (_str[i + j] != str[j]) // break; // } // if (j == len) // return i; //} if (pos >= _size) return npos; // 使用 strstr 函数查找子串,返回子串的位置 const char* p = strstr(_str + pos, str); if (p) return p - _str; return npos; } size_t find(char c, size_t pos = 0) const { // 若找不到,返回 npos if (pos >= _size) return npos; for (int i = pos; i < _size; i++) { if (_str[i] == c) return i; } return npos; } size_t rfind(const char* str, size_t pos = npos) const { int len = strlen(str); if (pos >= _size) { // 当 pos 大于 _size 直接从 _size - len 位置开始找 for (int i = _size - len; i >= 0; i--) { int j = 0; for (; j < len; j++) { if (str[j] != _str[i + j]) break; } if (j == len) return i; } } else { for (int i = pos - len; i >= 0; i--) { int j = 0; for (; j < len; j++) { if (str[j] != _str[i + j]) break; } if (j == len) return i; } } return npos; } size_t rfind(char c, size_t pos = npos) const { // 若 pos == npos 从最后开始找 if (pos >= _size) { for (int i = _size - 1; i >= 0; i--) { if (_str[i] == c) return i; } } else { for (int i = pos; i >= 0; i--) { if (_str[i] == c) return i; } } return npos; }
最后需要实现的函数为 substr ,拷贝子串,从一个 string 中拷贝这个 string 的一个子串过去,实现如下:
string substr(size_t pos = 0, size_t len = npos) const { // 只需要函数拷贝过去就可以了,需要判断获取子串的位置是否合法,若等于_size,那么就传递一个空串过去 assert(pos <= _size); // 直接传一个串过去,让在返回值是进行构造, if (len > _size - pos) return _str + pos; string tmp; strncpy(tmp._str, _str + pos, len); // 还需要在后面加上 /0 tmp._size = len; tmp += '\0'; return tmp; }
6. 默认成员函数
接下来将会讲解 string 中的默认成员函数,其中包括构造函数,拷贝构造函数,赋值运算符重载,析构函数,如下:
首先我们先讲解构造函数和析构函数,对于构造函数而言,我们只需要将其设置为一个全缺省函数即可,当在初始化不传值的时候,默认初始值为一个空串,然后我们进行开辟空间,赋值即可。析构函数则将空间释放即可,实现如下:
string(const char* str = "") :_size(strlen(str)) { _str = new char[_size + 1]; _capacity = _size; strcpy(_str, str); } ~string() { delete[] _str; _str = nullptr; _size = _capacity = 0; }
接下来为拷贝构造函数,对于拷贝构造函数而言,就是将 string 进行拷贝一份即可,所以我们只需要对 _str 按照 string.capacity() 进行开辟空间然后将传过来的 string._str 拷贝过去即可,然后给容量和size赋值。
以上这种写法为常规写法,另一种写法为:在拷贝构造函数中创建一个临时 string 变量,然后使用传过来的 string._str,对其进行初始化,也就是使用构造函数,然后调用 swap 函数交换 tmp 和 *this 的值。(不够这种写法需要将成员进行初始化,将 _str 初始化为 nullptr,因为交换之后,会对 tmp 调用析构函数,释放空间时只需要释放空指针处的空间,这样才不会报错)
代码如下:
//string(const string& s) { // _str = new char[s.capacity() + 1]; // strcpy(_str, s.c_str()); // _size = s.size(); // _capacity = s.capacity(); //} string(const string& s) { // 调用构造函数 string tmp(s._str); swap(tmp); // 出了函数之后,会将tmp进行析构,防止释放野生空间 ---> 成员初始化nullptr }
最后为赋值运算符重载,赋值运算符重载的思路基本和拷贝构造函数一致,创建出空间,然后拷贝,最后将 *this 返回即可。当然这也是普通写法。
另一种写法如下,在注释中解释:
//string& operator=(const string& s) { // char* tmp = new char[s.capacity() + 1]; // strcpy(tmp, s.c_str()); // delete[] _str; // _str = tmp; // _capacity = s.capacity(); // _size = s.size(); // // return *this; //} // 对传过来的串进行调用构造函数,将值构造给s,然后交换s和*this的值 // 析构的时候自然会将原来*this的值给析构掉。 string& operator=(string s) { swap(s); return *this; } string& operator=(const string& s) { // 调用构造函数 string tmp(s._str); // 然后交换tmp和*this的值 swap(tmp); return *this; }
7. 流提取和其他运算符重载
最后需要介绍的知识点便是流提取和其他运算符的重载,流提取是为了方便的使用string 串,因为流提取的特殊性,我们需要将流提取定义在类外,另外在介绍一个 getline 函数,获取一行的信息放入到 string 中,这个函数定义在类中,实现如下:
ostream& operator<<(ostream& _cout, const MyString::string& s) { //_cout << s.c_str(); // 使用范围for进行遍历打印 for (auto ch : s) _cout << ch; return _cout; } istream& operator>>(istream& _cin, MyString::string& s) { //_cin >> s._str; // 以下这样写不需要访问private成员,可以不用友元 s.clear(); char buff[128]; char ch = _cin.get(); int i = 0; while (ch != ' ' && ch != '\n') { //s += ch; //ch = _cin.get(); buff[i++] = ch; if (i == 127) { buff[i] = '\0'; s += buff; i = 0; } ch = _cin.get(); } if (i > 0) { buff[i] = '\0'; s += buff; } return _cin; } istream& getline(istream& _cin, MyString::string& s) { s.clear(); char ch = _cin.get(); int i = 0; char buff[128]; while (ch != '\n') { //s += ch; //ch = _cin.get(); buff[i++] = ch; if (i == 127) { buff[i] = '\0'; s += buff; i = 0; } ch = _cin.get(); } if (i > 0) { buff[i] = '\0'; s += buff; } return _cin; }
如上的流提取我们不需要将其在类中进行友元的声明,因为并没有使用到被 private 修饰的成员变量。对于 >> 流提取而言,我们设置一个 buff 的原因是,将读取进来的字符存入 buff 中,当 buff 满了,获取读取结束了,便将 buff 中的串放入到 string 中去,这样的好处在于,我们不需要频繁的对 string 进行开辟空间,而且每次开辟的空间大小都刚好合适。
加下来便是其他的运算符重载函数,如下:
bool operator<(const string& s) { if (strlen(s.begin()) == 0) return false; if (strlen(s.begin()) != 0 && _size == 0) return true; int ret = strcmp(_str, s.begin()); return ret < 0; } bool operator<=(const string& s) { // 直接判断相等 return *this < s || *this == s; } bool operator>(const string& s) { return !(*this <= s); } bool operator>=(const string& s) { return *this > s || *this == s; } bool operator==(const string& s) { if (strlen(s.begin()) != _size) return false; int ret = strcmp(_str, s.begin()); return ret == 0; } bool operator!=(const string& s) { return !(*this == s); }
8. 浅谈本篇中的 string
对于本篇中的 string 而言,其实现的原理是最简单的一种原理,只是用一个字符指针和两个常量来定义串,而在 vs 和 linux 下的实现就更显复杂。如下:
vs 下的 string 结构:
string 总共占28个字节,内部结构为一个联合体,联合体用来定义 string 中字符串的存储空间:
1. 当字符串长度小于16的时候,使用从内部固定的字符数组来存放;
2. 当字符串长度大于等于16时,从堆上开辟出空间。
union _Bxty { // storage for small buffer or pointer to larger one value_type _Buf[_BUF_SIZE]; pointer _Ptr; char _Alias[_BUF_SIZE]; // to permit aliasing } _Bx
28字节的来历:
大多数情况下字符串的长度都小于16,那string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。16
其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量。 4 + 4
最后:还有一个指针做一些其他事情。 ---> 16 + 4 + 4 + 4g++ 下的 string 结构
G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:
1. 总空间大小
2. 字符串有效长度
3. 引用计数
struct _Rep_base { size_type _M_length; size_type _M_capacity; _Atomic_word _M_refcount; };
9. All Code
namespace MyString { class string { public: static const int npos = -1; typedef char* iterator; typedef const char* const_iterator; const size_t size() const { return _size; } const size_t capacity() const { return _capacity; } // 构造函数 string(const char* str = "") :_size(strlen(str)) { _str = new char[_size + 1]; _capacity = _size; strcpy(_str, str); } // 拷贝构造函数 的两种写法 //string(const string& s) { // _str = new char[s.capacity() + 1]; // strcpy(_str, s.c_str()); // _size = s.size(); // _capacity = s.capacity(); //} string(const string& s) { // 调用构造函数 string tmp(s._str); swap(tmp); // 出了函数之后,会将tmp进行析构,防止释放野生空间 ---> 初始化nullptr } // 赋值运算符重载 --> 需要修改 //string& operator=(const string& s) { // char* tmp = new char[s.capacity() + 1]; // strcpy(tmp, s.c_str()); // delete[] _str; // _str = tmp; // _capacity = s.capacity(); // _size = s.size(); // // return *this; //} // 对传过来的串进行调用构造函数,将值构造给s,然后交换s和*this的值 // 析构的时候自然会将原来*this的值给析构掉。 string& operator=(string s) { swap(s); return *this; } string& operator=(const string& s) { // 调用构造函数 string tmp(s._str); // 然后交换tmp和*this的值 swap(tmp); return *this; } // 析构函数 ~string() { delete[] _str; _str = nullptr; _size = 0; _capacity = 0; } // c_str const char* c_str() const { return _str; } // 迭代器 iterator begin() { return _str; } const_iterator begin() const { return _str; } iterator end() { return _str + _size; } const_iterator end() const { return _str + _size; } void resize(size_t n, char c = '\0') { if (n <= _size) { _str[n] = '\0'; _size = n; } else { reserve(n); for (int i = _size; i < n; i++) _str[i] = c; _size = n; _str[_size] = '\0'; } } // 获取第 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 n) { // 仅仅 n 大于 capacity 的时候,我们才进行扩容 if (n > _capacity) { char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } } // 追加字符串 void append(const char* str) { // 判断是否需要扩容 int len = strlen(str); if (len > _capacity - _size) reserve(len + _size + 1); strcpy(_str + _size, str); _size += len; } string& operator+=(const char* str) { append(str); return *this; } string& operator+=(char c) { push_back(c); return *this; } void push_back(char c) { // 需要先判断 if (_size == _capacity) reserve(_capacity == 0 ? 4 : 2 * _capacity); _str[_size++] = c; _str[_size] = '\0'; } bool empty() const { return _size == 0; } void clear() { _str[0] = '\0'; _size = 0; } //void swap(string& str) { // // // 先进行扩容 // if (_capacity > str.capacity()) // str.reserve(_capacity); // else // reserve(str.capacity()); // // 然后进行交换,还要交换size // size_t max = _capacity > str.capacity() ? _capacity : str.capacity(); // char* tmp = new char[max + 1]; // strcpy(tmp, str.begin()); // str.clear(); // for (int i = 0; i <= _size; i++) // str += _str[i]; // strcpy(_str, tmp); // _size = str.size(); // // 修改str的size //} void swap(string& str) { // 使用 std 中的 swap,若不指定,就会选择就近的 swap std::swap(str._str, _str); std::swap(str._size, _size); std::swap(str._capacity, _capacity); } // 查找函数 size_t find(char c, size_t pos = 0) const { // 若找不到,返回 npos if (pos >= _size) return npos; for (int i = pos; i < _size; i++) { if (_str[i] == c) return i; } return npos; } size_t find(const char* str, size_t pos = 0) const { // 普通写法 //if (pos >= _size) // return npos; //size_t len = strlen(str); // //for (int i = pos; i < _size; i++) { // int j = 0; // for (; j < len; j++) { // if (_str[i + j] != str[j]) // break; // } // if (j == len) // return i; //} if (pos >= _size) return npos; // 使用 strstr 函数查找子串,返回子串的位置 const char* p = strstr(_str + pos, str); if (p) return p - _str; return npos; } // 从后面开始找,没有找到仍然返回npos size_t rfind(char c, size_t pos = npos) const { // 若 pos == npos 从最后开始找 if (pos >= _size) { for (int i = _size - 1; i >= 0; i--) { if (_str[i] == c) return i; } } else { for (int i = pos; i >= 0; i--) { if (_str[i] == c) return i; } } return npos; } size_t rfind(const char* str, size_t pos = npos) const{ int len = strlen(str); if (pos >= _size) { for (int i = _size - len; i >= 0; i--) { int j = 0; for (; j < len; j++) { if (str[j] != _str[i + j]) break; } if (j == len) return i; } } else { for (int i = pos - len; i >= 0; i--) { int j = 0; for (; j < len; j++) { if (str[j] != _str[i + j]) break; } if (j == len) return i; } } return npos; } string& insert(size_t pos, char c) { assert(pos <= _size); // 开始循环后移,判断是否需要扩容 if (_size == _capacity) reserve(2 * _capacity); int end = _size; // 需要强制类型转换 while (end >= (int)pos) { _str[end + 1] = _str[end]; end--; } _str[pos] = c; _size++; return *this; } string& insert(size_t pos, const char* str) { assert(pos <= _size); size_t len = strlen(str); if (len > _capacity - _size) reserve(_size + len + 1); // 进行循环拷贝 int end = len + _size; while (end >= (int)pos + len) { _str[end] = _str[end - len]; end--; } strncpy(_str + pos, str, len); //for (int i = 0; i < len; i++) // _str[i + pos] = str[i]; return *this; } string& erase(size_t pos = 0, size_t len = npos) { assert(pos < _size); if (pos + len >= _size) { _str[pos] = '\0'; return *this; } // 往前覆盖 int begin = pos + len; while (begin <= _size) { _str[begin - len] = _str[begin]; begin++; } return *this; } friend ostream& operator<<(ostream& _cout, const MyString::string& s); friend istream& operator>>(istream& _cin, MyString::string& s); // 子串 string substr(size_t pos = 0, size_t len = npos) const { string ret; if (len >= _size - pos) { for (int i = pos; i < _size; i++) ret += _str[i]; } else { for (int i = pos; i < len + pos; i++) ret += _str[i]; } return ret; } //relational operators bool operator<(const string& s) { if (strlen(s.begin()) == 0) return false; if (strlen(s.begin()) != 0 && _size == 0) return true; int ret = strcmp(_str, s.begin()); return ret < 0; } bool operator<=(const string& s) { // 直接判断相等 return *this < s || *this == s; } bool operator>(const string& s) { return !(*this <= s); } bool operator>=(const string& s) { return *this > s || *this == s; } bool operator==(const string& s) { if (strlen(s.begin()) != _size) return false; int ret = strcmp(_str, s.begin()); return ret == 0; } bool operator!=(const string& s) { return !(*this == s); } istream& getline(istream& _cin, MyString::string& s) { s.clear(); char ch = _cin.get(); int i = 0; char buff[128]; while (ch != '\n') { //s += ch; //ch = _cin.get(); buff[i++] = ch; if (i == 127) { buff[i] = '\0'; s += buff; i = 0; } ch = _cin.get(); } if (i > 0) { buff[i] = '\0'; s += buff; } return _cin; } private: char* _str = nullptr; size_t _capacity = 0; size_t _size = 0; }; ostream& operator<<(ostream& _cout, const MyString::string& s) { //_cout << s.c_str(); // 使用范围for进行遍历打印 for (auto ch : s) _cout << ch; return _cout; } istream& operator>>(istream& _cin, MyString::string& s) { //_cin >> s._str; // 以下这样写不需要访问private成员,可以不用友元 s.clear(); char buff[128]; char ch = _cin.get(); int i = 0; while (ch != ' ' && ch != '\n') { //s += ch; //ch = _cin.get(); buff[i++] = ch; if (i == 127) { buff[i] = '\0'; s += buff; i = 0; } ch = _cin.get(); } if (i > 0) { buff[i] = '\0'; s += buff; } return _cin; } }