?? 前言
hello hello~ ,这里是大耳朵土土垚~?? ,欢迎大家点赞??关注??收藏???
?个人主页:大耳朵土土垚的博客
? 所属专栏:C++入门至进阶
这里将会不定期更新有关C++的内容,希望大家多多点赞关注收藏??
1.string类基本框架
通过对string类的学习,我们知道string类的模拟实现最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数,以下是对模拟实现string类变量以及函数的封装:
为了防止与库里面string类名字重复,我们将自己模拟实现的string类放在tutu
的命名空间中(名字可以自定义),这样使用时就不会冲突了;其次对于string类的模拟实现我们采用声明与定义分离,方便大家查看string类中包含哪些函数;如下是对string类的声明:
#pragma once#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>#include<assert.h>using namespace std;namespace tutu//命名空间,可自定义名字{class string//定义string类{public://string();//构造函数string(const char* str = "");string(const string& s);//赋值运算符重载string& operator=(const string& s);//析构函数~string();typedef char* iterator;//定义迭代器typedef const char* const_iterator;//常量使用的迭代器iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;//获取字符串指针const char* c_str() const;//获取string类的容量size_t size() const;size_t capacity()const;//[]重载char& operator[](size_t pos);const char& operator[](size_t pos) const;//常量版本//空间操作void reserve(size_t n);//预留空间void resize(size_t n, char ch = '\0');//重设空间大小bool empty()const;//判断字符串是否为空void clear();//清空字符串//尾插操作void push_back(char ch);//尾插字符void append(const char* str);//插入字符串//+=重载string& operator+=(char ch);//尾插字符string& operator+=(const char* str);//尾插字符串//在pos位置插入void insert(size_t pos, char ch);void insert(size_t pos, const char* str);//删除pos位置的值void erase(size_t pos = 0, size_t len = npos);//查找字符/字符串size_t find(char ch, size_t pos = 0);size_t find(const char* str, size_t pos = 0);//交换void swap(string& s);//截取字符串string substr(size_t pos = 0, size_t len = npos);//运算符重载bool operator<(const string& s) const;bool operator>(const string& s) const;bool operator<=(const string& s) const;bool operator>=(const string& s) const;bool operator==(const string& s) const;bool operator!=(const string& s) const;private:// char _buff[16];char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;const static size_t npos = -1;};//流插入提取重载istream& operator>> (istream& is, string& str);ostream& operator<< (ostream& os, const string& str);}
2.string类模拟实现
因为定义和声明分离,所以我们对函数实现时,需要带作用限定符string::
表明是实现这个类中的函数
2.1构造函数实现
✨string(const char* str)
可以用字符串来构造string类:string s1("Hello World");
string:: string(const char* str) { size_t len = strlen(str);//计算字符串长度 _str = new char[len + 1];//开辟空间 _size = _capacity = len;//设置capacity和size值 strcpy(_str, str);//拷贝字符串 }
这里注意开辟空间 _str = new char[len + 1];
要+1,为了存放字符'\0'
;并且 strcpy(_str, str);
会将str中的'\0'
拷贝给_str,所以不需要手动拷贝。此外注意缺省参数在声明写,定义就不需要写了。
✨ string(const string& s)
可以用另一个string对象来构造string对象:string s2(s1);
string::string(const string& s){_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}
2.2赋值运算符重载
✨ string& operator=(const string& s)
类似于赋值,可以将string对象赋值给另一个string对象 string s1 = s2;
此外还可以string s1 = "Hello World;"
利用隐式类型转换使用字符串赋值,实现代码如下:
string& string::operator=(const string& s){if (this != &s){char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;}
这里注意在赋值之前需要将string类的对象中之前存放的空间释放掉delete[] _str;
2.3析构函数
✨ ~string()
string::~string(){ delete[] _str; _str = nullptr; _capacity = _size = 0;}
??测试代码如下:
namespace tutu{void Test1(){tutu::string s1 = "hello";tutu::string s2(s1);tutu::string s3 = s1;}}
结果如下:
2.4迭代器
迭代器可以通过指针类比,它们可以指向容器中的某个元素,并且可以通过操作迭代器来访问和修改容器中的元素。在实现时先要对它进行定义:typedef char* iterator; //定义迭代器 typedef const char* const_iterator;//常量使用的迭代器
,因为我们实现的是string类是字符串,所以将char*
typedef,这些都放在声明中。
✨普通迭代器
string::iterator string::begin(){ return _str;//返回字符串指针}string::iterator string::end(){ return _str + _size;//返回字符串指向'\0'的指针,也就是最后一个元素的下一个}
✨常量迭代器
string::const_iterator string::begin() const{ return _str;}string::const_iterator string::end() const{ return _str + _size;}
2.5获取字符串指针
✨const char* c_str() const
//获取字符串指针const char* string::c_str()const{ return _str;}
2.6获取string类的容量
✨ size_t size() const
size_t string::size()const { return _size; }
✨ size_t capacity()const
size_t string::capacity()const { return _capacity; }
2.7[]重载
✨ char& operator[](size_t pos)
char& string::operator[](size_t pos){ assert(pos < _size); return _str[pos];}
✨ const char& operator[](size_t pos) const
const char& string::operator[](size_t pos)const{ assert(pos < _size); return _str[pos];}
2.8空间操作
✨ void reserve(size_t n)
void string::reserve(size_t n){ //1.n <= capacity,不改变底层空间大小 if (n <= _capacity) { //什么都不干 } //2.n > capacity else { char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } }
为string类对象预留空间,这里要注意当n <= capacity
时,不会进行缩小空间。
✨ void resize(size_t n, char ch = ‘\0’)
void string::resize(size_t n, char ch)//?怎么将后面的都设置为ch,尾插{ //1.n <= capacity if (n <= _capacity) { if (n <= _size) { //什么都不干 } else { for (int i = _size; i < n; i++) { _str[i] = ch; } } } //2.n > capacity else { char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; for (int i = _size; i < n; i++) { _str[i] = ch; } _capacity = n; //扩容了capacity要改变 } _str[n] = '\0'; _size = n;}
这里注意当n<_size
时,也就是字符串本身大小大于要设置的空间大小时,直接将n位置的字符设置为'\0'
即可,不改变底层空间大小。
✨ bool empty()const
bool string::empty()const { return _size == 0; }
判断字符串是否为空串
✨ void clear()
//清空字符串 void string::clear() { //清除,底层空间不变 _str[0] = '/0'; _size = 0; }
这里清空字符串只是将它有效字符设为0,不改变底层空间大小
2.9尾插操作
✨void push_back(char ch)
void string::push_back(char ch) { if (_size == _capacity)//判断是否需要扩容 { size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity; reserve(newcapacity); _capacity = newcapacity; } _str[_size] = ch; _str[_size+1] = '\0'; _size++; }
✨void append(const char* str)
void string::append(const char* str){ size_t len = strlen(str); reserve(_size + len); strcpy(_str + _size, str); _size += len;}
因为reserve对于大于_size + len的空间不会缩小,所以我们直接使用reserve就行,不需要判断_capacity>_size+len
的情况
✨+=重载
string& string::operator+=(char ch)//+=字符 { push_back(ch); return *this; }
string& string::operator+=(const char* str)//+=字符串 { append(str); return *this; }
+=重载和push_back、append实现原理一样我们只需要复用它们即可
??测试代码如下:
using namespace tutu{void Test7(){tutu::string s1("hello");//尾插字符s1.push_back('x');cout << s1.c_str() << endl;s1 += "world";cout << s1.c_str() << endl;//尾插字符串s1.append("tutu");cout << s1.c_str() << endl;}}
结果如下:
2.10在pos位置插入
✨void insert(size_t pos, char ch)
// 在pos位置上插入字符c/字符串strvoid string::insert(size_t pos, char ch){ assert(pos <= _size); if (_size == _capacity) { size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2; reserve(newcapacity); } size_t end = _size ;//'\0'也有 while (end >= pos) { _str[end+1] = _str[end]; end--; } _size++; _str[pos] = ch; }
这里插入要挪动数据;注意while循环的判断条件end >= pos
,又因为end和pos都是size_t
无符号整型,所以当_size=0时,end无论怎么- - 都不会小于_size,也就是会陷入死循环
测试代码如下:
namespace tutu{void Test2(){tutu::string s1 = "hello";s1.insert(0, 'x');}}
结果如下:
程序出现异常
??解决办法有两种
将size_t类型强制转换为int类型// 在pos位置上插入字符c/字符串strvoid string::insert(size_t pos, char ch){ assert(pos <= _size); if (_size == _capacity) { size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2; reserve(newcapacity); } //size_t end = _size ;//'\0'也有 //while (end >= pos) //出现异常 //{ // _str[end+1] = _str[end]; // end--; //} int end = _size;//'\0'也有 while (end >= (int)pos) //防止越界 { _str[end + 1] = _str[end]; end--; } _size++; _str[pos] = ch; }
将循环条件改成> 将end=_size+1,这样循环条件就可以是end >= pos
// 在pos位置上插入字符ch/字符串strvoid string::insert(size_t pos, char ch){ assert(pos <= _size); if (_size == _capacity) { size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2; reserve(newcapacity); } //size_t end = _size ;//'\0'也有 //while (end >= pos) //出现异常 //{ // _str[end+1] = _str[end]; // end--; //} size_t end = _size+1; while (end > pos) { _str[end] = _str[end-1];//注意这里也要相应-1 end--; } _size++; _str[pos] = ch; }
✨void insert(size_t pos, const char* str)
void string::insert(size_t pos, const char* str){ int end = _size;//'\0'也有 size_t len = strlen(str); reserve(_size + len);//扩容 while (end >= (int)pos)//这里使用强制类型转换 { _str[end + len] = _str[end]; end--; } mencpy(_str + pos, str, len); _size += len;}
??测试代码如下:
namespace tutu{void Test3(){tutu::string s1 = "hello";s1.insert(0, 'x');s1.insert(3, "abc");cout << s1.c_str() << endl;}}
结果如下:
2.11在pos位置删除
✨void erase(size_t pos = 0, size_t len = npos)
// 删除pos位置上的元素void string::erase(size_t pos, size_t len) { assert(pos < _size); if (len >= _size - pos) //如果一直删到尾,就直接将pos位置置为'\0' { _str[pos] = '\0'; _size = pos; } else { strcpy(_str + pos, _str + pos + len); //将后面的字符串拷贝到前面 _size -= len; } }
??测试代码如下:
using namespace tutu{void Test8(){tutu::string s1("hello");//特定位置插入字符s1.insert(5, 'x');cout << s1.c_str() << endl;//删一个字符s1.erase(5, 1);cout << s1.c_str() << endl;//特定位置插入字符串s1.insert(5, "tutu");cout << s1.c_str() << endl;//删多个字符s1.erase(5, 4);cout << s1.c_str() << endl;}}
结果如下:
2.12查找字符/字符串
✨size_t find(char ch, size_t pos = 0)
// 返回ch在string中第一次出现的位置 size_t string::find(char ch, size_t pos) { for (size_t i = pos; i < _size; i++) { if (_str[i] == ch) { return i; //找到了返回下标 } } return npos; //没有找到返回npos }
✨size_t find(const char* str, size_t pos = 0)
// 返回子串str在string中第一次出现的位置 size_t string::find(const char* str, size_t pos) { char* tmp = strstr(_str + pos, str); if (tmp) //1.如果找到了 { return tmp - _str; } return npos;//2.没找到 }
??测试代码如下:
using namespace tutu{void Test6(){tutu::string s1("hello world");cout << s1.find('o') << endl;cout << s1.find("wor") << endl;}}
结果如下:
2.13截取字符串
//截取字符串string string::substr(size_t pos, size_t len){ //如果len>_size-pos,就将pos位置后面全部拷贝 if (len > _size - pos) { string sub(_str + pos); return sub; } else { string sub; sub.reserve(len); for (size_t i = 0; i < len; i++) { sub += _str[pos + i]; } return sub; }}
??测试代码如下:
using namespace tutu{void Test4(){tutu::string url("https://blog.csdn.net/Renswc?type=blog");size_t pos1 = url.find(':');tutu::string url1 = url.substr(0, pos1 - 0);cout << url1.c_str() << endl;size_t pos2 = url.find('/', pos1 + 3);tutu::string url2 = url.substr(pos1 + 3, pos2 - (pos1 + 3));cout << url2.c_str() << endl;tutu::string url3 = url.substr(pos2 + 1);cout << url3.c_str() << endl;}}
结果如下:
2.14交换函数
✨void swap(string& s)
//交换 void string::swap(string& s) { //使用标准库里面的交换函数 std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); }
2.15运算符重载
这里只要实现<和 == 或者<和 == 就可以代码复用到其他的运算符上,使用strcmp函数比较字符串
✨bool operator>(const string& s) const
bool string::operator>(const string& s)const{ return strcmp(_str, s._str) > 0;}
✨bool operator==(const string& s) const
bool string::operator==(const string& s)const{ return *_str == *s._str;}
✨bool operator<(const string& s) const
bool string::operator<(const string& s) const{ return !(*this >= s);}
✨bool operator<=(const string& s) const
bool string::operator<=(const string& s)const{ return !(*this > s);}
✨bool operator>=(const string& s) const
bool string::operator>=(const string& s)const{ return *this > s || *this == s;}
✨bool operator!=(const string& s) const
bool string::operator!=(const string& s)const{ return !(*this == s);}
2.16流插入提取
✨istream& operator>> (istream& is, string& str)
//流提取istream& operator>> (istream& is, string& str){ str.clear(); char ch = is.get(); while (ch != ' ' && ch != '\n') { str += ch; ch = is.get(); } return is;}
这里每次读取字符容量满了都要进行扩容非常消耗时间和空间,而且我们是两倍两倍的扩容,所以很可能会有很多剩余的空间无法使用,所以我们可以使用下面的方式来实现以节省空间:
//输入流重载istream& operator>>(istream& in, string& s){ s.clear(); char ch; ch = in.get(); char buff[128];//缓冲数组,最后一个存放'\0',所以最多读取127个 size_t i = 0; while (ch != ' ' && ch != '\n')//遇到空格和换行停止读取 { buff[i++] = ch; if (i == 127) //数组满了就将字符挪动到s中,然后再循环知道读完 { buff[127] = '\0'; s += buff; i = 0; } ch = in.get(); } //如果没有读满数组,还有剩余字符 if (i > 0) { buff[i] = '\0'; s += buff; } return in;}
输入流从屏幕上读取数据会覆盖原来的数据,所以我们使用clear
先清理一下,然后创建一个数组来存放流读取到的字符,因为数组在函数调用完会自动释放不会占用过多的空间,一次性读取127个,读完就将它**+=到s中**,然后再从0开始读取,直到遇到空格或者换行,读取结束,如果此时数组里面还有字符就将它+=到s中
✨ostream& operator<< (ostream& os, const string& str)
//流插入ostream& operator<< (ostream& os, const string& str){ for (size_t i = 0; i < str.size(); i++) { os << str[i]; } return os;}
这里我们不需要将上述插入提取函数设置为友元,因为我们没有访问成员变量,访问的是公有的成员函数,也就不会存在越界访问
??测试代码如下:
using namespace tutu{void Test5(){tutu::string s1("hello world");cout << s1 << endl;cin >> s1;cout << s1 << endl;}}
结果如下:
这里看到遇到空格就没有继续读取了,只读了hello
,后面的tutu
没读
3.另一种写法
对于拷贝构造函数以及赋值运算符重载我们还可以实现一种现代写法
拷贝构造string::string(const string& s){ /*_str = new char[s._capacity + 1]; strcpy(_str, s._str); _size = s._size; _capacity = s._capacity;*/ string tmp(s._str); swap(tmp);}
让swap函数帮助我们实现拷贝构造,在函数调用完成会自动调用析构函数释放原来的空间
赋值运算符重载string& string::operator=(const string& s){ if (this != &s) { /*char* tmp = new char[s._capacity + 1]; strcpy(tmp, s._str); delete[] _str; _str = tmp; _size = s._size; _capacity = s._capacity;*/ string tmp(s._str); swap(tmp); } return *this;}
此外对于赋值运算符重载还可以:
string& string::operator=(string tmp) { swap(tmp); return *this; }
这里注意给的参数没有使用引用,直接就是对参数的临时拷贝,交换完就释放空间;所以如果要使用的话记得把声明也改一下
4.string类模拟实现完整代码
?函数代码
#include"string.h"namespace tutu{ string::string(const char* str) { size_t len = strlen(str); _str = new char[len + 1]; _size = _capacity = len; strcpy(_str, str); } string::string(const string& s) { _str = new char[s._capacity + 1]; strcpy(_str, s._str); _size = s._size; _capacity = s._capacity; } string& string::operator=(const string& s) { if (this != &s) { char* tmp = new char[s._capacity + 1]; strcpy(tmp, s._str); delete[] _str; _str = tmp; _size = s._size; _capacity = s._capacity; } return *this; } string::~string() { delete[] _str; _str = nullptr; _capacity = _size = 0; } // // iterator string::iterator string::begin() { return _str; } string::iterator string::end() { return _str + _size; } string::const_iterator string::begin() const { return _str; } string::const_iterator string::end() const { return _str + _size; } //获取字符串指针 const char* string::c_str()const { return _str; } // void string::swap(string& s) // { // string tmp(s); // } / // access char& string::operator[](size_t pos) { assert(pos < _size); return _str[pos]; } const char& string::operator[](size_t pos)const { assert(pos < _size); return _str[pos]; } / // capacity size_t string::size()const { return _size; } size_t string::capacity()const { return _capacity; } void string::reserve(size_t n) { //1.n <= capacity,不改变底层空间大小 if (n <= _capacity) { //什么都不干 } //2.n > capacity else { char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } } void string::resize(size_t n, char ch)//?怎么将后面的都设置为ch,尾插 { //1.n <= capacity if (n <= _capacity) { if (n <= _size) { //什么都不干 } else { for (int i = _size; i < n; i++) { _str[i] = ch; } } } //2.n > capacity else { char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; for (int i = _size; i < n; i++) { _str[i] = ch; } _capacity = n; //扩容了capacity要改变 } _str[n] = '\0'; _size = n; } //判断字符串是否为空串 bool string::empty()const { return _size == 0; } //清空字符串 void string::clear() { //清除,底层空间不变 _size = 0; _str[0] = '/0'; } / // modify void string::push_back(char ch) { if (_size == _capacity)//判断是否需要扩容 { size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity; reserve(newcapacity); _capacity = newcapacity; } _str[_size] = ch; _str[_size+1] = '\0'; _size++; } string& string::operator+=(char ch) { push_back(ch); return *this; } void string::append(const char* str) { size_t len = strlen(str); reserve(_size + len); strcpy(_str + _size, str); _size += len; } string& string::operator+=(const char* str) { append(str); return *this; } // 在pos位置上插入字符ch / 字符串str void string::insert(size_t pos, char ch) { assert(pos <= _size); if (_size == _capacity) { size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2; reserve(newcapacity); } //size_t end = _size ;//'\0'也有 //while (end >= pos) //出现异常 //{ // _str[end+1] = _str[end]; // end--; //} size_t end = _size+1;//'\0'也有 while (end > pos) { _str[end] = _str[end-1]; end--; } _size++; _str[pos] = ch; } void string::insert(size_t pos, const char* str) { int end = _size; size_t len = strlen(str); reserve(_size + len);//扩容 while (end >= (int)pos) { _str[end + len] = _str[end]; end--; } memcpy(_str + pos, str, len); _size += len; } // 删除pos位置上的元素 void string::erase(size_t pos, size_t len) { assert(pos < _size); if (len >= _size - pos) //如果一直删到尾,就直接将pos位置置为'\0' { _str[pos] = '\0'; _size = pos; } else { strcpy(_str + pos, _str + pos + len); //将后面的字符串拷贝到前面 _size -= len; } } // 返回ch在string中第一次出现的位置 size_t string::find(char ch, size_t pos) { for (size_t i = pos; i < _size; i++) { if (_str[i] == ch) { return i; //找到了返回下标 } } return npos; //没有找到返回npos } // 返回子串str在string中第一次出现的位置 size_t string::find(const char* str, size_t pos) { char* tmp = strstr(_str + pos, str); if (tmp) //1.如果找到了 { return tmp -_str; } return npos;//2.没找到 } //截取字符串 string string::substr(size_t pos, size_t len) { //如果len>_size-pos,就将pos位置后面全部拷贝 if (len > _size - pos) { string sub(_str + pos); return sub; } else { string sub; sub.reserve(len); for (size_t i = 0; i < len; i++) { sub += _str[pos + i]; } return sub; } } //交换 void string::swap(string& s) { //使用标准库里面的交换函数 std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } / //relational operators bool string::operator>(const string& s)const { return strcmp(_str, s._str) > 0; } bool string::operator==(const string& s)const { return *_str == *s._str; } bool string::operator>=(const string& s)const { return *this > s || *this == s; } bool string::operator<(const string& s) const { return !(*this >= s); } bool string::operator<=(const string& s)const { return !(*this > s); } bool string::operator!=(const string& s)const { return !(*this == s); } 流提取 //istream& operator>> (istream& is, string& str) //{ // str.clear(); // char ch = is.get(); // while (ch != ' ' && ch != '\n') // { // str += ch; // ch = is.get(); // } // return is; //} //输入流重载 istream& operator>>(istream& in, string& s) { s.clear(); char ch; ch = in.get(); char buff[128];//缓冲数组 size_t i = 0; while (ch != ' ' && ch != '\n')//遇到空格和换行停止读取 { buff[i++] = ch; if (i == 127) //数组满了就将字符挪动到s中,然后再循环知道读完 { buff[127] = '\0'; s += buff; i = 0; } ch = in.get(); } //如果没有读满数组,还有剩余字符 if (i > 0) { buff[i] = '\0'; s += buff; } return in; } //输出流重载 ostream& operator<< (ostream& os, const string& str) { for (size_t i = 0; i < str.size(); i++) { os << str[i]; } return os; } }
?测试代码
namespace tutu{void Test1(){tutu::string s1 = "hello";tutu::string s2(s1);tutu::string s3 = s1;}void Test3(){tutu::string s1 = "hello";s1.insert(0, 'x');s1.insert(3, "abc");cout << s1.c_str() << endl;}void Test4(){tutu::string url("https://blog.csdn.net/Renswc?type=blog");size_t pos1 = url.find(':');tutu::string url1 = url.substr(0, pos1 - 0);cout << url1.c_str() << endl;size_t pos2 = url.find('/', pos1 + 3);tutu::string url2 = url.substr(pos1 + 3, pos2 - (pos1 + 3));cout << url2.c_str() << endl;tutu::string url3 = url.substr(pos2 + 1);cout << url3.c_str() << endl;}void Test5(){tutu::string s1("hello world");cout << s1 << endl;cin >> s1;cout << s1 << endl;}void Test6(){tutu::string s1("hello world");cout << s1.find('o') << endl;cout << s1.find("wor") << endl;}void Test7(){tutu::string s1("hello");//尾插字符s1.push_back('x');cout << s1.c_str() << endl;s1 += "world";cout << s1.c_str() << endl;//尾插字符串s1.append("tutu");cout << s1.c_str() << endl;}void Test8(){tutu::string s1("hello");//特定位置插入字符s1.insert(5, 'x');cout << s1.c_str() << endl;//删一个字符s1.erase(5, 1);cout << s1.c_str() << endl;//特定位置插入字符串s1.insert(5, "tutu");cout << s1.c_str() << endl;//删多个字符s1.erase(5, 4);cout << s1.c_str() << endl;}}int main(){tutu::Test8();return 0;}
5.结语
以上就是自主实现string类的所有内容啦~ 完结撒花 ~???