参考:https://blog.csdn.net/summerxiachen/article/details/59538640
https://blog.csdn.net/gogokongyin/article/details/51206225
什么是Iterator?
迭代器本质来说是一个对象,在使用iterator()方法后返回一个Iterator对象,Iterator实际上是一个指针,指向该容器的指针begin位置的指针。但是迭代器和指针是有区别的。
(1)迭代器本质不是指针,是类模板,表现的像指针。他只是模拟了指针的一些功能, 通过重载了指针的一些操作符,->,*,++ --等封装了指针,是一个“可遍历STL( Standard Template Library)容器内全部或部分元素”的对象, 本质是封装了原生指针,是指针概念的一种提升(lift),提供了比指针更高级的行为,相当于一种智能指针,他可以根据不同类型的数据结构来实现不同的++,--等操作;
(2) 迭代器返回的是对象引用而不是对象的值,所以cout只能输出迭代器使用*取值后的值而不能直接输出其自身。
(3)在设计模式中有一种模式叫迭代器模式,简单来说就是提供一种方法,在不需要暴露某个容器的内部表现形式情况下,使之能依次访问该容器中的各个元素,这种设计思维在STL中得到了广泛的应用,是STL的关键所在,通过迭代器,容器和算法可以有机的粘合在一起,只要对算法给予不同的迭代器,就可以对不同容器进行相同的操作。
注:迭代器在使用后就释放了,不能再继续使用,但是指针可以!!
指针:
指针能指向函数而迭代器不行,迭代器只能指向容器;指针是迭代器的一种。指针只能用于某些特定的容器;迭代器是指针的抽象和泛化。所以,指针满足迭代器的一切要求。
总之,指针和迭代器是有很大差别的,虽然他们表现的行为相似,但是本质是不一样的!一个是类模板,一个是存放一个家伙的地址的指针变量。
为什么需要Iterator?
如果我们来遍历访问容器中的元素,容器也分好多种,比如vector,list等,不同的容器遍历的方法肯定不一样。迭代器的作用就是提供一个遍历的统一接口。那么相对于不同的容器,实现肯定就不一样。这样的好处是在STL设计算法时,就可以脱离容器限制,从而设计出更加通用的算法。比如,在容器中查找一个元素。查找,这个操作一般来说就是遍历整个集合,然后找到那个要找的元素,但是如果没有迭代器,我们就必须要根据不同的容器设计不同的查找算法,比如vector和List就需要设计两种查找算法,因为找下一个元素在vector和list中的操作不通。同样的思想却要两套代码,显然这是不优秀的。
总之,迭代器是为了给所有的容器统一操作接口,迭代器和容器是密不可分的,不同的容器,它的迭代器不同,但是提供的接口是相同的。
详细介绍
它提供了一种访问一个容器(container)中各个元素的机制。Iterator不会暴露容器对象内部的细节方法。在使用迭代器的时候,我们不需要了解容器底层是如何来实现的,能够对容器进行遍历。因此迭代器也叫做轻量级的容器。
(1)用iterator()方法来建立迭代器,该方法返回的是一个对象。
(2)通过iterator中的next方法[iterator.next()]来得到下一个元素。第一次使用该方法的话就返回第一个元素。
(3)通过iterator中的hasNext()方法[iterator.hasNext()]来判断容器是否为空。
(4)通过Iterator中的remove()方法来删除迭代器返回来的元素。
值得注意的是,Iterator也支持派生,ListIterator该迭代器就只应用在List中,关键是能够在List中双向来操作元素。一般的Iterator是只正向的来遍历集合的。
ConcurrentModificationException异常
出现该异常,通常是由于在使用Iterator遍历容器的时候,对容器进行了删除或增加元素的操作,容器中的元素数目发生了变化。原因是:在用iterator()方法建立Iterator对象的时候,会将此时容器中包含的元素赋值给一个变量叫expectedModCount。在调用next()方法的时候,会比较变量expectedModCount与容器中的实际对象个数是否相等,如果不等,就会抛出该异常。一般会在多线程操作容器的时候出现。
解决办法:把要删除的对象都保存在一个集合中,遍历结束后再整个删除removeAll()或者iterator.remove()方法。
http://blog.csdn.net/Veahlin/article/details/68490756
这是个困扰我很久了的问题,可能一开始对面向对象的理解不够深。
刚刚想明白了,随手记录一下。
- 先从const iterator和const_iterator说起
const iterator 是iterator本身是个常量,iterator本身里面存的是指针,也就是iterator的值,也就是那个指针不能改变,也就是不能指向其他的位置,但是所指向的位置的元素是可以通过这个iterator来改变的。
const_iterator 其实本质来说,是另一个类。我们可以想象成,它的数据成员是一个指向常量元素的指针,(比如 const T*)也就是说,这个const_iterator里存着的指针是可以改变的,即可以 ++ 或 - - 操作,但是,这是一个指向常量的指针,指向的元素是常量,不可改变。
- 有点绕,我们再从一个例子扒一扒。
vector<int> ivec1(10);
const vector<int> ivec2(10);
vector<int>::iterator iter1 = ivec1.begin(); // true
vector<int>::iterator iter2 = ivec2.begin(); // error
vector<int>::const_iterator iter3 = ivec2.begin(); // true
vector<int>::const_iteartor iter4 = ivec1.begin(); // true
- 1
- 2
- 3
- 4
- 5
- 6
通过上面这个例子,我们就可以更直观的再深入理解一下了。
因为ivec2本身就是一个常量的vector,所以ivec2里面的元素必然是不能被改变的。如果直接定义一个iterator是会报错的,因为iterator意味着这个iterator可以遍历容器,且可以改变容器元素,显然如果容器被定义成常量了之后,这个iterator是不合理的。
所以这才有了const_iterator的出现。作为一个迭代器,遍历元素是必须要的,不然就丧失了一个迭代器的意义了。但是由于常量容器的存在,iterator不能满足这个需求。const_iteartor代表的就是不能改变容器元素,但是可以遍历容器的迭代器。
但是const_iterator不仅仅是只针对已经被声明为常量的容器用的,如果一个非常量容器,但是你不想改变容器的元素,那么也可以用const_iterator,因为里面存的是一个指向常量的指针。
- 最后再来看一下底层实现,更深入的扒一扒。
直接看最简单的vector容器吧,list和deque要更加复杂一些,但是本质都是一样的。(list是指向node节点的指针,duque的iterator更加复杂,因为它的内存分配是一段一段的,详情可见STL源码或者我的github www.github.com/linxiaoye/TinySTL)
/*vector的数据结构一般是这样的*/
T* start;
T* finish;
size_t n;
typedef T value_type;
typedef T* iterator;
typedef const T* const_iterator;
iterator begin() { return start; } // ①
const_iterator begin() const { return start; } // ②
const_iterator cbegin() const { return start; } // ③
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
我们可以看到,vector里面是这样实现的。
begin()有两个实现,外加一个cbegin(),有三个实现。
①就是最一般的实现,可遍历,可改变元素值。一般用于非常量容器。
②是可遍历,不可改变元素值,一般用于常量容器。当你对一个常量容器直接调用begin(),就是调用的②。调用值得一提的是,它这个不可改变元素值是全方位表现出来了的。从 typedef const T* const_iterator 开始,就限制了这是一个指向常量的指针,不可改变元素值;②中返回值是const_iterator也说明了返回的是一个存有指向常量的指针的迭代器;cosnt成员函数也说明了,不能通过这个函数来改变元素的值。
③其实跟②是几乎一样的,只是函数名不同,也就是说,当你实例化一个常量vector时,你调用begin() 和 cbegin()其实是一样的,没有区别。但是,当你定义的是一个非常量容器时,你想调用一个不能改变所指向元素的迭代器,那么就应该调用cbegin(),它可以满足你的要求。
- 最后总结一下
- iterator 可遍历,可改变所指元素
- const_iterator 可遍历,不可改变所指元素
- const iterator 不可遍历,可改变所指元素
- const_iterator 主要是在容器被定义成常量、或者非常量容器但不想改变元素值的情况下使用的,而且容器被定义成常量之后,它返回的迭代器只能是const_iterator
- begin() 与 cbegin() 其实在容器被定义成常量之后,本质上是一样的,没有区别,调用哪个都行;但是容器时非常量时,如果你不想改变元素值,就只能调用cbegin()