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

c++ stl iterator

65093 просмотра

25 ответа

Возьмите следующие две строки кода:

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
}

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

Автор: Jason Baker Источник Размещён: 11.11.2019 03:35

Ответы (25)


200 плюса

Решение

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

T elem = some_vector[i];

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

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

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

Автор: wilhelmtell Размещён: 25.09.2008 03:10

53 плюса

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

Автор: cruizer Размещён: 25.09.2008 03:02

51 плюса

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


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

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

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

Автор: Mark Ransom Размещён: 25.09.2008 03:35

33 плюса

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

Так что это обеспечивает две вещи:

  • Абстракция использования: вы просто хотите перебрать некоторые элементы, вам все равно, как это сделать
  • Представление
Автор: asterite Размещён: 25.09.2008 03:08

26 плюса

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

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

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

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

Автор: Chad Размещён: 25.09.2008 04:53

23 плюса

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

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;
    }
}

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

Автор: Brian Matthews Размещён: 25.09.2008 03:23

16 плюса

Разделение проблем

Очень приятно отделить итерационный код от «основной» задачи цикла. Это почти дизайнерское решение.

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

Кроме того, std::for_eachкстати, вы СКАЖИТЕ коллекции, что делать, вместо того, чтобы спросить ее о ее внутренностях.

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

Представление

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

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

Обязательным для прочтения справочником по итераторам является книга "Extended STL" .

У GoF есть небольшой абзац в конце паттерна Iterator, в котором говорится об этой марке итерации; это называется «внутренний итератор». Посмотрите здесь тоже.

Автор: xtofl Размещён: 27.09.2008 08:05

14 плюса

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

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

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

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

Автор: cynicalman Размещён: 25.09.2008 03:04

14 плюса

Еще одна приятная вещь об итераторах заключается в том, что они лучше позволяют вам выражать (и применять) ваши 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
}
Автор: Pat Notz Размещён: 25.09.2008 03:24

14 плюса

Помимо всех других отличных ответов ... 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
}
Автор: Pat Notz Размещён: 25.09.2008 03:19

11 плюса

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

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

Автор: MSalters Размещён: 26.09.2008 11:44

6 плюса

Итераторы 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
}
'
Автор: Jeroen Dirks Размещён: 25.09.2008 01:36

5 плюса

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

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

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

    // Do stuff to "item".
}

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

Автор: Adam Pierce Размещён: 25.09.2008 03:03

3 плюса

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

Автор: Colen Размещён: 25.09.2008 03:20

3 плюса

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

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 вы можете делать довольно интересные вещи, которые выходят за рамки администрирования индекса массива.

Автор: Jason Baker Размещён: 16.12.2008 10:46

3 плюса

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

Автор: Marc Eaddy Размещён: 08.05.2009 04:47

2 плюса

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

Автор: Sergey Stolyarov Размещён: 25.09.2008 04:25

2 плюса

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

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

Автор: danpla Размещён: 11.09.2017 05:32

1 плюс

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

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

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

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

for (some_iterator = some_vector.begin (); some_iterator! = some_vector.end (); ++ some_iterator) {// делать вещи}

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

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

Автор: user22044 Размещён: 25.09.2008 08:30

1 плюс

Я не использую итераторы по той же причине, по которой мне не нравятся операторы 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, например, потому что тогда вы не можете ясно видеть, из какого массива происходит это значение.

Автор: AareP Размещён: 13.07.2009 07:27

1 плюс

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

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

Автор: Engineer Размещён: 15.11.2015 12:58

0 плюса

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

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

Автор: Cyber Oliveira Размещён: 25.09.2008 04:58

0 плюса

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

Автор: all2one Размещён: 25.09.2008 11:14

0 плюса

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

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;
    }

}
Автор: Krirk Размещён: 25.09.2008 06:30

0 плюса

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

Автор: Messiah Размещён: 02.07.2012 11:04
32x32