Итераторы в C++: введение
Всем привет! Изучая контейнеры STL, мы использовали новый вид переменных - итераторы. Так давайте узнаем, зачем ими пользуются?
Что такое итератор
Итератор - это такая структура данных, которая используется для обращения к определенному элементу в контейнерах STL. Обычно из используют с контейнерами set
, list
, а у вектора для этого применяют индексы.
Кстати по мере того, как мы будем изучать итераторы, вам все больше будет казаться, что итераторы и есть указатели (это мы разберем ниже).
Как создать итератор
Для создания итератора мы должны с самого начала программы подключить библиотеку <iterator>
.
#include <iterator>
Далее для его создании нам потребуется использовать вот эту схему:
<контейнер> <его тип> :: iterator <имя итератора>;
<контейнер>
- указываем требуемый контейнер, на который и будет ссылаться итератор. Напримерmap
,vector
,list
.<его тип>
- указываем тип контейнера.
Вам нужно помнить! Если вы создали итератор и случайно ввели не тот тип данных, который указали при создании контейнера, то ваша программа будет работать неправильно и вообще может сломается.
Методы начала и конца контейнеров
У каждого контейнера имеются два метода, которые, как указатели передают итератору начало или конец контейнера - begin()
и end().
- Метод
begin()
отправит итератор на начала контейнера. - А метод
end()
отправит на конец. А если точнее, то на одну ячейку больше последней. Если мы попытаемся вывести эту ячейку у нас появятся проблемы с компилятором :) .
Их мы можем использовать даже без подключения библиотеки
<iterator>
, что очень удобно.
Также при инициализации итератора мы можем с самого начала написать, куда он будет указывать:
vector <int> i_am_vector;
vector <int> :: iterator it = i_am_vector.begin();
Итератор на vector
Для итератора на vector
вы можете:
- Выполнять операцию разыменования (обращаться к значению элемента на которое указывает итератор), как мы это делали с указателем.
int x = *it;
- Использовать инкремент (
it++, ++it
) и декремент (it--, --it
). - Применять арифметические операции. Так например мы можем сместить итератор на пять ячеек в право, вот так:
it += 5;
- Сравнивать на равенства.
if (it == it2) { ...
- Передать переменной разницу итераторов.
int x = it - it2;
Но о использовании арифметических и сравнительных операциях (>
, <
, ==
) с двумя итераторами, вам нужно кое что знать.
Использовать выше сказанные операции можно только с идентичными итераторами, которые указывают на одинаковый контейнер и тип.
Есть исключение из правил - если вы создадите два одинаковых итератора на
map
то при сравнивании они не будут одинаковы.
Например, если мы создали два итератора на один и тот же контейнер, но указали для них разный тип данных и решили использовать выше сказанные операции - то компилятор начнет ругаться.
vector <int> vector_first;
vector <double> vector_second;
vector <int> :: iterator it = vector_first.begin();
vector <double> :: iterator it2 = vector_second.begin();
if (it == it2) { // ошибка!
cout << "it == it2";
}
Итератор на list, set, map
Для итераторов на list
,set
,map
немного урезан функционал. Так вы не можете:
- Использовать арифметические операции.
it += 5; //
it *= 2; //
it /= 3; // ошибка
it -= 5; //
- Применять операции сравнения (
>
и<
):
if (it > it_second) { ... //
// ошибка
if (it < it_second) { ... //
Все остальное можно использовать:
- Применять инкремент и декремент.
it--; // все
it++; // нормально!
- Использовать операцию разыменования.
cout << *it;
*it += 5;
- Сравнивать два итератора на равенство и неравенства:
if (it == it_second) { ... //
// правильно
if (it != it_second) { ... //
Кстати использовать арифметические операции, чтобы увеличить итератор на один, как это делает инкремент - нельзя.
Но вы можете сказать: “Так что мы можем двигать итератор только на один элемент? Это же неудобно!“. Да было бы совсем не гибко со стороны C++ делать вот такое, но они позаботились и создали функцию - advanсe()
, она заменяет операции увеличения и уменьшения над итераторами.
Вот как она работает:
advance(<итератор>, <значение>);
<итератор>
- сюда мы должны указать итератор, который и нужно изменить.<значение>
- тут мы должны вписать число на которое должны увеличить или уменьшить итератор.
Если мы увеличиваем итератор, то используем оператор +
к числу. Но можно и просто записать число без оператора +
.
Если же нужно уменьшить итератор, то мы добавляем оператор -
.
advanсe(it, 5); // сместили на 5 ячеек
Как работают итераторы
Чтобы понять, как работают итераторы, давайте разберем их использование на практике. В примере ниже с помощью итератора мы выводим содержимое вектора на экран:
#include <iostream>
#include <iterator>
#include <vector>
using namespace std;
int main() {
setlocale(0, "");
vector name_vector;
name_vector.push_back(3);
name_vector.push_back(4);
name_vector.push_back(6);
vector <int> :: iterator it;
for (it = name_vector.end() - 1; it >= name_vector.begin(); it--) {
cout << *it << " ";
}
system("pause");
return 0;
}
- В строке 10: создали вектор
name_vector
. - Дальше в последующих трех строках занимаемся его заполнением.
- В строке 16: создали итератор под именем
it
. - В цикле for мы написали, что итератор указывает на последнюю ячейку вектора. С помощью вот такой не замысловатой конструкции :
it = name_vector.end() - 1;
Выше мы говорили, что метод
end()
указывает на одну ячейку больше последней. Поэтому, чтобы обратится к последнему элементу в векторе нам понадобилось отнять 1.
- Используя операцию разыменования, в теле цикла, мы вывели все элементы.
cout << *it;
Вы наверняка заметили, что мы выводим элеме нты задом наперед:
6 4 3
Process returned 0 (0x0) execution time : 0.010 s
Press any key to continue.
Важно знать! Если мы захотели выполнить операцию разыменования для ячейки, у которой индекс больше итератора на 5, то это сделать нужно именно так:
cout << *(it + 5);
А не вот так;
cout << *it + 5;
Если мы сделаем так, то мы выведем сумму ячейки на которую указывает итератор и пяти. А хотели же прибавить к итератору пять и применить операцию разыменования.
Все потому что, операция разыменования *
происходит быстрее, чем операция присваивание. У этих операций совсем разный приоритет использования. У этой *
больше, а у этой +
меньше.
Итератор это не указатель
Сейчас после всего узнанного многие могут подумать, что итераторы это и есть указатели. Так как очень много общего между ними.
- Итератор, как и указатель указы вает на какую-то ячейку, а в указателе может храниться адрес ячейки динамического массива.
- Чтобы обратится к значению ячейки мы применяем операцию разыменования.
Да итератор это усовершенствованная версия указателя, которая только работает с контейнерами. В некоторых языках указатель называют итератором, но это не значит, что он и есть указатель в C++.
Чтобы удостовериться в том, что итератор это не указатель давайте проверим это в программе ниже.
vector i_am_vector;
vector <int> :: iterator it;
int c = 10;
int *ykaz = &c;
it = ykaz; // ошибка!
cout << *it;
Как видим, если итератор был бы указателем он стал бы равен адресу указателя. И смог бы еще вывести этот адрес на экран. А вместо этого мы только словили две ошибки от компилятора.
Мы надеемся, что вы смогли узнать информацию, которая вас интересовала. Если у вас есть вопрос, который мы не обсудили в статье, то можете его написать в комментарии. Удачи!
Если хотите всегда быть в курсе последних новостей в мире программирования и IT, подписываетесь на мой Telegram-канал, где я делюсь свежими статьями, новостями и полезными советами. Буду рад видеть вас среди подписчиков!
Обсуждение