我们不仅要会使用strng的接口,还要模拟实现,更深地理解strng的底层逻辑。这里我们最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数这些比较核心的接口。
1.准备工作
我们依旧采用声明和定义分离的方式模拟实现string,跟之前模拟实现Stack、顺序表那些是同样的操作,建三个文件,一个头文件string.h,两个源文件test.cpp和string.cpp。
在string.h中用命名空间分隔一下,因为c++库里面也有string,避免冲突。string.h里面写一些会用到的头文件,一个string类,string类的成员变量都是老朋友了,size和capacity也是介绍过的,和模拟实现顺序表差不多的,前面的博文说过,string可以认为是char类型的顺序表。
#include <iostream>#include <assert.h>using namespace std;namespace lyj //用命名空间与库里的string分隔开{class string{private:char* _str;size_t _size;size_t _capacity;};}
2.基础接口
短小而且频繁被调用的函数,就直接放在string类里面,就不做声明和定义分离了。
2.1 无参构造
还是写在string.h里的string类,作为string类的成员函数。
class string{public:string() //无参构造:_str(new char[1]{'\0'}) //不能是nullptr,_size(0),_capacity(0){}private:char* _str;size_t _size;size_t _capacity;};
无参构造走初始化列表,一个一个初始化,不传参_size和_capacity都是0,但是_str不可以为nullptr,这里要开辟一个空间,初始化为'\0'。
2.2 带参构造
写在string类里面。带参构造就不要走初始化列表初始化了,因为不是很方便,我们就在函数体里实现。
class string{public:string() //无参构造:_str(new char[1]{'\0'}),_size(0),_capacity(0){}string(const char* str) //带参构造{_size = strlen(str);_capacity = _size; //capacity大小不包括\0_str = new char[_capacity + 1]; //开空间时多开一个strcpy(_str, str);//拷贝 }private:char* _str;size_t _size;size_t _capacity;};
传参的时候_size就是传过来的str的大小,_capacity也初始化的和size一样大,切记,_capacity大小是不包括'\0'的,但是我们开空间的时候要多开一个,所以new char的大小是_capacity+1。空间开好之后就把str的数据拷贝到_str去,就初始化好了。
在test.cpp中测试一下带参构造和无参构造。
#include "string.h"namespace lyj //命名空间保持一致{void test1(void){string s1; //不传参string s2("hello world");//传参}}int main(){lyj::test1(); //指定命名空间调用函数return 0;}
2.3 无参构造和带参构造结合
两种构造函数可以结合成一个构造函数,如下。
string(const char* str = "") //给缺省值,什么都没有的字符串{_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);//拷贝 }
什么都没有的字符串自带\0,strlen求的是\0之前的字符长度,如果不传参,strlen(str)的结果就是缺省值的空字符串大小,为0, _capacity = _size = 0,new的大小为1;如果传参就是带参构造的结果。
在test.cpp中测试一下这个结合的构造函数。
#include "string.h"namespace lyj //命名空间保持一致{void test1(void){string s1; //不传参string s2("hello world");//传参}}int main(){lyj::test1(); //指定命名空间调用函数return 0;}
结果是正确的。
2.3 析构函数
写在string类里面。
~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}
析构函数没啥多说的。
2.5 c_str
写在string类里面。这个接口也算是打印函数,我们还没实现流插入流提取函数,暂时用这个。
const char* c_str(){return _str;}
在test.cpp中测试一下。
namespace lyj //命名空间保持一致{void test1(void){string s1; //不传参string s2("hello world");//传参 //打印cout << s1.c_str() << endl;cout << s2.c_str() << endl;}}int main(){lyj::test1(); //指定命名空间调用函数return 0;}
2.6 size 、capacity 和 operator[]
都写在string类里面。
size_t size() const{return _size;}size_t capacity() const{return _capacity;}
operator提供两个版本,普通对象和const对象。 普通对象返回引用,可以修改,const对象返回const引用,不可修改。
char& operator[](size_t pos) //普通对象{assert(pos < _size);//断言,防止越界return _str[pos];}const char& operator[](size_t pos) const //const对象{assert(pos < _size);//断言,防止越界return _str[pos];}
2.7 拷贝构造
还是写在string.h里的string类,作为string类的成员函数。
//假设用s1拷贝s2,即s2(s1)string(const string& s)//拷贝构造{_str = new char[s._capacity]+ 1; //s2开和s1一样的大小strcpy(_str, s._str);//拷贝数据_size = s._size;_capacity = s._capacity;}
有资源的申请是深拷贝。
2.8 operator= 赋值
还是写在string.h里的string类,作为string类的成员函数。
//s2 = s1;string operator=(const string& s) //赋值{delete[] _str;_str = new char[s._capacity + 1];_size = s._size;_capacity = s._capacity;return *this;}
但是这个代码,自己赋值给自己就会出问题。所以我们要做一个改动。
string operator=(const string& s) //赋值{if (this != &s) //不是自己给自己赋值时{delete[] _str;_str = new char[s._capacity + 1];_size = s._size;_capacity = s._capacity;}return *this;}
3.迭代器和范围for
先实现迭代器。
typedef char* iterator; //给char*换个名字叫iteraatoriterator begin(){return _str;}iterator end(){return _str + _size;//_str + _size是'\0'的位置}
但是不可以认为迭代器就是指针,没有这么简单。 只能在string里面用指针这样实现一下。
在test.cpp里测试。
namespace lyj //命名空间保持一致{void test1(void){string s1; //不传参string s2("hello world");//传参//迭代器遍历string::iterator it = s2.begin();while (it != s2.end()){cout << *it << " ";++it;}cout << endl;}}int main(){lyj::test1(); //指定命名空间调用函数return 0;}
范围for可以直接用,因为我们说过,范围for其实底层就是迭代器。
如果我们现在把迭代器的实现注释掉,范围for也不能用。
除了普通迭代器,还有const迭代器。
typedef const char* const_iterator; //给const char*换个名字叫const_iteraatorconst_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}
到这我们就大概实现了string的大概框架,增删查改的接口我们下次再说,本篇就到这里,拜拜~