STL. Итераторы
В языке C++ обычно нет необходимости реализовывать классы для работы с массивами, списками и тд., то есть классы для хранения данных (их называют контейнеры).
Они уже давно созданы и оптимизированы, программисту достаточно ими пользоваться, что существенно сокращает время на разработку. Свои реализации нужны для каких-либо специфических задач.
Набор объектов таких классов-контейнеров называют коллекцией.
В принципе коллекцией можно называть любой набор объектов к которым можно обращаться (записывать и извлекать значения).
Как и в случае с классами и более простыми данными, логика работы с префиксным и постфиксным инкрементом/декрементом для контейнеров осталась, и префиксный проще постфиксного в реализации, т.к. не нужно создавать буферную переменную.
Примечание: если навести курсор на интересующий код и нажать f12 то зайдем в данный блок кода (реализацию функции и тд.)
Итераторы:
Для доступа к памяти в обычных массивах или списках используется адресация, в STL используются контейнеры, реализация которых обычно скрыта от пользователя, и для работы с памятью и передвижения по ней созданы так называемые итераторы.
По сути, это специфический (перегруженный) указатель только для элементов из коллекции STL, действует примерно как обычный указатель, с ним доступны те же арифметические операции и операция разыменования.
Для некоторых из контейнеров немного иначе реализованы переходы по адресам и как следствие итераторы (подробнее описано внизу в примечаниях).
Прямые итераторы:
Прямые итераторы можно использовать для работы с памятью «последовательно 1,2,3,4»
НЕ константный итератор (мы можем получать и изменять данные.):
classSTL<type>::iterator iter;
Итераторы также могут быть константными (мы можем только получать данные):
classSTL<type>::const_iterator iter; // const_iterator пишется через подчеркивание
iter = classSTL.begin(); //присваиваем указателю/итератору адрес первого элемента
iter = classSTL.end(); //присваиваем указателю/итератору адрес элемента, следующего ЗА последним
iter = classSTL.сbegin(); //присваиваем указателю/итератору константный адрес первого элемента
iter = classSTL.сend(); //присваиваем указателю/итератору константный адрес элемента, следующего ЗА последним
Реверсивные итераторы:
Реверсивные итераторы можно использовать для работы с памятью {1,2,3,4} «в обратном порядке 4,3,2,1»
Для реверсивного итератора инкремент означает переход на предыдущий элемент!
т.е. мы в «начале реверсивной памяти {1,2,3,4}» на элементе 4, iter ++ приведет нас в элемент равный 3.
НЕ константный итератор (мы можем получать и изменять данные.):
classSTL<type>:: reverse_iterator iter;
Итераторы также могут быть константными (мы можем только получать данные):
classSTL<type>:: const_reverse_iterator iter; // const_iterator пишется через подчеркивание
iter = classSTL.rbegin(); //присваиваем указателю/итератору адрес первого элемента
iter = classSTL.rend(); //присваиваем указателю/итератору адрес элемента, следующего ЗА последним
iter = classSTL.сrbegin(); //присваиваем указателю/итератору константный адрес первого элемента
iter = classSTL.сrend(); //присваиваем указателю/итератору константный адрес элемента.
//делаем операции с указателем (++ — * и тд.)
//Можно использовать c-методы (сbegin/cend/crbegin/crend) для получения константных адресов (но может работать и без этого)
Константный итератор – наследник обычного итератора, поэтому приведение типов и работает.
Примечание 1:
ВАЖНО ОТМЕТИТЬ, что не для всех контейнеров в STL итераторы являются итераторами произвольного доступа, т.е. не всегда мы можем написать iterator+N.
Сдвинуть оператор на указанное кол-во элементов:
classSTL<type>:: reverse_iterator iter;
advance(iterator,3); //итератор, шаг 3 (сдвигаем итератор на 3)
Пример:
vector<int>::iterator iter=myVector.begin(); //создаем итератор и указываем на начало
Для разных типов данных будет использоваться примерно одинаковый синтаксис – итератор как указатель и теперь классы/контейнеры можно рассматривать как более простые элементы и меньше задумываться о внутренней структуре например односвязного списка (по идее в нем мы не можем «шагать» как по массиву, но за счет итератора такая логика возможно, а внутри просто перегруженные операторы и сопутствующие методы)
Примечание 2:
Можно упростить запись итератора с использованием ключевого слова auto
vector<int>::iterator iterErase=myVector.begin();
а можем заменить
auto iterErase=myVector.begin();