шаблоны, используемые в итераторах - PullRequest
4 голосов
/ 20 января 2010

Я знаком с использованием итераторов 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 ]

7 голосов
/ 20 января 2010

В 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). В этой реализации вектора вы можете использовать необработанный указатель в качестве итератора:

//...
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 голосов
/ 20 января 2010
Итератор

- это просто абстрактное понятие, для которого определено, что разыменование его с помощью оператора * даст вам определенный элемент из последовательности, связанной с итератором, а приращение его даст вам итератор, связанный со следующим элементом в некоторых последовательность. Это означает, что конкретный итератор определяется последовательностью и не является отдельным классом, т. Е. Вам нужно использовать тип 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;
  }
};
1 голос
/ 20 января 2010

Хороший пример в C # от MSDN.

Существует стандартный интерфейс IEnumerable. Если ваш класс перечислим, то вы просто реализуете метод этого интерфейса GetEnumerator.

Но, как вы знаете, разные классы могут иметь разные методы для перечисления, для массива вы просто перемещаете указатель на 1, для дерева вам нужен метод обхода дерева. Но в целом, счетчик имеет тот же интерфейс, например, методы в IEnumerator в C #.

Таким образом, кроме класса, реализующего IEnumerable, вам все еще необходимо реализовать класс, реализующий IEnumerator, в частности MoveNext, Reset. А метод GetEnumerator возвращает экземпляр класса перечислителя.

1 голос
/ 20 января 2010

Проверьте ссылку на вики для Итератор шаблон. Это вполне объяснительно (и, как уже упоминалось в других ответах и ​​ссылках), целью этого шаблона является предоставление доступа к элементам совокупного объекта без раскрытия внутренних деталей.

Например, в Java итератор для AbstractList реализован во внутреннем классе, экземпляр которого создан для итерации списка. Проверьте код здесь.

1 голос
/ 20 января 2010

Одно из применений итераторов заключается в том, что вам не нужно знать о «внутренних деталях».

Тем не менее, всегда полезно понимать основные принципы, лежащие в его основе, и это одинаково на всех языках, которые вы упоминаете: у вас есть функция (или языковая функция), которая должна что-то делать с диапазоном объектов. Затем функция может взять объект итератора и сделать что-то вроде этого (используя 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 очень близок к тому, чтобы быть не чем иным, как простым целым числом, и итерация по нему выполняется под капотом так же, как итерация чистого массива.

1 голос
/ 20 января 2010

Проверьте шаблон итератора http://www.dofactory.com/patterns/PatternIterator.aspx

0 голосов
/ 20 января 2010

Кроме того: код, который вы разместили, не будет работать - вам не хватает скобок - ни в 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
...