Цикл итератора против цикла индекса [дубликат]
Возможные Дубликаты:
зачем использовать итераторы вместо индексов массива?
я пересматриваю свои знания на C++, и я наткнулся на итераторы. Одна вещь, я хочу знать, что делает их такими особенными, и я хочу знать, почему это:
using namespace std;
vector<int> myIntVector;
vector<int>::iterator myIntVectorIterator;
// Add some elements to myIntVector
myIntVector.push_back(1);
myIntVector.push_back(4);
myIntVector.push_back(8);
for(myIntVectorIterator = myIntVector.begin();
myIntVectorIterator != myIntVector.end();
myIntVectorIterator++)
{
cout<<*myIntVectorIterator<<" ";
//Should output 1 4 8
}
лучше, чем это:
using namespace std;
vector<int> myIntVector;
// Add some elements to myIntVector
myIntVector.push_back(1);
myIntVector.push_back(4);
myIntVector.push_back(8);
for(int y=0; y<myIntVector.size(); y++)
{
cout<<myIntVector[y]<<" ";
//Should output 1 4 8
}
и да, я знаю, что я не должен использовать пространство имен std. Я просто взял этот пример из cprogramming вебсайт. Так не могли бы вы сказать мне, почему последнее хуже? В чем же большая разница?
8 ответов:
особенность итераторов заключается в том, что они обеспечивают клея между алгоритмы и контейнеры. Для общего кода рекомендуется использовать комбинацию алгоритмов STL (например,
find,sort,remove,copy) и т. д. это выполняет вычисление, которое вы имеете в виду на вашей структуре данных (vector,list,mapetc.), и поставить этот алгоритм с итераторами в ваш контейнер.ваш конкретный пример может быть написано как комбинация и
vectorконтейнер (см. Вариант 3) ниже), но это только один из четырех различных способов итерации по std::vector:1) итерация на основе индекса
for (std::size_t i = 0; i != v.size(); ++i) { // access element as v[i] // any code including continue, break, return }преимущества: знакомый всем, кто знаком с кодом C-стиля, может использовать цикл с использованием разных шагов (например,
i += 2).недостатки: только для контейнеров с последовательным произвольным доступом (
vector,array,deque), не работаетlist,forward_listили ассоциативные контейнеры. Также управление циклом является немного подробным (init, check, increment). Люди должны знать об индексации на основе 0 в C++.2) итерация на основе итератора
for (auto it = v.begin(); it != v.end(); ++it) { // if the current index is needed: auto i = std::distance(v.begin(), it); // access element as *it // any code including continue, break, return }преимущества: более общий, работает для всех контейнеров (даже новые неупорядоченные ассоциативные контейнеры, также могут использовать различные шаги (например
std::advance(it, 2));недостатки: нужна дополнительная работа, чтобы получить индекс текущего элемента (может быть O(N) для списка или forward_list). Опять же, управление циклом является немного подробным (init, check, increment).
3) алгоритм STL for_each + lambda
std::for_each(v.begin(), v.end(), [](T const& elem) { // if the current index is needed: auto i = &elem - &v[0]; // cannot continue, break or return out of the loop });преимущества: такие же как 2) плюс небольшое уменьшение в управлении петли (отсутствие проверка и инкремент), это может значительно уменьшить ваш тариф ошибки (неправильный инит, проверка или инкремент, ошибки off-by-one).
недостатки: то же самое, что явный итератор-цикл плюс ограниченные возможности для управления потоком в цикле (не может использовать continue, break или return) и нет возможности для разных шагов (если вы не используете адаптер итератора, который перегружает
operator++).4) диапазон-для петли
for (auto& elem: v) { // if the current index is needed: auto i = &elem - &v[0]; // any code including continue, break, return }преимущества: очень компактное управление петли, сразу доступ к течению элемент.
недостатки: дополнительная инструкция для получения индекса. Нельзя использовать разные шаги.
что использовать?
для вашего конкретного примера перебора
std::vector: если вам действительно нужен индекс (например, доступ к предыдущему или следующему элементу, печать/лесозаготовки индекс внутри цикла и т. д.) или вам нужен шаг, отличный от 1, тогда я бы пошел на явно индексированный цикл, иначе я бы пошел на диапазон-for петля.для общих алгоритмов на общих контейнерах я бы пошел на явный цикл итератора, если бы код не содержал никакого управления потоком внутри цикла и не требовал шага 1, и в этом случае я бы пошел на STL
for_each+ лямбда.
итераторы делают ваш код более универсальным.
Каждый стандартный контейнер библиотеки предоставляет итератор, следовательно, если вы измените свой класс контейнера в будущем, цикл не будет затронут.
итераторы являются первым выбором за
operator[]. C++11 поддерживаетstd::begin(),std::end()функции.как ваш код использует только
std::vector, Я не могу сказать, что есть большая разница в обоих кодексов, однако,operator []может работать не так, как вы намереваетесь. Например, если вы используете карту,operator[]вставить элемент, если не нашли.кроме того, с помощью
iteratorваш код становится более переносимым между контейнерами. Вы можете переключать контейнеры изstd::vectorдоstd::listили другой контейнер свободно, не меняя многого, если вы используете итератор, такое правило не применяется кoperator[].
с векторными итераторами не предлагают никакого реального преимущества. Синтаксис страшнее, дольше и труднее читать.
итерация по вектору с использованием итераторов не быстрее и не безопаснее (на самом деле, если вектор может быть изменен во время итерации с использованием итераторов поставит вас в большие проблемы).
идея иметь общий цикл, который работает, когда вы позже измените тип контейнера, также в основном бессмысленна в реальных случаях. К сожалению темнота сторона строго типизированного языка без серьезного вывода типа (немного лучше теперь с C++11, однако) заключается в том, что вам нужно сказать, какой тип всего на каждом шаге. Если вы передумаете позже, вам все равно нужно будет пойти и изменить все. Кроме того, разные контейнеры имеют очень разные компромиссы, и изменение типа контейнера-это не то, что происходит так часто.
единственный случай, в котором итерация должна быть сохранена, если это возможно, общий при написании шаблонный код, но это (я надеюсь на вас) не самый частый случай.
единственная проблема, присутствующая в вашем явном цикле индекса, заключается в том, что
sizeвозвращает беззнаковое значение (ошибка проектирования C++) и сравнение между подписанным и беззнаковым опасно и удивительно, поэтому лучше избегать. Если вы используете приличный компилятор с включенными предупреждениями, на нем должна быть диагностика.обратите внимание, что решение не использовать unsiged в качестве индекса, потому что арифметика между беззнаковые значения также, по-видимому, нелогичны (это арифметика по модулю и
x-1может быть больше, чемx). Вместо этого вы должны привести размер к целому числу перед его использованием. Это мая имеет смысл использовать неподписанные размеры и индексы (уделяя большое внимание каждому написанному выражению) только в том случае, если вы работаете над 16-битной реализацией C++ (16 бит был причиной наличия беззнаковых значений в размерах).как типичная ошибка, что без знака размер может ввести рассмотрим:
void drawPolyline(const std::vector<P2d>& points) { for (int i=0; i<points.size()-1; i++) drawLine(points[i], points[i+1]); }здесь ошибка присутствует, потому что если вы передаете пустой
pointsвекторная величинаpoints.size()-1будет огромное положительное число, что делает вас цикл в segfault. Рабочее решение может бытьfor (int i=1; i<points.size(); i++) drawLine(points[i - 1], points[i]);но я лично предпочитаю всегда удалить
unsinged-Несс сint(v.size()).уродство использования итераторов в этом случае остается в качестве упражнения для читателя.
это всегда зависит от того, что вам нужно.
вы должны использовать
operator[]когда вы нужно прямой доступ к элементам в векторе (когда необходимо индексировать определенный элемент в векторе). Нет ничего плохого в использовании его над итераторами. Тем не менее, вы должны решить для себя, какие (operator[]или итераторы) лучше всего подходит для ваших нужд.использование итераторов позволит вам переключаться на другие типы контейнеров без особых изменений в коде. Другими словами, используя итераторы сделают ваш код более универсальным и не зависят от конкретного типа контейнера.
написав свой клиентский код в терминах итераторов, вы полностью абстрагируете контейнер.
рассмотрим этот код:
class ExpressionParser // some generic arbitrary expression parser { public: template<typename It> void parse(It begin, const It end) { using namespace std; using namespace std::placeholders; for_each(begin, end, bind(&ExpressionParser::process_next, this, _1); } // process next char in a stream (defined elsewhere) void process_next(char c); };клиентский код:
ExpressionParser p; std::string expression("SUM(A) FOR A in [1, 2, 3, 4]"); p.parse(expression.begin(), expression.end()); std::istringstream file("expression.txt"); p.parse(std::istringstream<char>(file), std::istringstream<char>()); char expr[] = "[12a^2 + 13a - 5] with a=108"; p.parse(std::begin(expr), std::end(expr));Edit: рассмотрим ваш исходный пример кода, реализованный с помощью:
using namespace std; vector<int> myIntVector; // Add some elements to myIntVector myIntVector.push_back(1); myIntVector.push_back(4); myIntVector.push_back(8); copy(myIntVector.begin(), myIntVector.end(), std::ostream_iterator<int>(cout, " "));
хорошая вещь об итераторе заключается в том, что позже, если вы хотите переключить свой вектор на другой контейнер STD. Тогда forloop все равно будет работать.
Это вопрос скорости. использование итератора обеспечивает более быстрый доступ к элементам. на аналогичный вопрос был дан ответ здесь:
что быстрее, повторяя вектор STL с vector:: iterator или с at()?
изменить: скорость доступа зависит от процессора и компилятора
Comments