迭代器示意图:
为什么迭代器遍历所有容器方式都是一样?
- 因为迭代器遍历完当前元素跳到下一个元素,底层数据结构的具体的遍历方式都封装在这个迭代器的++运算符函数了;
- 所以,作为使用方,我们不需要知道底层的数据结构原理;
- 我们只知道底层数据元素的遍历都封装在++运算符重载函数里面。
在之前实现的vector基础上,继续完善!
1、‘[]’ 重载函数 2、迭代器初步实现迭代器一般实现成容器的嵌套类型:
注意迭代器中提供的成员方法,非常经典!
- 只有容器底层数据结构内存是连续的,才提供[]运算符的重载(底层是链表,红黑树,哈希表,底层不是线性的,内存不连续,给它们提供[]重载函数没有意义)
- 只有通过迭代器访问才是对所有容器是有意义的!
对于vector来说,我们可以通过[]运算符重载函数遍历访问容器,也可以定义迭代器访问容器,也可以使用for_each访问容器
用库里的容器vector演示;
如果只删除容器中第一个偶数:
进程返回为0,没有问题。
如果是把容器中所有的偶数都删除:
进程运行出现了意外的中止,不可预期的错误。
进程不是正常结束运行。
第一次调用erase之后,迭代器就失效了,再进行++it就有问题了,是非法操作。
如果只增加元素1次:
进程是正常的结束。
如果把break去掉。需要添加元素多次。
进程意外中止。
第一次运行完之后,迭代器就失效了,对失效的迭代器++,就是非法的操作了。
我们分析一下:
当我们使用迭代器从首元素开始遍历的时候,遇到一个偶数88,要进行删除操作。
- 把it指向的元素删除掉,当我们把88删除以后,删除点到容器末尾的位置的所有生成的迭代器就都失效了
- 也就是删除元素的迭代器本身,和 后边有生成的迭代器都失效了,再使用就都出现错误了。
增加元素的情况也是一样的。
删除点或者增加点之前的迭代器都是好的,删除点或者增加点的迭代器及之后的迭代器都是失效的。
增加元素还有一种情况:
- 扩容了;
- 就是在其他地方重新开辟内存,相当于原来的容器底层上保持的迭代器就全部都失效了,因为vector底层是数组,vector的迭代器就是指针,原来的迭代器指向的是原来的数组内存空间,肯定完全失效了。
a: 当容器调用erase方法后,当前位置到容器末尾元素的所有的迭代器全部失效了
b: 当容器调用insert方法后,当前位置到容器末尾元素的所有的迭代器全部失效了
c:对于insert插入来说,如果引起容器内存扩容,那么原来容器的所有的迭代器就全部失效了
d: 不同容器的迭代器是不能进行比较运算的
对插入/删除点的迭代器进行更新操作
我们发现:
- erase和insert都会返回一个新的迭代器。
- 给当前位置插入元素或者删除元素,当前元素的后边元素都会发生移动,指向当前元素的迭代器就失效了。
当我们增加或者删除操作,会把操作完的当前位置的新的迭代器返回。(生成当前位置的合法的迭代器并返回)
我们用it去接收返回的更新的迭代器。
我们删除了元素以后,后边的元素都会前移,所以it就不要++了。
//把vec容器中所有的偶数全部删除
auto it = vec.begin();
while (it != vec.end())
{
if (*it % 2 == 0)
{
it = vec.erase(it);//更新删除当前元素的位置的迭代器
}
else
{
++it;
}
}
在写代码的过程中,涉及到用迭代器 删除 / 当前位置增加新元素的 时候,一定要对迭代器进行更新操作。
我们现在来解决插入元素的问题。
插入一个元素,就把原来当前位置的元素都后移了。
所以我们的it还要再多执行1次++了。
//给vec容器中所有的偶数前面添加一个小于偶数值1的数字
auto it = vec.begin();
for (; it != vec.end(); ++it)
{
if (*it % 2 == 0)
{
it = vec.insert(it, *it - 1);
++it;
}
}
#include完善vector类的迭代器代码#include using namespace std; int main() { vector vec; for (int i = 0; i < 20; ++i) { vec.push_back(rand() % 100 + 1); } for (int v : vec) { cout << v << " "; } cout << endl; #if 0 //给vec容器中所有的偶数前面添加一个小于偶数值1的数字 auto it = vec.begin(); for (; it != vec.end(); ++it) { if (*it % 2 == 0) { it = vec.insert(it, *it-1); ++it; } } #endif #if 0 //把vec容器中所有的偶数全部删除 auto it = vec.begin(); while (it != vec.end()) { if (*it % 2 == 0) { //迭代器失效的问题,第一次调用erase以后,迭代器it就失效了 it = vec.erase(it); // insert(it, val) erase(it) //break; 迭代器失效了,再进行++it就有问题了,是非法操作 } else { ++it; } } #endif for (int v : vec) { cout << v << " "; } cout << endl; return 0; } #endif
我们要把所有迭代器都记录下来!
当我们发现通过那个迭代器进行增加 删除,就要让相应的迭代器失效,让迭代器重新更新。
迭代器的构造函数:
构造完生成1个节点
构造函数的内容相当于头插法:
形成链表,记录用户从容器中获取的是哪个元素的迭代器
迭代器的成员:
#includeusing namespace std; //定义容器的空间配置器,和C++标准库的allocator实现一样 template struct Allocator { T* allocate(size_t size)//负责内存开辟 { return (T*)malloc(sizeof(T) * size); } void deallocate(void* p)//负责内存释放 { free(p); } void construct(T* p, const T& val)//负责对象构造 { new (p) T(val);//定位new } void destroy(T* p)//负责对象析构 { p->~T();// ~T()代表了T类型的析构函数 } }; template > class vector { public: vector(int size = 10)//构造函数 { //需要把内存开辟和对象构造分开处理 //_first = new T[size]; _first = _allocator.allocate(size); _last = _first; _end = _first + size; } ~vector()//析构函数 { //析构容器有效的元素,然后释放_first指针指向的堆内存 //delete[]_first; for (T* p = _first; p != _last; ++p) { _allocator.destroy(p);//把_first指针指向的数组的有效元素进行析构操作 } _allocator.deallocate(_first);//释放堆上的数组内存 _first = _last = _end = nullptr; } vector(const vector & rhs)//拷贝构造函数 { int size = rhs._end - rhs._first; //_first = new T[size]; _first = _allocator.allocate(size); int len = rhs._last - rhs._first; for (int i = 0; i < len; ++i) { //_first[i] = rhs._first[i]; _allocator.construct(_first + i, rhs._first[i]); } _last = _first + len; _end = _first + size; } vector & operator=(const vector & rhs)//重载赋值函数 { if (this == &rhs) return *this; //delete[]_first; for (T* p = _first; p != _last; ++p) { _allocator.destroy(p);//把_first指针指向的数组的有效元素进行析构操作 } _allocator.deallocate(_first); int size = rhs._end - rhs._first; //_first = new T[size]; _first = _allocator.allocate(size); int len = rhs._last - rhs._first; for (int i = 0; i < len; ++i) { //_first[i] = rhs._first[i]; _allocator.construct(_first + i, rhs._first[i]); } _last = _first + len; _end = _first + size; return *this; } void push_back(const T& val)//向容器末尾添加元素 { if (full()) expand(); / verify(it._ptr - 1, _last);//检查这个范围的迭代器,让其失效 T* p = _last;//最后一个元素的后继位置 while (p > it._ptr) { _allocator.construct(p, *(p - 1));//在当前位置p上构造*(p-1)的对象 _allocator.destroy(p - 1);//把p-1位置的对象析构掉 p--;//从后向前走 } _allocator.construct(p, val); _last++; return iterator(this, p);//返回p位置生成的新的迭代器 } //自定义vector容器erase方法的实现 iterator erase(iterator it) { verify(it._ptr - 1, _last); //检查这个范围的迭代器,让其失效 -1是为了让当前传入的这个迭代器也失效 T* p = it._ptr; while (p < _last - 1)//删除元素,元素的后边位置都要前移 { _allocator.destroy(p);//析构当前p位置的元素 _allocator.construct(p, *(p + 1));//在当前p位置上构建后一个位置上的元素 p++;//后移 } _allocator.destroy(p); _last--; return iterator(this, it._ptr); } private: T* _first;//指向数组起始的位置 T* _last;//指向数组中有效元素的后继位置 T* _end;//指向数组空间的后继位置 Alloc _allocator; // 定义容器的空间配置器对象 //容器迭代器失效增加代码 struct Iterator_Base { Iterator_Base(iterator* c = nullptr, Iterator_Base* n = nullptr) :_cur(c), _next(n) {} iterator* _cur;//指向某个迭代器的指针 Iterator_Base* _next;//next指针,保存地址 }; Iterator_Base _head;//头结点,形成链表,记录用户从容器中获取的是哪个元素的迭代器 void expand()//容器的二倍扩容 { int size = _end - _first; //T *ptmp = new T[2 * size]; T* ptmp = _allocator.allocate(2 * size); for (int i = 0; i < size; ++i) { //ptmp[i] = _first[i]; _allocator.construct(ptmp + i, _first[i]); } //delete[]_first; for (T* p = _first; p != _last; ++p) { _allocator.destroy(p); } _allocator.deallocate(_first); _first = ptmp; _last = _first + size; _end = _first + 2 * size; } }; int main() { vector vec(200); for (int i = 0; i < 20; ++i) { vec.push_back(rand() % 100 + 1); } auto it = vec.begin(); while (it != vec.end()) { if (*it % 2 == 0) { //迭代器失效的问题,第一次调用erase以后,迭代器it就失效了 it = vec.erase(it);//insert(it, val) erase(it) } else { ++it; } } for (int v : vec) { cout << v << " "; } cout << endl; #if 0 auto it1 = vec.end(); vec.pop_back();//verify(_last-1, _last) auto it2 = vec.end(); cout << (it1 != it2) << endl; int size = vec.size(); for (int i = 0; i < size; ++i) { cout << vec[i] << " "; } cout << endl; auto it = vec.begin(); for (; it != vec.end(); ++it) { cout << *it << " "; } cout << endl; //foreach for (int val : vec)//其底层原理,就是通过容器的迭代器来实现容器遍历的 { cout << val << " "; } cout << endl; #endif return 0; } #endif



