В библиотеке STL у некоторых контейнеров есть итераторы, и обычно считается, что они являются лучшим способом итерации через эти контейнеры, а не просто циклами for, например.
for ( int i=0; i < vecVector.size(); i++ )
{
..
}
Может ли кто-нибудь сказать мне, почему и в каких случаях мне следует использовать итераторы и в каких случаях приведенный выше фрагмент кода, пожалуйста?
Что ж, согласно STL, тип является Контейнером, если с ним связан тип итератора, поэтому по определению их нет.
Использование итераторов позволяет вашему коду не зависеть от реализации вашего контейнера. Если произвольный доступ к вашему контейнеру дешев, особой разницы в производительности нет.
Но во многих случаях вы не узнаете, так ли это. Если вы попытаетесь использовать свой метод в связанном списке, например, с индексированием, контейнер должен будет проходить по списку на каждой итерации, чтобы найти ваш элемент.
Поэтому, если вы точно не знаете, что произвольный доступ к вашему контейнеру дешев, используйте итератор.
Я не думаю, что у std :: list есть оператор подписки.
Верно, коллекция может даже не поддерживать произвольный доступ, поэтому подпрограмма может даже не работать. Итераторы будут работать независимо.
В вашем примере вызов vecVector.size () менее эффективен, чем использование итератора. Итератор, по сути, избавляет вас от необходимости беспокоиться о размере контейнера, по которому выполняется итерация. Более того, итератор не обязательно должен идти в последовательном порядке. Он просто должен отвечать на вызов .next так, как он считает нужным.
Не думаю, что это менее эффективно. Компилятор оптимизирует его, поместив вызов size () вне цикла. Кроме того, размер - это свойство вектора, он всегда известен и никогда не нуждается в вычислении, как и end ().
Что, если элемент добавлен к вектору внутри цикла?
Тогда, конечно, он не будет оптимизировать его вне цикла, но он также не будет оптимизировать использование end () вне цикла. Так что разницы все равно нет.
Ну, во-первых, вышеуказанное больше не будет работать, если вы превратите этот вектор в список.
Итераторы позволяют создавать шаблоны функций, которым не нужно знать тип контейнера, с которым они работают. Вы даже можете сделать следующее:
#include <algorithm>
void printvalue(double s)
{
// Do something with s
}
int _tmain(int argc, _TCHAR* argv[])
{
double s[20] = {0};
std::for_each(s, s+20, printvalue);
return 0;
}
Это потому, что стандартный указатель также является допустимым итератором для for_each.
Дэйв
Обратите внимание, что обычно реализация вектора не использует "int" в качестве типа индекса / размера. Таким образом, ваш код, как минимум, вызовет предупреждения компилятора.
Итераторы увеличивают универсальность вашего кода.
Например:
typedef std::vector<int> Container ;
void doSomething(Container & p_aC)
{
for(Container::iterator it = p_aC.begin(), itEnd = p_aC.end(); it != itEnd; ++it)
{
int & i = *it ; // i is now a reference to the value iterated
// do something with "i"
}
}
Теперь представим, что вы изменили вектор на список (потому что в вашем случае список стал лучше). Вам нужно только изменить объявление typedef и перекомпилировать код.
Если бы вместо этого вы использовали индексный код, его пришлось бы переписать.
Итератор следует рассматривать как своего рода супер-указатель. Он «указывает» на значение (или, в случае сопоставления, на пару ключ / значение).
Но у него есть методы для перехода к следующему элементу в контейнере. Или предыдущий. Некоторые контейнеры предлагают даже произвольный доступ (вектор и двухсторонняя очередь).
Большинство алгоритмов STL работают с итераторами или с диапазонами итераторов (опять же, из-за универсальности). Здесь вы не сможете использовать индекс.
Примечание: этот код особенно эффективен с библиотекой диапазона. Алгоритм, работающий с парами итераторов, может использоваться с подмножествами из контейнеров в дополнение к потокам и другим генераторам значений. См. Библиотеки boost.org, Range и Iterators.
Если вы используете итераторы в качестве аргументов вашей функции, вы можете отделить их от типа используемого «контейнера». Например, вы можете направить результаты функции в консольный вывод, а не в вектор (пример ниже). Этот трюк может быть чрезвычайно эффективным для уменьшения связи между вашими классами. Слабосвязанные классы намного легче тестировать.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
template <typename InputIterator, typename OutputIterator>
void AddOne(InputIterator begin, InputIterator end, OutputIterator dest)
{
while (begin != end)
{
*dest = *begin + 1;
++dest;
++begin;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
vector<int> data;
data.push_back(1);
data.push_back(2);
data.push_back(3);
// Compute intermediate results vector and dump to console
vector<int> results;
AddOne(data.begin(), data.end(), back_inserter(results));
copy(results.begin(), results.end(), ostream_iterator<int>(cout, " "));
cout << endl;
// Compute results and send directly to console, no intermediate vector required
AddOne(data.begin(), data.end(), ostream_iterator<int>(cout, " "));
cout << endl;
return 0;
}
Итератор - это в основном более высокий уровень абстракции.
Ваш фрагмент предполагает, что контейнер можно проиндексировать. Это верно для std::vector<>
и некоторых других контейнеров, например необработанных массивов.
Но в std::set<>
полностью отсутствует индексация, и оператор индекса std::map<>
вставит в карту любой переданный ему аргумент, а не ожидаемое поведение в вашем цикле for
.
Кроме того, проблемы с производительностью - это проблемы, только если их измерить и доказать.
Какие контейнеры STL <i> не </i> имеют итераторов?