STL之迭代器

1.iterator究竟是什么?

iterator,迭代器。是一种设计模式,它提供了一种方法,可以按照一定顺序访问一个聚合对象中各个元素而又不需暴露该对象的内部表示。每一种 STL 容器都有专属的迭代器,以避免暴露容器的内部结构(如果不专属的话,在声明定义一个用于某容器的迭代器时很可能暴露其内部函数或结构)。iterator实际是一个类似指针的模板类,重载了*和->。每种容器在内部都会存放一个专属的迭代器对象,而对于专门用于不同容器的迭代器,其内部都会存放有一个指向该容器中节点的指针,在使用*时就会返回该节点。当移动迭代器时,顺序容器的迭代器会直接++(实际上vector和dequeue都直接使用了value_type*作为迭代器类型),而list会获取指向的节点内部结构的next,而map和set会根据中序遍历红黑树的规则获取下一个节点。

迭代器一共有5种类型。顺序容器(vector,dequeue)使用的是随机迭代器,关联容器(map,set,list)使用双向迭代器

2.迭代器之相应型别与模板偏特化:

我们在使用迭代器时,需要在迭代器内部定义5种相应型别:迭代器指向的对象是value_type,两个迭代器之间的距离称为difference_type,通过迭代器传回的左值对象称为reference_type,通过迭代器传回的左值对象的地址称为pointer_type,至于迭代器的类型,则称为iterator_category。任何迭代器都需要为这5种相应型别提供定义与说明。STL为我们提供了一个iterator class,只要继承这个父类,新设计的迭代器就可以保证符合STL规范所需。该类源码如下:

template <  class Category,
            class T,
            class Distance = ptrdiff_t, //ptrdiff_t是两个指针之间的距离,属于C++的一个自带类型
            class Pointer = T*,
            class Reference =  T&>
struct iterator {
    typedef Category iterator_category;
    typedef T value_type;
    typedef Distance difference_type;
    typedef Pointer pointer,
    typedef Reference reference;
};

因此,如果我们想自己声明一个用于list的iterator,只需要这么写:

/*
五种迭代器类型的类型名:
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag : public input_iterator_tag {};
struct bidirectional_iterator_tag : public forward_iterator_tag {};
struct random_access_iterator_tag : public bidirectional_iterator_tag {};
*/
template <class Item>
struct ListIter : public std::iterator<std::forward_list_iterator_tag, Item>;

但问题是,不是所有的迭代器都是一个类(class type),比如C++的指针就不是一个类,因此无法为指针定义内嵌型别,但STL必须接受指针作为一种迭代器(实际上一个指针就是一个随机迭代器。在vector源码中定义iterator类型为value_type*)。因此就需要用到模板偏特化。模板偏特化是对模板函数或模板类的一部分template参数加以限制。比如对于一个模板类,T可以为任意类型:

template <typename T>
class C {...};

但被偏特化为以下形式后,就限制了T必须为指针类型。因此当T不是指针时,实例化的是上面的模板类。而当T是指针时,实例化的是下面的模板类。这两个模板类可以同时存在,且定义可以完全不同。

template <typename T>
class C <T*> {...}//类名后面的<T*>限制了T必须为指针。

因此,我们必须为指针迭代器设计出一个模板偏特化。但由于指针不是一个类,因此我们无法直接继承iterator。此时我们就需要用一个“特性萃取器” iterator_traits 来作为一个中介,使用者通过中介来获取内嵌型别。如果迭代器是一个类,iterator_traits可以直接返回该类内部定义的型别。而如果迭代器是一个指针,iterator_traits则可以根据指针指向的类型定义与返回与之对应的型别。

template <class Iterator>
struct iterator_traits {
    typedef typename Iterator::iterator_category iterator_category;
    typedef typename Iterator::value_type value_type;
    typedef typename Iterator::difference_type difference_type;
    typedef typename Iterator::pointer pointer;
    typedef typename Iterator::reference reference;
}

//为指针而设计的偏特化版本
template <class T>
struct iterator_traits <T*> {
    typedef random_access_iterator_tag iterator_category;//指针应该是一个可以随意移动的迭代器
    typedef T value_type;
    typedef ptrdiff_t difference_type;
    typedef T* pointer;
    typedef T& reference;
}

//为指向const类型的指针而设计的偏特化版本
template <class T>
struct iterator_traits <const T*> {
    typedef random_access_iterator_tag iterator_category;//指针应该是一个可以随意移动的迭代器
    typedef T value_type;
    typedef ptrdiff_t difference_type;
    typedef T* pointer;
    typedef T& reference;
}

因此,如果我想要获得某个迭代器类型的一个实例,我只需要设计以下模板函数:

template <class Iterator>
inline typename iterator_traits<Iterator>::iterator_category//这一行是定义返回类型,inline代表为内联函数
iterator_category(const Iterator&)//这一行的iterator_category为函数名
{
    typedef typename iterator_traits<Iterator>::iterator_category category;
    return category();//返回一个Iterator迭代器,该迭代器通过默认构造函数构造
}    

为何要专门让迭代器类型为一种型别,这是因为为由于不同迭代器类型的移动方法不一样,用户调用统一移动函数,该函数通过获取不同的类型,把类型作为参数传入内部的移动函数,通过函数重载就可以根据类型执行不同的内部移动函数。如果不作为型别,统一移动函数也可以通过if else判断传入的迭代器类型,再来调用内部移动函数,但由于if else的执行语句只能在程序运行时才能确定,因此会降低效率。

//内部一共定义了三个__advance函数,它们的第三个参数类型不同。
//通过传入不同的实参可以调用不同的__advance函数执行不同的移动操作

//InputIterator可以为各种类型的迭代器。只是STL命名规则为:以算法能接受最低阶的迭代器类型来为迭代器型别参数命名
template <class InputIterator, class Distance> 
inline void advance (InputIterator& i, Distance n)
{
    __advance(i,n,iterator_category(i));//函数名或变量名如果以双底线作为前缀,表明为内部所用
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值