c++领域展开第十五幕——STL(String类的模拟实现)超详细!!!!
文章目录
前言
上篇博客已经简单的介绍了string类的一些接口,并且做了一些了解
同时也刷了一些oj题目,熟练使用一些string的函数
今天我们来模拟实现一下string类
fellow me
string类的模拟实现
首先,string类是在stl库实现之前出现的,后面的stl库的内容大部分都和string类的接口类似
string类比起以前的一些数据结构,多了很多东西
迭代器就是一方面,能够更好的耦合算法和类和对象
string类——迭代器的模拟
我们先来看迭代器以及,string类的成员变量
classstring{public:typedefchar* iterator;//迭代器 begin endtypedefconstchar* const_iterator;constchar*c_str()const// 返回字符常量 {return _str;} iterator begin(){return _str;} iterator end(){return _str + _size;} const_iterator begin()const// 这里是const修饰的迭代器函数{// 在处理一些const修饰的对象时,如果直接访问上面的普通begin return _str;// 会引发权限的 放大 导致程序不能执行 所以这里复写了const的版本} const_iterator end()const{return _str + _size;} size_t size()const// 返回大小 // 这里从const是修饰大小和容量不能被改变{// 权限能缩小 不会放大return _size;} size_t capacity()const// 返回容量{return _size;}private:// char* _str =nullptr;// 字符串 size_t _size =0;// 大小 size_t _capacity =0;// 容量conststatic size_t npos;// 模拟string类里面缺省参数的 npos参数};string类——默认成员函数
构造函数、析构函数、拷贝构造、运算符重载
在string类标准库里面有好几个版本的构造函数
这里模拟实现了其中两个
析构函数还是比较简单的
主要是拷贝构造函数有两个版本来实现
string::string(size_t n,char ch)// 初始化列表:_str(newchar[n +1]),_size(n),_capacity(n){for(size_t i =0; i < n; i++){ _str[i]= ch;} _str[_size]='\0';} string::string(constchar* str)// 构造函数 :_size(strlen(str)){ _capacity = _size; _str =newchar[_size +1];strcpy(_str, str);}string::~string()// 析构函数{delete[] _str; _str =nullptr; _size =0; _capacity =0;} string::string(const string& s)// 拷贝构造 正常我们就是这样实现拷贝构造{// 防止在一些占用空间的参数 比如字符串 _str =newchar[s._capacity +1];// 调用系统的拷贝构造,导致浅拷贝,多次析构同一位置strcpy(_str, s._str);// 引起程序崩溃 _size = s._size; _capacity = s._capacity;}// 这里提供一种新的实现方法 同时也更简单明了// 而且这里的swap函数还有其他的作用 能被复用void string::swap(string& s){ std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity);}// 现代写法 直接复用 string::string(const string& s)// 这里直接定义新的string 如果this和其交换{// 两种方法原理是一样的 string tmp(s._str);swap(tmp);}// 另外还有一个 销毁函数voidclear()// 销毁{ _str[0]='\0'; _size =0;}下面就是运算符重载部分了
// s1 = s2// s1 = s1void string::swap(string& s){ std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity);} string& string::operator=(const string& s)// 赋值运算符重载{if(this!=&s){ string tmp(s._str);swap(tmp);// 现代写法就是直接复用swap//delete[] _str; // 传统的就是这样一步一步赋值给另外一个对象//_str = new char[s._capacity + 1];//strcpy(_str, s._str);//_size = s._size;//_capacity = s._capacity;}return*this;}// 处理string像访问数组一样 直接堆 [] 进行重载char&operator[](size_t pos)// []重载 {assert(pos < _size);return _str[pos];}constchar&operator[](size_t pos)const// const修饰 不能改变内容{assert(pos < _size);return _str[pos];}// 下面就是一些判断字符串大小的函数 比较大小的函数还是比较简单的bool string::operator==(const string& s)const{returnstrcmp(_str, s._str)==0;// 这里复用了c语言里面的 strcmp 比较函数}bool string::operator!=(const string& s)const{return!(*this== s);}bool string::operator<(const string& s)const{returnstrcmp(_str, s._str)<0;}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);}string类——常用函数接口
这里模拟实现一些string类的常用接口函数
reserve、push_back、append、insert、erase、find、substr等接口
实现string的增删改查功能
话不多说,上代码
// reserve函数 预留空间 void string::reserve(size_t n){if(n > _capacity){//cout << "reserve:" << n << endl;char* tmp =newchar[n +1];// 如果原本的空间没有到达指定大小 strcpy(tmp, _str);// 扩容delete[] _str; _str = tmp; _capacity = n;}}// 尾插函数 可以做 +=单个字符的复用函数void string::push_back(char ch){if(_size +1> _capacity)// 如果空间不够 扩容{// 扩容reserve(_capacity ==0?4: _capacity *2);} _str[_size]= ch;// 正常尾插 ++_size; _str[_size]='\0';// 注意\0 结尾}// append函数 在字符串尾部接入字符串 可以做+=字符串函数的复用void string::append(constchar* str){ size_t len =strlen(str);if(_size + len > _capacity){// 扩容 size_t newCapacity =2* _capacity;if(_size + len >2* _capacity){ newCapacity = _size + len;}reserve(newCapacity);}strcpy(_str + _size, str); _size += len;}// +=字符串 和 += 字符 函数的重载 string& string::operator+=(char ch){push_back(ch);return*this;} string& string::operator+=(constchar* str){append(str);return*this;}// 插入函数 在pos 位置 插入 n 个 ch 字符void string::insert(size_t pos, size_t n,char ch){assert(pos <= _size);assert(n >0);if(_size + n > _capacity)// 判断扩容{// 扩容 size_t newCapacity =2* _capacity;if(_size + n >2* _capacity){ newCapacity = _size + n;}reserve(newCapacity);}// 挪动数据/* int end = _size; // pos为size_t 在和int比较时 int 会转为size_t 导致程序出错 while (end >= (int)pos) { _str[end + n] = _str[end]; --end; }*/ size_t end = _size + n;// 这样写代码更健壮 而且end不会到负数部分while(end > pos + n -1){ _str[end]= _str[end - n];--end;}for(size_t i =0; i < n; i++){ _str[pos + i]= ch;} _size += n;/*string tmp(n, ch); insert(pos, tmp.c_str());*/// 这里复用insert的 在pos位置插入字符串}// insert 在pos位置插入字符串void string::insert(size_t pos,constchar* str){//assert(pos <= _size);//size_t n = strlen(str);//if (_size + n > _capacity)//{// // 扩容// size_t newCapacity = 2 * _capacity;// if (_size + n > 2 * _capacity)// {// newCapacity = _size + n;// }// reserve(newCapacity);//}//size_t end = _size + n;//while (end > pos + n - 1)//{// _str[end] = _str[end - n];// --end;//} // 正常写法 size_t n =strlen(str);// 间接扩容insert(pos, n,'x');// 直接用前面的insert复用 先插入 n 个字符 然后再覆盖一下for(size_t i =0; i < n; i++){ _str[pos + i]= str[i];// 直接覆盖 // 这样的代码就简洁很多 复用性高 好溯源}}// 删除函数 erase 指定位置删除长度为 len 的字符串void string::erase(size_t pos, size_t len){if(len >= _size - pos){// 删完了 _str[pos]='\0'; _size = pos;}else{ size_t end = pos + len;// size_t防止int强转while(end <= _size){ _str[end - len]= _str[end];++end;} _size -= len;}}// find查找函数 查找一个字符 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;}// 查找一个字符串 size_t string::find(constchar* str, size_t pos){constchar* p =strstr(_str + pos, str);// 这里复用c里面的strstr if(p ==nullptr){return npos;}else{return p - _str;}}//substr 截取字符串 string string::substr(size_t pos, size_t len){ size_t leftlen = _size - pos;if(len > leftlen) len = leftlen; string tmp;// 构造tmp tmp.reserve(len);for(size_t i =0; i < len; i++){ tmp += _str[pos + i];}return tmp;}string类——输入输出重载
剩下最后一个有点麻烦但又还好的接口
重载输入流和输出流,另外还有getline这个函数
// 输出函数是比较简单的 ostream&operator<<(ostream& out,const string& s){for(auto ch : s){ out << ch;}return out;}// 输入函数就有很多地方需要处理了 istream&operator>>(istream& in, string& s){ s.clear();// 先清除s里面的内容 防止意外// 输入短串,不会浪费空间// 输入长串,避免不断扩容const size_t N =1024;// 如果输入很长的字符串 一步一步输入的话会不断扩容char buff[N];// 这里开一个字符数组存起来 后序直接 += 就行 大大减少了扩容一步到位int i =0;char ch = in.get();while(ch !=' '&& ch !='\n'){ buff[i++]= ch;if(i == N -1){ buff[i]='\0'; s += buff; i =0;} ch = in.get();}if(i >0){ buff[i]='\0'; s += buff;}return in;}// getline 输入字符串 直到指定字符截止 istream&getline(istream& in, string& s,char delim){ s.clear();const size_t N =1024;char buff[N];int i =0;char ch = in.get();while(ch != delim)// 如果字符不是指定截止字符 就一直输入{// 不管是空格还是\n buff[i++]= ch;if(i == N -1){ buff[i]='\0'; s += buff; i =0;} ch = in.get();}if(i >0){ buff[i]='\0'; s += buff;}return in;}整个string类的模拟实现就差不多到这里啦
总结
今天把string类模拟实现了一遍
在模拟实现过程中,发现了很多的问题
比如哪些重复且作用相同的代码,复用问题
充分利用已有的一些东西,比如std::swap,这个在拷贝构造和赋值重载的时候用的很爽
还有就是一些优化,哪些地方一直构造会费时费力,哪些地方直接复用效果会更好而且效率高
还有就是一些简单的语法知识,比如不经意间的类型强转过程会有意想不到的误区
总之,在模仿中一步一步优化,一步一步学习
借前人之鉴,涨己人之学识,加油
种一棵树最好的时间是十年前,其次是现在