本篇中,将会详细的介绍 Cpp 中 string 的使用,以及 string 类常用函数的模拟实现。对于 string 的内置函数来说,存在很多很冗余的用法,很多函数都有很多种用法,本篇将会讲解常用内置函数的常用用法,模拟函数的时候,也只是模拟常用的用法代码。本篇实现的 string ,仅仅只是通过一种很简单的方式实现的,对于 vs 、g++ 下的 string 相比都是小巫见大巫。
本篇的函数讲解顺序按照 cplusplus 官网的顺序,网站:string - C++ Reference (cplusplus.com)
目录如下:
目录
1. iterator —— 迭代器
2. capacity —— 容量
3. Element access —— 访问成员
4. modify —— 修改串内容
5. string operations —— string 的操作
6. 默认成员函数
7. 流提取和其他运算符重载
8. 浅谈本篇中的 string
9. All Code
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; } }