c++迭代器详解

  • 迭代器介绍
    • 初步使用迭代器
      • 所有迭代器都支持的运算符
      • 迭代器类型
    • 迭代器范围
      • 使用左闭合范围蕴含的编程假定
    • 迭代器的类别
      • 为什么这样分类?
      • 输入迭代器(input iterator)
      • 输出迭代器(output iterator)
      • 前向迭代器(forward iterator)
      • 双向迭代器(bidirectional iterator)
      • 随机访问迭代器(random-access iterator)
      • 常见库类型的迭代器类型,以及标准库额外定义的迭代器类型
    • 迭代器的种类
      • 插入迭代器(insert iterator)
        • back_inserter
        • front_inserter
        • inserter
        • 理解插入器的工作过程
          • 对于inserter
          • 对于front_insert和back_insert
      • 流迭代器(stream iterator)
        • 流迭代器的作用
        • istream_iterator操作
        • istream_iterator允许懒惰求值
        • ostream_iterator操作
      • 反向迭代器(reverse iterator)
        • 反向迭代器转换为普通迭代器
      • 移动迭代器(move iterator)
  • 不同算法对迭代器类别的要求统计

迭代器介绍

所有的标准库容器都可以使用迭代器,但迭代器不仅仅只有标准库容器才能使用,比如string类型和iostream类型,虽然iostream类型不是容器,但是标准库定义了可以用于这些IO类型对象的迭代器,这些迭代器将它们对应的流当做一个特定类型的元素序列来处理。迭代器可以使用下标运算符来访问它们所绑定对象的元素,类似于指针类型,迭代器也提供了对对象的间接访问。就迭代器而言,其对象是容器中的元素或者string中的字符等等,使用迭代器可以访问某个元素,迭代器也能从一个元素移动到另一个元素。迭代器有有效和无效之分,这一点和指针差不多,有效的迭代器或者指向某个元素,或者指向容器中尾元素的下一位置,其他所有情况都属于无效;

初步使用迭代器

有迭代器的类型同时拥有返回迭代器的成员。比如,这些类型都拥有名为begin和end的成员,begin成员负责返回指向第一个元素(或第一个字符)的迭代器。end成员负责返回指向容器(或者string对象)“尾元素的下一位置”的迭代器,end成员返回的迭代器常被称作尾后迭代器,特殊情况下如果容器为空,则begin和end返回的是同一个迭代器;

所有迭代器都支持的运算符

标准容器迭代器的运算符
*iter ——————返回迭代器iter所指的元素的引用
iter->mem————解引用iter并获取该元素的名为mem的成员,等价于(*iter).mem
++iter,iter++————令iter指示容器中的下一个元素
iter1 == iter2,iter1 != iter2————判断两个迭代器是否相等,如果两个迭代器指的是同一个元素或者他们是同一个容器的尾后迭代器则相等,反之不想等

迭代器类型

拥有迭代器的标准库类型使用iterator和const_iterator来表示迭代器的类型
const_iterrator能读取但是不能修改它所指向的元素值,相反iterrator的对象可读可写。如果容器对象或者string对象是一个常量,那么只能使用const_iterrator;如果容器对象或者string对象不是常量,那么既能使用iterrator也能使用const_iterrator。
如果对象只需读操作而无需写操作的话最好使用常量类型,为了便于专门得到const_iterrator类型的返回值,c++11引入了两个新的函数,分别是cbegin和cend;

迭代器范围

一个迭代器范围由一对迭代器表示,这两个迭代器分别指向同一个容器中的元素或者是尾元素之后的位置,这两个迭代器通常被成为begin和end,它们标记了容器中元素的一个范围,迭代器范围中的元素包含从begin开始(包含begin)直到end(不包含end)之间的所有元素,end可以与begin指向相同的位置,但不能指向begin之前的位置。
这种元素范围被成为左闭合区间:[begin, end)

使用左闭合范围蕴含的编程假定

标准库使用左闭合范围是因为这种范围有三种方便的性质,假定begin和end构成一个合法的迭代器范围,那么:

  • 如果begin与end相等,则范围为空
  • 如果begin与end不相等,则范围中至少有一个元素,切begin指向该范围中的第一个元素
  • 我们可以对begin递增若干次,似的begin == end;

迭代器的类别

为什么这样分类?

任何算法最基本的特性是要求它的迭代器提供哪些操作,按照算法所要求的迭代器操作可以分为五个迭代器类别,迭代器是按照它们所提供的操作来分类的,而这种分类形成了一种层次,除了输出迭代器之外,一个高层类别的迭代器支持底层类别的迭代器的所有操作。

输入迭代器(input iterator)

只读,不写;单遍扫描,只能递增;
输入迭代器可以读取序列中的元素,一个输入迭代器必须支持:

  • 用于比较两个迭代器相等和不相等运算符(==, !=)
  • 用于推进迭代器的前置和后置递增运算符(++)
  • 用于读取元素的解引用运算符(*);解引用只会出现在赋值运算符的右侧(因为输入迭代器只读不写)
  • 箭头运算符(->),等价于(*it).member,即,解引用迭代器,并提取对象的成员
    输入迭代器只用于顺序访问。对于一个输入迭代器,*iter++保证是有效的,但递增它可能导致所有其他指向流的迭代器失效,所以输入迭代器只能用于单遍扫描算法,istream_iterator【见后文】是一种输入迭代器。

输出迭代器(output iterator)

只写,不读;单遍扫描,只能递增;
输出迭代器可以看作输入迭代器功能上的补集——只写而不读元素,输出迭代器必须支持:

  • 用于推进迭代器的前置和后置递增运算(++);
  • 解引用运算符(*),只出现在赋值运算符的左侧(因为输出迭代器只写不读)(向一个已经 解引用的输出迭代器赋值,就是将值写入它所指向的元素);

我们只能向一个输出迭代器赋值一次,类似于输入迭代器,输出迭代器也只能用于单遍扫描算法。ostream_iterator【见后文】是一种输出迭代器。

前向迭代器(forward iterator)

可读写,多遍扫描,只能递增
可以读写元素,这类迭代器只能在序列中沿一个方向移动。前向迭代器支持所有输入和输出迭代器的操作,而且可以多次读写同一元素。因此,我们可以保存前向迭代器的状态,使用前向迭代器的算法可以对序列进行多遍扫描。

双向迭代器(bidirectional iterator)

可读写,多遍扫描,可能递增递减
可以正向/反向读写序列中的元素。除了支持所有前向迭代器的操作之外,双向迭代器还支持前置和后置递减运算符(–)。

随机访问迭代器(random-access iterator)

可读写,多遍扫描,支持全部迭代器运算
随机访问迭代器提供在常量时间内访问任意元素的能力。此类迭代器支持双向迭代器的所有功能,此外还支持:

  • 用于比较两个迭代器相对位置的关系运算符(<,<=,>,>=)
  • 迭代器和一个整数值的加减运算(+,+=,-,-=),计算结果迭代器在序列中前进(后退)给定整数个元素后的位置
  • 用于两个迭代器上的减法运算符号(-),得到两个迭代器的距离
  • 下表运算符(iter[n])等价于*(iter[n])

常见库类型的迭代器类型,以及标准库额外定义的迭代器类型

  • istream_iterator【见后文】是一种输入迭代器;
  • ostream_iterator【见后文】是一种输出迭代器;
  • forward_list上的迭代器是前向迭代器;
  • list的迭代器是双向迭代器;
  • 关联容器的迭代器都是双向迭代器;
  • array,deque,string,vector的迭代器都是随机访问迭代器,用于访问内置数组元素的指针也是随机访问迭代器,反向迭代器【见后文】也是随机访问迭代器;

迭代器的种类

C++11除了为每个容器定义的迭代器之外,标准库在头文件iterator中还定义了额外几种迭代器

插入迭代器(insert iterator)

插入迭代器是一种保证算法有足够元素空间来容纳输出数据的方法,插入迭代器是一种向容器中添加元素的迭代器。这些迭代器被绑定到一个容器上,可以用来向容器插入元素,插入器是一种适配器,它接受一个容器,生成一个迭代器,能实现向给定容器插入元素,当我们通过一个插入迭代器赋值时,该迭代器调用容器操作来向给定容器的指定位置插入一个元素。

插入迭代器的操作
it = t; 在it指定的当前位置插入值t
*it++it,it++;这些操作虽然存在,但不会对it做任何事情,每个操作都返回it

插入器有三种类型,差异在于元素插入的位置是不同的:

back_inserter

back_inserter接受一个指向容器的引用,返回一个与该容器绑定的插入迭代器。当我们使用此迭代器赋值时,赋值运算符会调用push_back将一个具有给定值的元素添加到容器中,举个例子:

    vector<int> x;auto iter = back_inserter(x);iter = 4;fill_n(iter, 10, 3);                                                                                                                                for (auto &c : x) {   cout << c << endl;}

front_inserter

创建一个使用push_front的迭代器

inserter

创建一个使用insert的迭代器,此函数接受第二个参数,这个参数必须是一个指向给定容器的迭代器,元素将被插入到给定迭代器所表示的元素之前。
attention please :只有容器支持push_front或push_back的情况下我们才能分别使用front_inserter和back_inserter;

理解插入器的工作过程

对于inserter

当调用inserter(vec, iter);时,我们得到一个迭代器,接下来使用它时,会将元素插入到iter原来所指向的元素之前的位置;举个例子:
iter = val;等价于iter = vec.insert(iter, val);++iter;

对于front_insert和back_insert

当我们使用front_insert时,元素总是会被插入到第一个元素之前,back_insert反之;
举个例子:

    list<int> lst{1, 2, 3, 4}; deque<int> inserter_x, front_x, back_x;copy(lst.begin(), lst.end(), back_inserter(back_x));copy(lst.begin(), lst.end(), front_inserter(front_x));copy(lst.begin(), lst.end(), inserter(inserter_x, inserter_x.begin()));for (auto &c : front_x) {cout << c;}   cout << endl;for (auto &c : back_x) {cout << c;    }    cout << endl;    for (auto &c : inserter_x) {cout << c;    }    cout << endl;

输出结果为

4321
1234
1234

流迭代器(stream iterator)

这些迭代器被绑定到输入或输出流上,可以用来遍历所关联的IO流。
虽然iostream类型不是容器,但标准库定义了可以用于这些IO类型对象的迭代器,这些迭代器将它们对应的流当做一个特定类型的元素序列来处理。

流迭代器的作用

通过使用流迭代器,我们可以用泛型算法从流对象读取数据以及向其写入数据。

istream_iterator操作

istream_iterator读取输入流,当我们创建一个istream_iterator迭代器时必须注意一下几点:

  • 因为istream_iterator使用>>来读取输入流,所以istream_iterator要读取的类型必须定义了输入运算符;
  • 创建流迭代器时,必须指定迭代器将要读写的对象类型;
  • 当创建一个istream_iterator时我们可以将其绑定到一个流或者默认初始化一个迭代器,这样能创建一个当做尾后值使用的迭代器【对于一个绑定到流的迭代器,一旦与其关联的流遇到文件尾或遇到IO错误,迭代器的值就与尾后迭代器相等】

例子:

      istream_iterator<int> in_iter(cin);// 从cin读取intistream_iterator<int> eof; // 尾后迭代器ifstream in("xxx");istream_iterator<string> str_in(in);  //从"xxx"读取字符串

例子:使用istream_iterator从标准输入读取数据,存入一个vector

    vector<int> vec;istream_iterator<int> in_iter(cin);istream_iterator<int> eof;while (in_iter != eof) {vec.push_back(*in_iter++);}for (auto &c :vec) {cout << c;}cout << endl;

我们可以将上述程序重写为如下形式

    istream_iterator<int> in_iter(cin), eof;                                                                                                                                                                       vector<int> vec(in_iter, eof); // vec初始化为迭代器in_iter和eof指定范围中的元素的拷贝。范围中元素的类型必须与vec的元素类型相容(array不适用此方法)//这两个迭代器是istream_iterator的,这意味着元素范围是通过从关联的流中读取数据获得的。
istream_iterator支持的操作
istream_iterator in(is); in从输入流is读取类型为T的值
istream_iterator end; 读取类型为T的值的istream_iterator迭代器,表示尾后位置
in1 == in2;in1 != in2;in1和in2必须读取相同类型。如果它们都是尾后迭代器,或绑定到相同的输入,则两者相等
*in 返回从流中读取的值
in->mem 等价于(*in).mem
++in,in++;使用元素类型所定义的>>运算符从输入流中读取下一个值。

istream_iterator允许懒惰求值

当将一个istream_iterator绑定到一个流时,标准库并不保证迭代器立即从流读取数据,具体实现可以推迟从流中读取数据,直到我们使用迭代器时才真正读取。标准库中的实现所保证的是,在我们第一次解引用迭代器之前,从流中读取数据的操作已经完成了。在一般情况下,立即读取或者推迟读取并没有什么区别,但是,如果我们创建了一个istream_iterator,没有使用就销毁了,或者我们正在从两个不同的对象同步读取同一个流那么此时,何时读取可能就很重要了。

ostream_iterator操作

我们可以对任何具有输出运算符(<<)的类型定义ostream_iterator。当创建一个ostream_iterator时,有以下一些特点需要我们注意:

  • 提供(可选的)第二个参数,它时一个字符串,在输出每个元素后都会打印此字符串
  • 此字符串必须是一个C风格字符串
  • 必须将ostream_iterator绑定到一个指定的流,不允许空的或者表示尾后位置的ostream_iterator。
ostream_iterator支持的操作
ostream_iterator out(os); out将类型为T的值写到输出流os中
ostream_iterator out(os, d); out将类型为T的值写到输出流os中,每个值的后面都接一个d字符串
out = val;用<<运算符将val写入到out所绑定的ostream中,val的类型必须与out可写的类型兼容
*out,,++out,out++;这些运算符时存在的,但不对out做任何事,每个运算符返回out

例子:我们可以使用ostream_iterater来输出值的序列:

    vector<int> x{1, 2, 3, 4}; ostream_iterator<int> out_iter(cout, " ");for (auto c : x) {*out_iter++ = c;}   cout << endl;

每次向out_iter赋值时,写操作就会被提交。
可以使用copy来打印vec中的元素,这比编写循环更简单:

 copy(vec.begin(), vec.end(), out_iter);cout << endl;

反向迭代器(reverse iterator)

反向迭代器就是在容器中从尾元素向首元素反向移动的迭代器,对于反向迭代器,递增递减操作的含义和普通迭代器相比会颠倒过来,递增一个反向迭代器,迭代器所绑定的元素下标会递减,而递减一个反向迭代器,迭代器所绑定的元素下标会递增,除了forward_list之外,其他容器都支持反向迭代器(因为forward_list上的迭代器是前向迭代器)。我们通过调用rbegin,rend,crbegin和crend成员函数来获得反向迭代器,与普通迭代器一样,反向迭代器也有const和非const版本。
通过下图,我们可以显而易见的明白普通迭代和反向迭代器的异同:

反向迭代器转换为普通迭代器

我们通过调用reverse_iterator的base成员函数来完成这一转换,此成员函数会返回该反向迭代器绑定的对象的普通迭代器。反向迭代器会反向的处理string,所以当我们想要使用反向迭代器将string中的内容按照正常的顺序输出的时候我们就需要将反向迭代器转换为普通迭代器。举个例子:

    vector<string> x{"ahhdka", ",", "dada", ",", "sdgjfsk"};auto iter = find (x.crbegin(), x.crend(), ",");cout << string(x.crbegin(), iter) << endl;cout << string(iter.base(), x.cend()) << endl;

移动迭代器(move iterator)

这些专用的迭代器不是拷贝其中的元素,而是移动它们。

不同算法对迭代器类别的要求统计

要求输入迭代器的算法:accumulate,find
要求输出迭代器的算法:copy,其第三个迭代器表示目的位置,必须至少是输出迭代器。
要求前向迭代器的算法:replace,replace_copy
要求双向迭代器的算法:reverse
要求随机访问迭代器的算法:sort

参考文献《C++ primer》第五版

C++11迭代器专栏详解相关推荐

  1. python平方数迭代器_对python中的高效迭代器函数详解

    python中内置的库中有个itertools,可以满足我们在编程中绝大多数需要迭代的场合,当然也可以自己造轮子,但是有现成的好用的轮子不妨也学习一下,看哪个用的顺手~ 首先还是要先import一下: ...

  2. mysql int(3)与int(11)的区别详解

    这篇文章主要介绍了mysql int(3)与int(11)的区别详解的相关资料,需要的朋友可以参考下 mysql int(3)与int(11)的区别 总结,int(M) zerofill,加上zero ...

  3. mysql8.0.11 安装顺序_mysql 8.0.11 安装步骤详解

    本文为大家分享了mysql 8.0.11 安装步骤,供大家参考,具体内容如下 第一步:下载安装包 MYSQL官方下载地址:官方下载 这里第一项是在线安装,第二项是离线包安装,我选择的是第二项(不用管你 ...

  4. Java集合迭代器原理图解_Java Iterator接口遍历单列集合迭代器原理详解

    这篇文章主要介绍了Java Iterator接口遍历单列集合迭代器原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 Iterator接口概述 ...

  5. WindowsMediaPlayer 11 控件详解

    WindowsMediaPlayer 11 控件详解 转载别人的VB内容 C#中大多也通用 都是Microsoft的作品 . 属性/方法名: 说明: [基本属性] URL:String; 指定媒体位置 ...

  6. ROS(11)move_base详解

    11. move_base详解 11.1. move_base配置参数解读 参考链接:https://blog.csdn.net/banzhuan133/article/details/9023925 ...

  7. vue 生命周期的11中方法详解

    vue 生命周期的十一中方法详解 vue 生命周期的定义 vue实例 从创建到销毁,也就是说从创建 -> 初始化 数据-> 编译模版 -> 挂载Dom -> 渲染 更新 -&g ...

  8. 【C++】C++11新增关键字详解

    目录 一.auto 1.auto 用来声明自动变量,表明变量存储在栈(C++11之前) 2.auto用于推断变量类型示例(C++11) 3.声明或定义函数时作为函数返回值的占位符,此时需要与关键字 d ...

  9. Php-SPL库中的迭代器类详解(转)

    SPL提供了多个迭代器类,分别提供了迭代访问.过滤数据.缓存结果.控制分页等功能.,因为php总是在不断壮大,我尽可能列出SPL中所有的迭代类.下面其中一些迭代器类是需要php5.4,另外一些如Sea ...

最新文章

  1. VS2017 OpenCV配置
  2. 比原链Bytom错误码一览
  3. python绘制基因结构图_从 gff 到 gggenes 绘制基因结构图
  4. python代码有时候在命令行下和Python Shell中执行的结果不一样?
  5. synchronized的基本认识
  6. java计算并显示学生的成绩_Java开学测试-学生成绩管理系统
  7. 小米造车是智能手机进入红海后的突围之举
  8. 历时 4 年,阿里云推出金融核心系统转型实践书
  9. python之路_django分页及session介绍
  10. IT大公司面试流程与总结
  11. A Lookahead Read Cache论文总结
  12. 小凯的疑惑(Noip 提高组 2017 d1 1)+[USACO4.1]麦香牛块Beef McNuggets
  13. Markdown字体,字号,颜色和背景色设置
  14. 计算机技术专硕学制两年的学校,专硕一般读几年
  15. 从华为智慧生活APP的智能场景,谈到建成华为专属的智慧生活管理后台
  16. 实训十四:路由器策略路由(PBR)配置
  17. linux主板上的网卡不显示,linux服务器更换主板后无法识别网卡解决办法
  18. 写博客有什么意义以及为什么?
  19. 给斐讯K1刷机并拨号e信(湖北地区测试无问题)
  20. [RS] 基础概念区分:DN-辐射率-反射率

热门文章

  1. MATLAB里面的mghglobal函数,Matlab讨论区 - 声振论坛 - 振动,动力学,声学,信号处理,故障诊断 - Powered by Discuz!...
  2. Java springboot压缩文件上传,解压,删除压缩包
  3. 陈儒老师的《Python源代码剖析》需要理解的东西(源码解析的pdf文档需要自己网上下载)
  4. OpenGL 中文教程
  5. tiptop 编译运行_TIPTOP开发技术试题-答案
  6. JAVA翻译官_java的一些基础知识(一)
  7. JavaScript 中如何实现大文件并行下载?
  8. [乡土民间故事_徐苟三传奇]第廿六回_孟老板三次遭戏弄
  9. 微信小程序——使用excel-export导出excel
  10. Java项目:在线服装销售商城系统(java+SpringBoot+Maven+Vue+mysql)