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


239

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

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
}

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


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

6
Вы должны также внести end()в пункт декларации.
Гонки легкости на орбите

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

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

7
@ Томалак: преждевременно, если это не узкое место. Ваш второй пункт кажется абсурдным, поскольку правильное сравнение не между 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). Мне не нужно считать персонажей. Безусловно, предпочитайте один другому, но несогласие других людей с вашими предпочтениями - это не «неряшливость», это предпочтение более простого кода с меньшим количеством переменных и, следовательно, меньшим количеством размышлений при его чтении.
Стив Джессоп

Ответы:


210

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

T elem = some_vector[i];

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

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

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


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

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

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

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

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

54

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


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

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

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


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

5
@aberglas они нисколько не сломлены, вы просто к ним не привыкли, поэтому я рекомендую использовать их, даже если вам не нужно! Полуоткрытые диапазоны являются распространенной концепцией, а сторожам, к которым никогда не предполагается прямой доступ, почти столько же лет, сколько и самому программированию.
Марк Рэнсом

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

53

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


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

33

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

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

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

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

27

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

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

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

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


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

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


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

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

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

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

16

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

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

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

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

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

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

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

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

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

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


15

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

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

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

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


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

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

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

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

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

15

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

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

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

@underscore_d, мне тоже интересно. Я не эксперт в этом, просто ответ мне не убедителен.
neevek

15

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

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

1
Не говоря уже о том , что типы размера знака и подписанный ИНТА, поэтому у вас есть не-интуитивное, преобразование ошибки скрытие происходит только для сравнения int iс myvector.size().
Адриан МакКарти

12

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

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


7

Итераторы 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
}
'

5

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

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

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

    // Do stuff to "item".
}

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


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

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

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

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

3

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


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


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

3

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


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

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

2

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


2

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

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


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

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


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


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

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

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

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

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


1

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

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

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

Ноты:

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

0

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

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



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

}

0

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


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

0

Я чувствовал, что ни один из ответов здесь не объясняет, почему я люблю итераторы как общую концепцию индексации в контейнеры. Обратите внимание, что большая часть моего опыта использования итераторов на самом деле не из 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 фиксированной длины , превращается в простую и эффективную арифметику указателей , что делает его действительно абстракцией с нулевой стоимостью.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.