迭代器中使用的模式

时间:2010-01-20 08:54:47

标签: c# java data-structures stl iterator

我熟悉C ++ STL迭代器的用法,例如

for(map<pair<int,int>>::iterator it=m.begin(); it!=m.end(); ++it)
  int  a = it->first;
  int b = it->second;

但我不知道其中的内在细节。有人可以向我解释一下吗?无论是C ++,Java,C#还是Python。

7 个答案:

答案 0 :(得分:7)

在C ++中,迭代器进入容器类似于指向数组的指针(我假设你熟悉指针)。迭代器有不同的风格,但最后它们只是引用容器中的元素(通过解引用运算符*->)并横向移动容器中的元素。

重要的部分不是实施,而是概念。您不需要知道如何实现列表或向量中的迭代器(或者在许多情况下它们之间的区别),只需知道它提供的操作。迭代器进入不同的容器将有不同的实现(对于一个列表,它将遵循节点中的一些next指针,对于一个映射,它将遵循平衡的right子指针或parent指针实际上,同一个容器中的迭代器可以以不同的方式实现(并且一些编译器对任何给定的容器都有多个实现),具体取决于编译标志或模式。但是,重要的部分是你真的没有关心它们是什么,只是它们允许你做的事情。

作为一个简单的例子,在g ++中,STL实现std::vector包含三个指针,如:

//...
class vector {
   T * _b;  // beginning of allocated memory
   T * _e;  // one past the last inserted element 
   T * _c;  // one past the end of the allocated memory
//...
}

size() = (_e - _b) / sizeof(T)capacity = (_c - _b) / sizeof(T)。使用vector的这个实现,您可以使用原始指针作为迭代器:

//...
class vector {
public:
   typedef T* iterator;
   T* begin() { return _b; }
   T* end() { return _e; }
//...
}

但你也可以构建更复杂(更慢但更安全)的迭代器实现,比如如果迭代器已经失效则会触发断言的已检查迭代器(此代码仅为了示例目的而过于简化):

template <typename T>
class checked_iterator {
public:
   checked_iterator( std::vector<T> & v, std::size_t e )
      : _check_begin(v._b), _vector(v), _pos( v._b + e )
   {}
   T& operator*() {
      assert( _check_begin == _vector._b && "Dereferencing an invalidated iterator");
      return *_pos; 
   }
   // ...
private:
   T * _pos;
   T const * const _check_begin;
   std::vector<T>& _vector;
};

此迭代器实现将检测取消引用无效迭代器(仅在整个向量重定位的情况下,但通过存储更多数据,它可以进行全面检查)并且在仍处于开发阶段时将中止执行不正确的程序。从用户的角度来看,它将是一个简单的RandomAccessIterator(应该是,这是矢量迭代器的要求)但在幕后它将提供一种识别其他难以检测错误的机制。

这是VS编译器中的方法:在调试模式下(并且取决于编译器标志),它将使用缓慢安全的迭代器,它将通过无效的迭代器帮助检测访问(对于向量,只要元素是迭代器,迭代器就会失效)添加到容器中)。同时,更改编译器标志可以获得生产系统更快的普通原始指针实现,但调试无效迭代器使用会更加困难。

在Java和C#中,它们实际上是实现几个简单操作的对象(在Java hasNext()next()remove()中),允许横向整个容器隐藏如何容器已实现。它们非常相似,其意图是从用户代码封装在特定容器上执行的操作。

一个重要的区别是,在这两种情况下,您都可以使用它们迭代整个容器,但在c ++中它们是可比较的,您可以将任意两个迭代器迭代到同一个容器中。例如,在包含城市电话簿的地图中,您可以使用操作将迭代器转换为以ac开头的第一个名称,并使用另一个搜索来获取名称以“d”开头的第一个元素(假设名称排序)并且您可以使用任何STL(或您自己的)算法与这两个迭代器一起仅对该子集执行操作。

答案 1 :(得分:2)

迭代器只是一个抽象概念,定义它的解引用它,使用*运算符将给出与迭代器关联的序列中的某个元素,并且递增它会给你一个与下一个元素关联的迭代器序列。这意味着具体迭代器由序列定义,而不是单独定义的类,即为什么需要使用类型map<pair<int,int> >::iterator而不仅仅是iterator。每个STL序列的迭代器类型都有它自己的实现,++运算符被重载以提供指向序列中下一个元素的迭代器。

这意味着一个指向字符数组的简单指针也是一个迭代器,因为取消引用指针将为您提供与迭代器(指针)相关联的对象,并且递增它将为您提供与下一个相关联的新迭代器(指针)。序列中的元素。

双向链表的部分示例(注意:未经测试,只是写了这个,可能需要添加一些朋友条款和内容):

class doubly_linked_list {
  class node {
    node* prev;
    node* next;
    int data;
    node(const int data, node* prev, node* next) : data(data), prev(prev), next(next) {}
  };
  static const node nil; // = node(0, 0, 0)
  node* head;
  node* tail;
public:
  class iterator {
    node* current;
    iterator(node* n) : current(n) {}
  public:
    int& operator*() const {
      return current->obj;
    }
    iterator& operator++() {
      current = current->next;
      return *this;
    }
    iterator& operator--() {
      current = current->prev;
      return *this;
    }
  };
  double_linked_list() : head(nil), tail(nil) {}
  iterator begin() const {
    return iterator(head);
  }
  iterator end() const {
    return iterator(tail);
  }
};

并说明不同的数据结构将如何使用不同的迭代器作为矢量示例(就像未经测试一样)

class vector {
  int* data;
  size_t len;
public:
  typedef int* iterator;
  vector(const size_t len) : len(len) {
    data = new int[len];
  }
  int& operator[](int i) const {
    return data[i];
  }
  iterator begin() const {
    return data;
  }
  iterator end() const {
    return data + len;
  }
};

答案 2 :(得分:1)

答案 3 :(得分:1)

使用迭代器的一点是你不需要知道“内部细节”。

然而,理解它背后的基本原理总是有用的,并且在你提到的所有语言中都是一样的:你有一个功能(或语言特性)需要对一系列对象做一些事情。然后该函数可以获取迭代器对象,并执行类似的操作(使用Python):

def DoSomething(start_iter, end_iter):
    while (start_iter != end_iter):
        calculate(start_iter.get_data())
        start_iter = start_iter.next()

原则在所有情况下都是相同的。泛型函数接受迭代器并如上所述使用它们:获取与迭代器关联的数据,对其执行任何操作,递增迭代器,并在迭代器到达结束时退出。

例如,在C ++中,STL :: vector的迭代器非常接近于只是一个简单的整数,并且在底层迭代完成它,就像迭代纯数组一样。

答案 4 :(得分:1)

检查wiki链接是否为Iterator模式。这种模式的目的是提供对聚合对象元素的访问,而不暴露内部细节。

这是非常的解释(以及其他答案和链接提及)。

例如,在Java中,AbstractList的迭代器在内部类中实现,该内部类的实例是为迭代List而创建的。 检查code此处。

答案 5 :(得分:1)

来自MSDN的C#中的example好。

有一个标准接口,IEnumerable。如果您的类是可枚举的,那么您只需实现此接口的GetEnumerator方法。

但是你可能知道,不同的类可能有不同的枚举方法,对于一个数组,你只需将指针移动1,对于一棵树,你需要一个树遍历方法。但总而言之,枚举器具有相同的接口,例如C#中IEnumerator中的方法。

除了实现IEnumerable的类之外,您仍然需要实现一个实现IEnumerator的类,特别是MoveNextReset。 GetEnumerator方法返回枚举器类的实例。

答案 6 :(得分:0)

除此之外:您发布的代码无效 - 您缺少大括号 - C#,C ++和Java都没有神奇的空白(而且代码看起来不像Python)。

此外,>>将被解释为右移操作员(感谢Prasoon Saurav)。我想你想要这个:

for(map<pair<int,int> >::iterator it=m.begin(); it!=m.end(); ++it) { // <- here
  int a = it->first;
  int b = it->second;
} // <- and here