Первое, с чем я знакомился, начав изучать STL, был вектор (vector). При изучении и описании векторов столкнулся с некоторыми новыми понятиями. Первое такое понятие — итератор (iterator). Применить и понять смысл применения итератора в векторе мне было не трудно, но, наверное, найдутся такие люди, которым что-то очевидное мне может быть совсем не очевидным.
Первый простой пример — это перебор элементов массива с помощью итераторов:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//C++ Листинг #1 Перебор массива с помощью итераторов
#include <iostream>
#include <vector> //подключаем директиву вектор
usingnamespacestd;
intmain(void)
{
intA[3]={100,200,300};//массив для инициализации вектора
vector<int>v(A,A+3);//объявляю вектор и записываю в него три элемента из массива
vector<int>::iterator it;//объявляю итератор it
for(it=v.begin();it!=v.end();it++){
cout<<*it<<" ";//использую итератор для доступа к элементам вектора
}
cout<<endl;
for(inti=0;i<3;i++){
cout<<*(A+i)<<" ";//это не относится к итератору, но относится к теме
}
cout<<endl;
}
Кода в #1 очень мало, и он не должен вызывать вопросов. По стандарту С++ любой контейнер должен иметь методы begin и end. Что такое begin и end, должно быть понятно по названию: границы контейнера. Эти методы что-то вроде указателей на первый и последний элементы контейнера.
vector::begin() — метод, возвращающий итератор на первый элемент вектора.
vector::end() — метод, возвращающий итератор на последний элемент вектора.
В показанном примере объект вектора назывался v, к нему и применялись.
Последний элемент вектора — это не тот элемент, который мы последним записали в вектор, а тот элемент, что идёт за последним записанным. Нагляднее всего это демонстрирует следующий пример:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//C++ Листинг #2 Последний элемент вектора
#include <iostream>
#include <vector>
usingnamespacestd;
intmain()
{
vectorv;//Вектор v
v.push_back(5);//Запихиваем 1 элемент
v.push_back(6);//Запиcываем второй элемент
vector::iterator it=v.end();//инициализируем итератор тем, что возвращает end()
cout<<*it<<"\n";//Последний элемент не 6, но это-таки последний элемент строения вектора
cout<<*(it-1)<<"\n";//последний элемент для нас, это предыдущий элемент элементу, возвращаемому из end()
}
К листингу #1 может возникнуть вопрос, что находится во втором цикле. В теле второго цикла используется арифметика указателей. Благодаря арифметике указателей перебор элементов массива можно осуществлять прибавлением к указателю или вычитанием из указателя целого значения. Показан второй цикл только для того, чтобы продемонстрировать очень большую схожесть итератора с указателем. Такая большая схожесть может сильно запутать. Только вот хоть итератор и очень похож на указатель, указателем он не является.
Чтобы начать видеть некоторую разницу между итератором и указателем, в первую очередь нужно пройтись по определениям и предназначениям первого и второго:
Указатель — это переменная, обозначающая адрес памяти. Предназначен указатель для того, чтоб мы могли напрямую обращаться по некоторому адресу памяти к объекту, расположенному на данном адресе.
Итератор — это объект, позволяющий программисту перебирать все элементы коллекции без учёта особенностей её реализации. Предназначен итератор исключительно для последовательного доступа к элементам.
Что такое последовательный доступ, легче понять тем, кто затрагивал динамическое программирование и стеки. При последовательном доступе обход происходит элемент за элементом, а при прямом с помощью прямого указания номера элемента.
В случае с вектором итератор иногда является указателем, но не всегда, с остальными стандартными контейнерами нет. Пример работы с итератором как с указателем:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//C++ Листинг #3
#include <iostream>
#include <vector> //нужна директива vector
usingnamespacestd;//нужно пространство имен std
intmain()
{
intA[3]={11,12,14};
vector<int>v(A,A+3);//копируем массив А в вектор v
vector<int>::iterator it=v.begin();//создаем итератор it
Итераторы подчиняются принципу чистой абстракции, то есть любой объект, который ведет себя как итератор, является итератором, поэтому про указатель иногда (не всегда) можно сказать, что он (указатель) является итератором. Чтобы указатель был итератором, указатель должен вести себя как итератор.
В С++ типы итераторов разделяют на:
Однонаправленные итераторы
Обратные итераторы (реверсные)
Двунаправленные итераторы
Итераторы произвольного доступа
Итераторы ввода и итераторы вывода
Пользователи могут создавать собственные типы итераторов, определяя подклассы на основе стандартного шаблона классов std::iterator
Обычно итераторы используется парами. Первый обозначает точку начала перебора, второй обозначает, что дальше перебирать нечего и нужно остановиться. Это должно было быть понятно по приведенным ранее примерам.
У кого-то могут зарождаться сомнения, которые я только что так старался убить. Итераторы произвольного доступа сеют семя этих сомнений (вот у меня сейчас сеют). Но итераторы произвольного доступа — это такие специальные двунаправленные итераторы. Для меня сочетание двунаправленный указатель звучит немного странно, вы о таком слышали? — нет! И вряд ли услышите.
Синтаксис для стандартных итераторов сделан похожим на cинтаксис для обычных указатели языка C, где операции * и -> используются для указания элемента, на который указывает итератор, а такие арифметические операции указателя, как ++, используются для перехода итератора к следующему элементу.
Основное предназначение итераторов заключается в предоставлении возможности пользователю обращаться к любому элементу контейнера при сокрытии внутренней структуры контейнера от пользователя. Это позволяет контейнеру хранить элементы любым способом при допустимости работы пользователя с собой как с простой последовательностью или списком. Проектирование класса итератора обычно тесно связано с соответствующим классом контейнера. Обычно контейнер предоставляет методы создания итераторов.
Как указатель не всегда является итератором, так и итератор не всегда является указателем
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//C++ Листинг #4
#include <iostream>
#include <vector>
usingnamespacestd;
intmain(void)
{
int*p;//объявили указатель типа int
vector<int>::iterator it;//объвили итератор типа int
it=p;//если итератор является указателем, то нестыковочка
cout<<it;//снова проблема, вместо увиденного адреса на экране видим ошибку
}
Надеюсь листинг #4 достаточно убедителен, чтобы не считать итератор указателем.
Итератор является «умным» указателем на элемент контейнера. В отличие от обычного указателя он помнит полный тип данных, на который ссылается, причём с учётом типа контейнера, к элементу которого производится обращение.
6 комментариев на «“STL Итераторы Первое знакомство и долой сомнения Итератор — это не указатель”»
Returns an iterator referring to the past the end element in the vector container.
Возвращает итератор, ссылаясь на последний конечный элемент в векторе контейнер
Чтобы было понятнее, последний не для нас как для людей, а последний в строении вектора. Этот последний в строении вектора элемент для нас как для людей последующий за конечным и есть.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <vector>
usingnamespacestd;
intmain()
{
vector<int>v;//Вектор v
v.push_back(5);//Запихиваем 1 элемент
v.push_back(6);//Запихиваем второй элемент
vector<int>::iterator it=v.end();//инициализируем итератор тем, что возвращает end()
cout<<*it<<"\n";//Последний элемент не 6, но это-таки последний элемент строения вектора
cout<<*(it-1)<<"\n";//последний элемент для нас, это предыдущий элемент элементу, возвращаемому из end()
разница между итератором и указателем — на самом деле в том, что первый может указывать на абсолютно любой участок памяти, а второй — строго на начало конкретного элемента, по сути, превращаясь в индекс. И второе, отдельный тип iterator — в первую очередь нужен самому STL, а именно, компилятору, чтоб правильно выбрать прототип шаблонной функции и, соответственно, саму функцию.
и последнее, насчёт «последнего элемента» — на примере вектора:
struct VectorAllocator
{
int allocator.dummy; //это поле занимает dword в памяти но, реально используется только первый байт
int _First; //первый элемент
int _Last; //последний элемент
int _End; //аналог поля «_Cap» в basicstring
} // не силён в «этих ваших» — поэтому правильность синтаксиса при описании структуру может хромать. Но это 100% рабочий вариант
т.е. другими словами, поле _End — это максимальный размер вектора при превышении которого произойдёт его реаллокация. Т.е. создастся новый вектор большего размера, старый вектор скопируется в новый, а старый — удалится. Отчасти из-за этого безопаснее использовать итераторы, а не такой «прямой» доступ «в лоб», как указатели. Т.е. это своего рода аналог хендлов (в части, касающейся доступа к элементам)
админ — не адекват. ладно честно признаётся, что весьма не далёкий в с++ — так ещё и дельный коммент затёр. ну… надеюсь, что хоть не остальные так он его прочёл и принял к сведению)))
«vector::end() – Возвращает итератор на последний элемент вектора»
Метод end() указывает не НА последний елемент последовательности, а ЗА него.
Returns an iterator referring to the past the end element in the vector container.
Возвращает итератор, ссылаясь на последний конечный элемент в векторе контейнер
Чтобы было понятнее, последний не для нас как для людей, а последний в строении вектора. Этот последний в строении вектора элемент для нас как для людей последующий за конечным и есть.
Я подправлю это в статье
пример2 -зыбыли
строка 8 :
строка12:
разница между итератором и указателем — на самом деле в том, что первый может указывать на абсолютно любой участок памяти, а второй — строго на начало конкретного элемента, по сути, превращаясь в индекс. И второе, отдельный тип iterator — в первую очередь нужен самому STL, а именно, компилятору, чтоб правильно выбрать прототип шаблонной функции и, соответственно, саму функцию.
и последнее, насчёт «последнего элемента» — на примере вектора:
struct VectorAllocator
{
int allocator.dummy; //это поле занимает dword в памяти но, реально используется только первый байт
int _First; //первый элемент
int _Last; //последний элемент
int _End; //аналог поля «_Cap» в basicstring
} // не силён в «этих ваших» — поэтому правильность синтаксиса при описании структуру может хромать. Но это 100% рабочий вариант
т.е. другими словами, поле _End — это максимальный размер вектора при превышении которого произойдёт его реаллокация. Т.е. создастся новый вектор большего размера, старый вектор скопируется в новый, а старый — удалится. Отчасти из-за этого безопаснее использовать итераторы, а не такой «прямой» доступ «в лоб», как указатели. Т.е. это своего рода аналог хендлов (в части, касающейся доступа к элементам)
админ — не адекват. ладно честно признаётся, что весьма не далёкий в с++ — так ещё и дельный коммент затёр. ну… надеюсь, что хоть не остальные так он его прочёл и принял к сведению)))
удачи тебе, с твоим «бложиком».