前言
因为学习了string的相关知识,了解了string大部分接口的底层实现原理,所以我决定自己模拟实现一个mini版的string类,用来加深对string各方面知识的理解。
如果有错误或不足之处,还望各位读者小伙伴们指出。
一、包含的相关头文件
#include<iostream> #include<assert.h> #include<cstring> using std::ostream; using std::istream; using std:: cout; using std:: endl;
二、构造和析构
1.构造函数
//构造 string(const char* str = "") { size_t len = strlen(str); _capacity = _size = len; _str = new char[_capacity + 1]; strcpy(_str, str); }
2.拷贝构造
1.传统写法
该对象自己一点一点的进行深拷贝
//拷贝构造 string(const string& s) { _str = new char[s._capacity + 1]; _size = s._size; _capacity = s._capacity; strcpy(_str, s._str); }
2.现代写法
找一个中间对象,让这个中间对象用参数的值进行直接构造,再将这个中间对象的内容与自己的内容进行交换。相较于传统写法,现代写法更加简洁。
//拷贝构造 string(const string& s) :_str(nullptr),//此处要注意将该对象的地址赋值为nullptr,否则析构中间对象时会因为发生野指针的访问而导致程序崩溃。 _size(0), _capacity(0) { string temp(s); swap(temp); }
此处的swap用的是string自己实现的swap,为什么不用库里的swap呢?因为库里的swap是进行深拷贝,会降低效率。我们自己实现的swap只需要进行浅拷贝,即将它们对应的资源进行交换即可。
void swap(string& s) { std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); }
3.赋值运算符重载
1.传统写法
//赋值运算符重载 string& operator=(const string &s) { if (this != &s) { char* temp = new char[s._capacity + 1];//用一个中间值记录新开辟的空间,如果开辟空间成功,再将该空间的地址给_str。避免因为开辟空间失败,_str原本的内容也销毁的情况发生。 strcpy(temp, s._str); delete[] _str; _str = temp; _capacity = s._capacity; _size = s._size; } return *this; }
2.现代写法
与拷贝构造的现代写法思想类似,可以使程序更加简洁。并且此处不需要专门定义一个中间对象,因为传值传参传过来的形参是实参拷贝构造出来的对象,它就是一个很好的中间对象,我们直接和它进行交换即可。
//赋值运算符重载 string& operator=(string s) { swap(s); return *this; }
4.析构函数
//析构 ~string() { delete[] _str; _str = nullptr;//释放指针所指向的空间后要将该指针置为空(避免出现野指针的非法访问) _size = _capacity = 0; }
三、iterator
迭代器是一个使用起来像指针的东西,实际上string的迭代器就是char*类型的指针,因此我们先在最开始进行typedef
typedef char* iterator;
定义两个迭代器:分别指向字符串开头和结尾
iterator begin() { return _str; } iterator end() { return _str + _size; }
四、modify
1.push_back(尾插一个字符)
string只提供了push_back的接口没有提供头插的接口,因为头插需要移动数据,效率很低,所以尽量不要在string中使用头插。
void push_back(char c) { if (_size == _capacity) { size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;//要考虑到如果初始字符串的容量为0的情况 reserve(newcapacity); _capacity = newcapacity; } _str[_size++] = c; _str[_size] = '\0'; }
2.append(尾插一个字符串)
void append(const char* str) { size_t len = strlen(str); if (len + _size > _capacity) { reserve(len + _size); } strcpy(end(), str); _size += len; }
3.运算符重载+=
1.尾插字符
尾插一个字符(复用尾插)
string& operator+=(char c) { push_back(c); return *this; }
2.尾插字符串
尾插一个字符串(复用append)
string& operator+=(const char* str) { append(str); return *this; }
4.clear
清除string中所有元素
void clear() { _size = 0; _str[0] = '\0'; }
5.insert
1.插入一个字符
在字符串某位置插入一个字符:
// 在pos位置上插入字符c/字符串str string& insert(size_t pos, char c) { assert(pos <= _size); if (_size + 1 > _capacity) { reserve(_size + 1); } for (size_t i = _size + 1; i > pos; --i)//要注意避免i减为-1变成一个极大的无符号数,导致死循环 { _str[i] = _str[i - 1]; } _str[pos] = c; ++_size; return *this; }
2.插入一个字符串
在字符串某位置插入一个字符串:
string& insert(size_t pos, const char* str) { assert(pos <= _size); size_t i = 0; size_t len = strlen(str); if (_size + len > _capacity) { reserve(_size + len); } for (i = _size + len; i > pos + len - 1; --i) { _str[i] = _str[i - len]; } strncpy(_str + pos, str, len); _size += len; return *this; }
6.erase
删除从字符串某位置开始,某长度的子串(如果不说明删除子串的长度,则默认从开始位置到最后的所有内容都删除)
// 删除pos位置上的元素 string& erase(size_t pos, size_t len = npos) { if (len == npos || pos + len > _size) { _str[pos] = '\0'; _size = pos; } else { strcpy(_str + pos, _str + pos + len); _size -= len; } return *this; }