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

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

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
}

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

Второй способ предпочтительнее - заменить some_iterator++ на ++some_iterator. Постинкремент создает ненужный временный итератор.

jason 03.01.2010 18:21

Вы также должны указать end() в объявлении.

Lightness Races in Orbit 31.05.2011 19:11

@Tomalak: любой, кто использует реализацию C++ с неэффективным vector::end, вероятно, имеет более серьезные проблемы, о которых нужно беспокоиться, чем то, будет ли он выведен из цикла или нет. Лично я предпочитаю ясность - если бы это был вызов find в состоянии завершения, я бы волновался.

Steve Jessop 31.05.2011 21:16

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

Steve Jessop 31.05.2011 21:19

@Steve: Это не преждевременно, когда компилятор математически неспособен выполнить оптимизацию самостоятельно. И, IMO, it != end намного яснее; это конечно более лаконично. Хранить все ссылки (не T&) на контейнер в одном месте тоже удобно.

Lightness Races in Orbit 31.05.2011 21:22

@Tomalak: это преждевременно, если это не узкое место. Ваш второй пункт кажется мне абсурдным, поскольку правильное сравнение не между it != vec.end() и it != end, а между (vector<T>::iterator it = vec.begin(); it != vec.end(); ++it) и (vector<T>::iterator it = vec.begin(), end = vec.end(); it != end; ++it). Мне не нужно считать персонажей. Во что бы то ни стало предпочитайте одно другому, но несогласие других людей с вашими предпочтениями - это не «небрежность», а предпочтение более простого кода с меньшим количеством переменных, и, следовательно, меньше о чем нужно думать при его чтении.

Steve Jessop 31.05.2011 21:26

@Steve: «Избыточная микрооптимизация», я думаю, не то же самое, что «преждевременная оптимизация». Я не называю эту методологию «небрежной», Стив, потому что это не методология мой; как раз наоборот ... это не моя методология, потому что я считаю ее небрежной. :)

Lightness Races in Orbit 31.05.2011 21:27

@Tomalak: Это не то же самое, но бессмысленная микрооптимизация - это подмножество преждевременной оптимизации. Если все ссылки на вектор должны храниться вместе, то я полагаю, что вообще не следует упоминать вектор в цикле for. Вместо этого напишите цикл в функции, которая принимает в качестве параметров два итератора. Вы все равно получаете подъемник, и еще более многоразовую петлю в придачу :-)

Steve Jessop 31.05.2011 21:32

Обратите внимание, что в настоящее время вы должны просто использовать for (auto& item : some_vector) { ..., который, кстати, использует итераторы.

Claudiu 13.12.2015 04:35

Начиная с C++ 11 существует другой путь для перебора контейнера.

honk 25.02.2020 15:27
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
249
10
72 561
27
Перейти к ответу Данный вопрос помечен как решенный

Ответы 27

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

Интерфейс std :: list намеренно не предлагает operator [] (size_t n), потому что это будет O (n).

MSalters 26.09.2008 15:41

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

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

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

    // Do stuff to "item".
}

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

самый algorithms operate once on each element of a container, sequentially. Of course there are exceptions in which you'd want to traverse a collection in a specific order or manner, but in this case I'd try hard and write an algorithm that integrates with the STL and that works with iterators.
wilhelmtell 25.09.2008 07:30

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

wilhelmtell 25.09.2008 07:32

Даже не нужно заранее (). Итератор имеет те же операторы + = и - =, что и индекс (для векторных и векторных контейнеров).

MSalters 26.09.2008 15:42

I prefer to use an index myself as I consider it to be more readable только в некоторых ситуациях; в других индексы быстро становятся беспорядочными. and you can do random access, который вообще не является уникальной особенностью индексов: см. en.cppreference.com/w/cpp/concept/RandomAccessIterator

underscore_d 11.05.2017 13:29

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

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

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

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

Я не думаю, что «объектно-ориентированный» - правильный термин. Итераторы не являются объектно-ориентированными в дизайне. Они продвигают функциональное программирование больше, чем объектно-ориентированное программирование, поскольку поощряют отделение алгоритмов от классов.

wilhelmtell 25.09.2008 07:14

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

wilhelmtell 25.09.2008 07:15

Достаточно справедливо @wilhelmtell, я, очевидно, думаю об этом с точки зрения Java.

cynicalman 25.09.2008 07:18

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

cynicalman 25.09.2008 07:19

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

Daemin 25.09.2008 10:52

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

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

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

«он будет поддерживать указатель на текущий узел и продвигать его [хорошие вещи об эффективности]» - да, я не понимаю, почему у людей возникают проблемы с пониманием концепции итераторов. концептуально они просто надмножество указателей. зачем вычислять смещение некоторого элемента снова и снова, если можно просто кэшировать указатель на него? ну, это то, что делают итераторы.

underscore_d 25.02.2016 00:56
Ответ принят как подходящий

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

T elem = some_vector[i];

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

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

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

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

Marcin 22.10.2008 02:25

Это меня смущает: «Это верно для векторов, но не для списков, например». Почему? Любой, у кого есть мозг, будет отслеживать переменную-член size_t, отслеживая size().

GManNickG 13.07.2009 11:39

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

Daniel Earwicker 13.07.2009 11:41

@GMan: для хранения размера списка требуется, чтобы нарезка и склейка списка были O (n) вместо O (1).

Roger Pate 06.11.2010 18:38

В C++ 0x функция-член size() должна иметь постоянную временную сложность для всех поддерживающих ее контейнеров, включая std::list.

James McNellis 06.11.2010 19:43

«... делая предположение, что вектор определен operator[]() ...» Что ж, это очень разумное предположение. Ты сделал. случайно, значит сказать "... что в контейнер есть ..."?

sbi 07.11.2010 00:21

и я думал, что .size всегда можно оптимизировать путем извлечения. поэтому я не вижу efficient. наверное reusable

zinking 19.08.2016 18:21

Я отклонил этот ответ, потому что считаю, что он требует совершенно хорошего, ясного, функционального кода и делает вид, что он может или должен быть другим. На каком основании? Тот факт, что кто-то выбрал вектор, а не список (велосипед или слона), предполагает, что вектор, вероятно, имеет наибольший смысл. Более того, они явно понимают индексацию. Итак, что же на самом деле добавляет использование итератора? Это для меня вопрос, и этот ответ в основном упускает суть. С таким же успехом можно было бы сказать: «Если бы слон был бананом, то ...» ИМХО Марк Рэнсом дает более прагматичный и обоснованный ответ.

omatai 28.03.2017 03:51

Помимо всех других отличных ответов ... 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
}

@ Пат Нотц, это очень хороший аргумент. В ходе переноса приложения Windows на основе STL на x64 мне приходилось сталкиваться с сотнями предупреждений о назначении size_t для int, что могло вызвать усечение.

bk1e 25.09.2008 09:28

Не говоря уже о том, что типы размера беззнаковые, а int подписано, поэтому у вас будут неинтуитивные преобразования, скрывающие ошибки, просто для сравнения int i с myvector.size().

Adrian McCarthy 08.07.2015 21:24

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

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

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

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

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

wilhelmtell 25.09.2008 07:39

Итерации по всем элементам в std::list довольно дорого по сравнению с std::vector, однако, если вы рекомендуете использовать связанный список вместо std::vector. См. Стр. 43: ecn.channel9.msdn.com/events/GoingNative12/GN12Cpp11Style.pd‌ f По моему опыту, я обнаружил, что std::vector быстрее, чем std::list, даже если я просматриваю все и удаляю элементы в произвольных позициях.

David Stone 17.04.2012 21:05

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

musiphil 28.03.2013 01:11

... А со связанным списком - который и должен использоваться здесь - ваш оператор цикла будет иметь вид for (node = list->head; node != NULL; node = node->next), который короче, чем ваши первые две строки кода вместе взятые (объявление и заголовок цикла). Итак, я повторяю еще раз - нет большой принципиальной разницы в краткости между использованием итераторов и их неиспользованием - вы все равно удовлетворяете трем частям оператора for, даже если вы используете while: объявление, итерация, проверка завершения.

Engineer 15.11.2015 15:54

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


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
}

Это выглядит разумно, но я все еще сомневаюсь, что это причина наличия const_iterator. Если я изменяю вектор в цикле, я делаю это по определенной причине, и в 99,9% случаев это изменение не является случайностью, а в остальном это просто ошибка, как и любые другие ошибки в коде, автор нужно исправить. Потому что в Java и многих других языках вообще нет константного объекта, но у пользователей этих языков никогда не возникает проблем с отсутствием поддержки констант на этих языках.

neevek 27.11.2016 18:30

@neevek Если это не причина наличия const_iterator, то в чем, черт возьми, может быть причина?

underscore_d 11.05.2017 13:23

@underscore_d, мне тоже интересно. Я не разбираюсь в этом вопросе, просто ответ для меня неубедителен.

neevek 11.05.2017 13:35

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


Wow, this is still getting downvoted after three weeks. I guess it doesn't pay to be a little tongue-in-cheek.

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

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

Итераторы C++ также ужасно сломаны концептуально. Что касается векторов, меня просто поймали, потому что конечный указатель на самом деле end + 1 (!). Для потоков модель итератора просто сюрреалистична - воображаемый токен, которого не существует. То же самое для связанных списков. Эта парадигма имеет смысл только для массивов, и то не особо. Зачем мне два объекта-итератора, а не один ...

Tuntable 21.01.2016 08:14

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

Mark Ransom 21.01.2016 09:25

взгляните на итераторы потока и подумайте, что == было извращено, чтобы соответствовать шаблону, а затем скажите мне, что итераторы не сломаны! Или для связанных списков. Даже для массивов необходимость указывать один за концом - сломанная идея стиля C - указатель на никогда. Они должны быть похожи на итераторы Java, C# или любого другого языка, с одним необходимым итератором (вместо двух объектов) и простым конечным тестом.

Tuntable 01.02.2016 12:34

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

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

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

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

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

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

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

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

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

}

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

  1. Предполагая, что мы говорим о стандартной библиотеке C++, «вектор» подразумевает контейнер с произвольным доступом, который имеет гарантии C-массива (произвольный доступ, непрерывное расположение памяти и т. д.). Если бы вы сказали 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 != some_vector.end(); ++some_iterator){ //do stuff }

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

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

За независимость от контейнеров

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

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

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

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

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

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

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

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

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

Кроме того, в случае std::for_each вы СКАЗЫВАЙТЕ коллекции, что делать, а не СПРОСИТЕ это что-то о его внутреннем устройстве

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

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

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

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

Обязательным к прочтению справочником по итераторам является книга «Расширенный STL».

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

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

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

По правде говоря, если бы все итераторы были такими же компактными, как ваш последний пример, прямо из коробки, у меня не было бы проблем с ними. Конечно, это на самом деле равно for (Iter it = {0}; it != end; ++it) {...} - вы просто опустили декларацию - поэтому краткость не сильно отличается от вашего второго примера. Тем не менее, +1.

Engineer 15.11.2015 15:46

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

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

Brent Bradburn 01.03.2010 07:33

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

underscore_d 25.02.2016 01:03

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

Не вижу, насколько в этом смысле лучше индексы. Вы можете легко использовать итераторы и просто выбрать соглашение для их имен: it, jt, kt и т. д. Или даже просто продолжать использовать i, j, k и т. д. И если вам нужно точно знать, что представляет собой итератор, то для меня что-то вроде for (auto anim = anims.begin(); ...) for (auto anim_bone = anim->bones.begin(); ...) anim_bone->wobble() было бы более наглядным, чем необходимость постоянно индексировать, например, animation_matrices[animIndex][boneIndex].

underscore_d 11.05.2017 13:32

вау, я как будто писал это мнение давным-давно. в настоящее время без особого беспокойства можно использовать итераторы foreach и C++. Думаю, работа с ошибочным кодом в течение многих лет повышает терпимость, поэтому легче принять все синтаксисы и соглашения ... пока это работает, и пока человек может вернуться домой, знаете;)

AareP 12.05.2017 22:19

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

underscore_d 12.05.2017 23:52

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

«Использование итераторов с векторами приведет к потере самого преимущества наличия объектов в непрерывных блоках памяти, которые облегчают доступ к ним». [нужна цитата]. Почему? Считаете ли вы, что приращение итератора к непрерывному контейнеру не может быть реализовано как простое добавление?

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

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

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

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

Я чувствовал, что ни один из ответов здесь не объясняет, почему мне нравятся итераторы как общая концепция по сравнению с индексированием в контейнеры. Обратите внимание, что большая часть моего опыта использования итераторов на самом деле происходит не от C++, а от языков программирования более высокого уровня, таких как Python.

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

Если все, что вам нужно, это возможность пересылки итерации, разработчик не ограничивается использованием индексируемых контейнеров - он может использовать любой класс, реализующий operator++(T&), operator*(T) и operator!=(const &T, const &T).

#include <iostream>
template <class InputIterator>
void printAll(InputIterator& begin, InputIterator& end)
{
    for (auto current = begin; current != end; ++current) {
        std::cout << *current << "\n";
    }
}

// elsewhere...

printAll(myVector.begin(), myVector.end());

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

#include <random>

class RandomIterator
{
private:
    std::mt19937 random;
    std::uint_fast32_t current;
    std::uint_fast32_t floor;
    std::uint_fast32_t ceil;

public:
    RandomIterator(
        std::uint_fast32_t floor = 0,
        std::uint_fast32_t ceil = UINT_FAST32_MAX,
        std::uint_fast32_t seed = std::mt19937::default_seed
    ) :
        floor(floor),
        ceil(ceil)
    {
        random.seed(seed);
        ++(*this);
    }

    RandomIterator& operator++()
    {
        current = floor + (random() % (ceil - floor));
    }

    std::uint_fast32_t operator*() const
    {
        return current;
    }

    bool operator!=(const RandomIterator &that) const
    {
        return current != that.current;
    }
};

int main()
{
    // roll a 1d6 until we get a 6 and print the results
    RandomIterator firstRandom(1, 7, std::random_device()());
    RandomIterator secondRandom(6, 7);
    printAll(firstRandom, secondRandom);

    return 0;
}

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

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

template<class InputIterator, typename T>
class FilterIterator
{
private:
    InputIterator internalIterator;

public:
    FilterIterator(const InputIterator &iterator):
        internalIterator(iterator)
    {
    }

    virtual bool condition(T) = 0;

    FilterIterator<InputIterator, T>& operator++()
    {
        do {
            ++(internalIterator);
        } while (!condition(*internalIterator));

        return *this;
    }

    T operator*()
    {
        // Needed for the first result
        if (!condition(*internalIterator))
            ++(*this);
        return *internalIterator;
    }

    virtual bool operator!=(const FilterIterator& that) const
    {
        return internalIterator != that.internalIterator;
    }
};

template <class InputIterator>
class EvenIterator : public FilterIterator<InputIterator, std::uint_fast32_t>
{
public:
    EvenIterator(const InputIterator &internalIterator) :
        FilterIterator<InputIterator, std::uint_fast32_t>(internalIterator)
    {
    }

    bool condition(std::uint_fast32_t n)
    {
        return !(n % 2);
    }
};


int main()
{
    // Rolls a d20 until a 20 is rolled and discards odd rolls
    EvenIterator<RandomIterator> firstRandom(RandomIterator(1, 21, std::random_device()()));
    EvenIterator<RandomIterator> secondRandom(RandomIterator(20, 21));
    printAll(firstRandom, secondRandom);

    return 0;
}

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

Частью мощи шаблонов C++ является ваш интерфейс итератора, когда он применяется к подобным массивам C фиксированной длины, распадается на простую и эффективную арифметику указателей, что делает его действительно абстракцией с нулевой стоимостью.

Если у вас есть доступ к функциям C++ 11, вы также можете использовать петля for на основе диапазона для итерации по вашему вектору (или любому другому контейнеру) следующим образом:

for (auto &item : some_vector)
{
     //do stuff
}

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

Заметки:

  • Если вам нужен индекс элемента в вашем цикле, а operator[] существует для вашего контейнера (и достаточно быстрый для вас), тогда лучше пойти по первому пути.
  • Цикл for на основе диапазона нельзя использовать для добавления / удаления элементов в / из контейнера. Если вы хотите это сделать, лучше придерживайтесь решение, данного Брайаном Мэтьюзом.
  • Если вы не хотите изменять элементы в своем контейнере, вам следует использовать ключевое слово const следующим образом: for (auto const &item : some_vector) { ... }.

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