Цикл итератора против цикла индекса [дубликат]




Возможные Дубликаты:
зачем использовать итераторы вместо индексов массива?






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

775   8  

8 ответов:

особенность итераторов заключается в том, что они обеспечивают клея между алгоритмы и контейнеры. Для общего кода рекомендуется использовать комбинацию алгоритмов STL (например,find,sort,remove,copy) и т. д. это выполняет вычисление, которое вы имеете в виду на вашей структуре данных (vector,list,map etc.), и поставить этот алгоритм с итераторами в ваш контейнер.

ваш конкретный пример может быть написано как комбинация и 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

    Ничего не найдено.