目录
模拟实现string类的默认成员函数
模拟实现构造函数
模拟实现拷贝构造函数
模拟实现赋值运算符重载
模拟实现析构函数
string类的增容
模拟实现reserve
模拟实现resize
string类的遍历和查询
模拟实现const迭代器
模拟实现普通迭代器
模拟实现c_str
模拟实现size
模拟实现[ ]重载
模拟实现find,查找字符
模拟实现find,查找字符串
string类的修改
模拟实现push_back
模拟实现append
重载+=
模拟实现insert,插入字符
模拟实现insert,插入字符串
string类的删除
模拟实现erase,删除字符或者字符串
模拟实现string类整体代码
在前几期我们学习了库中string类相关函数接口的使用,为了更深层次的理解string类,本期我们需要学习string类的模拟实现。
模拟实现string类的默认成员函数
模拟实现构造函数
string(const char* str=""):_str(new char[strlen(str) + 1]),_size(strlen(str)),_capacity(_size){strcpy(_str, str);}
构造函数,给缺省值的目的是为了让字符串有默认的"\0",使得编译器能够识别字符串的结束。
模拟实现拷贝构造函数
string(string& s):_str(nullptr),_size(0),_capacity(0){string tmp(s._str);std::swap(_str, tmp._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}
创建了对象,交换了中间对象和新创建的对象的成员变量。
模拟实现赋值运算符重载
string& operator =(string s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);return *this;}
形参就是一个中间对象,通过实参对象的值传递进行了拷贝构造生成了中间对象。
模拟实现析构函数
~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}
对象声明周期结束前先调用析构函数完成资源的清理。资源清理后,编译器再进行对象的销毁,与对象的创建和调用构造函数的顺序刚好相反。
string类的增容
模拟实现reserve
void reserve(size_t n){if (n > _capacity){//多申请一个空间是为了给'\0'char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}
需要多申请一个空间,是为了留给'\0'。
模拟实现resize
void resize(size_t n,char ch= '\0'){if (n <= _size){_str[n] = '\0';}else{if (n > _capacity){reserve(n);}memset(_str + _size, ch, n - _size);_size = n;_str[_size] = '\0';}}
resize扩容时有三种情况,扩容的空间小于原来字符串有效字符的个数,扩容的空间大于原来字符的个数但是小于容量,扩容的空间大于原来字符的容量,后两种的实际操作可以分为同一种。
string类的遍历和查询
模拟实现const迭代器
const_iterator begin()const{return _str;}const_iterator end() const{return _str + _size;}
迭代器本质上就是一个指针。 const成员函数const迭代器普通对象和const对象都可以进行访问。
模拟实现普通迭代器
iterator begin(){return _str;}iterator end(){return _str + _size;}
普通迭代器只有普通对象可以访问。
模拟实现c_str
const char* c_str()const{return _str;}
重载流提取之外的输出字符串的方法。
模拟实现size
size_t size()const{return _size;}
什么时候设置为const成员函数什么时候设置成普通成员函数,这取决于我们对字符串的操作,求大小,打印,这些不需要改变结构的可以设置成const,方便const对象去进行访问。
模拟实现[ ]重载
char& operator[] (size_t pos){return _str[pos];}
模拟实现find,查找字符
size_t find(char ch){for (size_t i = 0; i < _size; ++i){if (ch == _str[i]){return i;}}return npos;}
npos一个静态的无符号const成员变量,表示整型的最大值,再找不到元素时返回npos。
模拟实现find,查找字符串
size_t find(const char* s, size_t pos = 0){const char* ptr = strstr(_str + pos, s);if (ptr == nullptr){return npos;}else{return ptr - _str;}}
因为返回的是下标,所以最终通过指针相减获得了对应字符串的首元素的下标,字符串首元素的下标就是整个字符串的下标,因为字符串是连续存储的。
string类的修改
模拟实现push_back
void push_back(char ch){if (_size = _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;_size++;_str[_size] = '\0';}
与顺序表类似,因为string类本身就是一个数组,可以理解为它就是一个顺序表。因为刚开始会覆盖'\0',最终一定要在最后一个有效字符的下一位置添加上'\0' 。
模拟实现append
void append(const char* s){//先得求出要尾插的字符串的长度,因为此时空间不足的话不能以2倍扩容,因为一个字符串的长度可能远远大于容量size_t len = strlen(s);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str + _size, s);_size += len;}
需要注意,扩容时不能像以往一样二倍扩容,因为字符串的长度是不能预期的。
重载+=
//重载+=,尾插字符
string& operator+=(char ch){push_back(ch);return *this;}//重载+=,尾插字符串
string& operator+=(const char* str){append(str);return *this;}
本质上就是复用了push_back,append两个函数。
模拟实现insert,插入字符
string& insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;++_size;return *this;}
插入字符时与顺序表类似,但是需要注意end的位置,因为pos时无符号整型,所以一般情况下要将end的前一个的位置移动到end位置,而不是把end位置移动到end+1位置,这点当无符号整型判断时要注意。
模拟实现insert,插入字符串
string& insert(size_t pos, const char* s){assert(pos <= _size);size_t len = strlen(s);if (_size + len > _capacity){reserve(_size + len);}size_t end = _size + len;while (end > pos){_str[end] = _str[end - len];--end;}strncpy(_str + pos, s, len);return *this;}
我们要注意一定要使用strncpy函数控制字符的个数,如果使用strcpy会将'\0'拷贝过去,导致字符串异常结束。
string类的删除
模拟实现erase,删除字符或者字符串
string& erase(size_t pos = 0, size_t len = npos){assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;}
要注意pos的范围,因为我们一般删除的都是有效字符,所以pos的范围就是有效字符下标的范围。缺省值是npos,所以如果不给定初始值,默认全部删除完。
模拟实现string类整体代码
namespace yjd
{class string{public://迭代器typedef char* iterator;typedef const char* const_iterator;//构造函数,给缺省值的目的是为了让字符串有默认的"\0",使得编译器能够识别字符串的结束string(const char* str=""):_str(new char[strlen(str) + 1]),_size(0),_capacity(0){strcpy(_str, str);}//拷贝构造函数的深拷贝进阶版本string(string& s):_str(nullptr),_size(s._size),_capacity(s._capacity){string tmp(s._str);std::swap(_str, tmp._str);}//赋值运算符重载的深拷贝进阶版本2string& operator =(string s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);return *this;}//析构函数~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}//const迭代器,const对象和普通对象都可以调用调用const_iterator begin()const{return _str;}const_iterator end() const{return _str + _size;}//普通迭代器,普通对象调用iterator begin(){return _str;}iterator end(){return _str + _size;}//以cout<<对象.c_string的方式打印字符串,什么时候设置成const类型的成员变量取决于我们的用法,如果只是打印,对象自然不会更改,所以我们就设置成const成员函数const char* c_string()const{return _str;}//求字符串有效字符的个数size_t size()const{return _size;}//重载[],从而以数组的形式访问字符串的每个元素char& operator[] (size_t pos){return _str[pos];}//进行空间的扩容,实现reserve的功能void reserve(size_t n){if (n > _capacity){//多申请一个空间是为了给'\0'char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}//进行空间的扩容,并且进行初始化,实现resize的功能void resize(size_t n,char ch= '\0'){if (n <= _size){_str[n] = '\0';}else{if (n > _capacity){reserve(n);}memset(_str + _size, ch, n - _size);_size = n;_str[_size] = '\0';}}//尾插一个字符,实现push_back的功能void push_back(char ch){if (_size = _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;_size++;_str[_size] = '\0';}//尾插一个字符,实现append的功能void append(const char* s){//先得求出要尾插的字符串的长度,因为此时空间不足的话不能以2倍扩容,因为一个字符串的长度可能远远大于容量size_t len = strlen(s);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str + _size, s);_size += len;}//重载+=,尾插字符string& operator+=(char ch){push_back(ch);return *this;}//重载+=,尾插字符串string& operator+=(const char* str){append(str);return *this;}//实现find,进行字符的查找size_t find(char ch){for (size_t i = 0; i < _size; ++i){if (ch == _str[i]){return i;}}return npos;}//实现find,查找某个字符串size_t find(const char* s, size_t pos = 0){const char* ptr = strstr(_str + pos, s);if (ptr == nullptr){return npos;}else{return ptr - _str;}}//插入元素,实现insertstring& insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;++_size;return *this;}//实现insert,插入字符串string& insert(size_t pos, const char* s){assert(pos <= _size);size_t len = strlen(s);if (_size + len > _capacity){reserve(_size + len);}size_t end = _size + len;while (end > pos){_str[end] = _str[end - len];--end;}strncpy(_str + pos, s, len);return *this;}//进行字符串的删除,实现erasestring& erase(size_t pos = 0, size_t len = npos){assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;}private:char* _str;size_t _size;size_t _capacity;//定义静态常变量npos,找不到是就返回nposstatic const size_t npos;};const size_t string::npos = -1;
以上便是string类常见接口的模拟实现,可以帮助大家对string类更深一步的了解,模拟实现不一定要全部掌握,但是库中的string类中的相关常见接口一定要掌握。
本期内容到此结束^_^