Возьмите следующие две строки кода:
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
}
Мне сказали, что предпочтительнее второй способ. Почему именно это?
Вы также должны указать end() в объявлении.
@Tomalak: любой, кто использует реализацию C++ с неэффективным vector::end, вероятно, имеет более серьезные проблемы, о которых нужно беспокоиться, чем то, будет ли он выведен из цикла или нет. Лично я предпочитаю ясность - если бы это был вызов find в состоянии завершения, я бы волновался.
@Tomalak: Этот код не небрежный (ну, может быть, пост-инкремент), он лаконичен и ясен, поскольку итераторы C++ допускают лаконичность. Добавление большего количества переменных увеличивает когнитивные усилия ради преждевременной оптимизации. Это небрежно.
@Steve: Это не преждевременно, когда компилятор математически неспособен выполнить оптимизацию самостоятельно. И, IMO, it != end намного яснее; это конечно более лаконично. Хранить все ссылки (не T&) на контейнер в одном месте тоже удобно.
@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: «Избыточная микрооптимизация», я думаю, не то же самое, что «преждевременная оптимизация». Я не называю эту методологию «небрежной», Стив, потому что это не методология мой; как раз наоборот ... это не моя методология, потому что я считаю ее небрежной. :)
@Tomalak: Это не то же самое, но бессмысленная микрооптимизация - это подмножество преждевременной оптимизации. Если все ссылки на вектор должны храниться вместе, то я полагаю, что вообще не следует упоминать вектор в цикле for. Вместо этого напишите цикл в функции, которая принимает в качестве параметров два итератора. Вы все равно получаете подъемник, и еще более многоразовую петлю в придачу :-)
Обратите внимание, что в настоящее время вы должны просто использовать for (auto& item : some_vector) { ..., который, кстати, использует итераторы.
Начиная с C++ 11 существует другой путь для перебора контейнера.





потому что вы не привязываете свой код к конкретной реализации списка some_vector. если вы используете индексы массива, это должна быть какая-то форма массива; если вы используете итераторы, вы можете использовать этот код в любой реализации списка.
Интерфейс std :: list намеренно не предлагает operator [] (size_t n), потому что это будет O (n).
Я не думаю, что это имеет большое значение для вектора. Я предпочитаю использовать индекс сам, так как считаю его более читаемым, и вы можете выполнять произвольный доступ, например, переходить на 6 элементов вперед или назад, если это необходимо.
Я также хотел бы сделать ссылку на элемент внутри цикла следующим образом, чтобы вокруг места не было много квадратных скобок:
for(size_t i = 0; i < myvector.size(); i++)
{
MyClass &item = myvector[i];
// Do stuff to "item".
}
Использование итератора может быть полезным, если вы думаете, что вам может потребоваться заменить вектор списком в какой-то момент в будущем, и он также выглядит более стильно для фанатов STL, но я не могу придумать никакой другой причины.
Это будет способствовать повторному использованию и избежать повторных ошибок в дальнейшем. Затем я бы назвал этот алгоритм, как и любой другой стандартный алгоритм, с итераторами.
Даже не нужно заранее (). Итератор имеет те же операторы + = и - =, что и индекс (для векторных и векторных контейнеров).
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
Потому что он более объектно-ориентированный. если вы выполняете итерацию с индексом, вы предполагаете:
а) что эти объекты упорядочены
б) эти объекты могут быть получены с помощью индекса
.
c) что приращение индекса коснется каждого элемента
г) что этот индекс начинается с нуля
С итератором вы говорите: «Дайте мне все, чтобы я мог с ним работать», не зная, какова основная реализация. (В Java есть коллекции, к которым нельзя получить доступ через индекс)
Кроме того, с итератором не нужно беспокоиться о выходе за пределы массива.
Я не думаю, что «объектно-ориентированный» - правильный термин. Итераторы не являются объектно-ориентированными в дизайне. Они продвигают функциональное программирование больше, чем объектно-ориентированное программирование, поскольку поощряют отделение алгоритмов от классов.
Кроме того, итераторы не помогают избежать выхода за пределы. Стандартные алгоритмы работают, а вот итераторы - нет.
Достаточно справедливо @wilhelmtell, я, очевидно, думаю об этом с точки зрения Java.
И я думаю, что это продвигает объектно-ориентированный подход, потому что он отделяет операции над коллекциями от реализации этой коллекции. Коллекция объектов не обязательно должна знать, какие алгоритмы следует использовать для работы с ними.
На самом деле существуют версии STL, в которых есть проверенные итераторы, а это означает, что при попытке сделать что-то с этим итератором будет выдано какое-то исключение, выходящее за границы.
Представьте, что some_vector реализован со связным списком. Затем запрос элемента на i-м месте требует выполнения i операций для обхода списка узлов. Теперь, если вы используете итератор, вообще говоря, он сделает все возможное, чтобы быть как можно более эффективным (в случае связанного списка он будет поддерживать указатель на текущий узел и продвигать его на каждой итерации, требуя только разовая операция).
Таким образом, он обеспечивает две вещи:
«он будет поддерживать указатель на текущий узел и продвигать его [хорошие вещи об эффективности]» - да, я не понимаю, почему у людей возникают проблемы с пониманием концепции итераторов. концептуально они просто надмножество указателей. зачем вычислять смещение некоторого элемента снова и снова, если можно просто кэшировать указатель на него? ну, это то, что делают итераторы.
Первая форма эффективна, только если vector.size () - быстрая операция. Это верно для векторов, но не для списков, например. Кроме того, что вы планируете делать в теле цикла? Если вы планируете получить доступ к элементам, как в
T elem = some_vector[i];
то вы делаете предположение, что для контейнера определен operator[](std::size_t). Опять же, это верно для вектора, но не для других контейнеров.
Использование итераторов приближает вас к независимость от контейнера. Вы не делаете предположений о возможности произвольного доступа или быстрой операции size(), только о том, что контейнер имеет возможности итератора.
Вы можете улучшить свой код, используя стандартные алгоритмы. В зависимости от того, чего вы пытаетесь достичь, вы можете выбрать использование std::for_each(), std::transform() и так далее. Используя стандартный алгоритм вместо явного цикла, вы избегаете повторного изобретения колеса. Ваш код, вероятно, будет более эффективным (при выборе правильного алгоритма), правильным и многоразовым.
Также вы забыли, что итераторы могут выполнять такие действия, как отказоустойчивость, так что если есть одновременное изменение структуры, к которой вы обращаетесь, вы будете знать об этом. Вы не можете сделать это с помощью целого числа.
Это меня смущает: «Это верно для векторов, но не для списков, например». Почему? Любой, у кого есть мозг, будет отслеживать переменную-член size_t, отслеживая size().
@GMan - почти во всех реализациях size () работает для списков так же быстро, как и для векторов. Следующая версия стандарта потребует, чтобы это было правдой. Настоящая проблема - это медленность возврата по позициям.
@GMan: для хранения размера списка требуется, чтобы нарезка и склейка списка были O (n) вместо O (1).
В C++ 0x функция-член size() должна иметь постоянную временную сложность для всех поддерживающих ее контейнеров, включая std::list.
«... делая предположение, что вектор определен operator[]() ...» Что ж, это очень разумное предположение. Ты сделал. случайно, значит сказать "... что в контейнер есть ..."?
и я думал, что .size всегда можно оптимизировать путем извлечения. поэтому я не вижу efficient. наверное reusable
Я отклонил этот ответ, потому что считаю, что он требует совершенно хорошего, ясного, функционального кода и делает вид, что он может или должен быть другим. На каком основании? Тот факт, что кто-то выбрал вектор, а не список (велосипед или слона), предполагает, что вектор, вероятно, имеет наибольший смысл. Более того, они явно понимают индексацию. Итак, что же на самом деле добавляет использование итератора? Это для меня вопрос, и этот ответ в основном упускает суть. С таким же успехом можно было бы сказать: «Если бы слон был бананом, то ...» ИМХО Марк Рэнсом дает более прагматичный и обоснованный ответ.
Помимо всех других отличных ответов ... 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, что могло вызвать усечение.
Не говоря уже о том, что типы размера беззнаковые, а int подписано, поэтому у вас будут неинтуитивные преобразования, скрывающие ошибки, просто для сравнения int i с myvector.size().
Вторая форма более точно отображает то, что вы делаете. В вашем примере вас действительно не волнует значение 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;
}
}
Если бы вы использовали индексы, вам пришлось бы перемещать элементы вверх / вниз в массиве для обработки вставок и удалений.
если вы хотите вставить элементы в середину контейнера, тогда, возможно, вектор не лучший выбор контейнера для начала. конечно, мы вернулись к тому, почему итераторы - это круто; переключиться на список тривиально.
Итерации по всем элементам в std::list довольно дорого по сравнению с std::vector, однако, если вы рекомендуете использовать связанный список вместо std::vector. См. Стр. 43: ecn.channel9.msdn.com/events/GoingNative12/GN12Cpp11Style.pd f По моему опыту, я обнаружил, что std::vector быстрее, чем std::list, даже если я просматриваю все и удаляю элементы в произвольных позициях.
Индексы стабильны, поэтому я не вижу, какая дополнительная перетасовка нужна для вставок и удалений.
... А со связанным списком - который и должен использоваться здесь - ваш оператор цикла будет иметь вид for (node = list->head; node != NULL; node = node->next), который короче, чем ваши первые две строки кода вместе взятые (объявление и заголовок цикла). Итак, я повторяю еще раз - нет большой принципиальной разницы в краткости между использованием итераторов и их неиспользованием - вы все равно удовлетворяете трем частям оператора for, даже если вы используете while: объявление, итерация, проверка завершения.
Еще одна приятная вещь об итераторах - это то, что они лучше позволяют вам выражать (и обеспечивать) ваши константные предпочтения. Этот пример гарантирует, что вы не будете изменять вектор в середине цикла:
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 Если это не причина наличия const_iterator, то в чем, черт возьми, может быть причина?
@underscore_d, мне тоже интересно. Я не разбираюсь в этом вопросе, просто ответ для меня неубедителен.
Это часть современного процесса идеологической обработки C++. Итераторы - единственный способ перебирать большинство контейнеров, поэтому вы используете их даже с векторами, чтобы сформировать правильное мышление. Серьезно, это единственная причина, по которой я это делаю - я не думаю, что когда-либо заменял вектор контейнером другого типа.
Я думаю, что индекс массива более читабельный. Он соответствует синтаксису, используемому в других языках, и синтаксису, используемому для устаревших массивов C. Это также менее многословно. Эффективность должна быть плачевной, если ваш компилятор хоть сколько-нибудь хорош, да и вообще нет случаев, когда это имеет значение.
Тем не менее, я все еще часто использую итераторы с векторами. Я считаю, что итератор - важная концепция, поэтому я продвигаю его, когда могу.
Итераторы C++ также ужасно сломаны концептуально. Что касается векторов, меня просто поймали, потому что конечный указатель на самом деле end + 1 (!). Для потоков модель итератора просто сюрреалистична - воображаемый токен, которого не существует. То же самое для связанных списков. Эта парадигма имеет смысл только для массивов, и то не особо. Зачем мне два объекта-итератора, а не один ...
@aberglas, они совсем не сломаны, вы просто не привыкли к ним, поэтому я рекомендую использовать их, даже когда вам это не нужно! Полуоткрытые диапазоны - это обычное понятие, и дозорные, к которым никогда нельзя обращаться напрямую, примерно так же стары, как и само программирование.
взгляните на итераторы потока и подумайте, что == было извращено, чтобы соответствовать шаблону, а затем скажите мне, что итераторы не сломаны! Или для связанных списков. Даже для массивов необходимость указывать один за концом - сломанная идея стиля C - указатель на никогда. Они должны быть похожи на итераторы Java, C# или любого другого языка, с одним необходимым итератором (вместо двух объектов) и простым конечным тестом.
Во время итерации вам не нужно знать номер обрабатываемого элемента. Вам просто нужен элемент, и итераторы очень хорошо с этим справляются.
Я буду здесь сторонником дьяволов и не рекомендую итераторы. Основная причина этого заключается в том, что весь исходный код, над которым я работал, от разработки настольных приложений до разработки игр, мне и не нужно использовать итераторы. Все время они не требовались, а во-вторых, скрытые предположения, беспорядок кода и кошмары отладки, которые вы получаете с итераторами, делают их ярким примером того, чтобы не использовать их в любых приложениях, требующих скорости.
Даже с точки зрения обслуживания они беспорядок. Это не из-за них, а из-за всех псевдонимов, которые происходят за сценой. Откуда мне знать, что вы не реализовали свой собственный список виртуальных векторов или массивов, который делает что-то совершенно отличное от стандартов. Знаю ли я, какой тип сейчас находится во время выполнения? Вы перегрузили оператора? У меня не было времени проверить весь ваш исходный код. Черт, я вообще знаю, какую версию 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;
}
}
Уже несколько хороших моментов. У меня есть несколько дополнительных комментариев:
Предполагая, что мы говорим о стандартной библиотеке C++, «вектор» подразумевает контейнер с произвольным доступом, который имеет гарантии C-массива (произвольный доступ, непрерывное расположение памяти и т. д.). Если бы вы сказали some_container, многие из приведенных выше ответов были бы более точными (независимость от контейнера и т. д.).
Чтобы устранить любые зависимости от оптимизации компилятора, вы можете переместить some_vector.size () из цикла в индексированном коде, например:
const size_t numElems = some_vector.size(); for (size_t i = 0; i
Всегда увеличивайте итераторы до инкремента и рассматривайте постинкременты как исключительные случаи.
Так что, предполагая индексируемый 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.
Для индексации требуется дополнительная операция mul. Например, для vector<int> v компилятор преобразует v[i] в &v + sizeof(int) * i.
В большинстве случаев это, вероятно, незначительный недостаток по сравнению с итераторами, но об этом следует помнить.
Возможно, для изолированных одноэлементных доступов. Но если мы говорим о циклах - как это было с OP - то я почти уверен, что этот ответ основан на воображаемом неоптимизирующем компиляторе. У любого полуприличного будет достаточно возможностей и вероятности кэшировать sizeof и просто добавлять его один раз за итерацию, вместо того, чтобы каждый раз заново выполнять все вычисления смещения.
Я не использую итераторы по той же причине, что мне не нравятся выражения 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].
вау, я как будто писал это мнение давным-давно. в настоящее время без особого беспокойства можно использовать итераторы foreach и C++. Думаю, работа с ошибочным кодом в течение многих лет повышает терпимость, поэтому легче принять все синтаксисы и соглашения ... пока это работает, и пока человек может вернуться домой, знаете;)
Ха-ха, правда, я раньше не особо смотрел, сколько ему лет! Что-то еще, о чем я почему-то не подумал в прошлый раз, так это то, что в настоящее время у нас также есть цикл for на основе диапазона, который делает этот способ на основе итератора еще более кратким.
Обе реализации верны, но я бы предпочел цикл for. Поскольку мы решили использовать вектор, а не какой-либо другой контейнер, использование индексов было бы лучшим вариантом. Использование итераторов с векторами приведет к потере самого преимущества наличия объектов в непрерывных блоках памяти, которые облегчают их доступ.
«Использование итераторов с векторами приведет к потере самого преимущества наличия объектов в непрерывных блоках памяти, которые облегчают доступ к ним». [нужна цитата]. Почему? Считаете ли вы, что приращение итератора к непрерывному контейнеру не может быть реализовано как простое добавление?
На самом деле, вот и все. Это не значит, что в любом случае вы собираетесь добиться большей краткости в любом случае, и если краткость действительно является вашей целью, вы всегда можете вернуться к макросам.
Еще никто не упомянул, что одним из преимуществ индексов является то, что они не становятся недействительными при добавлении в непрерывный контейнер, такой как 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) { ... }.
Второй способ предпочтительнее - заменить
some_iterator++на++some_iterator. Постинкремент создает ненужный временный итератор.