Итерировать или использовать счетчик, вот в чем вопрос - PullRequest
4 голосов
/ 06 декабря 2009

Когда кто-то начинает использовать STL и у него есть вектор, вы обычно видите:

vector<int> vec ;

//... code ...

for( vector<int>::iterator iter = vec.begin() ;
     iter != vec.end() ;
     ++iter )
{
  // do stuff
}

Я просто нахожу весь этот синтаксис vector<int>::iterator больным. Я знаю, что вы можете typedef vector<int>::iterator VecIterInt, и что немного лучше ..

Но вопрос в том, что не так с добрым старым:

for( int i = 0 ; i < vec.size() ; i++ )
{
  // code
}

Ответы [ 12 ]

11 голосов
/ 06 декабря 2009

Когда вы используете индекс для выполнения по существу последовательного доступа к контейнеру (std::vector или чего-либо еще), вы накладываете требование произвольного доступа на базовую структуру данных, когда на самом деле вам не нужен такой доступ в вашем алгоритме. Требуется произвольный доступ - довольно сильное требование по сравнению со значительно более слабым требованием последовательный доступ . Установление более строгих требований без уважительной причины является серьезной ошибкой проектирования.

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

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

Конечно, вышеприведенное правило, хотя и истинно, имеет смысл только в коде, является универсальным в определенной степени. Если какая-то другая часть кода настолько специфична , что вы точно знаете, что структура данных, с которой вы работаете, равна std::vector и всегда будет std::vector, то метод доступа больше не будет вопросы. Используйте то, что вы предпочитаете. Однако я бы по-прежнему избегал доступа к индексам в ситуациях, когда последовательный доступ вполне достаточен.

7 голосов
/ 06 декабря 2009

Ну, когда дело доходит до std::vector Я думаю, что использование оператора индекса в цикле просто прекрасно и, вероятно, примерно одинаково с точки зрения производительности. Преимущество использования итератора возникает тогда, когда вы хотите использовать вектор с другими std функциями, как в .

2 голосов
/ 06 декабря 2009

Я не думаю, что мой аргумент очень силен, но я почти всегда использую версию итератора.

typedef std::vector<int> MyIndexes; // or whatever
MyIndexes indexes;
for (Something::iterator iter = indexes.begin(); iter != indexes.end(); ++iter);

Теперь, если мне нужно изменить вектор на список или что-то подобное, мне нужно только изменить свой typedef. Это было полезно в нескольких случаях. Ключевое слово auto сделает это лучше, но я не могу ждать C ++ 0x для цикла:)

1 голос
/ 06 декабря 2009

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

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

std::vector<int> v1, v2;
// ...

std::copy(v1.begin(), v1.end(), v2.begin());

Как насчет std::cout вместо?

std::ostream_iterator<int> output(std::cout, " ");
std::copy(v1.begin(), v2.end(), output);

Итераторы позволяют одному шаблону обрабатывать все виды случаев.

1 голос
/ 06 декабря 2009

Я бы рекомендовал использовать итераторы просто потому, что они более общие. Если позже в вашем цикле разработки вы решите, что std :: list <> будет лучше, чем std :: vector <> (возможно, вы обнаружите, что у вас есть проблема с производительностью, потому что вам нужно вытолкнуть элементы из заголовка контейнера ), для изменения версии итератора потребуется меньше усилий.

1 голос
/ 06 декабря 2009

С C ++ 0x у вас не будет этой дилеммы. Вы будете использовать новый for, который на самом деле foreach :)

1 голос
/ 06 декабря 2009

Как правило, не ограничивая себя std :: vector, итераторы могут быть более эффективными, чем индексирование, поскольку они имеют доступ к внутренним объектам коллекции и знают состояние итерации.

В случае std :: vector, итератор в цикле сведется к арифметике указателя при компиляции с включенной оптимизацией. Умный компилятор может сделать то же самое с циклом счетчика, и не должно быть разницы в производительности, но в целом (для более сложных коллекций) вы должны предполагать, что итератор даст вам самый быстрый код. *

1 голос
/ 06 декабря 2009

Я обычно использую цикл for для векторов - для меня это канонический способ иметь дело с такими вещами. И не позволяйте никому говорить, что итераторы быстрее - см. Что быстрее, итерация вектора STL с помощью vector :: iterator или с at ()? .

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

Однако, если бы я использовал алгоритм STL или перебрал другой тип контейнера, такой как std :: map, я бы, конечно, использовал итераторы.

0 голосов
/ 08 декабря 2009

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

Это один из тех довольно странных случаев, когда общий код часто проще (и легче писать), чем наиболее специализированный код. В частности, вместо того, чтобы итератор был чем-то вроде std::map<std::string, my_type>::iterator, тип итератора - это параметр шаблона с любым именем, которое вы считаете удобным:

template <class iter>
void my_algorithm(iter a, iter b) { 
    for (iter i=a; i!=b; ++i) 
        do_stuff(*i); 
}

Но я повторюсь: довольно часто вы можете использовать существующий алгоритм. Для приведенного вами случая, похоже, что std::for_each (или Boost FOR_EACH), вероятно, будет работать очень хорошо.

0 голосов
/ 07 декабря 2009

Отмеченный ответ неверен. Этот код вообще не имеет доступа,

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

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

...