Зачем использовать итераторы вместо индексов массива?

принять следующие две строки кода:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

и так:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

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

25 ответов


первая форма эффективна только в том случае, если вектор.size () - быстрая операция. Это верно для векторов, но не для списков, например. Кроме того, что вы планируете делать в теле цикла? Если вы планируете получить доступ к элементам, как в

T elem = some_vector[i];

затем вы делаете предположение, что контейнер operator[](std::size_t) определены. Опять же, это верно для vector, но не для других контейнеров.

использование итераторов приближает вас к независимости контейнера. Вы не делаете предположений о способности произвольного доступа или fast size() операция, только то, что контейнер имеет возможности итератора.

вы можете улучшить свой код дальше, используя стандартные алгоритмы. В зависимости от того, чего вы пытаетесь достичь, вы можете использовать std::for_each(), std::transform() и так далее. Используя стандартный алгоритм, а не явный цикл, вы избегаете повторного изобретения колеса. Ваш код, вероятно, будет более эффективным (учитывая, что правильный алгоритм выбранный), правильно и многоразово.


потому что вы не привязываете свой код к конкретной реализации списка some_vector. если вы используете индексы массива, это должна быть какая-то форма массива; если вы используете итераторы, вы можете использовать этот код в любой реализации списка.


Это часть современного процесса индоктринации C++. Итераторы-единственный способ итерации большинства контейнеров, поэтому вы используете его даже с векторами, чтобы войти в правильное мышление. Серьезно, это единственная причина, по которой я это делаю - я не думаю, что я когда-либо заменял вектор другим видом контейнера.


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

Я думаю, что индекс массива больше читаемый. Он соответствует синтаксису, используемому в других языках,и синтаксису, используемому для старомодных массивов C. Это также менее многословно. Эффективность должна быть стиркой, если ваш компилятор хорош, и вряд ли есть какие-либо случаи, когда это имеет значение.

тем не менее, я все еще часто использую итераторы с векторами. Я считаю, что итератор-важная концепция, поэтому я продвигаю ее, когда могу.


представьте, что some_vector реализован со связанным списком. Затем запрос элемента в i-м месте требует I операций, которые должны быть сделаны, чтобы пересечь список узлов. Теперь, если вы используете итератор, вообще говоря, он приложит все усилия, чтобы быть максимально эффективным (в случае связанного списка он будет поддерживать указатель на текущий узел и продвигать его на каждой итерации, требуя только одной операции).

таким образом, он обеспечивает два вещи:

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

Я буду чертей защитник и не рекомендую итераторы. Основная причина этого - весь исходный код, над которым я работал от разработки настольных приложений до разработки игр, у меня нет необходимости использовать итераторы. Все время они не требовались, а во-вторых, скрытые предположения и беспорядок кода и кошмары отладки, которые вы получаете с итераторами, делают их ярким примером того, чтобы не использовать его в любых приложениях, требующих скорости.

даже от maintence стоят они в беспорядке. Это не из-за них, а из-за всех сглаживаний, которые происходят за сценой. Откуда мне знать, что вы не реализовали свой собственный виртуальный вектор или список массивов, который делает что-то совершенно отличное от стандартов. Знаю ли я, какой тип в настоящее время находится во время выполнения? Вы перегрузили оператора, у меня не было времени проверить весь ваш исходный код. Черт, я даже не знаю, какую версию STL вы используете?

следующая проблема с итераторами это дырявая абстракция, хотя есть множество веб-сайтов, которые подробно обсуждают это с ними.

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


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

some_iterator = some_vector.begin(); 
while (some_iterator != some_vector.end())
{
    if (/* some condition */)
    {
        some_iterator = some_vector.erase(some_iterator);
        // some_iterator now positioned at the element after the deleted element
    }
    else
    {
        if (/* some other condition */)
        {
            some_iterator = some_vector.insert(some_iterator, some_new_value);
            // some_iterator now positioned at new element
        }
        ++some_iterator;
    }
}

если бы вы использовали индексы, вам пришлось бы перетасовывать элементы вверх / вниз в массиве для обработки вставок и удалений.


разделение

очень приятно отделить код итерации от "основной" проблемы цикла. Это почти дизайнерское решение.

действительно, итерация по индексу связывает вас с реализацией контейнера. Запрос контейнера для итератора начала и конца включает код цикла для использования с другими типами контейнеров.

также в std::for_each кстати, вы скажите коллекции, Что делать, вместо того, чтобы спрашивать это что-то о его внутренностях

стандарт 0x собирается ввести закрытие, которое сделает этот подход гораздо более простым в использовании - посмотрите на выразительную силу, например, Ruby's [1..6].each { |i| print i; }...

производительность

но, может быть, очень наблюдаемая проблема заключается в том, что, используя for_each подход дает возможность распараллелить итерацию -Intel threading blocks смогите распределить блок кода над числом процессоров в система!

Примечание: после обнаружения algorithms библиотека, и особенно foreach, Я прошел через два или три месяца написания смехотворно маленьких "вспомогательных" операторов, которые будут сводить с ума ваших коллег-разработчиков. После этого времени я вернулся к прагматичному подходу-маленькие тела петли не заслуживают foreach больше нет :)

необходимо прочитать ссылку на итераторы-это книга "расширенный STL".

GoF имеют крошечный маленький абзац в конец шаблона итератора, который говорит об этом бренде итерации; он называется "внутренним итератором". Посмотрите здесь тоже.


потому что он более объектно-ориентирован. если вы выполняете итерацию с индексом, вы предполагаете:

a) что эти объекты упорядочены
б) что эти объекты могут быть получены с помощью индекса
c) что приращение индекса ударит по каждому элементу
d) что этот индекс начинается с нуля

с итератором вы говорите: "Дайте мне все, чтобы я мог работать с ним", не зная, что такое базовая реализация. (В Java, есть коллекции, которые не могут доступ через индекс)

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


еще одна хорошая вещь об итераторах заключается в том, что они лучше позволяют вам выражать (и применять) ваши const-предпочтения. Этот пример гарантирует, что вы не будете изменять вектор в середине цикла:


for(std::vector<Foo>::const_iterator pos=foos.begin(); pos != foos.end(); ++pos)
{
    // Foo & foo = *pos; // this won't compile
    const Foo & foo = *pos; // this will compile
}

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

for (std::vector<Foo>::size_type i = 0; i < myvector.size(); ++i)
{
    Foo& this_foo = myvector[i];
    // Do stuff with this_foo
}

Я, вероятно, должен указать, что вы также можете позвонить

std::for_each(some_vector.begin(), some_vector.end(), &do_stuff);


итераторы STL в основном существуют, так что алгоритмы STL, такие как sort, могут быть независимыми от контейнера.

Если вы просто хотите зациклить все записи в векторе, просто используйте стиль цикла индекса.

Это меньше ввода и легче анализировать для большинства людей. Было бы неплохо, если бы C++ имел простой цикл foreach, не выходя за рамки магии шаблонов.

for( size_t i = 0; i < some_vector.size(); ++i )
{
   T& rT = some_vector[i];
   // now do something with rT
}
'

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

Я также хотел бы сделать ссылку на элемент внутри цикла, как это так, есть не много квадратные скобки месте:

for(size_t i = 0; i < myvector.size(); i++)
{
    MyClass &item = myvector[i];

    // Do stuff to "item".
}

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


вторая форма представляет собой то, что вы делаете более точно. В вашем примере вас не волнует значение i, На самом деле-все, что вам нужно, это следующий элемент в итераторе.


for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

и этот цикл:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

- Это довольно минимальный. На самом деле, синтаксис выполнения циклов таким образом, кажется, растет на мне:

while (it != end){
    //do stuff
    ++it;
}

итераторы разблокировать некоторые довольно мощные декларативные функции, и в сочетании с библиотекой алгоритмов STL вы можете сделать некоторые довольно интересные вещи это выходит за рамки индекса массива administrivia.


индексирование требует дополнительной mul операции. Например, для vector<int> v компилятор преобразует v[i] на &v + sizeof(int) * i.


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


уже несколько хороших моментов. У меня есть несколько дополнительных комментариев:

  1. предполагая, что мы говорим о стандартной библиотеке C++, "вектор" подразумевает контейнер произвольного доступа, который имеет гарантии c-массива (произвольный доступ, компоновка памяти contiguos и т. д.). Если бы вы сказали "some_container", многие из приведенных выше ответов были бы более точными (независимость контейнера и т. д.).

  2. для устранения зависимости от оптимизации компилятора , вы можете переместить some_vector.size () из цикла в индексированном коде, например:

    const size_t numElems = some_vector.size();
    for (size_t i = 0; i 
  3. всегда итераторы предварительного приращения и рассматривают пост-приращения как исключительные случаи.

for (some_iterator = some_vector.begin (); some_iterator != некоторый вектор.end (); ++some_iterator) {//do stuff }

предположим и режущих std::vector<> как контейнер, нет веских причин предпочесть один над другим, последовательно проходя через контейнер. Если вам придется обратиться к более старой или новой elemnent индексы часто, потом проиндексированные версия более appropropriate.

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


Я не использую итераторы по той же причине, по которой мне не нравятся операторы foreach. При наличии нескольких внутренних циклов достаточно сложно отслеживать глобальные переменные / переменные-члены без необходимости запоминать все локальные значения и имена итераторов. Я считаю полезным использовать два набора индексов для разных случаев:

for(int i=0;i<anims.size();i++)
  for(int j=0;j<bones.size();j++)
  {
     int animIndex = i;
     int boneIndex = j;


     // in relatively short code I use indices i and j
     ... animation_matrices[i][j] ...

     // in long and complicated code I use indices animIndex and boneIndex
     ... animation_matrices[animIndex][boneIndex] ...


  }

Я даже не хочу сокращать такие вещи, как" animation_matrices[i] "до некоторого случайного"anim_matrix" -named-iterator, например, потому что тогда вы не можете см. отчетливо, из какого массива происходит это значение.


даже лучше, чем" говорить процессору, что делать "(императив), это" говорить библиотекам, что вы хотите " (функциональный).

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


для независимости контейнера


Я всегда использую индекс массива, потому что многие мои приложения требуют чего-то вроде "отображения эскиза изображения". Поэтому я написал что-то вроде этого:--2-->

some_vector[0].left=0;
some_vector[0].top =0;<br>

for (int i = 1; i < some_vector.size(); i++)
{

    some_vector[i].left = some_vector[i-1].width +  some_vector[i-1].left;
    if(i % 6 ==0)
    {
        some_vector[i].top = some_vector[i].top.height + some_vector[i].top;
        some_vector[i].left = 0;
    }

}

обе реализации верны, но я бы предпочел цикл "for". Поскольку мы решили использовать вектор, а не какой-либо другой контейнер, использование индексов было бы лучшим вариантом. Использование итераторов с векторами потеряет преимущество наличия объектов в блоках непрерывной памяти, которые облегчают их доступ.


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

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


никто еще не упоминал, что одним из преимуществ индексов является то, что они не становятся недействительными при добавлении к смежному контейнеру, такому как std::vector, Так что вы можете добавлять элементы в контейнер во время итерации.

Это также возможно с итераторами, но вы должны назвать reserve(), и поэтому нужно знать, сколько элементов вы будете добавлять.