STL源码学习----迭代器及其适配器

“迭代器是连接容器和算法的纽带,它们为数据提供了一种抽象的观点,使写算法的人不必关心多种多样的数据结构的具体细节。”-----<C++程序设计语言>

  SGI STL 3.3中的stl_iterator_base.h 和stl_iterator.h两个头文件中定义了跟迭代器相关的一些类。本文首先会介绍迭代器的基本概念,然后分析与迭代器相关的五种类型及traits(萃取)方法,最后简要介绍迭代器的几种适配器。

 

1,迭代器概述

  迭代器是指向序列元素的指针的一种抽象。通过使用迭代器,我们可以访问序列中的某个元素、改变序列中的某个元素的值、使迭代器向前或向后行走等等。

  依据有效执行操作的情况,迭代器可以分为五类:输入迭代器、输出迭代器、前向迭代器、双向迭代器和随机存取迭代器。STL中用五个类来代表这五种迭代器类别:

 

  其中

 

  · Input Iterator 所指的对象不允许外界改变

 

  · Output Iterator 支持对该迭代器所指对象的写操作

 

  · Forward Iterator 不仅支持Input Iterator和Output Iterator的操作,还能在序列中前向移动指向下一个元素

 

  · Bidirectional Iterator 可以双向移动,既可指向序列中的下一个元素,也可以指向序列中的前一个元素

 

  · Random Iterator 既可以双向移动,又可以跨越多个元素存取对象

1 struct input_iterator_tag {};
2 struct output_iterator_tag {};
3 struct forward_iterator_tag : public input_iterator_tag {};
4 struct bidirectional_iterator_tag : public forward_iterator_tag {};
5 struct random_access_iterator_tag : public bidirectional_iterator_tag {};

 

从上面的代码中可以看出,五种类型的迭代器有如下的层次结构:

  每个容器都会根据自身情况定义自己的迭代器类型。

 

2, 与迭代器操作相关的类型及其萃取机制

  使用迭代器是为了获取被指向的对象和被指向的序列的信息。只要给出描述某个序列的迭代器,用户就可以通过迭代器间接去操作被指向的对象,并且可以确定序列中元素的个数。为了表述这种操作,STL必须能提供与迭代器有关的各种类型。

   迭代器在元素操作的时候需要用到以下五种类型:

  value_type: 代表迭代器所指对象的类型。

  difference_type:代表两个迭代器之间的距离

  reference_type:代表迭代器所指对象的引用类型。简言之,它是operator*()的返回类型

  pointer_type:代表迭代器所致对象的指针类型。简言之,它是operator->()的返回类型

  iterator_category:代表1中提出的五种迭代器的类型标识

  每种容器都会提供自身迭代器的这五种类型,以下是从list的实现代码stl_list.h中摘录的迭代器类型定义:

复制代码
1 template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
2 class list : protected _List_base<_Tp, _Alloc> {
3  ...//此处胜率若干代码
4 public:
5   typedef _List_iterator<_Tp,_Tp&,_Tp*>             iterator;
6  ...
7 };
复制代码

  迭代器类_List_iterator中定义了我们所需要的五种类型

复制代码
 1 template<class _Tp, class _Ref, class _Ptr>
 2 struct _List_iterator : public _List_iterator_base {
 3   ... //此处省略若干代码
 4   typedef _Tp value_type;
 5   typedef _Ptr pointer;
 6   typedef _Ref reference;
 7    //注:下面两行在基类_List_iterator_base中
 8   typedef ptrdiff_t                  difference_type;
 9   typedef bidirectional_iterator_tag iterator_category;
10  ...
11 };
复制代码

  为了方便算法的使用,iterator_traits会将每种容器定义的五种类型萃取出来:

复制代码
1 template <class _Iterator>
2 struct iterator_traits {
3   typedef typename _Iterator::iterator_category iterator_category;
4   typedef typename _Iterator::value_type        value_type;
5   typedef typename _Iterator::difference_type   difference_type;
6   typedef typename _Iterator::pointer           pointer;
7   typedef typename _Iterator::reference         reference;
8 };
复制代码

  另外,STL还针对<T*>和<const T*>提供了特化版本:

复制代码
1 template <class _Tp>
2 struct iterator_traits<_Tp*> {
3   ...//省略
4 };
5 
6 template <class _Tp>
7 struct iterator_traits<const _Tp*> {
8   ...//省略
9 };
复制代码

   iterator_traits扮演了类似于下图的一个角色:

   iterator_traits为屏蔽了下层各容器类型的不同,为上层的应用(主要是一些算法,如advance()等)提供了统一的用户界面。

  下图是advance函数的调用关系图:

  

 

3,迭代器的适配器

  STL提供了许多基于迭代器的适配器,如back_insert_iterator, front_insert_iterator, inser_iterator, reverse_iterator, istream_iterator, ostream_iterator, istreambuf_iterator, ostreambuf_iterator等。

  这些适配器大致可以分为三类:插入迭代器、反向迭代器和IO迭代器。下面一一介绍这三类迭代器,重点会放在反向迭代器上。

  3.1 插入迭代器

    插入迭代器用于将值插入到容器中。插入迭代器是一个模板类,模板参数为容器,迭代器只需要在重载操作符函数operator=()中调用容器的插入操作(对应的push_back, push_front或insert)即可。   

insert_iterator实现

 

  3.2 反向迭代器

    顾名思义,反向迭代器会提供与普通迭代器相反方向的遍历功能。反向迭代器其实一个正向迭代器的适配器,它的实现都是通过调用正向迭代器的操作,为了与迭代器的概念保持一致(begin指向迭代器的第一个元素,end指向迭代器的最后一个元素的下一个位置),又与正向迭代器有一点点不同。

    reverse_iterator的实现中有一个名为current的Iterator,它是模板参数,即正向迭代器。正向迭代器指向的范围是序列中的第一个元素到最后一个元素的下一个位置,为了保持迭代器概念的统一,反向迭代器的rbegin应该是序列的最后一个元素,rend应该是第一个元素前面的元素。

    所以,current总是指向reverse_iterator所指元素之后的一个元素。这也意味这*返回的是值*(current-1),++通过对current的--实现。下面贴上reverse_iterator的代码。  

reverse_iterator实现

 

  3.3 IO迭代器

  标准库提供4个迭代器类型,以使流能够融入容器和算法的框架中:

  ostream_iterator: 用于向ostream中写入

  istream_iterator: 用于向istream中读出

  ostreambuf_iterator: 用于向流缓冲区写入

  istreambuf_iterator: 用于从流缓冲区读出

  输入输出迭代器的思想是将输入输出流当作序列,ostream_iterator和istream_iterator相当于指向序列的迭代器,用户可以通过这个迭代器对输入输出流做操作。但是,ostream_iterator的迭代器类型是input_iterator,只支持写操作(*p=X)和迭代操作(++);istream_iterator的迭代器类型是output_iterator,它可以支持读(=*p), 访问(->),迭代(++),比较(==, !=)操作

  下面的代码是ostream_iterator的实现代码:

ostream_iterator的实现

 

  用户可以像下面一样使用ostream_iterator:

1     ostream_iterator<string> os(cout);
2     *++os = "cobb";
3     *os = "liu";

  上面的这段程序会产生输出cobbliu。在使用ostream_iterator的时候需要像使用正常迭代器一样将迭代器作迭代操作(++)。

  istream_iterator的实现跟ostream_iterator的实现大相径庭,值得注意的是istream_iterator中有一个标志标识输入序列是否结束,它的默认构造函数会将istream_iterator指向输入序列的结束位置(M_ok=false),表示输入序列结束。当对输入迭代器作迭代操作(++)的时候,会判断M_ok的值,如果该值为false,则终止读过程。

  

  istreambuf_iterator和ostreambuf_iterator可以使用户跨过iostream,直接跟流缓冲区打交道。

 

4,总结

  迭代器将算法和数据结构有效地分离并粘合起来。实际上,STL中的很多容器都设计有自己专属的迭代器,因为只有它自己才能知道自身数据结构的布局及其遍历方法,只需要按照标准声明上面提到的五种迭代器要用到的数据类型即可。迭代器更多的是运用到算法中,有了泛型和迭代器的支持,算法可以达到很高的内聚性。

 

5,参考资料

  1)《STL源码剖析》 侯捷著

  2) SGI STL 3.3源代码

  3)《C++程序设计语言》 Bjarne Stroustrip先生。

猜你喜欢

转载自blog.csdn.net/dp323/article/details/80597307