目录
vector 例子引入
迭代器的价值
通过vector和list 迭代器的差异再次深入理解
vector 例子引入
在学习vector底层erase,碰到迭代器失效的时候我有个疑惑,为什么sgi 版(Linux g++使用的)库里实现的迭代器对于有些迭代器失效情况依旧可以使用,而PJ版(VS使用的)库里实现的迭代器无论什么情况都一定会对失效情况报错,检查十分严格。(详情可以参考下面博客)
vector迭代器失效问题_SAKURAjinx的博客-CSDN博客
我当时说了,因为两个版本对迭代器的底层实现不同,sgi 版迭代器是内置类型,是通过typedef 指针的形式创建的。而通过这种形式访问数据,++一次跳过的是4字节(32位平台下,也就是一个指针大小),解引用*也就是直接访问当前位置数据(对于vector来说)。
PJ版实现迭代器不再是内嵌类型,它是封装了迭代器,将它封装成了一个类,内部重载++,* 等运算符。这种方式在访问数据的时候就和指针访问不同。
vector<int>::iterator it = v.begin();
while (it != v.end()) {
cout << (*it) << " ";
++it;
}
cout << endl;
以上述代码为例,指针的访问方式刚刚说了,
封装成类,重载运算符访问方式:
这种方式,*it不是像指针那样单单解引用,而是会去调用重载的函数,it++也是一样调用重载函数,函数内部怎样实现这一过程,解引用、++就返回什么结果。
所以两种方式本质上是不一样的。也正因如此,指针访问检查没有那么严格,有些迭代器失效情况还能继续使用it,而封装重载运算则会经过底层函数的检查,无论什么情况迭代器失效都不会让你再使用或者*it。
迭代器的价值
通过上面的例子我们知道了迭代器的实现方式有不同,并且不同的实现方式带来的结果也有所差异。迭代器真是个十分好用的东西,只能说大佬能设计出这种通用性强功能也强大的组件真的很了不起,一般来说要普通人实现对容器的访问,大概就是string、vector用下标,list、map、set等容器就想不到特别好的方式进行访问了,可能会暴露底层将不应该给程序员展现的节点供其使用,不仅效率低下、暴露底层而且容易使内部节点被误删误改。
迭代器作为六大组件之一,其主要价值就体现在2点:
1、封装性。 将迭代器封装起来使得程序员使用时不会接触到底层,避免了对底层实现的误操作。
复杂的容器底层访问数据的时候是比较复杂的,像set访问数据实际就是搜索树,而我们在外部使用迭代器访问的时候根本不会察觉底层这些麻烦的访问,因为外部都是很统一的形式,仅仅调用迭代器就可以实现。
2、通用适应性。所有容器都能使用迭代器,不像下标访问方式只适合string和vector。
通过vector和list 迭代器的差异再次深入理解
这里vector迭代器指sgi 版本下的实现方式。
1、 对于vector来说,vector<int>:: iterator it = v.begin(); begin()返回的是数组头部的位置,也就是下标为0的位置,end() 则是最后一个下标的后一个位置。
对于list来说,list<int>:: iterator it = st.begin(); begin()返回的是头结点的下一个位置,也就是第一个有效数据的位置。end() 则是哨兵位的头结点。
sgi vector迭代器就是通过指针遍历,每次走4byte大小,也就是一个数据的位置。
list 迭代器就是通过上一个指针的_next访问下一个指针。
2、
vector<int>::iterator it = v.begin();
while (it != v.end()) {
cout << (*it) << " ";
++it;
}
cout << endl;
对于迭代器的遍历代码,写的是while(it != v.end()),是否可以写成while(it < v.end()) ?
vector是可以的,但是list 则不行,前面说了list 物理空间不连续,可能后面的节点地址 < 前面节点的地址,因此只能 !=
再从虚拟地址存储的角度来看一下两者的区别:
vector:
可以看到刚进函数 it 的地址是00 f7 da 20(小端存储,高位存高地址),对应vector对象a的_start地址也是00 f7 da 20,下面 it++,迭代器按指针方式访问,地址+4byte,也就是变为 00 f7 da 24,
来看结果:
list:
可以看到刚进函数 it 的地址是00 79 f8 50(小端存储,高位存高地址),对应list对象st的头结点的地址也是00 79 f8 50,下面 it++,st头结点要指向下一个节点的位置,也就是_head->_next->_next,地址是
再看此时&it的地址,
小端存储倒着读,也是一样,由此可知,list迭代器的存储方式如我所说就是节点的_next连接,
每次地址不一定跳到哪,可能跳到前面也可能跳到后面。
由地址也可以看出两者访问方式的区别,并且由地址了解迭代器的实现方式后,我们也不用过分觉得迭代器那么神奇,其实过程也就如上面所示。
但迭代器的封装性、通用适应性真的很有价值,容器普遍的访问方式就是迭代器。