Итераторы.. зачем их использовать?
В библиотеке STL в некоторых контейнерах есть итераторы, и обычно считается, что они являются превосходным способом итерации через эти контейнеры, а не просто для циклов, например.
for ( int i=0; i < vecVector.size(); i++ )
{
..
}
Может кто-нибудь сказать мне, почему и в каких случаях я должен использовать итераторы и в каких случаях фрагмент кода выше, пожалуйста?
Ответы
Ответ 1
Обратите внимание, что обычно реализация вектора не будет использовать "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 работают на итераторах или на диапазонах итераторов (опять же, из-за общности). Здесь вы не сможете использовать индекс.
Ответ 2
Использование итераторов позволяет вашему коду быть агностиком в отношении реализации вашего контейнера. Если случайный доступ для вашего контейнера дешев, разница в производительности невелика.
Но во многих случаях вы не будете знать, так ли это. Если вы попытаетесь использовать свой метод в связанном списке, например, с подпиской, контейнер должен будет ходить по списку на каждой итерации, чтобы найти ваш элемент.
Итак, если вы точно не знаете, что случайный доступ к вашему контейнеру дешев, используйте итератор.
Ответ 3
Если вы используете итераторы в качестве аргументов вашей функции, вы можете отделить ее от используемого типа "контейнер". Например, вы можете направить результаты функции на консольный вывод, а не на вектор (пример ниже). Этот трюк может быть чрезвычайно мощным для уменьшения связи между вашими классами. Легко связанные классы намного легче тестировать.
#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;
}
Ответ 4
В вашем примере вызов vecVector.size() менее эффективен, чем использование итератора. Итератор, по сути, инкапсулирует вас от необходимости беспокоиться о размере повторного контейнера. Кроме того, итератору необязательно идти в последовательном порядке. Он просто должен отвечать на вызов .next любым способом, который он считает нужным.
Ответ 5
Ну, с одной стороны, вышеуказанное больше не будет работать, если вы превратите этот вектор в список.
Итераторы позволяют создавать шаблоны функций, которым не нужно знать тип контейнера, над которым они работают. Вы даже можете сделать следующее:
#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.
Dave
Ответ 6
Итератор в основном представляет собой более высокий уровень абстракции.
В вашем фрагменте предполагается, что контейнер может быть проиндексирован. Это справедливо для std::vector<>
и некоторых других контейнеров, например необработанных массивов.
Но std::set<>
полностью не индексируется, а оператор индекса std::map<>
будет вставлять в него любой аргумент, а не ожидаемое поведение вашего for
-loop.
Кроме того, проблемы с производительностью - это только проблемы при измерении и доказательстве.