Итераторы .. зачем их использовать?

В библиотеке STL у некоторых контейнеров есть итераторы, и обычно считается, что они являются лучшим способом итерации через эти контейнеры, а не просто циклами for, например.

for ( int i=0; i < vecVector.size(); i++ )
{

..

}

Может ли кто-нибудь сказать мне, почему и в каких случаях мне следует использовать итераторы и в каких случаях приведенный выше фрагмент кода, пожалуйста?

Какие контейнеры STL <i> не </i> имеют итераторов?

Chris Charabaruk 07.10.2008 19:19

Что ж, согласно STL, тип является Контейнером, если с ним связан тип итератора, поэтому по определению их нет.

Dave Van den Eynde 07.10.2008 19:26
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
22
2
10 248
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

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

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

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

Я не думаю, что у std :: list есть оператор подписки.

Dave Van den Eynde 07.10.2008 19:23

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

JohnMcG 07.10.2008 19:44

В вашем примере вызов vecVector.size () менее эффективен, чем использование итератора. Итератор, по сути, избавляет вас от необходимости беспокоиться о размере контейнера, по которому выполняется итерация. Более того, итератор не обязательно должен идти в последовательном порядке. Он просто должен отвечать на вызов .next так, как он считает нужным.

Не думаю, что это менее эффективно. Компилятор оптимизирует его, поместив вызов size () вне цикла. Кроме того, размер - это свойство вектора, он всегда известен и никогда не нуждается в вычислении, как и end ().

Dave Van den Eynde 07.10.2008 19:21

Что, если элемент добавлен к вектору внутри цикла?

Ben Hoffstein 07.10.2008 19:48

Тогда, конечно, он не будет оптимизировать его вне цикла, но он также не будет оптимизировать использование end () вне цикла. Так что разницы все равно нет.

Dave Van den Eynde 08.10.2008 11:51

Ну, во-первых, вышеуказанное больше не будет работать, если вы превратите этот вектор в список.

Итераторы позволяют создавать шаблоны функций, которым не нужно знать тип контейнера, с которым они работают. Вы даже можете сделать следующее:

#include <algorithm>

void printvalue(double s)
{
    // Do something with s
}

int _tmain(int argc, _TCHAR* argv[])
{
    double s[20] = {0};

    std::for_each(s, s+20, printvalue);

    return 0;
}

Это потому, что стандартный указатель также является допустимым итератором для for_each.

Дэйв

Ответ принят как подходящий

Обратите внимание, что обычно реализация вектора не использует "int" в качестве типа индекса / размера. Таким образом, ваш код, как минимум, вызовет предупреждения компилятора.

Универсальность

Итераторы увеличивают универсальность вашего кода.

Например:

typedef std::vector<int> Container ;

void doSomething(Container & p_aC)
{
    for(Container::iterator it = p_aC.begin(), itEnd = p_aC.end(); it != itEnd; ++it)
    {
       int & i = *it ; // i is now a reference to the value iterated
       // do something with "i"
    }
}

Теперь представим, что вы изменили вектор на список (потому что в вашем случае список стал лучше). Вам нужно только изменить объявление typedef и перекомпилировать код.

Если бы вместо этого вы использовали индексный код, его пришлось бы переписать.

Доступ

Итератор следует рассматривать как своего рода супер-указатель. Он «указывает» на значение (или, в случае сопоставления, на пару ключ / значение).

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

Алгоритмы

Большинство алгоритмов STL работают с итераторами или с диапазонами итераторов (опять же, из-за универсальности). Здесь вы не сможете использовать индекс.

Примечание: этот код особенно эффективен с библиотекой диапазона. Алгоритм, работающий с парами итераторов, может использоваться с подмножествами из контейнеров в дополнение к потокам и другим генераторам значений. См. Библиотеки boost.org, Range и Iterators.

Aaron 07.10.2008 21:12

Если вы используете итераторы в качестве аргументов вашей функции, вы можете отделить их от типа используемого «контейнера». Например, вы можете направить результаты функции в консольный вывод, а не в вектор (пример ниже). Этот трюк может быть чрезвычайно эффективным для уменьшения связи между вашими классами. Слабосвязанные классы намного легче тестировать.

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

template <typename InputIterator, typename OutputIterator>
void AddOne(InputIterator begin, InputIterator end, OutputIterator dest)
{
    while (begin != end)
    {
        *dest = *begin + 1;
        ++dest;
        ++begin;
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    vector<int> data;
    data.push_back(1);
    data.push_back(2);
    data.push_back(3);

    // Compute intermediate results vector and dump to console
    vector<int> results;
    AddOne(data.begin(), data.end(), back_inserter(results));
    copy(results.begin(), results.end(), ostream_iterator<int>(cout, " "));
    cout << endl;

    // Compute results and send directly to console, no intermediate vector required
    AddOne(data.begin(), data.end(), ostream_iterator<int>(cout, " "));
    cout << endl;

    return 0;
}

Итератор - это в основном более высокий уровень абстракции.

Ваш фрагмент предполагает, что контейнер можно проиндексировать. Это верно для std::vector<> и некоторых других контейнеров, например необработанных массивов.

Но в std::set<> полностью отсутствует индексация, и оператор индекса std::map<> вставит в карту любой переданный ему аргумент, а не ожидаемое поведение в вашем цикле for.

Кроме того, проблемы с производительностью - это проблемы, только если их измерить и доказать.

Другие вопросы по теме