4. 析构函数
析构函数就非常简单了,使用delete[]释放空间即可
~st
3.迭代器
对于string的迭代器,原生指针就能很好的支持迭代器行为,所以我们直接用原生指针。这里我们只实现正向迭代器的const和非const版本。
typedef char* iterator; typedef const char* const_iterator; iterator beign() { return _str; } iterator end() { return _str + _size; } const_iterator beign() const { return _str; } const_iterator end() const { return _str + _size; }
4.容量相关
1.size和capacity
size和capacity直接返回对象中的成员变量即可
size_t size() { return _size; } size_t capacity() { return _capacity; }
2. reserve
reserve是对string对象的容量进行操控的,当容量小于传入的值时,将会扩容,如果容量大于传入的值将不会做任何操作
{ if (n > _capacity) { char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } }
3. resize
resize的功能就是对string对象的size重新规划,当传入的n > capacity时,需要扩容,然后使用‘\0’来填充(默认情况下),当n小于元素个数时,将会把元素个数直接变成n,当n在size和capacity之间时,将会把后续的内容填充能‘\0’(默认情况下)
void reseize(size_t n, char ch = '\0') { if (n > _capacity) { reserve(n); } if (n > _size) { for (size_t i = _size; i < n; ++i) { _str[i] = ch; } } _size = n; _str[n] = '\0';
4. clear和empty
clear是清除所有数据,但是不销毁的函数,empty是判断是否为空的函数
void clear() { _size = 0; _str[0] = '\0'; } bool empty() { return _size == 0; }
5.数据访问
对于string的数据访问,我们一般有两种方式,第一就是通过迭代器(范围for也是迭代器的方式),第二种就是通过operator[],所以这里我们只实现一下operator[]
//非const版本,可读可写 char operator[](size_t pos) { assert(pos < _size); return _str[pos]; } //const版本,可读不可写 const char operator[](size_t pos) const { assert(pos < _size); return _str[pos]; }
6.数据操作
在学习数据结构的时候,我们一般学习的就是这种数据结构的增删查改四种操作,对于string类也是这样,所以在数据操作中,也分为增删查改四种。
1.插入数据
1. push_back
尾插一个字符,在增加数据的时候,我们需要考虑是否需要扩容
void push_back(char ch) { if (_capacity == _size) { size_t newCapacity = _capacity == 0 ? 4 : 2 * _capacity;//扩容扩二倍 reserve(newCapacity); } _str[_size] = ch; ++_size; _str[_size] = '\0';//这里注意尾插之后要对字符串结尾的\0做一下补充 }
2. append
尾插一串字符(一个字符串),这里有很多重载,我们只实现一个C-string类型的。
void append(const char* str) { //这里需要判断一下需不需要扩容,如果需要的话要扩多大 size_t len = strlen(str); if (_size + len > _capacity) { reserve(_size + len); } strcpy(_str + _size, str); _size += len; }
3. operator+=
对于上述的两种插入方式,我们可以使用一个运算符重载全部解决,直接复用即可
string& operator+=(char ch) { push_back(ch); return *this; } string& operator+=(const char* str) { append(str); return *this; }
4.insert
1. 插入字符
按照insert的逻辑,我们很容易的可以写出以下代码
string insert(size_t pos, char ch) { //判断位置是否合法 assert(pos <= _size); //扩容 if (_size == _capacity) { size_t newCapacity = _capacity == 0 ? 4 : 2 * _capacity; reserve(newCapacity); } //挪动数据 size_t end = _size; while (pos <= end) { _str[end + 1] = _str[end]; --end; } _str[pos] = ch; ++_size; return *this; }
但是,如果当传入的pos==0时,将会出现死循环的问题,因为pos的类型是size_t,所以在进行比较的时候,编译器会进行隐式类型转换,把end转换成size_t的类型进行比较,所以如果传入的pos==0,就不可能出现pos > end的情况,也就是死循环了,那么我们要怎么解决这个问题呢?其实有两种解决方案
- 方案一:强转
将end定义成int类型,然后while中的判断把pos强转成int,这样就可以避免隐式类型转换,从而规避死循环的情况发生
int end = _size; while ((int)pos <= end) { _str[end + 1] = _str[end]; --end; }
但是这种方法看起来有点不太高级,所以这里我们有了另一种方法
- 方案二:将end指向\0后面的位置,这样就不会出现end<0的情况
size_t end = _size + 1; while (pos < end) { _str[end] = _str[end - 1]; --end; }
2. 插入字符串
插入字符串之前,我们同样要判断容量是否充足。这里在拷贝的过程中,我们要注意使用strcpy的时候,会把\0拷贝进去,所以我们不能使用strcpy而是使用strncpy。
string& insert(size_t pos, const char* str) { //判断位置是否合法 assert(pos <= _size); size_t len = strlen(str); //扩容 if (_capacity < len + _size) { reserve(len + _size); } //挪动数据 size_t end = _size + pos; while (pos + len - 1 < end) { _str[end] = _str[end - len]; --end; } strncpy(_str + pos, str, len); _size += len; return *this; }