string类的模拟实现
前文对于string的常用函数做了讲解,由于string是一个面试官常考的点,总喜欢让模拟实现string类,下面来模拟实现一下string,赋予基本的功能,且逐步完善函数实现方式。
string基本框架的实现
string类的基本框架,比如构造函数,拷贝构造,析构函数,成员变量,起码的push_back等一些能正常使得string运行的函数的实现。
namespace String { class string { public: //迭代器: string中的迭代器实际上就是指针 typedef char* iterator; typedef const char* const_iterator; iterator begin() { //begin 表示的是string的首元素地址 return _str; } iterator end() { //end 返回string最后一个元素的下一个位置,也就是'\0' return _str + _size; } const_iterator begin() const { //begin 表示的是string的首元素地址 return _str; } const_iterator end() const { //end 返回string最后一个元素的下一个位置,也就是'\0' return _str + _size; } //默认构造和带参构造合并,使用缺省参数 string(const char* str = "") //""字符串自带'\0' :_str(new char[strlen(str) + 1]) , _size(strlen(str)) , _capacity(strlen(str)) { //存储字符串 strcpy(_str, str); } //拷贝构造 //string(const string& s) //{ // //深拷贝,就是创建一个大小一样的空间 // _str = new char[s._capacity + 1]; // strcpy(_str, s._str); // _size = s._size; // _capacity = s._capacity; //} string(const string& s) { _str = new char[s._capacity + 1]; memcpy(_str, s._str, s._size + 1); _size = s._size; _capacity = s._capacity; } //析构函数 ~string() { delete[] _str; _str = nullptr; _size = _capacity = 0; } void Print() { cout << _str << "\t" << _size << "\t" << _capacity << endl; } //reserve 保留容量 可以扩容 void reserve(size_t n) //只是改变capacity 不改变size { if (n > _capacity) { //新建一个字符数组 cout << "reserve->" << n << endl; char* new_str = new char[n + 1]; //更改容量 //strcpy(new_str, _str); memcpy(new_str, _str, _size + 1); delete[] _str; _str = new_str; _capacity = n; } } //push_back void push_back(char ch) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } //加入字符 _str[_size] = ch; ++_size; _str[_size] = '\0'; } void append(const char* str) { if (_size + strlen(str) > _capacity) { reserve(_size + strlen(str));//至少保留_size + strlen(str) } //加入字符串 memcpy(_str + _size, str, strlen(str) + 1);//在'\0'位置(就是_str末尾)+str _size += strlen(str); } //实现+= 也是使用push_back 和append函数 string& operator+=(char ch) { push_back(ch); return *this; } string& operator+=(const char* str) { append(str); return *this; } //返回size size_t size() const //const表示修饰this指针,也就是说只读,如果是const对象,也可以访问,普通用户相当于权限的缩小也可也访问 { return _size; } size_t capacity() { return _capacity; } private: char* _str; int _size; int _capacity; }; }
构造函数和拷贝构造
默认构造函数和带参构造函数合并,使用缺省参数
拷贝构造函数,我们要使用深拷贝,因为如果是浅拷贝,仅仅是将数值传给新的string对象,但是两者对应一个地址一个空间,当析构一个string对象后,另一个对象再次析构就会报错。
string(const char* str = "") //""字符串自带'\0' :_str(new char[strlen(str) + 1]) //因为我们底层用的数组,所以一定要多开一位空间存放'\0' , _size(strlen(str)) , _capacity(strlen(str)) { //存储字符串 strcpy(_str, str); //传入字符串的时候,一般都是结尾为'\0',中间有'\0'的都是我们为string对象增加的。所以这个地方还是使用strcpy即可 } //拷贝构造 string(const string& s) { //深拷贝,就是创建一个大小一样的空间 _str = new char[s._capacity + 1]; strcpy(_str, s._str); _size = s._size; _capacity = s._capacity; }
push_back和append的实现
想要实现push_back和append函数,都要在底层中考虑是否需要扩容,那么我们就顺势要写出reserve函数,让其来判断是否需要扩容。
//reserve 保留容量 可以扩容 void reserve(size_t n) //只是改变capacity 不改变size { if (n > _capacity) { //新建一个字符数组 cout << "reserve->" << n << endl; char* new_str = new char[n + 1]; //更改容量 //strcpy(new_str, _str); memcpy(new_str, _str, _size + 1); delete[] _str; _str = new_str; _capacity = n; } } void push_back(char ch) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } //加入字符 _str[_size] = ch; ++_size; _str[_size] = '\0'; } void append(const char* str) { if (_size + strlen(str) > _capacity) { reserve(_size + strlen(str));//至少保留_size + strlen(str) } //加入字符串 memcpy(_str + _size, str, strlen(str) + 1);//在'\0'位置(就是_str末尾)+str _size += strlen(str); }
注意:为什么拷贝字符串内容的时候用memcpy而不是strcpy,这是因为,string中可能中间会有'\0',memcpy是根据第三个参数来定要拷贝的字符长度,而strcmpy,是根据要拷贝的字符串的'\0'出现的位置,所以使用memcpy更加合适。
strcpy和memcpy的对比
- char * strcpy ( char * destination, const char * source );
- void * memcpy ( void * destination, const void * source, size_t num );