迭代器
1.什么是迭代器,迭代器有什么用
迭代器提供了对容器中对象的访问方法,它使算法独立于具体容器存在,解决了算法思想相同,却因容器元素访问方法不同导致需要实现多个版本算法的问题。举个例子find算法,对于list和vector来说,它接受的参数和返回值,甚至是内部实现都是一样的,但是list这个容器的结构是一个双向链表;而vector却是一个数组。照理来说由于链表和数组的访问元素的方法不同,我们需要实现多个与具体容器对应的find算法,但是由于迭代器的存在,我们现在只需要实现一个版本的find算法就行了。也就是说,迭代器使算法独立于具体容器而存在。
2.迭代器的分类
a. (前向+只能用于读出数据)输入迭代器:只读,支持++、==、!=
b. (前向+只能用于写入数据)输出迭代器:只写,支持++
c. 前向迭代器:即输入迭代器+输出迭代器
d. 双向迭代器:前向迭代器+后向操作(--)【stl容器的迭代器都是双向迭代器其以上】
e. 随机访问迭代器:除了双向迭代器提供的自增++和自减--外,还提供[n]、+n等随机访问的功能
例子:set、map这种用红黑树实现的,也就是说不是连续内存的,就是双向迭代器,所以它们的不可能支持+n、[n]的操作。而vector(string、deque)这种整块内存的,一般就是随机访问迭代器了,支持+n、[n]操作。当然也有stack、queue、priority_queue这种因为容器本身的特性,而不支持迭代器的情况出现。
3.迭代器失效(主要还是随机访问迭代器的连续内存)
迭代器失效是指,迭代器原来所指向的元素不存在或者移动了,此时如果不更新迭代器,将无法使用该过时的迭代器达到想要的结果。
a. Vector迭代器失效(随机访问迭代器,整块内存)
a) Push_back操作:vector内存满时,用push_back,导致容器扩容,所有内容搬移,导致原来迭代器失效。
b) Erase操作:擦除vector中间的某个对象,后面元素拷贝覆盖前面元素,导致原先迭代器失效。
c) Resize操作:扩容,导致元素整体拷贝,原先迭代器失效。
d) Swap操作:被交换对象的迭代器失效
e) 赋值运算操作:被赋值容器的迭代器全部失效
b. Deque迭代器失效(随机访问迭代器,多块连续内存)
a) 插入操作:在双端队列的队首和队尾插入不会导致迭代器失效;而在中间插入会(回想一下双端队列的内存布局)
b) 删除操作:队首队尾的删除操作只会导致,被删除的元素的迭代器失效。中间删除会导致其他元素的迭代器失效。
c. List/set/map迭代器失效(双向迭代器)
a) 只有被删除元素的迭代器失效(回想一下内存布局,都是链表,删除某个节点并不影响到其他节点)
4.迭代器、模板偏特化与traits编程技法
a) 模板特化与偏特化:
i. (类模板特化与偏特化)与(普通版本)的不同之处在于类名之后的尖括号<>中,进一步加了限制条件;(特化)与(偏特化)的不同之处在于template后的<>中是不是有参数。
ii. 其执行机制是这样的:当实例化一个模板时,编译器会把目前存在的所有偏特化和特化,泛化版本做一下比较,找出其中最合适、最匹配的实现。
b) 所谓的traits编程技法:
i. 给普通迭代器写个类型萃取 再给原生指针写个偏特化版的类型萃取
这样不管是T是类类型的迭代器还是原始指针,我们都可以这么写了:
Template<class I> Typename iterator_traits<I>::valtype function(I ite){ Return *ite; }
ii. 所谓traits编程技法,就是在有多个模板版本的情况下,又包了个类,向使用者提供了同一接口。
【模板偏特化这边,复习的时候仍需要看一下课本,此处不够详尽】