Зачем использовать итераторы вместо индексов массива?
Возьмите следующие две строки кода:
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
}
Мне говорят, что второй вариант предпочтительнее. Почему именно это?
Ответы
Ответ 1
Первая форма эффективна, только если vector.size() является быстрой операцией. Это справедливо для векторов, но не для списков, например. Кроме того, что вы планируете делать в теле цикла? Если вы планируете получать доступ к элементам, как в
T elem = some_vector[i];
то вы делаете предположение, что контейнер имеет operator[](std::size_t)
. Опять же, это верно для вектора, но не для других контейнеров.
Использование итераторов приближает вас к независимости контейнера. Вы не делаете предположений о возможности произвольного доступа или быстрой операции size()
, только в том, что в контейнере есть возможности итератора.
Вы можете улучшить свой код, используя стандартные алгоритмы. В зависимости от того, чего вы пытаетесь достичь, вы можете выбрать std::for_each()
, std::transform()
и т.д. Используя стандартный алгоритм, а не явный цикл, вы избегаете повторного создания колеса. Вероятно, ваш код будет более эффективным (при выборе правильного алгоритма), правильным и многоразовым.
Ответ 2
потому что вы не привязываете свой код к конкретной реализации списка some_vector. если вы используете индексы массива, это должен быть некоторый вид массива; если вы используете итераторы, вы можете использовать этот код для любой реализации списка.
Ответ 3
Это часть современного процесса интродукции С++. Итераторы - единственный способ итерации большинства контейнеров, поэтому вы используете его даже с векторами, чтобы просто вникать в правильное мышление. Серьезно, что единственная причина, по которой я это делаю, - я не думаю, что когда-либо менял вектор с другим контейнером.
Ничего себе, это все еще происходит вниз после трех недель. Наверное, он не платит, чтобы быть маленьким языком в щеке.
Я думаю, что индекс массива более читабельен. Он соответствует синтаксису, используемому на других языках, и синтаксису, используемому для старомодных C-массивов. Он также менее подробный. Эффективность должна быть стиранием, если ваш компилятор хорош, и вряд ли есть случаи, когда это имеет значение.
Тем не менее, я по-прежнему часто использую итераторы с векторами. Я считаю, что итератор - важная концепция, поэтому я продвигаю ее, когда только могу.
Ответ 4
Представьте, что some_vector реализован со связанным списком. Затем запрос элемента в i-ом месте требует выполнения я операций для перемещения списка узлов. Теперь, если вы используете итератор, он, как правило, прилагает все усилия, чтобы быть как можно более эффективным (в случае связанного списка он будет поддерживать указатель на текущий node и продвигать его на каждой итерации, требуя всего одна операция).
Таким образом, это обеспечивает две вещи:
- Абстракция использования: вы просто хотите итерировать некоторые элементы, вам все равно, как это сделать.
- Производительность
Ответ 5
Я буду адвокатом дьяволов здесь, а не рекомендую итераторов. Основная причина, почему, это весь исходный код, с которым я работал, от разработки настольных приложений до разработки игр, и мне не нужно было использовать итераторы. Все время они не требовались, а во-вторых, скрытые предположения и кодовые кошмары и отладки кошмаров, которые вы получаете с помощью итераторов, делают их ярким примером, чтобы не использовать его в каких-либо приложениях, требующих скорости.
Даже с точки зрения поддержания они беспорядок. Это не из-за них, а из-за всех псевдонимов, которые происходят за сценой. Как я знаю, что вы не реализовали свой собственный список виртуальных векторов или массивов, который делает что-то совершенно отличное от стандартов. Знаю ли я, какой тип сейчас находится во время выполнения? Вы перегрузили оператора, у меня не было времени проверить весь исходный код. Черт, я даже знаю, какую версию STL вы используете?
Следующая проблема, с которой вы столкнулись с итераторами, - это непроницаемая абстракция, хотя есть множество веб-сайтов, которые подробно обсуждают это с ними.
Извините, я до сих пор не вижу игорного смысла в итераторах. Если они абстрагируют список или вектор от вас, когда на самом деле вы уже должны знать, с каким вектором или списком имеете дело, если вы этого не сделаете, тогда вы просто настроитесь на какие-то отличные сеансы отладки в будущем.
Ответ 6
Возможно, вы захотите использовать итератор, если вы собираетесь добавлять/удалять элементы в вектор, когда вы выполняете итерацию по нему.
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;
}
}
Если вы использовали индексы, вам придется перетасовывать элементы вверх/вниз в массиве, чтобы обрабатывать вставки и удаления.
Ответ 7
Разделение проблем
Очень приятно отделить итерационный код от основной проблемы цикла. Это почти дизайнерское решение.
Действительно, итерация по индексу связывает вас с реализацией контейнера. Задавая контейнер для начинающего и конечного итератора, позволяет использовать код цикла для использования с другими типами контейнеров.
Кроме того, в std::for_each
вы РАССКАЗАТЬ коллекцию, что делать, а не ASKing это что-то о ее внутренних функциях
В стандарте 0x будут введены замыкания, которые сделают этот подход более простым в использовании - взгляните на выразительную силу, например. Ruby [1..6].each { |i| print i; }
...
Производительность
Но, может быть, большая проблема заключается в том, что использование подхода for_each
дает возможность распараллеливать итерацию - блоки потока Intel может распределять блок кода по количеству процессоров в системе!
Примечание: после обнаружения библиотеки algorithms
, и особенно foreach
, я через два-три месяца написал смехотворно небольшие "вспомогательные" структуры операторов, которые сбивают с ума ваших соратников. После этого времени я вернулся к прагматичному подходу - тела с малым телом не заслуживают foreach
не более:)
A необходимо прочитать ссылку на итераторы - это книга "Extended STL" .
У GoF есть небольшой маленький абзац в конце шаблона Iterator, в котором говорится об этой марке итераций; он назывался "внутренним итератором". Посмотрите здесь.
Ответ 8
Потому что он более объектно-ориентирован. если вы повторяете с индексом, который вы принимаете:
a), что эти объекты упорядочены
б), что эти объекты могут быть получены с помощью индекса
c), что приращение индекса ударит по каждому элементу
d), что этот индекс начинается с нуля
С помощью итератора вы говорите "дайте мне все, чтобы я мог работать с ним", не зная, что такое базовая реализация. (В Java есть коллекции, к которым нельзя получить доступ через индекс)
Кроме того, с итератором не нужно беспокоиться о выходе за пределы массива.
Ответ 9
Еще одна приятная вещь об итераторах заключается в том, что они лучше позволяют вам выражать (и обеспечивать) свое предпочтение 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
}
Ответ 10
Помимо всех других отличных ответов... 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
}
Ответ 11
Я, вероятно, должен указать, что вы также можете позвонить
std::for_each(some_vector.begin(), some_vector.end(), &do_stuff);
Ответ 12
Итераторы STL в основном существуют, поэтому алгоритмы STL, подобные сортировке, могут быть независимыми от контейнеров.
Если вы просто хотите перебрать все записи в векторе, просто используйте стиль цикла индекса.
Это меньше набирает текст и проще анализировать для большинства людей. Было бы неплохо, если бы С++ имел простой цикл foreach, не выходя за борт с магией шаблонов.
for( size_t i = 0; i < some_vector.size(); ++i )
{
T& rT = some_vector[i];
// now do something with rT
}
'
Ответ 13
Я не думаю, что это имеет большое значение для вектора. Я предпочитаю использовать индекс самостоятельно, поскольку считаю его более читаемым, и вы можете делать произвольный доступ, например, перепрыгивать 6 предметов или прыгать назад, если это необходимо.
Я также хотел бы сделать ссылку на элемент внутри цикла таким образом, чтобы вокруг не было много квадратных скобок:
for(size_t i = 0; i < myvector.size(); i++)
{
MyClass &item = myvector[i];
// Do stuff to "item".
}
Использование итератора может быть хорошим, если вы считаете, что вам может понадобиться заменить вектор списком в какой-то момент в будущем, и он также выглядит более стильным для уродов STL, но я не могу думать о какой-либо другой причине.
Ответ 14
Вторая форма представляет собой то, что вы делаете более точно. В вашем примере вам не важно значение i, действительно - все, что вам нужно, это следующий элемент в итераторе.
Ответ 15
Узнав немного больше о предмете этого ответа, я понимаю, что это было немного упрощением. Разница между этим циклом:
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 вы можете делать довольно интересные вещи, выходящие за рамки администраторов индексов массивов.
Ответ 16
Для индексирования требуется дополнительная операция mul. Например, для вектора v компилятор преобразует v [i] в & v + sizeof (int) * i.
Ответ 17
Во время итерации вам не нужно знать количество обрабатываемого элемента. Вам просто нужен элемент, и итераторы делают такие вещи очень хорошо.
Ответ 18
Несколько хороших моментов уже есть. У меня есть несколько дополнительных комментариев:
-
Предполагая, что мы говорим о стандартной библиотеке С++, "вектор" подразумевает контейнер произвольного доступа, который имеет гарантии C-массива (произвольный доступ, макет памяти contiguos и т.д.). Если вы сказали "some_container", многие из вышеперечисленных ответов были бы более точными (независимость контейнеров и т.д.).
-
Чтобы устранить любые зависимости от оптимизации компилятора, вы можете переместить some_vector.size() из цикла в индексированном коде, например:
const size_t numElems = some_vector.size();
for (size_t i = 0; i
-
Всегда предустановлять итераторы и обрабатывать пост-приращения как исключительные случаи.
for (some_iterator = some_vector.begin(); some_iterator!= some_vector.end(); ++ some_iterator) {//do stuff}
Таким образом, предполагая и индексируя std::vector<>
как контейнер, нет веских оснований предпочитать один над другим, последовательно проходя через контейнер. Если вам приходится часто ссылаться на более старые или более новые индексы, то индексированная версия более приемлема.
В общем, использование итераторов является предпочтительным, потому что алгоритмы используют их, и поведение можно контролировать (и неявно документировать), изменяя тип итератора. Вместо итераторов можно использовать расположение массивов, но синтаксическая разница будет торчать.
Ответ 19
Я не использую итераторы по той же причине, что мне не нравятся foreach-statements. При наличии нескольких внутренних циклов достаточно сложно отслеживать переменные global/member без необходимости запоминать все локальные значения и имена итераторов. Я считаю полезным использовать два набора индексов для разных случаев:
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" -именованному-итератору, например, потому что тогда вы не можете четко видеть, из какого массива это значение возникло.
Ответ 20
Даже лучше, чем "рассказывать CPU о том, что делать" (обязательно), "говорит библиотекам, что вы хотите" (функционально).
Поэтому вместо использования циклов вы должны изучить алгоритмы, присутствующие в stl.
Ответ 21
Я всегда использую индекс массива, потому что многие приложения моего типа требуют чего-то вроде "изображения миниатюр дисплея". Поэтому я написал что-то вроде этого:
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;
}
}
Ответ 22
Для независимости контейнера
Ответ 23
Обе реализации верны, но я бы предпочел цикл "для". Поскольку мы решили использовать Vector, а не какой-либо другой контейнер, лучшим вариантом будет использование индексов. Использование итераторов с Векторами потеряло бы выгоду от наличия объектов в непрерывных блоках памяти, которые облегчили бы их доступ.
Ответ 24
- Если вам нравится быть рядом с металлом/не доверять их деталям реализации, не использовать итераторы.
- Если вы регулярно выставляете один тип коллекции для другого во время разработки, итераторы use.
- Если вам сложно запомнить, как итерировать разные типы коллекций (возможно, у вас есть несколько типов из нескольких разных внешних источников), использовать итераторы для объединения средств, с помощью которых вы переходите элементы. Это относится, например, к переключению связанного списка со списком массивов.
Действительно, все, что есть. Это не так, как если бы вы в большинстве случаев получили более краткость в любом случае, и если краткость действительно является вашей целью, вы всегда можете отбросить макросы.
Ответ 25
Никто еще не упомянул, что одним из преимуществ индексов является то, что они не становятся недействительными при добавлении к непрерывному контейнеру типа std::vector
, поэтому вы можете добавлять элементы в контейнер во время итерации.
Это также возможно с помощью итераторов, но вы должны называть reserve()
, и поэтому вам нужно знать, сколько элементов вы добавите.