String底层实现
string在C++也是一个重要的知识,但是想要用好它,就要知道它的底层是如何写的,才能更好的用好这个string,那么这次就来实现string的底层,但是string的接口功能是非常多的,我们无法一一来实现它,这次实现的主要是它常用的接口,和重要的接口这次实现的功能有:string的构造函数,拷贝构造函数,析构函数,迭代器(分为const与非const两个版本),reserve,push_back,append,+=(分为+=字符与字符串),clear,swap,流插入,流输出,c_str,size,capacity,empty,resize,[]的重载(非为const与非const两个版本),<,<=,>,>=,==,!=的重载,find(分为查找字符与字符串两个版本)insert(分为插入字符与插入字符串的两个版本),erase。(至于实现了多少个,这里就不数了,挺多的了...
首先做好准备工作: 因为要单独实现string的底层,所以为了避免与库内的string冲突,所以我们把它封装在一个单独命名空间中,其次它的四个私有成员:_str(存储字符串),_capacity(记录容量大小),_size(记录有效字符),npos(在某些函数需要用到它)
一:构造函数
1它的有效个数与大小就是它的长度,用strlen计算即可。
2开一个空间(这里+1为了给 预留位置 开空间时都必须给 多开一个)
3在把字符串拷贝到开好的空间内
1 string(const char* str = "") 2 :_size(strlen(str)) 3 , _capacity(_size) 4 { 5 _str = new char[_capacity + 1];//+1是为了给 ' ' 留位置 string开空间都必须多一个位置 6 strcpy(_str, str); 7 }
二:拷贝构造
拷贝构造分为:传统写法与现代写法
传统写法:该构造就构造,该拷贝就拷贝
1它的有效个数与大小就是它的长度,用strlen计算即可。
2开一个空间 (给 多开一个空间)
3讲字符串拷贝到该空间内
4把该空间赋值给_str
5把它的size与capacity与s同步
1 string(const string& s) 2 :_size(strlen(s._str)) 3 ,_capacity(_size) 4 { 5 char* tmp = new char[_capacity + 1]; 6 strcpy(tmp, s._str); 7 _str = tmp; 8 _size = s._size; 9 _capacity = s._capacity; 10 }
现代写法:利用tmp拷贝,再让他们交换
1因为拷贝构造是拷贝给一个不存在的,所以要先把他们初始化,才能让他们交换
2利用tmp构造一个需要拷贝的字符串
3再让_str与tmp交换
string(const string& s) :_str(nullptr) ,_size(0) ,_capacity(0) { string tmp(s._str); swap(tmp); }
(这里更推荐现代写法)
三:析构函数
1当str不为空
2释放,并且置空,再把它的有效字符与大小置0
1 ~string() 2 { 3 if (_str) 4 { 5 delete[] _str; 6 _str = nullptr; 7 _size = _capacity = 0; 8 } 9 }
四:赋值重载
分为传统写法与现代写法①和②
传统写法
1当不是自己给自己赋值时
2开一个空间
3把字符串拷贝进该空间
4释放掉_str的空间
5把tmp空间赋值给_str
6再把_str与赋值的字符串的有效字符与大小相同
7返回
1 string& operator=(const string &s) 2 { 3 if (this != &s) 4 { 5 char* tmp = new char[s._capacity + 1]; 6 strcpy(tmp, s._str); 7 delete[] _str; 8 _str = tmp; 9 _size = s._size; 10 _capacity = s._capacity; 11 } 12 return *this; 13 }
现代写法①
1利用tmp构造一个字符串
2让_str与tmp交换
3返回
1 string& operator=(const string &s) 2 { 3 string tmp(s._str); 4 swap(tmp); 5 return *this; 6 }
现代写法②
1这里有些特殊,因为需要直接交换而不改变s的数据,就不用引用,而是用传值返回
2把_str与字符串交换
3返回
1 string& operator=(string s) 2 { 3 swap(s); 4 return *this; 5 }
五:swap
因为要完成三个私有成员的交换操作,所以swap里直接交换三个私有成员
这里要借助库里的,但编译器在命名空间内会默认使用该命名空间下的swap,所以我们需要加上std,使用库里的swap
1 void swap(string& s) 2 { 3 std::swap(_str, s._str); 4 std::swap(_size, s._size); 5 std::swap(_capacity, s._capacity); 6 }
六:c_str
因为还没实现流插入,所以展示可以用这个函数打印
因为此函数只是打印,而不需要改变字符串,所以要加上const,防止意外改变
1 const char* c_str()const 2 { 3 return _str; 4 }
七:reserve
这个函数是专门用来扩容的。
1当需要的值大于空间时,就需要扩容
2创建一个空间(为 多开一个空间)
3把_str拷贝到新空间
4再把_str的空间释放
5把新空间赋值给_str
6把新大小capacity改成扩容的大小
1 void reserve(size_t n) 2 { 3 if (n > _capacity) 4 { 5 char* tmp = new char[n + 1]; 6 strcpy(tmp, _str); 7 delete[] _str; 8 _str = tmp; 9 _capacity = n; 10 } 11 }
八:push_back
此函数是用来尾插一个字符的
1先判断空间是否满了
2利用reserve开空间 (这里有特殊情况:有时候我们开的空间是0 那么再继续以2倍扩,是无法扩容的(0*n=0) 所以当capacity为0时 扩容4 如果不为0 二倍扩容
3扩容完后或者不需要扩容时 在有效字符的地方添加要添加的字符
4再让有效字符往后移
5再添加 不然打印时没有 无法停止
1 void push_back(char c) 2 { 3 if (_size == _capacity) 4 { 5 reserve(_capacity == 0 ? 4 : _capacity * 2); 6 } 7 _str[_size] = c; 8 ++_size; 9 _str[_size] = '