【问题标题】:the patterns used in iterators迭代器中使用的模式
【发布时间】:2011-01-07 04:33:21
【问题描述】:

我熟悉 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。

【问题讨论】:

  • 迭代器概念在 C++ 和 Java/C# 中有所不同。在这两种情况下,它们都是可递增和可取消引用的对象,但操作集和用法不同。

标签: c# java data-structures stl iterator


【解决方案1】:

在 C++ 中,容器中的迭代器类似于指向数组的指针(我假设您熟悉指针)。迭代器有不同的风格,但归根结底,它们只是引用容器内元素的一种方式(通过取消引用运算符*-&gt;)并遍历容器中的元素。

重要的部分不是实现,而是概念。您不需要知道如何实现列表或向量的迭代器(或在许多情况下它们有何不同),只需要知道它提供了哪些操作。进入不同容器的迭代器将有不同的实现(对于列表,它将遵循节点中的一些 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)。通过这个向量实现,您可以使用原始指针作为迭代器:

//...
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(或您自己的)算法与这两个迭代器一起使用,以仅对那部分人执行操作。

【讨论】:

    【解决方案2】:

    iterator 只是一个抽象概念,它被定义为取消引用它,使用 * 运算符将为您提供与迭代器关联的序列中的某个元素,并增加它将为您提供与某些中的下一个元素相关联的迭代器顺序。这意味着具体迭代器是由序列定义的,而不是单独定义的类,即为什么需要使用类型map&lt;pair&lt;int,int&gt; &gt;::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;
      }
    };
    

    【讨论】:

      【解决方案3】:

      【讨论】:

      • 如果您对模式感兴趣,最好对 UML 有一个基本的了解,因为它是描述它们的通用语言。
      • 引用的文章是迭代器在 Java 和 C# 中的表现,但在 C++ 中却没有(问题似乎与此有关)
      【解决方案4】:

      使用迭代器的一点是您不需要了解“内部细节”。

      但是,了解其背后的基本原理总是有用的,并且在您提到的所有语言中都是一样的:您有一个函数(或语言特性)需要对一系列对象执行某些操作。然后该函数可以获取一个迭代器对象,并执行以下操作(使用 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 的迭代器非常接近于一个简单的整数,并且在底层完成迭代,就像迭代纯数组一样。

      【讨论】:

        【解决方案5】:

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

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

        【讨论】:

          【解决方案6】:

          来自 MSDN 的 C# 中的一个很好的 example

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

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

          所以除了实现 IEnumerable 的类之外,您还需要实现一个实现 IEnumerator 的类,具体而言,MoveNextReset。而GetEnumerator 方法返回枚举器类的一个实例。

          【讨论】:

            【解决方案7】:

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

            另外,&gt;&gt; 将被解释为右移运算符(感谢 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
            

            【讨论】:

            • 另外&gt;&gt;会被解释为右移运算符。
            猜你喜欢
            • 2013-09-01
            • 1970-01-01
            • 1970-01-01
            • 2010-09-20
            • 2012-03-11
            • 2021-02-20
            • 1970-01-01
            • 1970-01-01
            • 2019-11-27
            相关资源
            最近更新 更多