迭代器(iterator)
stl的思想是将算法和数据结构分开,而迭代器正是扮演了将他们分开的角色。不管数据结构如何,只要实现了相应的迭代器,就能够使用算法而无需顾及数据内部实现。
迭代器类型
试想一下,让你为自己的数据结构设计访问接口,你会怎么设计?比如针对链表,只能单步访问;而针对数组,则可以进行跨步。不同的数据结构有不同的访问限制,由此就引入了不同类型的“接口”。迭代器考虑到了不同数据结构可能存在的不同访问模式,做出了如下分类:
1. 输入迭代器(即单纯的访问)
2. 输出迭代器(即能修改数据的迭代器)
3. 正向迭代器(只能往前走一步的迭代器,如单指针链表)
4. 双向迭代器(可以前后走一步的迭代器,如双向链表)
5. 随机访问迭代器(能任意访问的迭代器,如数组)
Trait技法
不同类型的迭代器代表不同的访问效率,算法可能需要根据不同的迭代器类型来选择不同的处理方式,求得最大效率。比如如果数据结构支持随机访问迭代器,那你在对数据进行快排的时候想必不会单步访问排序(正向迭代器)。
我们当然可以在算法里用if来判断迭代器是什么类型来采取不同策略,但进行判断会影响运行时的效率,那有没有办法可以在运行之前知道迭代器类型?答案是trait技法。trait技法利用c++模板特性在编译时就可以根据类型推导知道迭代器类型,保证了运行效率。
下面以advance函数为例子:
根据不同的迭代器设计不同的_advance函数:
//针对输入迭代器(inputiterator)设计的_advance
template <class InputIterator, class Distance>
inline void _advance(InputIterator& i,
Distance n, //走多少步
input_iterator_tag) //标志迭代器是否为输入迭代器,是一个空结构体
{
while(n--) ++i;
}
//针对前向迭代器(forwarditerator)设计的_advance
template <class ForwardIterator, class Distance>
inline void _advance(ForwardIterator& i,
Distance n, //走多少步
forward_iterator_tag) //标志迭代器是否为前向迭代器,是一个空结构体
{
while(n--) ++i; //和输入迭代器一样
}
//针对双向迭代器(Bidirectionaliterator)设计的_advance
template <class BidirectionalIterator, class Distance>
inline void _advance(BidirectionalIterator& i,
Distance n, //走多少步
bidirectional_iterator_tag) //标志迭代器是否为双向迭代器,是一个空结构体
{
//双向迭代器,可以前后移动
if(n>=0)
while(n--) ++i;
else
while(n++) --i;
}
//针对随机访问迭代器(RandomAccessiterator)设计的_advance
template <class RamdomAccessIterator, class Distance>
inline void _advance(RamdomAccessIterator& i,
Distance n, //走多少步
RandomAccessiterator_iterator_tag) //标志迭代器是否为随机访问迭代器,是一个空结构体
{
i+=n;
}
以上是不同迭代器类型对应的函数,那么,如果让编译器决定调用哪个函数呢?trait技法。trait实际上是利用c++模板的偏特化特性实现的,代码如下:
//trait
template<class I>
struct iterator_traits{
typedef typename I::iterator_type iterator_type;
}
//实际上,以输入迭代器为例,迭代器代码内部会有这么一句
//typename input_iterator_tag iterator_type
//通过将标志输入迭代器的空结构体重命名,编译器可以根据不同类型实例化结构体对象
//模板根据传入的tag对象类型,使用对应迭代器类型的算法
//相当于所有I内部定义的iterator_type都被重名为iterator_type
有了上面都traits,我们就可以将前面都_advance打包成统一都advance函数,给用户调用而无需手动输入迭代器类型。
template<class InputIterator, class Distance>
inline void advance(InputIterator& i,
Distance n)
{
_advance(i,n,
iterator_traits<InputIterator>::iterator_type())
}
iterator_traits函数作用比较像“重命名”,因为针对不同迭代器的特化算法实际上是由tag空结构体来标记的,编译器会根据传入tag的类型来决定使用哪个特化版本的算法。而每个迭代器内部都将对应自己的tag位重新命名成iterator_type,iterator_traits类就得以将其对应类型以统一的名字给出。本质上还是将不同的tag位结构体实例化,让编译器根据tag类型不同选择不同的模板。