STL之迭代器(Iterator)

左心房为你撑大大i 提交于 2020-04-13 14:59:58

【今日推荐】:为什么一到面试就懵逼!>>>

目录

一、迭代器是什么?

1.1、本质

迭代器本质上是什么?当然,任何一种特定的迭代器都是某种类型的对象。不过,迭代器的类型非常多,毕竟每个迭代器都是与某个特定容器类型相关联的。它需要保存一些必要的信息,以便我们对容器执行某些特定的任务。因此,有多少种容器就有多少种迭代器,有多少种特殊要求就有多少种迭代器。

1.2、作用

STL的中心思想在于:将数据容器和算法分开,彼此独立设计,最后再以一帖胶着剂将它们撮合在一起。这个胶着剂就是迭代器。反过来,也可以说迭代器机制是为了最小化算法与所操作的数据结构间的依赖性:
在这里插入图片描述

1.3、迭代器与指针

迭代器是一种行为类似指针的对象,迭代器提供了间接访问的操作(如解引用操作*)和移动到新元素的操作(例如,++操作移动到下一个元素)。

迭代器并未实现为指针,它们是所谓的“泛型指针”。你甚至可以反过来思考它,由于迭代器是泛型的,这意味着指针是迭代器,在任何可以使用迭代器的地方,都可以使用指针。1

从实现的角度来看,迭代器是一种将operator*、operator->、operator++、operator-- 等指针相关操作予以重载的class template。

1.4、迭代器类别

标准库提供了5种迭代器(5个迭代器类别,Iterator category):

  • 输入迭代器(input Iterator):只用于顺序访问,只读。
  • 输出迭代器(output Iterator):顺序访问,只写。
  • 前向迭代器(forward iterator):只能在序列中沿一个方向移动,可以读写元素。
  • 双向迭代器(bidirectional Iterator):可以正向/反向移动,可读写元素。
  • 随机访问迭代器(random-access iterator):提供在常量时间内访问序列中任意元素的能力。

1.4.1、5种迭代器的关系

在这里插入图片描述
图中直线与箭头代表的并非C++的继承关系,而是所谓concept(概念)与refinement(强化)的关系。

二、Traits编程技法

2.1、引子

在算法中运用迭代器时,很可能会用到其相应类型。什么是相应类型?迭代器所指之物的类型便是其一。假设算法中有必要声明一个变量,以“迭代器所指对象的类型”为类型,如何是好?毕竟C++只支持sizeof(),并未支持typeof()!即便动用RTTI性质中的typeid(),获得的也只是类型名称,不能拿来做变量声明之用。

解决办法是:利用function template的参数推导机制。如:
在这里插入图片描述
以func()为对外接口,却把实际操作全部置于func_impl()之中。由于func_impl()是一个function template,一旦被调用,编译器会自动进行template参数推导,于是导出类型T,顺利解决了问题。

迭代器相应类型不只是"迭代器所指对象的类型"一种而已。根据经验,最常用的相应类型有五种:value_type、differencee_type、pointer、reference、Iterator_category,并非任何情况下任何一种都可以利用上述的template参数推导机制来取得。

2.2、Traits编程技法

上述参数类型推导技巧虽然可以用于value_type,却非全面可用:万一value_type必须用于函数的返回值,就束手无策了,毕竟函数的"template参数推导机制"推而导之的只是参数,无法推导函数的返回值类型。2

声明内嵌类型似乎是个好主意,像这样:
在这里插入图片描述
看起来不错,但是有个隐晦的陷阱:并不是所有迭代器都是class type。原生指针就不是。如果不是class type,就无法为它定义内嵌类型。但STL绝对必须接受原生指针作为一种迭代器,所以上面这样还不够。

为了让上述一般化概念针对特定情况(例如针对原生指针)做特殊化处理,可以使用模板部分特例化(template partial specialization)

先前的问题是,原生指针并非class,因此无法为它们定义内嵌类型。现在,我们可以针对“迭代器之template参数为指针”者,设计特例化版本的迭代器。

下面这个class template专门用来“萃取”迭代器的特性,而value_type正是迭代器的特性之一:
在这里插入图片描述
这个所谓的traits,其意义是,如果 I 定义有自己的value_type,那么通过这个traits的作用,萃取出来的value_type就是 I::value_type。换句话说,如果 I 定义有自己的value_type,先前那个func()可以改写成这样:
在这里插入图片描述
除了多一层间接性,带来的好处是什么呢?好处是traits可以拥有特例化版本。现在,我们令iterator_traits拥有一个partial specialization如下:
在这里插入图片描述
于是,原生指针int*虽然不是一种class type,亦可以通过traits取其value_type。这就解决了先前的问题。





针对“指向常量对象的指针(pointer to const)”,设计的特例化版本如下:
在这里插入图片描述
traits扮演的“特性萃取机”,萃取各个迭代器的特性,这里所谓的迭代器特性,指的是迭代器的相应类型。当然,若要这个“特性萃取机”traits能够有效运作,每一个迭代器必须遵循约定,自行以内嵌类型定义的方式定义出相应类型。
在这里插入图片描述


三、迭代器相应类型

本节只介绍iterator_category,其他迭代器相应类型的具体实现,请参看下一节四、具体迭代器示例

共有5种迭代器,详情请参考1.4、迭代器类别。设计算法时,如果可能,我们应尽量针对某种迭代器提供一个明确定义,并针对更强化的某种迭代器提供另一种定义,这样才能在不同情况下提供最大效率。

为了区别不同的迭代器,traits中定义了5个类,代表5种迭代器类型:
文件位置:libstdc++ -v3\include\bits\stl_iterator_base_types.h

  /**
   *  @defgroup iterator_tags Iterator Tags
   *  These are empty types, used to distinguish different iterators.  The
   *  distinction is not made by what they contain, but simply by what they
   *  are.  Different underlying algorithms can then be used based on the
   *  different operations supported by different iterator types.
  */
  //@{ 
  ///  Marking input iterators.
  struct input_iterator_tag { };

  ///  Marking output iterators.
  struct output_iterator_tag { };

  /// Forward iterators support a superset of input iterator operations.
  struct forward_iterator_tag : public input_iterator_tag { };

  /// Bidirectional iterators support a superset of forward iterator
  /// operations.
  struct bidirectional_iterator_tag : public forward_iterator_tag { };

  /// Random-access iterators support a superset of bidirectional
  /// iterator operations.
  struct random_access_iterator_tag : public bidirectional_iterator_tag { };
  //@}

四、具体迭代器示例

4.1、__iterator_traits

文件位置:libstdc++ -v3\include\bits\stl_iterator_base_types.h

#if __cplusplus >= 201103L
  // _GLIBCXX_RESOLVE_LIB_DEFECTS
  // 2408. SFINAE-friendly common_type/iterator_traits is missing in C++14
  template<typename _Iterator, typename = __void_t<>>
    struct __iterator_traits { };

  template<typename _Iterator>
    struct __iterator_traits<_Iterator,
			     __void_t<typename _Iterator::iterator_category,
				      typename _Iterator::value_type,
				      typename _Iterator::difference_type,
				      typename _Iterator::pointer,
				      typename _Iterator::reference>>
    {
      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<typename _Iterator>
    struct iterator_traits
    : public __iterator_traits<_Iterator> { };
#else
  template<typename _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;
    };
#endif

  /// Partial specialization for pointer types.
  template<typename _Tp>
    struct iterator_traits<_Tp*>
    {
      typedef random_access_iterator_tag iterator_category;
      typedef _Tp                         value_type;
      typedef ptrdiff_t                   difference_type;
      typedef _Tp*                        pointer;
      typedef _Tp&                        reference;
    };

  /// Partial specialization for const pointer types.
  template<typename _Tp>
    struct iterator_traits<const _Tp*>
    {
      typedef random_access_iterator_tag iterator_category;
      typedef _Tp                         value_type;
      typedef ptrdiff_t                   difference_type;
      typedef const _Tp*                  pointer;
      typedef const _Tp&                  reference;
    };

4.2、iterator

为了符合规范,任何迭代器都应该提供5个内嵌相应类型,以利于traits萃取,否则便是自别于整个STL架构,可能无法与其他STL组件顺利搭配。因此,STL提供了一个iterator class,如果每个新设计的迭代器都继承自它,就可以保证符合STL所需规范:

 /**
   *  @brief  Common %iterator class.
   *
   *  This class does nothing but define nested typedefs.  %Iterator classes
   *  can inherit from this class to save some work.  The typedefs are then
   *  used in specializations and overloading.
   *
   *  In particular, there are no default implementations of requirements
   *  such as @c operator++ and the like.  (How could there be?)
  */
  template<typename _Category, typename _Tp, typename _Distance = ptrdiff_t,
           typename _Pointer = _Tp*, typename _Reference = _Tp&>
    struct iterator
    {
      /// One of the @link iterator_tags tag types@endlink.
      typedef _Category  iterator_category;
      /// The type "pointed to" by the iterator.
      typedef _Tp        value_type;
      /// Distance between iterators is represented as this type.
      typedef _Distance  difference_type;
      /// This type represents a pointer-to-value_type.
      typedef _Pointer   pointer;
      /// This type represents a reference-to-value_type.
      typedef _Reference reference;
    };

五、迭代器、容器和算法

设计适当的相应类型是迭代器的责任,设计适当的迭代器则容器的责任。只有容器本身,才知道该设计出怎样的迭代器来遍历自己,并执行迭代器该有的各种行为(前进、后退、取值…)。至于算法,完全可以独立于容器和迭代器之外自行发展,只要设计时以迭代器为对外接口就行。


  1. https://gcc.gnu.org/onlinedocs/libstdc++/manual/iterators.html ↩︎

  2. 可以使用尾置返回类型和decltype()。 ↩︎

发布了26 篇原创文章 · 获赞 6 · 访问量 1万+
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!