模拟实现的节奏比较快,大家可以先去看看博主的关于string的使用,然后再来看这里的模拟实现过程
String模拟实现大致框架迭代器以及迭代器的获取(public定义,要有可读可写的也要有可读不可写的)/成员变量(private定义) 并且为了不和库的string冲突,我们需要自己搞一个命名空间
namespace cyx { class string { 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; } private: char* _str; size_t _size; size_t _capacity; static const size_t npos; //static const size_t npos=-1 vs下int类型支持给static和const同时修饰的变量用缺省值 }; const size_t string::npos = -1;//静态成员遍历类外初始化 }
nops是一个静态变量,要类内定义类外初始化,由于nops是size_t类型,赋值-1会被强转成最大的无符号整数
一、构造+析构+赋值重载(Member functions)
1.1 全缺省构造函数
//构造函数 string(const char* str = "") :_size(strlen(str)) { _capacity = _size == 0 ? 3 : _size; _str = new char[_capacity + 1];// 多开一块\0的空间 strcpy(_str, str); }
1、 “ ”空字符串其实里面默认就有\0,所以缺省值直接给空字符串就行
2、_capacity 一定要给初始空间,不然后面如果涉及到2倍扩容,为0的话就扩不了了
3、要多开一块空间,连这个\0
4、可以复用strcpy函数
1.2 拷贝构造函数的传统写法
传统的思路就是拷贝,也就是我们先根据被拷贝的对象的_capacity开空间,然后再进行拷贝
string(const string& s) :_size(s._size) , _capacity(s._capacity) { _str = new char[_capacity + 1]; strcpy(_str, s._str); }
1.3 拷贝构造函数的现代写法和swap函数
现代的思路就是,尝试去复用,比如说我们可不可以直接去利用前面的构造函数去构造一个新对象,然后再窃取新对象的成果(利用swap)
//交换字符串 void swap(string& s) { std::swap(_str, s._str);//浅拷贝,没有开空间,只是改变指针指向 std::swap(_size, s._size); std::swap(_capacity, s._capacity); }
string(const string& s) :_str(nullptr) { string temp(s._str); swap(temp); }
传统写法和现代写法参数一样,不能重载,只能保留一个
1. 4 迭代器区间构造
//迭代器区间构造 template <class InputIterator> string(InputIterator first, InputIterator last) :_str(new char[last-first+1]) ,_size(0) ,_capacity(last-first) { while (first != last) { push_back (*first); ++first; } }
这里定义的模版InputIterator的意思其实是这边我们可以传不同类型对象的迭代器,我们并不知道这个迭代器里面有多少元素,所以得用指针-指针,即last-first来确定我们的容量,然后再开空间,一个个进行尾插。
1.5 赋值重载的传统写法
传统的思路就是,先开一块新空间拷贝旧数据,然后再释放掉原空间,这里尽量是先开空间再释放,避免我们开空间失败导致原始数据的丢失。
//赋值重载(传统写法) string& operator=(const string& s) { if (this != &s)//避免自赋值 { //先开新空间再毁旧空间,避免新空间开失败导致数据丢失 char* temp = new char[s._capacity + 1]; strcpy(temp, s._str); delete[]_str; _str = temp; _size = s._size; _capacity = s._capacity; } return *this; }
注意:要注意自赋值情况!!否则刚拷贝完自己就被释放了
1.6 赋值重载的现代写法
现代的思路就是,既然被赋值这个空间不想要,那就和形参直接交换吧!!但是要注意的是,这里就不能像传统的一样用const引用了,否则不想要的空间就给到我们的赋值对象了,这边就得用传值传参,这样被交换的就只是一个临时拷贝,不想要的空间随着栈帧的结束被销毁。
//赋值重载(现代写法) string& operator=(string s)//必须用值传递,否则会导致原数据的丢失 { swap(s); return *this; }
传统写法和现代写法参数不一样,一个是const引用,一个传值传参,所以可以同时存在。
1.7 析构函数
~string() { delete[]_str; _str = nullptr; _size = _capacity = 0; }
二、容量相关的接口(Capacity)
2.1 size、capacity
//获取当前size size_t size() const { return _size; } //获取当前capacity size_t capacity() const { return _capacity; }
2.2 reserve
如果n比_capacity大,思路就是先开新空间进行拷贝,然后再释放旧空间
//改变capacity void reserve(size_t n) { if (n > _capacity) { char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } }
2.3 resize
如果n比_size大,我们根据n去扩容,然后因为string的底层是字符数组,所以memset就很适合,他就是可以去一个字节一个字节设置成我们想要的。缺省值给‘\0’
//改变size void resize(size_t n, char ch = '\0') { if (n > _size) { reserve(n); memset(_str + _size, ch, n - _size); } _size = n; _str[_size] = '\0'; }
2.4 clear和empty
//清理字符串 void clear() { _str[0] = '\0'; _size = 0; } //判断字符串是否为空 bool empty() { return _capacity == 0; }
2.5 shrink_to_fit(用得少)
缩容到size位置,平时用的很少,我们要尽量减少扩容,思路也是一样的,开辟新空间去拷贝,再释放旧空间
void shrink_to_fit() { char* temp = new char[_size + 1]; strcpy(temp, _str); delete[] _str; _str = temp; _capacity = _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]; } //比较类型重载 //> bool operator>(const string& s) const { return strcmp(_str, s._str) > 0; } //== bool operator==(const string& s) const { return strcmp(_str, s._str) == 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); }
有了[ ]、迭代器,我们可以展示3种遍历方法:下标访问、迭代器区间访问、范围for访问
void Print(const string& s) { //下标遍历 for (size_t i = 0; i < s.size(); ++i) cout << s[i] << " "; cout << endl; //迭代器遍历访问 string::const_iterator it = s.begin(); while (it != s.end()) { cout << *it << " "; ++it; } cout << endl; //范围for for (auto &e : s) cout << e << " "; cout << endl; }
四、增删接口(Modifiers)
字符串的增删接口一般要设置两个版本,一个是操作字符,一个是操作字符串,我们先把最难的insert和erase搞了,其他的就可以复用了
4.1 insert
//指定位置插入一个字符 string& insert(size_t pos, char ch) { assert(pos <= _size); //判断是否需要扩容 if (_size + 1 > _capacity) reserve(2 * _capacity); //pos后的数据要往后挪,所以要从后往前移 size_t end = _size + 1; while (end > pos) { _str[end] = _str[end - 1]; --end; } _str[pos] = ch; ++_size; return *this; } //指定位置插入一个字符串 string& insert(size_t pos, const char* str) { assert(pos <= _size); //判断是否需要扩容 size_t len = strlen(str); if (_size + len > _capacity) reserve(_size + len); size_t end = _size + len; while (end > pos + len - 1) { _str[end] = _str[end - len]; --end; } //拷贝插入 strncpy(_str + pos, str, len); _size += len; return *this; }
4.2 erase
//删除指定位置之后的字符 string& erase(const size_t pos, size_t len = npos) { assert(pos <= _size); //有两种情况,一种是全删完,一种是删中间的一部分 //全删完 if (len == npos || pos + len > _size)//len == npos必须写,因为nops是无符号最大值,+的话会溢出 { _str[pos] = '\0'; _size = pos; } //删一部分 else { strcpy(_str + pos, _str + pos + len); _size -= len; } return *this; }
len == npos这个判断条件必须写,因为nops已经是无符号最大值了,再+会溢出
4.3 push_back
//尾插一个字符 void push_back(char ch) { insert(_size, ch); }
4.4 append
//尾插一个字符串 string& append(const char* str) { return insert(_size, str); }
4.5 +=重载(用的多)
//+=重载 字符 string& operator+=(char ch) { push_back(ch); return *this; } //+=重载 字符串 string& operator+=(const char* str) { append(str); return *this; }
五、字符串操作(String operations)
5.1 获取c类型字符串
//获取c类型字符串 const char* c_str() const { return _str; }
5.2 寻找指定字符串并返回下标
//寻找指定字符串并返回下标 size_t find(const char* str, size_t pos = 0)const { assert(pos < _size); char* p = strstr(_str + pos, str); if (p == nullptr) return npos; return p - _str; }
六、重载流插入和流提取
我们不能写在类内,否则会*this会占用第一个操作数,不符合我们的使用习惯。
6.1 流提取
//重载<< std::ostream& operator<< (std::ostream& out, const string& s) { //可以用范围for,也可以用迭代器 范围for是用宏写的,本质上也是迭代器! for (auto ch : s) out << ch; return out; }
6.2 流插入
首先我们要知道两点,1.>>只会读取到空格或者换行结束 2.读取前会清理掉原空间的数据
//重载>> std::istream& operator>> (std::istream& in, string& s) { //读取前要先清理掉原来存在的字符 s.clear(); //用get获取字符 char ch = in.get(); //先用一个数组存起来,再一起加 char buff[128]; size_t i = 0; while (ch != ' ' && ch != '\n') { //原始方法,一个字符一个字符加太麻烦,先用一个数组存起来,再一起加 //s += ch; buff[i++] = ch; if (i == 127) { buff[127] = '\0'; s += buff; i = 0;//重置i } ch = in.get(); } //循环结束后可能还要一些字母没有存进去 if (i != 0) { buff[i] = '\0'; s += buff; } return in; }
我们用一个buff数组来暂时存储需要插入的字符,等存完了再+=,这样可以提高效率,以空间换时间
七、string模拟实现全部代码
namespace cyx { using std::endl; using std::cout; class string { 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; } //交换字符串 void swap(string& s) { std::swap(_str, s._str);//浅拷贝,没有开空间,只是改变指针指向 std::swap(_size, s._size); std::swap(_capacity, s._capacity); } //构造函数 string(const char* str = "") :_size(strlen(str)) { _capacity = _size == 0 ? 3 : _size; _str = new char[_capacity + 1];// 多开一块\0的空间 strcpy(_str, str); } //拷贝构造函数(传统写法) /*string(const string& s) :_size(s._size) , _capacity(s._capacity) { _str = new char[_capacity + 1]; strcpy(_str, s._str); }*/ //拷贝函数的现代写法 string(const string& s) :_str(nullptr) { string temp(s._str); swap(temp); } //迭代器区间构造 template <class InputIterator> string(InputIterator first, InputIterator last) :_str(new char[last-first+1]) ,_size(0) ,_capacity(last-first) { while (first != last) { push_back (*first); ++first; } } //赋值重载(传统写法) string& operator=(const string& s) { if (this != &s)//避免自赋值 { //先开新空间再毁旧空间,避免新空间开失败导致数据丢失 char* temp = new char[s._capacity + 1]; strcpy(temp, s._str); delete[]_str; _str = temp; _size = s._size; _capacity = s._capacity; } return *this; } //赋值重载(现代写法) string& operator=(string s)//必须用值传递,否则会导致原数据的丢失 { swap(s); return *this; } //析构函数 ~string() { delete[]_str; _str = nullptr; _size = _capacity = 0; } //获取c类型字符串 const char* c_str() const { return _str; } //获取当前size size_t size() const { return _size; } //获取当前capacity size_t capacity() const { return _capacity; } //[]重载(可读可写) char& operator[](size_t pos) { assert(pos < _size);//确保地址有效 return _str[pos]; } //[]重载(可读不可写) const char& operator[](size_t pos) const { assert(pos < _size);//确保地址有效 return _str[pos]; } //比较类型重载 //> bool operator>(const string& s) const { return strcmp(_str, s._str) > 0; } //== bool operator==(const string& s) const { return strcmp(_str, s._str) == 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); } //改变size void resize(size_t n, char ch = '\0') { if (n > _size) { reserve(n); memset(_str + _size, ch, n - _size); } _size = n; _str[_size] = '\0'; } //改变capacity void reserve(size_t n) { if (n > _capacity) { char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } } //尾插一个字符 void push_back(char ch) { insert(_size, ch); } //尾插一个字符串 string& append(const char* str) { return insert(_size, str); } //+=重载 字符 string& operator+=(char ch) { push_back(ch); return *this; } //+=重载 字符串 string& operator+=(const char* str) { append(str); return *this; } //指定位置插入一个字符 string& insert(size_t pos, char ch) { assert(pos <= _size); //判断是否需要扩容 if (_size + 1 > _capacity) reserve(2 * _capacity); //pos后的数据要往后挪,所以要从后往前移 size_t end = _size + 1; while (end > pos) { _str[end] = _str[end - 1]; --end; } _str[pos] = ch; ++_size; return *this; } //指定位置插入一个字符串 string& insert(size_t pos, const char* str) { assert(pos <= _size); //判断是否需要扩容 size_t len = strlen(str); if (_size + len > _capacity) reserve(_size + len); size_t end = _size + len; while (end > pos + len - 1) { _str[end] = _str[end - len]; --end; } //拷贝插入 strncpy(_str + pos, str, len); _size += len; return *this; } //删除指定位置之后的字符 string& erase(const size_t pos, size_t len = npos) { assert(pos <= _size); //有两种情况,一种是全删完,一种是删中间的一部分 //全删完 if (len == npos || pos + len > _size)//len == npos必须写,因为nops是无符号最大值,+的话会溢出 { _str[pos] = '\0'; _size = pos; } //删一部分 else { strcpy(_str + pos, _str + pos + len); _size -= len; } return *this; } //寻找指定字符并返回下标 size_t find(char ch, size_t pos = 0)const { assert(pos < _size); for (size_t i = pos; i < _size; ++i) if (_str[i] == ch) return i; return npos; } //寻找指定字符串并返回下标 size_t find(const char* str, size_t pos = 0)const { assert(pos < _size); char* p = strstr(_str + pos, str); if (p == nullptr) return npos; return p - _str; } //清理字符串 void clear() { _str[0] = '\0'; _size = 0; } //缩容到他的size void shrink_to_fit() { char* temp = new char[_size + 1]; strcpy(temp, _str); delete[] _str; _str = temp; _capacity = _size; } //判断字符串是否为空 bool empty() { return _capacity == 0; } private: char* _str; size_t _size; size_t _capacity; static const size_t npos; //static const size_t npos=-1 vs下int类型支持给static和const同时修饰的变量用缺省值 }; const size_t string::npos = -1;//静态成员遍历类外初始化 //重载<< std::ostream& operator<< (std::ostream& out, const string& s) { //可以用范围for,也可以用迭代器 范围for是用宏写的,本质上也是迭代器! for (auto ch : s) out << ch; return out; } //重载>> std::istream& operator>> (std::istream& in, string& s) { //读取前要先清理掉原来存在的字符 s.clear(); //用get获取字符 char ch = in.get(); //先用一个数组存起来,再一起加 char buff[128]; size_t i = 0; while (ch != ' ' && ch != '\n') { //原始方法,一个字符一个字符加太麻烦,先用一个数组存起来,再一起加 //s += ch; buff[i++] = ch; if (i == 127) { buff[127] = '\0'; s += buff; i = 0;//重置i } ch = in.get(); } //循环结束后可能还要一些字母没有存进去 if (i != 0) { buff[i] = '\0'; s += buff; } return in; } //遍历方法的展示 void Print(const string& s) { //下标遍历 for (size_t i = 0; i < s.size(); ++i) cout << s[i] << " "; cout << endl; //迭代器遍历访问 string::const_iterator it = s.begin(); while (it != s.end()) { cout << *it << " "; ++it; } cout << endl; //范围for for (auto &e : s) cout << e << " "; cout << endl; }
有新的后面再补充哦!