前言:

  以下的内容为我阅读c++沉思录18,19,20章的笔记以及自己的想法.

正文:

  总所周知,c++的stl中提出了iterator的概念,这是C所没有的.在一般的使用中,iterator的行为很像c内建的指针.而在java和c#中索性就直接取消了指针,而采用类似iterator的做法来代替了指针.很多编程人员在使用iterator的时候也仅仅把他当作了指针的一个变体而没有多加注意.

  不过既然是学习,那我们在使用的时候也要知道其存在的原因,其分类以及用法吧.

  首先是问题的提出:

  很多人会觉得,既然C++沿用了C的指针这么强大的东西了,为什么还要iterator这么一群类来工作呢?

  我们知道,在我们编写模板的时候,对于使用了iterator做为参数的函数,往往该函数对于iterator有些特定的操作.比如下列2个函数

template<class P,class T>
P find(P start,P beyond,const T& x)
{
while( start != beyond && * start != x)
++start;
return start;
} template<class P, class T>
void reverse(P start, P beyond)
{
while(start != beyond) {
--beyond;
if (start != beyond) {
T t = *start;
*start = *beyond;
*beyond = t;
++ start;
}
}
}

  我们可以看到,这两个函数都对模板参数P做了一定要求,在find中,我们要求P必须允许 != ,++和*(P)这三个运算符的操作,而对于reverse函数来说,其要求更多,运算符++,--,*(),!=都必须支持.问题就这么出来了,我们怎么让所有人都遵守这一要求呢?或者说,后续采用这个模板的使用者怎么能在不知道实现细节的情况下了解并遵守这些要求呢?显然,我们需要一个分类方法来知道如何界定不同种类的迭代器.

  不过这里还是没有解释一个疑惑,即这两个函数改成T的指针也能完成的很好,我们还要iterator做什么?

  答案很简单,不是所有数据结构都是线性的!对于一个链表来说,如果要实现上述的功能,我们必须重新写一个几乎一样仅仅是把++start变成start = start->next的函数.显然,这样重复的事情我们是不希望碰到的,尤其如果函数还非常大的情况下.而这时候,iterator的作用就出来了.对于链表,我们的链表的iterator(list_iterator)仅仅只要把operator++()重写成operator++(){now = now->next;},就能正常的使用find函数了.完全不需要重新编写.这就减少了我们算法的编写量.

  现在,既然我们知道了iterator的好处了之后,接下来我们就想知道之前提到的分类法是怎么回事了.经常听说输入迭代器,输出迭代器,前向迭代器,双向迭代器,随机存取迭代器是怎么回事.

  迭代器的分类:

  1.输入迭代器(input iterator)

input iterator就像其名字所说的,工作的就像输入流一样.我们必须能

  • 取出其所指向的值
  • 访问下一个元素
  • 判断是否到达了最后一个元素
  • 可以复制

  因此其支持的操作符有  *p,++p,p++,p!=q,p == q这五个.凡是支持这五个操作的类都可以称作是输入迭代器.当然指针是符合的.

  2.输出迭代器(output iterator)

    output iterator工作方式类似输出流,我们能对其指向的序列进行写操作,其与input iterator不相同的就是*p所返回的值允许修改,而不一定要读取,而input只允许读取,不允许修改.

    支持的操作和上头一样,支持的操作符也是 *p,++p,p++,p!=q,p == q.

  使用Input iterator和output iterator的例子:

 

 1 template<class In,class Out>
2 void copy(In start,In beyond, Out result)
3 {
4 while(start != beyond) {
5 *result = *start; //result是输出迭代器,其*result返回的值允许修改
6 ++result;
7 ++start;
8 }
9 }
10
11 //简写
12 template<class In,class Out>
13 void copy(In start,In beyond, Out result)
14 {
15 while(start != beyond)
16 *result++ = *start++;//这个应该能看懂的...
17 }

  3.前向迭代器(forward iterator)

     前向迭代器就像是输入和输出迭代器的结合体,其*p既可以访问元素,也可以修改元素.因此支持的操作也是相同的.

   4.双向迭代器(bidirectional iterator)

     双向迭代器在前向迭代器上更近一步,其要求该种迭代器支持operator--,因此其支持的操作有  *p,++p,p++,p!=q,p == q,--p,p--

   5. 随机存取迭代器(random access iterator)

    即如其名字所显示的一样,其在双向迭代器的功能上,允许随机访问序列的任意值.显然,指针就是这样的一个迭代器.

    对于随机存取迭代器来说, 其要求高了很多:

  • 可以判断是否到结尾(  a==b or a != b)
  • 可以双向递增或递减( --a or ++a)
  • 可以比较大小( a < b or a > b or a>=b ...etc)
  • 支持算术运算( a + n)
  • 支持随机访问( a[n] )
  • 支持复合运算( a+= n)

  结构图:

  上面即为我们所讨论到的几个迭代器的层次.看起来很像继承是麽?

  但是实际上在STL中,这些并不是通过继承关联的.这些只不过是一些符合条件的集合.这样的好处是:少去了一个特殊的基类(input iterator),其次,使得内建类型指针可以成为iterator.

其实只要明白iterator是满足某些特别的功能的集合(包括类和内建类型),就不会觉得是继承了.

  

  Iterator使用:

  一个ostream_iteartor的例子:

  

 1 #include <iostream>
2
3 using namespace std;
4
5 template<class T>
6 class Ostream_iterator {
7 public:
8 Ostream_iterator(ostream &os,const char* s):
9 strm(&os), str(s){}
10 Ostream_iterator& operator++() {return *this;}
11 Ostream_iterator& operator++(int) {return *this;}
12 Ostream_iterator& operator*() {return *this;}
13 Ostream_iterator& operator=(const T& t)
14 {
15 *strm << t << str;
16 return *this;
17 }
18
19 private:
20 ostream* strm;
21 const char *str;
22 };
23
24 template<class In,class Out>
25 Out Copy(In start,In beyond,Out dest)
26 {
27 while(start != beyond)
28 *dest++ = *start++;
29 return dest;
30 }
31
32 int main()
33 {
34 Ostream_iterator<int> oi(cout, " \n");
35
36 int a[10];
37 for (int i = 0;i!=10;++i)
38 a[i] = i+1;
39 Copy(a,a+10,oi);
40
41 return 0;
42 }

  在这个例子中,我们简单的构造了一个ostream_iterator,并且使用了copy来输出..这个ostream_iterator和STL差别还是很大,不过功能上已经差不多了.我想读者们应该也能看懂代码吧,所以就不用多做什么解释了.
   第二个例子中,我们讲构造一个istream_iterator.

  对于一个istream_iterator来说,我们必须要存的是T buffer和istream *strm.不过由于istream的特殊性,我们必须知道buffer是否满,以及是否到达尾部,因此,我们的结构是这样的

1 template <class T>
2 class Istream_Iterator{
3
4 private:
5 istream *strm;
6 T buffer;
7 int full;
8 int eof;
9 };

  对于构造函数来说,因为有eof的存在,因此,我们自然想到,我们的默认构造函数就应该把eof设为1.而对于有istream的iterator,都应该为0.

  

1 Istream_iterator(istream &is):
2 strm(&is),full(0),eof(0){}
3 Istream_iterator():strm(0),full(0),eof(1){}

  对于我们的版本的istream_iterator来说,我们需要完成的功能有

  • 进行取值操作(dereference),既*(p)
  • 自增操作
  • 比较

  自增操作:

  我们知道,在istream中,一旦获取到一个值了之后,我们将不在访问这个值.我们的iterator也要符合这样的要求,因此,我们通过full来控制.

1     Istream_iterator& operator++(){
2 full = 0;
3 return *this;
4 }
5 Istream_iterator operator++(int){
6 Istream_iterator r = *this;
7 full = 0;
8 return r;
9 }

  Dereference:

  对于解除引用操作来说,我们需要将流中缓存的值存入buffer内.同时,我们要判断读取是否到结尾,到了结尾则应当出错.

  在这里我们写了一个私有函数fill(),这个将在比较的时候用到

  

 T operator*() {
fill();
assert(eof);//我们断定不应该出现eof
return buffer;
}
void fill(){
if (!full && !eof) {
if (*strm >> buffer)
full = 1;
else
eof = 1;
}
}

  比较:

  对于比较来说,只有两个istream_iterator都同一个对象或者都处于文件尾时,这两个istream_iterator才相等.因此这里其比较对象为istream_iterator而不是const istream_iterator

template<class T>
int operator==(Istream_iterator<T> &p,Istream_iterator<T>& q)
{
if (p.eof && q.eof)
return 1;
if (!p.eof && !q.eof)
return &p == &q;
p.fill();q.fill();
return p.eof == q.eof;
}

最后的测试例子,读者可以把之前的copy和ostream_iterator拷下来

int main()
{
Ostream_iterator<int> o(cout,"\t");
Istream_iterator<int> i(cin);
Istream_iterator<int> eof; Copy(i,eof,o);
return 0;
}

结论:

  iterator是STL实现所有算法已经其通用型的基础.通过对iterator分类,使得算法的使用者在使用时不需要知道具体实现就可知道算法对于参数的要求,形成一个通用的体系.

原文转自:http://www.cnblogs.com/marchtea/archive/2012/02/27/2370068.html

原作者为 marchtea. 请尊重原作者版权

c++ iterator(迭代器)分类及其使用的更多相关文章

  1. [设计模式] Iterator - 迭代器模式:由一份奥利奥早餐联想到的设计模式

    Iterator - 迭代器模式 目录 前言 回顾 UML 类图 代码分析 抽象的 UML 类图 思考 前言 这是一包奥利奥(数组),里面藏了很多块奥利奥饼干(数组中的元素),我将它们放在一个碟子上慢 ...

  2. [C# 设计模式] Iterator - 迭代器模式:我与一份奥利奥早餐的故事

    Iterator - 迭代器模式 目录 前言 回顾 UML 类图 代码分析 抽象的 UML 类图 思考 前言 这是一包奥利奥(数组),里面藏了很多块奥利奥饼干(数组中的元素),我将它们放在一个碟子上慢 ...

  3. ES6笔记(6)-- Set、Map结构和Iterator迭代器

    系列文章 -- ES6笔记系列 搞ES6的人也是够无聊,把JS弄得越来越像Java.C++,连Iterator迭代器.Set集合.Map结构都出来了,不知道说什么好... 一.简单使用 1. iter ...

  4. vector容器+iterator迭代器

    关于vector容器的详细描述,可参考:http://www.jb51.net/article/41648.htm   关于iterator迭代器的描述,可参考http://www.cppblog.c ...

  5. C++ Iterator迭代器介绍及Iterator迭代器用法代码举例

    C++ Iterator迭代器介绍 迭代器可被用来访问一个容器类的所包函的全部元素,其行为像一个指针.举一个例子,你可用一个迭代器来实现对vector容器中所含元素的遍历.有这么几种迭代器如下: 迭代 ...

  6. 【转】Java学习之Iterator(迭代器)的一般用法 (转)

    [转]Java学习之Iterator(迭代器)的一般用法 (转) 迭代器(Iterator) 迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构.迭 ...

  7. 设计模式(十五):Iterator迭代器模式 -- 行为型模式

    1.概述 类中的面向对象编程封装应用逻辑.类,就是实例化的对象,每个单独的对象都有一个特定的身份和状态.单独的对象是一种组织代码的有用方法,但通常你会处理一组对象或者集合. 集合不一定是均一的.图形用 ...

  8. C#:iterator 迭代器/partial class 分布类/泛型

    C#:iterator 迭代器/partial class 分布类/泛型 iterator 迭代器 写个最简单的迭代,(迭代一个字符串数组): 1.实现接口中的方法: 1 using System; ...

  9. Python 中 Iterator(迭代器)和Iterable(迭代对象)的区别

    直接可以用作for循环的数据类型有以下几种: tuple.list.dict.str等, 上述数据类型可以用作for循环的叫做可迭代对象Iterable.可以使用isinstance判断一个对象是否是 ...

随机推荐

  1. 浅谈C++设计模式之工厂方法(Factory Method)

    为什么要用设计模式?根本原因是为了代码复用,增加可维护性. 面向对象设计坚持的原则:开闭原则(Open Closed Principle,OCP).里氏代换原则(Liskov Substitution ...

  2. C# .NET 动态调用webservice的三种方式

    转载自 百度文库 http://wenku.baidu.com/link?url=Q2q50wohf5W6UX44zqotXFEe_XOMaib4UtI3BigaNwipOHKNETloMF4ax4W ...

  3. 准备使用 Office 365 中国版--安装

    温故而知新,先附上一个链接:Office 365常见问题 Office 365中Office套件的安装介质和传统Office套件的安装介质是有些区别的,虽然功能都一样.因此,如果购买了带Office套 ...

  4. for循环的省略

    for (初始化语句; 条件语句; 控制语句){ 被执行的代码块 }; 例如: for (var i=0;i<cars.length;i++) { document.write(cars[i] ...

  5. IE自动跳转到标准模式

    <meta http-equiv="X-UA-Compatible" content="IE=edge">

  6. [转]使用Oracle SQL Developer 17410 提示无法从套接字获取更多数据如何解决

    本文转自:http://m.educity.cn/ite/1121475.html 将oracle?安装目录下的Network/admin/listener.ora文件中的 (PROGRAM = ex ...

  7. POJ2157Maze[DFS !]

    Maze Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 3818   Accepted: 1208 Description ...

  8. 深入探讨 java.lang.ref 包

    深入探讨 java.lang.ref 包 本文主要探讨了 java.lang.ref 包的使用方法,以及源码解读.并就该包在不同 JVM 上的表现进行了比较与分析.通过阅读本文,读者可以加深对 jav ...

  9. 直接拿来用!最火的Android开源项目(完结篇)

    直接拿来用!最火的Android开源项目(完结篇) 2014-01-06 19:59 4785人阅读 评论(1) 收藏 举报 分类: android 高手进阶教程(100) 摘要:截至目前,在GitH ...

  10. Nginx虚拟目录alias和root目录

    nginx是通过alias设置虚拟目录,在nginx的配置中,alias目录和root目录是有区别的:1)alias指定的目录是准确的,即location匹配访问的path目录下的文件直接是在alia ...