C++实现String类的方法详解
string模拟实现string简单实现首先我们不考虑string类的增删查改只是先给string类搭建一个最简单的框架出来。和C语言中相同为了存储一个字符串我们的string类需要一个char*的指针来指向字符像这个对象。作为一个对象string还需要有构造函数析构函数和拷贝构造。12345678910111213141516171819202122232425classstring{private:char*_str;public:string(constchar*str): _str(newchar[strlen(str) 1])// 1 是给\0留出位置{strcpy(_str, str);}string(conststring str): _str(newchar[strlen(str._str) 1]){strcpy(_str, str._str);}~string(){if(_str){delete[] _str;_str nullptr;}}};有的朋友可能会疑惑这里的构造函数和拷贝构造函数为什么不用编译器自动生成的直接将_str指向原本的字符串就可以了为什么还要开辟空间呢这是因为我们在日常使用中假如有两个string类 a 和 bb是由a拷贝构造而来一般情况下我们在修改b的同时不希望a也被改。此外如果直接将_str指向原本的字符串会导致的问题是当 a 和 b用完被销毁时会对同一片空间调用两次析构函数对同一片空间释放两次。所以在这里我们需要重新开辟一片空间来给这个string。这也就是所谓的深拷贝。然后为了访问string类中的元素我们需要对运算符[]进行重载。12345char operator[](size_tpos){assert(pos strlen())return_str[pos];}这样我们就实现了一个简单的string类。string完整实现构造函数析构函数拷贝构造之前我们实现的一个string类是一个最简单的string类它没有办法进行增删查改接下来我们就来一点一点完善它。要实现增删查改我们还需要两个变量一个记录string类当前长度一个记录string类的容量大小。加入这两个变量后我们原本的构造函数拷贝构造和析构函数需要发生一点点变化。1234567891011121314151617181920212223242526272829303132classstring{private:char*_str;size_t_size;size_t_capacity;public:string(constchar*str ): _size(strlen(str)), _capacity(_size){_str newchar[_capacity 1];strcpy(_str, str);}string(conststring str): _size(str._size), _capacity(str._capacity){_str newchar[_size 1];strcpy(_str, str._str);}~string(){if(_str){delete[] _str;_str nullptr;_size _capacity 0;}}};运算符重载接下来我们来实现一下string类的运算符。在实现运算符重载时我们需要做的只是实现少数几个运算符即可其他的运算符可复用前面实现的运算符来达到我们想要的效果。123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657//关系运算符的重载booloperator(conststring s){returnstrcmp(_str, s.c_str());}booloperator(conststring s){returnstrcmp(_str, s.c_str()) 0;}booloperator!(conststring s){return!(*this s);}booloperator(conststring s){return*this s || *this s;}booloperator(conststring s){return!(*this s);}booloperator(conststring s){return!(*this s);}//操作运算符的重载string operator(string str){if(*this! str){char*tmp newchar[str._capacity 1];strcpy(tmp,str._str);delete[] _str;_str tmp;_size str._size;_capacity str._capacity;}return*this;}charoperator[](size_tpos){assert(pos _size);return*(_str pos);}constcharoperator[](size_tpos)const{assert(pos _size);return*(_str pos);}string接口实现首先是比较简单的size()empty()capacity()clear()。这些接口大部分直接访问string类的成员变量就可以得到结果。123456789101112131415161718192021size_tsize()const{return_size;}size_tcapacity()const{return_capacity;}boolempty()const{return0 _size;}//后面添加const的目的是为了让所有对象都可以进行访问voidclear(){_str[0] \0;_size 0;_capacity 0;}因为后面的接口大部分都需要进行空间的调整所以首先我们将调整空间的接口reserve和resize实现。1234567891011121314151617181920212223242526voidreserve(size_tn){if(n _capacity)//判断是否需要扩容{char*tmp newchar[n 1];strcpy(tmp, _str);delete[] _str;_str tmp;_capacity n;}}//resize和reserve的区别在于reserve只是开空间而resize还要进行初始化voidresize(size_tn,charc \0){if(n _capacity){reserve(n);//开空间复用reserve}for(size_ti _size; i n; i){_str[i] c;}_size n;_str[_size] \0;}接下来是插入的实现首先是push_back这个比较简单找到尾部进行插入即可。123456789voidpush_back(charn){if(_size _capacity){reserve(_capacity 0 ? 4 : _capacity * 2);//开空间复用reserve}_str[_size] n;_str[_size] \0;}接下来是insert这个较push_back而言要麻烦一些因为除了尾插其他地方去插入数据你都需要挪动后面数据的位置。1234567891011121314151617181920212223string insert(size_tpos,constchar*str){//检查空间是否足够assert(pos _size);size_tlen strlen(str);if(len _size _capacity){reserve(len _size);}//挪动后面的数据size_tend _size len;while(end ! pos len - 1){_str[end] _str[end - len];--end;}//数据插入strncpy(_str pos, str, len);_size len;return*this;}写完了插入接下来当然就是删除接口eraser123456789101112131415161718192021string eraser(size_tpos,size_tlen npos)//npos为静态变量值为-1{assert(pos _size);if(len npos || pos len _size)//将位置后的元素全部删除{_str[pos] \0;_size pos;}else//删除位置后的部分元素{size_tbegin pos len;while(begin _size){_str[begin - len] _str[begin];begin;}_size _size - len;}return*this;}迭代器的实现C中的迭代器和指针类似。为什么要有迭代器呢因为C中有各种各样的容器每个容器它背后的存储方式不同访问方式也不同为了让使用者的使用成本降低使大部分容器可以以相同的方式去访问就有了迭代器的产生。接下来我们来实现string的迭代器其实string的迭代器就是一个指针。并不用去封装特别的东西。12345678910111213141516171819202122typedefchar*iterator;typedefconstchar*const_iterator;const_iterator begin()const{return_str;}const_iterator end()const{return_str _size;}iterator begin(){return_str;}iterator end(){return_str _size;}部分函数优化和完善前面在写运算符重载时还有部分运算符未重载在此加上12345678910string operator(constchar*str){append(str);}string operator(charn){push_back(n);return*this;}同时增加拷贝构造和operator的现代写法之前我们写拷贝构造和operator时都需要自己去重新开空间那么这个活可不可以让其他人帮我做呢我们来看看下面这一段代码12345678910111213141516171819voidswap(string str){std::swap(_str, str._str);std::swap(_size, str._size);std::swap(_capacity, str._capacity);}string(conststring s): _str(nullptr), _size(0), _capacity(0){string tmp(s._str);swap(tmp);}string operator(string s){swap(s);return*this;}上述代码同样可以帮我们完成拷贝构造和operator 原理如下1.首先是拷贝构造我们在拷贝构造中使用构造函数去创建一个临时对象这个临时对象在创建时就帮我们开辟了空间。然后我们将临时对象和此对象的所有成员进行一个交换这样此对象就可以接管临时对象创建的那块空间我们的拷贝构造也就成功了2.在operator这我们使用的是传值传参。好处在于由于我们的string类是自定义对象所以在传参时会去调用拷贝构造这样传过来的str参数也拥有了自己的空间此时我们和拷贝构造一样将str所开辟的那块空间接管同时由于str是函数参数当函数结束时str会去调用析构函数进行一个空间释放。