Итератори
итератор
Итераторите са обобщени указатели, позволяващи на програмистите
(алгоритмите) да работят със различни по вид контейнери по унифициран
(общ) начин.
Видове итератори
Видове итератори
- input iterator
- output iterator
- forward iterator
- bidirectional iterator
- random access iterator
В ред на нарастваща функционалност.
input iterator
- най-ограничени възможности (заедно с output iterator)
- istream_iterator,istreambuf_iterator- позволяват да разглеждаме
файловете като контейнери
- поддържат само one-pass алгоритми
input iterator
input_iterator x, y;
value = *x; // дереференциране
*x = value; // ERROR, read-only values
++x;
x++;
if (x == y) { } // сравняване
if (x != y) { } // сравняване
x = y; // копиране
istream_iterator, istreambuf_iterator
- итераторите пазят последната прочетена стойност за да ползволяват
многократно дереференциране
- x++предизвиква прочитане на следващата стойност от файла
- ако имате повече от единistream_iterator(istreambuf_iterator) и го
инкрементирате ще получите изненадващи резултати (представете си, че четете
едновременно един файл от две или повече функции)
output iterator
- най-ограничени възможности (заедно с input iterator)
- ostream_iterator,ostreambuf_iterator
- отново поддържат само one-pass алгоритми
output iterator
input_iterator x, y;
*x = value; // дереференциране и записване на стойност
value = *x; // ERROR, write-only values
++x;
x++;
if (x == y) { } // сравняване
if (x != y) { } // сравняване
x = y; // копиране
ostream_iterator, ostreambuf_iterator
- *x\=v;записва стойността във файла ипреместваитератора
- ако имате повече от единostream_iterator(ostreambuf_iterator)
асоцииран с един и същи файл и ги записвате ще получите изненадващи
резултати (представете си, че пишете едновременно един файл от две или
повече функции)
stream vs streambuf iterators
- istream_iteratorиostream_iteratorизползват форматираните
входно-изходни операции, т.е.operator<<
- istreambuf_iteratorиostreambuf_iteratorизползват неформатирани
входно-изходни операции и четат/пишат директно буфера на потока
- Ако не се нуждаете от форматирани входно-изходни операции използвайтеistreambuf_iteratorиostreambuf_iterator- те са по - ефективни
forward iterator
- позволяват няколкократно итериране (с няколко итератора)
- само в една посока
- нито един стандартен контейнер не предоставя forward iterators
- slist::iteratorе forward iterator
- категорията съществува за да представи изискванията на голяма част от
алгоритмите към итераторите, върху които се прилагат
bidirectional iterator
- позволяват няколкократно итериране (с няколко итератора)
- и в двете посоки, но само последователно
- list,set,map,multiset,multimapпредоставят
bidirectional iterators
random access iterator
- позволяват произволен достъп до обектите в контейнера
- този достъп не се проверява за грешка
- vectorиdequeпредоставят random access iterators
random access iterator
random_iterator x, y;
y = x + 5;
value v = x[42];
iterator tags
За всеки тип итератори има съответстващ C++ тип, който се нарича таг.
(Всеки итератор, напримерvector::iterator, е таг-нат със своя семантичен
тип итератор, в случая сrandom_access_iterator)
- input_iterator_tag
- output_iterator_tag
- forward_iterator_tag
- bidirectional_iterator_tag
- random_access_iterator_tag
iterator traits
Позволяват достъп до характеристиките на даден итератор
- типа на стойността, към която итераторът сочи
- типа на указател към стойността, към която итераторът сочи
- типа на псевдоним към стойността, към която итераторът сочи
- типа на разликата на два итератори (x-y)
iterator traits
template <typename Iterator>
struct iterator_traits {
typedef typename Iterator::difference_type difference_type;
typedef typename Iterator::value_type value_type;
typedef typename Iterator::pointer pointer;
typedef typename Iterator::reference reference;
typedef typename Iterator::iterator_category iterator_category;
} ;
?
Защо имаiterator_traitsкогато явно итераторите сами иматtypedefсъс
всичко необходимо?
Отговор
T*също е итератор, но няма никакъвtypedefв себе си
iterator_traits
template <typename T>
struct iterator_traits<T*> {
typedef ptrdiff_t difference_type;
typedef T value_type;
typedef T* pointer;
typedef T& reference;
typedef random_access_iterator_tag iterator_category;
} ;
Защо ни еiterator_traits?
// note: stupid example
template <typename Iterator>
void swap_values(Iterator x, Iterator y)
{
typename iterator_traits<Iterator>::value_type tmp = *x;
*x = *y;
*y = tmp;
}
advance()
template <typename Iterator, typename Distance>
void advance(Iterator& i, Distance n)
{
// ???
while (n--) ++i;
}
// доста бавно за random_access iterator
advance()
template <typename Iterator, typename Distance>
void advance(Iterator& i, Distance n)
{
advance_impl(i, n, iterator_traits<Iterator>::iterator_category());
}
advance_impl()
template <typename Iterator, typename Distance>
void advance_impl(Iterator& i, Distance n, random_iterator_tag tag)
{
i += n;
}
template <typename Iterator, typename Distance, typename Tag>
void advance_impl(Iterator& i, Distance n, Tag tag)
{
while (n--) ++i;
}
iterator adaptors
- front_inserter
- back_inserter
- insert_iterator
Дефинирание са в
front_inserter
- писането в този итератор извикваpush_frontна контейнера
- Истинския тип еstd::front_insert_iterator, но много рядко се налага да
се ползва явно.
- std::front_inserterе функция, която връща такъв итератор
Пример за front_inserter
vector<int> v;
list<int> l;
// ...
copy(v.begin(), v.end(), front_inserter(l));
// това ще обърне реда на елементите на v в r
back_inserter
- писането в този итератор извикваpush_backна контейнера
- типът на итератора еstd::back_insert_iterator
- std::back_inserterе функция, която връща такъв итератор
insert_iterator
- писането в този итератор правиinsertвъв контейнера. Първияinsertсе прави в позицията определена при създаването на итератора, а всеки
следващ - в позицията след предходно вмъкнатия елемент
- std::inserter(container,it)създава такъв итератор
Пример за insert_iterator
list<int> l, r;
list<int>::iterator i;
for (int i = 0; i < 5; ++i)
l.push_back(i);
r = l;
i = r.begin();
advance(i, 3);
copy(l.begin(), l.end(), inserter(r, i))
// 0 1 2 0 1 2 3 4 3 4
const_iterator
всички контейнери предоставятconst_iterator
повечето методи на контейнерите изискватiterator
за да превърнетеconst_iteratorвiteratorизползвайте:
Vector v;
Vector::const_iterator c;
// ...
Vector::iterator i = advance(v.begin(), distance(v.begin, c));
// работи за всички видове, контейнери
// ще бъде О(N) за forward iterators
reverse_iterator
- позволяват обхождането на елементите в контейнерите да става в обратен ред
- за да се запазят изискванията за валидност на указателитеreverse_iteratorсочи към елемента след (при нормално обхождане) текущия
- reverse_iteratorима метод base(), който връща нормален итератор към
сочещ към елемента след (при нормално обхождане) след текущия
vector<int> v;
// ... // v is [0, 1, 2, 3]
vector<int>::reverse_iterator i = v.rbegin();
// i.base() == v.end()
++i;
// *i == 2;
// *(i.base()) == 3;
Вмъкване и изтриване през reverse_iterator
- c.insert(i,v)- вмъкваvпредиi
- c.insert(ri.base(),v)- вмъкваvпредиreverse_iterator-ariпогледнато отзад напред
- c.erase((++ri).base())ще изтрие елемента сочен отri
Custom iterator
Writing custom iterator is a tedious task
Bare minimum
- all
typedef
operator *
, operator ->
operator ==
, operator !=
operator ++
, operator ++(int)
Random-access iterator
All pointer
-like semantics:
operator[]
operator <
, operator <=
,
operator >
, operator >=
,
explicit operator bool
(or SafeBool idiom)
operator !
- helper functions for creating instances
Iterators must go
Andrei Alexandrescu
BoostCon 2009
Slides
- Iterators are error-prone
- You always need begin and end of the same container
- Given two iterators there is no way to detect whether they
belong to the same container
- Writing a custom iterator is too much code
- Iterators are almost non-composable
range
- basically it is the pair [begin, end)
- doesn't have to be pointer-like
- easily composable