最后一个迭代器的相应类型就是iterator_category,就是迭代器本身的类型,根据移动特性与实行的操作,迭代器被分为了五类:

  • Input Iterator:这种迭代器所指的对象,不允许外界改变。只读(read only)。
  • Output Iterator:唯写(write only)
  • Forward Iterator:允许写入型算法在此种迭代器所形成的区间上进行读写操作,具有Input迭代器的全部功能和Output迭代器的大部分功能。支持++运算符。
  • Bidirectional Iterator:可双向移动。某些算法需要逆向遍历某个迭代器区间,可以使用Bidirectional Iterator。在Forward的基础上支持--运算符。
  • Random Access Iterator:前四种迭代器都只供应一部分指针算术能力(operator++、operator--),而这种则涵盖所有指针算数能力,包括p+n、p-n、p[n]、p1-p2、p1<p2

  这些迭代器的分类与从属关系,下图可以表示,直线与箭头代表的并非C++的继承关系,而是concept(概念)与refinement(强化)的关系。

  设计算符时,如果可能,我们尽量针对图中的某种迭代器提供一个明确的定义,并针对更强化的某种迭代器提供另一种定义,这样才能在不同情况下提供最大效率。假设有个算法可接受Forward Iterator,你以Random Access Iterator喂给它,它当然也接受,因为一个Random Access Iterator一定是一个Forward Iteartor。但是可用并不是代表最佳。

  例如最近我刚好看到copy()函数,它就为不同的迭代器定义了不同的复制方法,它提供了Input Iterator的特化版本和针对更强化的Random Access Iteartor的特化版本,为什么如此?因为于非随机迭代器而言,它们无法做到迭代器之间的减法处理(operator-()),所以每次循环判断是否达到被复制区间的尾部时,只能与last(区间为[first, last) )迭代器进行比较,这个就是Input Iterator的特化版本;而针对更强化的随机迭代器而言,因为其提供的减法处理,所以可以利用difference type记录区间大小(last - first),然后在复制循环中只用判断循环次数是否超过区间大小就好了,这个就是Random Access Iterator的特化版本,相对而言后者的速度显然比前者快很多,这就是在不同情况下提供最大效率。

  但重点是,我们必须有能力去萃取出迭代器的种类,才能调用到相应的特化版本。这最后一个迭代器的相应类型一定必须是一个类(class type),不能只是数值号码之类的东西,因为编译器需要仰赖它来进行重载判断。下面定义五个classes,代表五种迭代器类型:

 struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag :public input_iterator {};
struct bidirectional_iterator_tag :public forward_iterator_tag {};
struct random_access_iterator_tag :public bidirectional_iterator_tag {};

  这些classes只作为标记用,所以不需要任何成员。至于为什么运用继承机制,它可以促成重载机制的成功运作,另一个好处是,通过继承,我们可以不必写“单纯只做传递调用”的函数,我们可以做一个小测试:

 struct B {};            //B 可比拟为Input Iterator
struct D1 :public B {}; //D1可比拟为Forward Iterator
struct D2 :public D1 {};//D2可比拟为Bidirectional Iterator template<typename I>
func(I& p, B)
{
cout << "B version" << endl;
}
template<typename I>
func(I& p, D2)
{
cout << "D2 version" << endl;
}
int main()
{
int *p;
func(p, B()); //输出“B version”
func(p, D1()); //输出“B version”
func(p, D2()); //输出“D2 version”
}

  何为“单纯只做传递调用”的函数,如果Forward Iteartor的特化版本的功能Input Iterator也能做到,那在非继承情况下,它是如下的,以advance函数的内部函数__advance()为例,该函数具有跳到容器内某指定位置的功能:

 //Distance即为两迭代器距离类型(difference type)
template<typename InputIterator, typename Distance>
inline void __advance(InputIterator& i, Distance n, input_iterator_tag)
{
//单向,逐一前进
while (n--)++i;
} template<typename InputIterator, typename Distance>
inline void __advance(InputIterator& i, Distance n, forward_iterator_tag)
{
__advance(i, n, input_iterator_tag()); //单纯的转调用
}

  如果是继承的情况下,只需写一个Input Iteartor版本即可,而无需再写一个Forward Iteartor特化版本然后做传递调用处理。而因处理方式不一,它还提供了Bidirectional Iteartor的特化版本和Random Access Iteartor的特化版本,这里不再赘述,但有必要提一下其上层对外开放接口advance函数,这一上层接口只需两个参数,当它准备将工作转给上述的__advance()时,才自行加上第三参数:迭代器类型。因此,这个上层函数必须有能力从它所获得的迭代器推导出其类型——这份工作自然是交给traits机制:

 template<typename InputIterator, typename Distance>
inline void advance(InputIterator& i, Distance n)
{
__advance(i, n,
iterator_traits<InputIterator>::iteartor_category());
}

  iterator_traits<InputIterator>::iteartor_category()将产生一个临时对象,其类型应该隶属于前述四个迭代器类型(I、F、B、R)之一。然后,根据这个类型,编译器才决定调用哪一个__advance()重载函数。

  因此,为了满足上述行为,traits必须在增加一个相应类型:

 template<typename I>
struct iterator_traits
{
...
typedef typename I::iterator_category iterator_category;
};
template<typename T>
struct iterator_traits<T*>
{
...
//原生指针是一种Random Access Iterator
typedef typename random_access_iterator_tag iterator_category;
};
struct iterator_traits<const T*>
{
...
//原生pointer-to-const是一种Random Access Iterator
typedef typename random_access_iterator_tag iterator_category;
};

  一个迭代器的类型,应该是各种迭代器类型中最强的那个,例如int*,既是Random Access Iterator,又是Bidirectional Iterator,同时也是Forward Iterator,而且也是Input Iteartor,那么,其类型应该归属为randm_access_iterator_tag。

  另外还需注意的是STL算法的一个命名规则,如advance(),其迭代器类型参数命名为InputIteartor,这表示只要其迭代器类型的基类为input_iteartor_tag,该函数就能接受,即接受各种类型的迭代器。以算法所能接受之最低阶迭代器类型,来为其迭代器类型参数命名。

std::iterator的保证

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

template <class Category,
class T,
class Distance = ptrdiff_t,
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;
};

  iteartor class不含任何成员,纯粹只是类型定义,所以继承它并不会带来任何额外的负担。由于后三个参数皆有默认值,故新的迭代器只需提供前两个参数即可。如我们第一节土法炼钢的ListIter,如果改用正式规格,应该这么写:

 template<typename Item>
struct ListIter: public std::iterator<std::forward_iterator_tag, Item>
{...}

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

STL源码剖析——iterators与trait编程#3 iterator_category的更多相关文章

  1. STL源码剖析——iterators与trait编程#2 Traits编程技法

    在算法中运用迭代器时,很可能用到其相应类型.什么是相应类型?迭代器所指对象的类型便是其中一个.我曾有一个错误的理解,那就是认为相应类型就是迭代器所指对象的类型,其实不然,相应类型是一个大的类别,迭代器 ...

  2. STL源码剖析——iterators与trait编程#4 iterator源码

    在前两节介绍了迭代器的五个相应类型,并讲述如何利用traits机制提取迭代器的类型,但始终是把iteartor_traits类分割开来讨论,这影响我们的理解,本节将给出iteator的部分源码,里面涵 ...

  3. STL源码剖析——iterators与trait编程#1 尝试设计一个迭代器

    STL的中心思想在于:将数据容器与算法分开,独立设计,再用一帖粘着剂将它们撮合在一起.而扮演粘着剂这个角色的就是迭代器.容器和算法泛型化,从技术角度来看并不困难,C++的模板类和模板函数可分别达成目标 ...

  4. STL源码剖析——Iterators与Traits编程#5 __type_traits

    上节给出了iterator_traits以及用到traits机制的部分函数的完整代码,可以看到traits机制能够提取迭代器的特性从而调用不同的函数,实现效率的最大化.显然这么好的机制不应该仅局限于在 ...

  5. 《STL源码剖析》学习之traits编程

    侯捷老师在<STL源码剖析>中说到:了解traits编程技术,就像获得“芝麻开门”的口诀一样,从此得以一窥STL源码的奥秘.如此一说,其重要性就不言而喻了.      之前已经介绍过迭代器 ...

  6. 【STL 源码剖析】浅谈 STL 迭代器与 traits 编程技法

    大家好,我是小贺. 点赞再看,养成习惯 文章每周持续更新,可以微信搜索「herongwei」第一时间阅读和催更,本文 GitHub : https://github.com/rongweihe/Mor ...

  7. STL源码剖析 迭代器(iterator)概念与编程技法(三)

    1 STL迭代器原理 1.1  迭代器(iterator)是一中检查容器内元素并遍历元素的数据类型,STL设计的精髓在于,把容器(Containers)和算法(Algorithms)分开,而迭代器(i ...

  8. STL源码剖析之序列式容器

    最近由于找工作需要,准备深入学习一下STL源码,我看的是侯捷所著的<STL源码剖析>.之所以看这本书主要是由于我过去曾经接触过一些台湾人,我一直觉得台湾人非常不错(这里不涉及任何政治,仅限 ...

  9. STL"源码"剖析-重点知识总结

    STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...

随机推荐

  1. ICEM-hollow

    原视频下载地址: http://yunpan.cn/cumt7U7ufYfA3  访问密码 a46d

  2. Linux文件的权限的基本介绍

    一. ls  -l    显示的内容如下: 二.rwx权限详解 1.rwx作用到文件 2. rwx作用在目录 三.文件及目录实际案例 四.修改权限  -  chmod 1. 基本说明: 2.第一种方式 ...

  3. 三大框架 之 Struts2

    目录 Struts2 Struts2简介 Struts2框架的作用 常见web层的框架 web框架特点 Struts2基本使用 Struts2执行流程 Struts2配置 struts2的加载顺序 P ...

  4. Spring Bean相关配置

    Bean相关配置 1.名称与标识 id 使用了约束中的唯一约束.里面不能出现特殊字符的 name 没有使用约束中的唯一约束.里面可以出现特殊字符. 设置对象的生命周期方法 init-method Be ...

  5. Flutter扫码识别二维码内容

    前面一篇写了生成二维码图片,这篇来写使用相机扫描识别二维码 识别二维码需要用到插件 barcode_scan 首先在 pubspec.yaml 文件中添加以下依赖,添加依赖后在 pubspec.yam ...

  6. Wrapper: Error - Unable to execute Java command

    在64位的系统下 将短信程序运行于服务中,出现以下错误: Error: [size=14px; line-height: 26px;]FATAL  | wrapper  | 2012/06/18 17 ...

  7. laravel 存储base64格式图片

    laravel 存储base64格式图片 一.总结 一句话总结: 用正则替换base64图片编码的编码头即可 存储图片的话,用laravel可以用Storage的put方法,原生php可以用file_ ...

  8. 前端 MV*框架的意义

    早期前端都是比较简单,基本以页面为工作单元,内容以浏览型为主,也偶尔有简单的表单操作,基本不太需要框架. 随着 AJAX 的出现,Web2.0的兴起,人们可以在页面上可以做比较复杂的事情了,然后前端框 ...

  9. numpy中的mean()函数

    本文链接:https://blog.csdn.net/lilong117194/article/details/78397329mean() 函数定义:numpy.mean(a, axis, dtyp ...

  10. Tosca 给定义变量,取内容放到变量里

    可以在TOOLS里 buffer viewer里面搜索查自己的变量