C++|【C++】探讨迭代器的秘密 和 迭代器失效问题(学精、学好必知)

C++|【C++】探讨迭代器的秘密 和 迭代器失效问题(学精、学好必知)
文章图片


大家好,我是 兔7 ,一位努力学习C++的博主~
如果文章知识点有错误的地方,请指正!和大家一起学习,一起进步
如有不懂,可以随时向我提问,我会全力讲解~
如果感觉博主的文章还不错的话,希望大家关注、点赞、收藏三连支持一下博主哦~!
本文章CSDN首发
目录
0.前言
1. 迭代器是什么
2. 迭代器的用法
3. 迭代器那层迷雾
3.1 while (it != v.end()) 问题
3.2 范围for 问题
3.3 迭代器失效 问题(重点)
3.3.1 迭代器失效的概念:
问题一:意义变了
问题二:变成野指针了
3.3.2 小试牛刀进行检测:
3.3.3 如何解决迭代器失效:
4. 实现
0.前言 此博客为博主以后复习的资料,所以大家放心学习,总结的很全面,每段代码都给大家发了出来,大家如果有疑问可以尝试去调试。
大家一定要认真看图,图里的文字都是精华,好多的细节都在图中展示、写出来了,所以大家一定要仔细哦~
感谢大家的喜欢,感谢大家的支持~!
1. 迭代器是什么 迭代器(iterators)是一个超级接口! 是可以遍历集合的对象,为各种容器提供了公共的操作接口,隔离对容器的遍历操作和底层实现,从而解耦。
迭代器是类似指针一样的东西,因为它的用法就是模拟指针,但是它不是指针。
迭代器是行为型 设计模式 ,提供了一种方法来遍历一个聚合的容器(集合)中的各个元素,而不用暴露其内部的表示。
对于容器的访问而不需要关注容器内部的实现细节,可以使用迭代器,
也就是说,不管你是什么容器,都可以用迭代器进行遍历操作和底层实现。
2. 迭代器的用法
int main() { vector v; v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(4); v.push_back(5); v.push_back(6); vector::iterator it = v.begin(); while (it != v.end()) { cout << *it << " "; it++; } return 0; }

C++|【C++】探讨迭代器的秘密 和 迭代器失效问题(学精、学好必知)
文章图片

这就是迭代器的使用方法,有的小伙伴直接将这段代码当作迭代器的公式进行记忆,这样其实是错误的!屏幕前的小伙伴如果也是将这段代码当作迭代器的公式进行记忆的话,可能会反驳我:你也这么写,我也这么写,而且都是对的,你凭什么说我这么记下来是错的?
那我问问大家,迭代器的循环条件可以写成 while (it < v.end()) 么?可以的话,为什么可以?不可以的话,又是为什么呢?
C++|【C++】探讨迭代器的秘密 和 迭代器失效问题(学精、学好必知)
文章图片

如果不是很清楚,那么继续向下看~
3. 迭代器那层迷雾 3.1 while (it != v.end()) 问题 其实迭代器的循环条件有时可以写成while (it < v.end()),有时就不行。我说这个答案,大家可能会有点迷惑,为什么有时就可以有时就不可以呢,要明白这个问题,就先要清楚迭代器。
C++|【C++】探讨迭代器的秘密 和 迭代器失效问题(学精、学好必知)
文章图片

前面说过迭代器是类似指针一样的东西,因为它的用法就是模拟指针,但是它不是指针。那么大家先来想想,指针可以比较大小么?答案是肯定的,当然可以比较大小啊,但是有一个限定条件就是用指针比较大小的时候,这两个指针在物理空间上是要连续的,也就是像数组那样比较指针的大小。
当然,这里的再前提条件就是比较大小要有意义。如果没有意义,为了比较大小而比较大小,那就随意了。所以是在有意义的前提下的。
现在我先来公布上面问题的答案:
C++|【C++】探讨迭代器的秘密 和 迭代器失效问题(学精、学好必知)
文章图片

在vector这个容器是可以的,可以的原因是:vector的底层实现就是用指针来实现它的迭代器,而且它的物理空间也是在空间上连续的,也就是下图所示:
C++|【C++】探讨迭代器的秘密 和 迭代器失效问题(学精、学好必知)
文章图片

在string这个容器当然也是可以的,因为string在底层的实现也是用的类似这种方式(string的实现和讲解我已经写过一篇博客了,很清楚,大家可以去学习一下)
【C++】string类(学习并实现)(学习复习兼顾)_兔7的博客-CSDN博客
大家可能会疑惑,既然string和vector可以,那你说说哪个不可以啊。
这两种容器之所以可以是因为它们是连续的内存进行对数据存储的,但是list、map等链式结构就不可以。因为链式结构也就是用链表进行关联各个数据的:
C++|【C++】探讨迭代器的秘密 和 迭代器失效问题(学精、学好必知)
文章图片

所以我之所以说不对,是因为你可能只知道怎么写,但不知道自己为什么这么写,也就是说,如果问你可不可以写成 while (it < v.end()),你可能因为不清楚而答错。
3.2 范围for 问题 C++|【C++】探讨迭代器的秘密 和 迭代器失效问题(学精、学好必知)
文章图片

大家可能认为范围for是很高级的一个形式,其实如果了解它就知道,它不过如此,范围for其实是根据其他简洁的语言进行引入的。
C++在底层实现范围for其实就是用迭代器进行实现的,只不过看起来很高级而已,所以只要可以用迭代器遍历,就一定可以用范围for遍历,范围for的实现是依赖于迭代器的。
3.3 迭代器失效 问题(重点) 3.3.1 迭代器失效的概念:
迭代器失效有两种情况:一是pos的意义变了(指向的位置不是想要指向位置),二是pos变成了野指针。
大家看了概念可能还不是很理解,不用担心,大家继续向下走。
如果向在一个位置插入数据,我们要做的就是先找到要插入的那个位置,然后再插入,也就是下图所示:
C++|【C++】探讨迭代器的秘密 和 迭代器失效问题(学精、学好必知)
文章图片

那么如果我想要将pos位置 ' 3 ' 删掉,我们应该像下图所示写:
C++|【C++】探讨迭代器的秘密 和 迭代器失效问题(学精、学好必知)
文章图片

问题一:意义变了
但是这样就会有问题,我给大家画图来展示为什么会有问题:
C++|【C++】探讨迭代器的秘密 和 迭代器失效问题(学精、学好必知)
文章图片

pos失效的原因:意义变了。
其实这里虽然pos的意义变了,但是运行起来还是会出错:
C++|【C++】探讨迭代器的秘密 和 迭代器失效问题(学精、学好必知)
文章图片

我在别人博客中看见过有人说这里出错的原因是:pos这个迭代器在遍历一遍后就销毁了,所以会出错。
这个说法是错误的!大错特错!!!
没有被销毁,只是 pos 的意义变了,其实这里出错的原因是VS编译器对它进行了检查,因为pos已经失效了,所以编译器检查出来了,所以才报错的,并不是销毁了!!!
问题二:变成野指针了
C++|【C++】探讨迭代器的秘密 和 迭代器失效问题(学精、学好必知)
文章图片

当我们添加数据,比如说如果到8个数据数组就满了,再增加就要增容了。这时也会有问题,我还是用画图的方式给大家展示:
C++|【C++】探讨迭代器的秘密 和 迭代器失效问题(学精、学好必知)
文章图片

pos失效的原因:pos变成了野指针。
这时肯定有小伙伴会感到奇怪,为什么pos在旧地址处, ' 30 ' 还是可以加上去,其实这里就要懂底层的实现就可以理解,我先给大家一段我写的对 insert 的实现吧,最后我会把所有的实现给大家发出来~
void insert(iterator pos, const T& x) { assert(pos < capacity()); // 这里要注意的是,增容后空间改变,pos会失效。 if(_finish == _endofstorage) { size_t len = pos - _start; // 记录下位置,要不就找不到pos位置了size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2; reserve(newcapacity); // 更新pos,解决增容后pos失效的问题 pos = _start + len; }iterator end = _finish - 1; while (end >= pos) { *(end + 1) = *end; --end; } *pos = x; ++_finish; }

C++|【C++】探讨迭代器的秘密 和 迭代器失效问题(学精、学好必知)
文章图片

所以我这么讲也就解决了大家的问题了吧~大家如果想更清楚的了解,可以去像我一样去实现一下各个容器(最后会给大家)。
前面的两个问题最后都会出现下面的报错:
C++|【C++】探讨迭代器的秘密 和 迭代器失效问题(学精、学好必知)
文章图片

所以大家在使用的时候一定要注意避免此事情的发生,接下来我会给大家说解决这个问题的方法。
3.3.2 小试牛刀进行检测:
问题:找出偶数,并对其删除,输出奇数。
大家看看我下面写的代码对不对:
int main() { vector v; v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(4); v.push_back(5); v.push_back(6); v.push_back(7); vector::iterator it = v.begin(); while(it != v.end()) { if(*it % 2 == 0) v.erase(it); it++; } for (auto e : v) { cout << e << " "; } return 0; }

C++|【C++】探讨迭代器的秘密 和 迭代器失效问题(学精、学好必知)
文章图片

但是还是那个问题,我继续用图来表示:
C++|【C++】探讨迭代器的秘密 和 迭代器失效问题(学精、学好必知)
文章图片

这里可能看不出问题,那么如果都是偶数呢?
C++|【C++】探讨迭代器的秘密 和 迭代器失效问题(学精、学好必知)
文章图片

发现问题了吧,如果是偶数的话,删掉再++直接跳过去了,这里就是pos失效的结果,所以大家在写的时候一定要注意这个问题,当然也有对应的解决方案,大家继续向下看。
3.3.3 如何解决迭代器失效:
int main() { vector v; v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(4); v.push_back(5); v.push_back(6); v.push_back(7); vector::iterator it = v.begin(); while(it != v.end()) { if (*it % 2 == 0) it = v.erase(it); else it++; } for (auto e : v) { cout << e << " "; } return 0; }

C++|【C++】探讨迭代器的秘密 和 迭代器失效问题(学精、学好必知)
文章图片

所以我们只要在里面进行对 it 的位置进行控制就可以了,这样就解决的 pos 失效的问题,当然其他的情况也有可能失效,按照这个方法对你面对的问题进行控制即可,都是换汤不换药的方式~!
4. 实现
#pragma once #include #include #include using namespace std; namespace 兔7 { template class vector { public: typedef T* iterator; typedef const T* const_iterator; vector() :_start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) {}// 类模板的成员函数,还可以再是函数模板 //template //vector(InputIterator first, InputIterator last) // :_start(nullptr) // , _finish(nullptr) // , _endofstorage(nullptr) //{ // while (first != last) // { //push_back(*first); //first++; // } //} vector(T* first, T* last) :_start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) { while (first != last) { push_back(*first); first++; } }~vector() { if (_start) { delete[] _start; }_start = _finish = _endofstorage = nullptr; }iterator begin() { return _start; } iterator end() { return _finish; } const iterator begin() const { return _start; } const iterator end() const { return _finish; }size_t capacity() const { return _endofstorage - _start; } size_t size() const { return _finish - _start; } bool empty() const { return _start == _finish; }T& operator[](size_t i) // 可读可写 { assert(i < size()); return _start[i]; }const T& operator[](size_t i) const // 只读 { assert(i < size()); return _start[i]; }// v2(v1) //vector(const vector& v)// 也可以,但是不推荐 vector(const vector& v) :_start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) { _start = new T[v.capacity()]; // memcpy(_start, v._start, sizeof(T)*v.size()); // vector不可以,对string相当于浅拷贝了 for (size_t i = 0; i < v.size(); i++) { _start[i] = v._start[i]; // 然而这里是用的赋值拷贝,是深拷贝 } _finish = _start + v.size(); _endofstorage = _start + v.capacity(); }// v1 = v3//vector& operator=(const vector& v) //{ // if (this != &v) // { //delete[] _start; //_start = new T[v.capacity()]; //memcpy(_start, v._start, sizeof(T) * v.size()); // 不可以使用memcpy //_finish = _start + v.size(); //_endofstorage = _start + v.capacity(); // } // return *this; //} void swap(vector& v) { ::swap(_start, v._start); ::swap(_finish, v._finish); ::swap(_endofstorage, v._endofstorage); }vector& operator=(vector v) { swap(v); return *this; }void resize(size_t n, T val = T()) // C++支持 int() \ char() { if (n < size()) { _finish = _start + n; } else { if (n > capacity()) { reserve(n); } while (_finish < _start + n) { *_finish = val; _finish++; } } }void reserve(size_t n) {if (n > capacity()) { size_t sz = size(); T* tmp = new T[n]; if (_start) // 如果是nullptr就会崩 { for (size_t i = 0; i < sz; i++) { tmp[i] = _start[i]; } delete[] _start; } _start = tmp; _finish = _start + sz; _endofstorage = _start + n; } }void push_back(const T& x) { if (_finish == _endofstorage) { size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2; reserve(newcapacity); } *_finish = x; _finish++; }void pop_back() { assert(!empty()); _finish--; }void insert(iterator pos, const T& x) { assert(pos < capacity()); // 这里要注意的是,增容后空间改变,pos会失效。 if(_finish == _endofstorage) { size_t len = pos - _start; // 记录下位置,要不就找不到pos位置了size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2; reserve(newcapacity); // 更新pos,解决增容后pos失效的问题 pos = _start + len; }iterator end = _finish - 1; while (end >= pos) { *(end + 1) = *end; --end; } *pos = x; ++_finish; }iterator erase(iterator pos) { iterator it = pos + 1; while (it != _finish) { *(it - 1) = *it; ++it; } --_finish; return pos; }void PrintVector() const { const_iterator it = begin(); while (it != end()) { // *it += 1; 只能读,不能写 cout << *it << " "; ++it; } cout << endl; } private: iterator _start; iterator _finish; iterator _endofstorage; };

如上就是 迭代器 的所有知识,如果大家喜欢看此文章并且有收获,可不可以给 兔7 三连关注~
【C++|【C++】探讨迭代器的秘密 和 迭代器失效问题(学精、学好必知)】感谢大家观看,感谢大家支持!

    推荐阅读