Итераторы в C++: введение
Всем привет! Изучая контейнеры STL мы использовали новый вид переменных — итераторы. Так давайте узнаем, зачем ими пользуются?
Что такое итератор
Итератор — это такая структура данных, которая используется для обращения к определенному элементу в контейнерах STL. Обычно из используют с контейнерами set , list , а у вектора для этого применяют индексы.
Кстати по мере того, как мы будем изучать итераторы, вам все больше будет казаться, что итераторы и есть указатели (это мы разберем ниже).
Как создать итератор
Для создания итератора мы должны с самого начала программы подключить библиотеку <iterator> .
Далее для его создании нам потребуется использовать вот эту схему:
- <контейнер> — указываем требуемый контейнер, на который и будет ссылаться итератор. Например map , vector , list .
- <его тип> — указываем тип контейнера.
Вам нужно помнить! Если вы создали итератор и случайно ввели не тот тип данных, который указали при создании контейнера, то ваша программа будет работать неправильно и вообще может сломается.
Методы начала и конца контейнеров
У каждого контейнера имеются два метода, которые, как указатели передают итератору начало или конец контейнера — begin() и end().
- Метод begin() отправит итератор на начала контейнера.
- А метод end() отправит на конец. А если точнее, то на одну ячейку больше последней. Если мы попытаемся вывести эту ячейку у нас появятся проблемы с компилятором .
Их мы можем использовать даже без подключения библиотеки <iterator> , что очень удобно.
Также при инициализации итератора мы можем с самого начала написать, куда он будет указывать:
Итератор на vector
Для итератора на vector вы можете:
- Выполнять операцию разыменования (обращаться к значению элемента на которое указывает итератор), как мы это делали с указателем.
- Использовать инкремент ( it++, ++it ) и декремент ( it—, —it ).
- Применять арифметические операции. Так например мы можем сместить итератор на пять ячеек в право, вот так:
- Сравнивать на равенства.
- Передать переменной разницу итераторов.
Но о использовании арифметических и сравнительных операциях ( > , < , == ) с двумя итераторами, вам нужно кое что знать.
Использовать выше сказанные операции можно только с идентичными итераторами, которые указывают на одинаковый контейнер и тип.
Есть исключение из правил — если вы создадите два одинаковых итератора на map то при сравнивании они не будут одинаковы.
Например, если мы создали два итератора на один и тот же контейнер, но указали для них разный тип данных и решили использовать выше сказанные операции — то компилятор начнет ругаться.
Итератор на list, set, map
Для итераторов на list , set , map немного урезан функционал. Так вы не можете:
- Использовать арифметические операции.
- Применять операции сравнения ( > и < ):
Все остальное можно использовать:
- Применять инкремент и декремент.
- Использовать операцию разыменования.
- Сравнивать два итератора на равенство и неравенства:
Кстати использовать арифметические операции, чтобы увеличить итератор на один, как это делает инкремент — нельзя.
Но вы можете сказать: «Так что мы можем двигать итератор только на один элемент? Это же неудобно!» . Да было бы совсем не гибко со стороны C++ делать вот такое, но они позаботились и создали функцию — advanсe() , она заменяет операции увеличения и уменьшения над итераторами.
Вот как она работает:
- <итератор> — сюда мы должны указать итератор, который и нужно изменить.
- <значение> — тут мы должны вписать число на которое должны увеличить или уменьшить итератор.
Если мы увеличиваем итератор, то используем оператор + к числу. Но можно и просто записать число без оператора + .
Если же нужно уменьшить итератор, то мы добавляем оператор — .
Как работают итераторы
Чтобы понять, как работают итераторы, давайте разберем их использование на практике. В примере ниже с помощью итератора мы выводим содержимое вектора на экран:
- В строке 10: создали вектор name_vector .
- Дальше в последующих трех строках занимаемся его заполнением.
- В строке 16: создали итератор под именем it .
- В цикле for мы написали, что итератор указывает на последнюю ячейку вектора. С помощью вот такой не замысловатой конструкции :
Выше мы говорили, что метод end() указывает на одну ячейку больше последней. Поэтому, чтобы обратится к последнему элементу в векторе нам понадобилось отнять 1.
- Используя операцию разыменования, в теле цикла, мы вывели все элементы.
Вы наверняка заметили, что мы выводим элементы задом наперед:
Важно знать! Если мы захотели выполнить операцию разыменования для ячейки, у которой индекс больше итератора на 5, то это сделать нужно именно так:
Если мы сделаем так, то мы выведем сумму ячейки на которую указывает итератор и пяти. А хотели же прибавить к итератору пять и применить операцию разыменования.
Все потому что, операция разыменования * происходит быстрее, чем операция присваивание. У этих операций совсем разный приоритет использования. У этой * больше, а у этой + меньше.
Итератор это не указатель
Сейчас после всего узнанного многие могут подумать, что итераторы это и есть указатели. Так как очень много общего между ними.
- Итератор, как и указатель указывает на какую-то ячейку, а в указателе может храниться адрес ячейки динамического массива.
- Чтобы обратится к значению ячейки мы применяем операцию разыменования.
Да итератор это усовершенствованная версия указателя, которая только работает с контейнерами. В некоторых языках указатель называют итератором, но это не значит, что он и есть указатель в C++.
Чтобы удостовериться в том, что итератор это не указатель давайте проверим это в программе ниже.