从C语言到C++_13(string的模拟实现)深浅拷贝+传统/现代写法(上):https://developer.aliyun.com/article/1513672
3. string的迭代器
在上上篇中,我们首次讲解迭代器,为了方便理解,我们当时解释其为像指针一样的类型。
实际上,有没有一种可能,它就是一种指针呢?
遗憾的是,迭代器并非指针,而是类模板。 只是它表现地像指针,模拟了指针的部分功能。
string迭代器的实现非常简单,它就是一个 char* 的指针罢了。
后面我们讲解 list 的时候它的迭代器又不是指针了,又是自定义类型了。
所以迭代器是一个像指针的东西,有可能是指针有可能不是指针。
3.1 string迭代器的实现
实现迭代器的 begin() 和 end() :
typedef char* iterator; iterator begin() { return _str; } iterator end() { return _str + _size; }
const 迭代器就是可以读但是不可以写的迭代器,实现一下 const 迭代器:
typedef const char* const_iterator; const_iterator begin() const { return _str; } const_iterator end() const { return _str + _size; }
3.2 迭代器和范围for再思考
迭代器的底层是连续的物理空间,给原生指针++解引用能正好贴合迭代器的行为,能做到遍历。
但是对于链表和树型结构来说,迭代器的实现就没有这么简单了。
但是,强大的迭代器通过统一的封装,无论是树、链表还是数组……
它都能用统一的方式遍历,这就是迭代器的优势,也是它的强大之处。
我们上一章提到过范围for遍历string,我们能不能直接用在我们写的迭代器上:
void test_string3() { string s1("hello world"); string::iterator it = s1.begin(); while (it != s1.end()) { cout << *it << " ";// 读 it++; } cout << endl; it = s1.begin(); while (it != s1.end()) { (*it)++;// 写,++的优先级比*高,+=就低,可以 *it += 1; cout << *it << " "; it++; } cout << endl; for (auto& e : s1) { cout << e << " "; } cout << endl; }
我们也妹写范围 for 啊,怎么就能直接用了?
所以范围 for 根本就不需要自己实现,你只要把迭代器实现好,范围 for 直接就可以用。
范围 for 的本质是由迭代器支持的,编译时范围 for 会被替换成迭代器。
这么一看,又是自动加加,又是自动判断结束的范围 for,好像也没那么回事儿。
4. string的增删查改函数实现
4.1 reserve() 的实现
我们先实现一下 reserve 增容:
这里可以检查一下是否真的需要增容,万一接收的 new_capacity 比 _capacity 小,就不动。
void reserve(size_t new_capacity) { if (new_capacity > _capacity) { char* tmp = new char[new_capacity + 1];// 开新空间 strcpy(tmp, _str);// 搬运 delete[] _str; // 释放原空间 _str = tmp;// 没问题,递交给_str _capacity = new_capacity;// 更新容量 } }
这里我们之前讲数据结构用的是 realloc,现在我们熟悉熟悉用 new,
还是用申请新空间、原空间数据拷贝到新空间,再释放空间地方式去扩容。
我们的 _capacity 存储的是有效字符,没算 \0,所以这里还要 +1 为 \0 开一个空间。
4.2 push_back() 的实现
void push_back(const char ch) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } _str[_size++] = ch;// 在_size位置放字符后++ _str[_size] = '\0';// 易漏 }
首先检查是否需要增容,如果需要就调用我们上面实现的 reserve 函数,
参数传递可以用三目操作符,防止容量是0的情况,0乘任何数都是0从而引发问题的情况。
测试一下效果如何:
4.3 append() 的实现
append 是追加字符串的,首先我们把要追加的字符串长度计算出来,
然后看容量够不够,不够我们就交给 reserve 去扩容,扩 _size + len,够用就行。
void append(const char* str) { int len = strlen(str); if (_size + len > _capacity) { reserve(_size + len); } strcpy(_str + _size, str);// 首字符+_size大小就是\0位置 _size += len; }
这里我们甚至都不需要用 strcat,因为它的位置我们很清楚,不就在 _str + _size 后面插入吗。
用 strcat 还需要遍历找到原来位置的 \0,麻烦且效率低,strcat 函数我们以后尽量都不用。
4.4 operator+= 的实现
这就是我们一章说的 "用起来爽到飞起" 的 += ,因为字符和字符串都可以用 += 去操作。
所以我们需要两个重载版本,一个是字符的,一个是字符串的。
我们不需要自己实现了,直接复用 push_back 和 append 就好了:
string& operator+=(const char ch) { push_back(ch); return *this; } string& operator+=(const char* str) { append(str); return *this; }
测试:
void test_string4() { string s1("hello world"); cout << s1.c_str() << endl; s1.push_back('!'); cout << s1.c_str() << endl; s1.push_back('R'); cout << s1.c_str() << endl; s1.append("abcd"); cout << s1.c_str() << endl; s1 += 'e'; s1 += "fgh"; cout << s1.c_str() << endl; }
4.5 insert() 的实现
insert:字符
string& insert(size_t pos, const char ch) { assert(pos < _size); if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } for (size_t i = _size + 1;i > pos; --i)// 挪动数据,+1是挪动\0 { _str[i] = _str[i - 1]; } _str[pos] = ch; ++_size; return *this; }
insert:字符串
string& insert(size_t pos, const char* str) { assert(pos <= _size); int len = strlen(str); if (_size + len > _capacity) { reserve(_size + len); } for (size_t i = _size + len ;i > pos + len - 1; --i)// 挪动数据,画图注意边界,参考上面inser字符的len == 1 { _str[i] = _str[i - len];// 首先看\0 _size+len-len就是\0的位置 } strncpy(_str + pos, str, len); _size += len; return *this; }
测试:
void test_string4() { string s1("hello world"); cout << s1.c_str() << endl; s1.push_back('!'); cout << s1.c_str() << endl; s1.push_back('R'); cout << s1.c_str() << endl; s1.append("abcd"); cout << s1.c_str() << endl; s1 += 'e'; s1 += "fgh"; cout << s1.c_str() << endl; s1.insert(0, 'x'); s1.insert(6, 'T'); cout << s1.c_str() << endl; s1.insert(6, "PPPPPPPPPP"); cout << s1.c_str() << endl; s1.insert(0, "PPPPPPPPPP"); cout << s1.c_str() << endl; }
(测试后push_back 和 append 直接复用还能测试一波)
+=又是复用push_back 和 append 的,直接套娃开始:
void reserve(size_t new_capacity) { if (new_capacity > _capacity) { char* tmp = new char[new_capacity + 1];// 开新空间 strcpy(tmp, _str);// 搬运 delete[] _str; // 释放原空间 _str = tmp;// 没问题,递交给_str _capacity = new_capacity;// 更新容量 } } void push_back(const char ch) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } _str[_size++] = ch;// 在_size位置放字符后++ _str[_size] = '\0';// 易漏 //insert(_size, ch); }
4.6 resize() 的实现
我们为了扩容,先实现了 reverse,现在我们再顺便实现一下 resize。
这里再提一下 reverse 和 resize 的区别:
resize 分给初始值和不给初始值的情况,所以有两种:
库里面也是这么实现的。
但是我们上面讲构造函数的时候说过,我们可以使用全缺省的方式,这样就可以二合一了。
resize 实现的难点是要考虑种种情况,我们来举个例子分析一下:
如果欲增容量比 _size 小的情况:
因为标准库是没有缩容的,所以我们实现的时候也不考虑去缩容。我们可以加一个 \0 去截断。
如果预增容量比 _size 大的情况:
resize 是开空间 + 初始化,开空间的工作我们就可以交给已经实现好的 reserve,
然后再写 resize 的初始化的功能,我们这里可以使用 memset 函数。
void resize(size_t new_capacity, const char ch = '\0') { if (new_capacity > _size)// 插入数据 { reserve(new_capacity); //for (size_t i = _size; i < new_capacity; ++i) //{ // _str[i] = ch; //} memset(_str + _size, ch, new_capacity - _size);// 上面的for循环即memset的功能 _str[new_capacity] = '\0'; _size = new_capacity; } else// 删除数据 { _str[new_capacity] = '\0'; _size = new_capacity; } }
从C语言到C++_13(string的模拟实现)深浅拷贝+传统/现代写法(下):https://developer.aliyun.com/article/1513674?spm=a2c6h.13148508.setting.14.5e0d4f0eaZIrWK