Что такое итератор в c
Перейти к содержимому

Что такое итератор в c

Что такое итератор в c

Итераторы обеспечивают доступ к элементам контейнера. С помощью итераторов очень удобно перебирать элементы. Итератор описывается типом iterator . Но для каждого контейнера конкретный тип итератора будет отличаться. Так, итератор для контейнера list<int> представляет тип list<int>::iterator , а итератор контейнера vector<int> представляет тип vector<int>::iterator и так далее.

Для получения итераторов контейнеры в C++ обладают такими функциями, как begin() и end() . Функция begin() возвращает итератор, который указывает на первый элемент контейнера (при наличии в контейнере элементов). Функция end() возвращает итератор, который указывает на следующую позицию после последнего элемента, то есть по сути на конец контейнера. Если контейнер пуст, то итераторы, возвращаемые обоими методами begin и end совпадают. Если итератор begin не равен итератору end, то между ними есть как минимум один элемент.

Обе этих функции возвращают итератор для конкретного типа контейнера:

В данном случае создается вектор — контейнер типа vector, который содержит значения типа int. И этот контейнер инициализируется набором <1, 2, 3, 4>. И через метод begin() можно получить итератор для этого контейнера. Причем этот итератор будет указывать на первый элемент контейнера.

Операции с итераторами

С итераторами можно проводить следующие операции:

*iter : получение элемента, на который указывает итератор

++iter : перемещение итератора вперед для обращения к следующему элементу

—iter : перемещение итератора назад для обращения к предыдущему элементу. Итераторы контейнера forward_list не поддерживают операцию декремента.

iter1 == iter2 : два итератора равны, если они указывают на один и тот же элемент

iter1 != iter2 : два итератора не равны, если они указывают на разные элементы

Например, используем итераторы для перебора элементов вектора:

При работе с контейнерами следует учитывать, что добавление или удаление элементов в контейнере может привести к тому, что все текущие итераторы для данного контейнера, а также ссылки и указатели на его элементы станут недопустимыми.

Итераторы позволяют не только получать элементы, но и изменять их:

В данном случае в цикле while элементы вектора возводятся в квадрат. Консольный вывод данной программы:

Константные итераторы

Если контейнер представляет константу, то для обращения к элементам этого контейнера можно использовать только константный итератор (тип const_iterator ). Такой итератор позволяет считывать элементы, но не изменять их:

Для получения константного итератора также можно использовать функции cbegin() и cend . При этом даже если контейнер не представляет константу, но при этом для его перебора используется константный итератор, то опять же нельзя изменять значения элементов этого контейнера:

Реверсивные итераторы

Реверсивные итераторы позволяют перебирать элементы контейнера в обратном направлении. Для получения реверсивного итератора применяются функции rbegin() и rend() , а сам итератор представляет тип :

Консольный вывод программы:

Если надо обеспечить защиту от изменения значений контейнера, то можно использовать константный реверсивный итератор, который представлен типом const_reverse_iterator и который можно получить с помощью функций crbegin() и crend() :

Также итераторы для всех типов, кроме list и forward_list, поддерживают ряд дополнительных операций:

iter + n : возвращает итератор, который смещен от итератора iter на n позиций вперед

iter — n : возвращает итератор, который смещен от итератора iter на n позиций назад

iter += n : перемещает итератор на n позиций вперед

iter -= n : перемещает итератор на n позиций назад

iter1 — iter2 : возвращает количество позиций между итераторами iter1 и iter2

>, >=, <, <= : операции сравнения. Один итератор больше другого, если указывает на элемент, который ближе к концу

Введение в итераторы в С++

На этом уроке мы рассмотрим тему использования итераторов в языке С++, а также связанные с этим нюансы.

Итерация по элементам структур данных

Итерация/перемещение по элементам массива (или какой-нибудь другой структуры) является довольно распространенным действием в программировании. Мы уже рассматривали множество различных способов выполнения данной задачи, а именно: с использованием циклов и индексов (циклы for и while), с помощью указателей и адресной арифметики, а также с помощью циклов for с явным указанием диапазона:

Использование циклов с индексами в ситуациях, когда мы используем их только для доступа к элементам, требует написания большего количества кода, нежели могло бы быть.

При этом данный способ работает только в том случае, если контейнер (например, массив), содержащий данные, дает возможность прямого доступа к своим элементам (что делают массивы, но не делают некоторые другие типы контейнеров, например, списки).

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

Примечание для продвинутых читателей: Указатели (без адресной арифметики) могут использоваться для перебора/итерации некоторых структур данных с непоследовательным расположением элементов. Например, в связном списке каждый элемент соединен указателем с предыдущим элементом, поэтому мы можем перебирать список, следуя по цепочке указателей.

Циклы for с явным указанием диапазона чуть более интересны, поскольку у них скрыт механизм перебора нашего контейнера, но при всем этом они всё равно могут быть применены к различным структурам данных (массивы, списки, деревья, карты и т.д.). «Как же они работают?» — спросите вы. Они используют итераторы.

Итераторы в С++

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

Контейнер может предоставлять различные типы итераторов. Например, контейнер на основе массива может предлагать прямой итератор, который проходится по массиву в прямом порядке, и реверсивный итератор, который проходится по массиву в обратном порядке.

После того, как итератор соответствующего типа создан, программист может использовать интерфейс, предоставляемый данным итератором, для перемещения по элементам контейнера или доступа к его элементам, не беспокоясь при этом о том, какой тип перебора элементов задействован или каким образом в контейнере хранятся данные. И, поскольку итераторы в языке С++ обычно используют один и тот же интерфейс как для перемещения по элементам контейнера (оператор ++ для перехода к следующему элементу), так и для доступа (оператор * для доступа к текущему элементу) к ним, итерации можно выполнять по разнообразным типам контейнеров, используя последовательный метод.

Указатели в качестве итераторов

Простейший пример итератора — это указатель, который (используя адресную арифметику) работает с последовательно расположенными элементами данных. Давайте снова рассмотрим пример перемещения по элементам массива, используя указатель и адресную арифметику:

Реализация итераторов в C# (часть 1)

От переводчика:
Не так давно мой менее опытный коллега спросил меня о том, для чего используется yield return в C#. Я не очень часто пишу свои итераторы, поэтому, отвечая ему, я сомневался в своих словах. Справившись в MSDN, я укрепился в сказанном, но у меня возник вопрос: “А во что же всё таки компилируется эта инструкция?” К тому моменту, я уже был знаком с переводимой статьёй, однако всё, что в ней сказано, давно “выветрилось”. Статья старая, но мне думается, что она может быть полезна для определённой группы разработчиков, привыкшей читать русскоязычные статьи и документы.

Как и анонимные методы, итераторы в C# являются сложным синтаксическим сахаром. Вы можете реализовать их полностью самостоятельно (в конце концов, в ранних версиях C# вам приходилось делать это), но использовать компилятор намного удобнее. Идея, стоящая за итераторами заключается в том, что они принимают функцию с yield return выражениями (и, возможно, yield break выражениями) и конвертируют её в конечный автомат. Когда вызывается yield return, состояние функции сохраняется, и при повторном обращении к итератору для получения очередного объекта это состояние восстанавливается. Главное в итераторах то, что все локальные переменные итератора (в том числе параметры итератора как предварительно инициализировнные локальные переменные, включая скрытый параметр this) становятся переменными-членами (далее полями) вспомогательного класса. Помимо этого вспомогательный класс содержит поле state, которое следит, где произошло прерывание исполнения, и поле current, хранящее самый последний из уже перечисленных объектов.

Метод CountFrom создаёт перечислитель целых чисел, который производит целые числа от start до limit включительно с шагом 1. Компилятор неявно конвертирует этот перечислитель во что-то вроде этого:

Перечисляющий класс автоматически генерируется компилятором и, как было обещано, он содержит поля для состояния и текущего объекта, плюс по одному полю на каждую локальную переменную. Свойство Current просто возвращает текущий объект. Вся же настоящая работа происходит в методе MoveNext.

  • Все ссылки на переменные должны быть скорректированы, поскольку код перенёсся во вспомогательный класс.
  • this становится this$0, поскольку внутри сгенерированной функции this указывает на автоматически сгенерированный класс вместо оригинального.
  • m становится this$0.m, если m является членом исходного класса (нестатическим полем, свойством или методом). В действительности, это правило излишне в сочетании с предыдущим, т.к. запись имени члена класса без префикса m это просто сокращение для this.m.
  • v становится this.v, если v это параметр или локальная переменная. Это правило также излишне, т.к. запись v равносильна this.v, но я обращаюсь явно, чтобы вы обратили внимание, что хранилище переменной изменилось.

Кроме этого есть выражения yield break.
Каждое выражение yield break преобразуется в
где n2 — число, на единицу большее, чем наибольший номер из всех состояний, использующихся в выражениях yield return. Не забывайте, что в конце каждой функции подразумевается вызов yield break.

Наконец, компилятор вставляет большой диспетчер состояний в самом начале функции.
по одному case-выражению создаётся для каждого состояния, плюс на начальное и конечное состояние n2.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *