Итераторы

   
На этом шаге рассмотрим итераторы.

   
Наверняка вам потребуется перемещаться по элементам контейнера. Для этих целей предназначен итератор. Итераторы позволяют абстрагироваться от структуры данных контейнеров, т. е., если в какой-либо момент вы решите, что применение другого типа контейнера было бы гораздо эффективнее, то все, что вам нужно будет сделать, — это просто заменить тип контейнера нужным. На остальном коде, использующем итераторы, это никак не отразится.

   
Qt предоставляет два стиля итераторов:

  • итераторы в стиле Java;
  • итераторы в стиле STL.

   
В качестве альтернативы существует вариант обхода элементов при помощи ключевого слова foreach.

   
Итераторы в стиле Java

   
Итераторы в стиле Java очень просты в использовании. Они были разработаны специально для программистов, не имеющих опыта с контейнерами STL. Основным их отличием от последних является то обстоятельство, что они указывают не на сам элемент, а на его двух соседей. Таким образом, вначале итератор будет указывать на положение перед первым элементом контейнера и с каждым вызовом метода next() (см. табл. 1) будет перемещать указатель на одну позицию вперед. Но на самом деле итераторы в стиле Java представляют собой объекты, а не указатели. Их применение в большинстве случаев делает код более компактным, чем при использовании итераторов в стиле STL:

QList<QString> list;
list << "Профессиональное"<<"программирование"<<"на С++";
QListIterator<QString> it(list);
while(it.hasNext()) {
    qDebug() <<"Элемент:" << it.next();
}

   
В табл. 1 указаны методы класса QListIterator. Эти методы также применимы и для QLinkedListIterator, QVectorIterator, QHashIterator, QMapIterator. Эти итераторы являются константными, а это значит, что изменение значений элементов, их вставка и удаление не возможны.

Таблица 1. Методы QListIterator

МетодОписание
toFront()Перемещает итератор на начало списка
toBack()Перемещает итератор на конец списка
hasNext()Возвращает значение true, если итератор не находится в конце списка
next()Возвращает значение следующего элемента списка и перемещает итератор на следующую позицию
peekNext()Просто возвращает следующее значение без изменения позиции итератора
hasPrevious()Возвращает значение true, если итератор не находится в начале списка
previous()Возвращает значение предыдущего элемента списка и перемещает итератор на предыдущую позицию
peekPrevious()Просто возвращает предыдущее значение без изменения позиции итератора
findNext(const T&)Поиск заданного элемента в прямом направлении
findPrevious(const& T)Поиск заданного элемента в обратном направлении

   
Если необходимо производить изменения в процессе прохождения итератором элементов, то для этого следует воспользоваться изменяющимися (mutable) итераторами. Их классы называются аналогично, но с добавлением "Mutable": QMutableListIterator, QMutableHashIterator, QMutableLinkedListIterator, QMutableMapIterator и QMutableVectorIterator. Метод remove() удаляет текущий элемент, а insert() производит вставку элемента на текущую позицию. При помощи метода setValue() можно присвоить элементу другое значение. Давайте присвоим элементу списка "Библиотека Qt" значение "Профессиональное программирование на С++":

QList<QString> list;
list <<"Turbo Pascal" << "Lisp" << "Библиотека Qt";
QMutableListIterator<QString> it(list);
while(it.hasNext()) {
    if (it.next() == "Библиотека Qt") {
       it.setValue("Профессиональное программирование на С++");
       }
    qDebug() << it.peekPrevious();
}

   
Основным недостатком итераторов в стиле Java является то, что их применение, как правило, заметно увеличивает объем созданного объектного модуля, в сравнении с использованием итераторов стиля STL.

   
Итераторы в стиле STL

   
Итераторы в стиле STL немного эффективнее итераторов Java-стиля и могут быть использованы совместно с алгоритмами STL. Подобный итератор можно представить как некоторый обобщенный указатель, ссылающийся на элементы контейнера.

   
Вызов метода begin() из объекта контейнера возвращает итератор, указывающий на первый его элемент, а вызов метода end() возвращает итератор, указывающий на конец контейнера. Обратите внимание: именно на конец контейнера, а не на последний элемент, т. е. на позицию, на которой мог бы быть размещен следующий элемент. Другими словами, этот итератор не указывает на элемент, а служит только для обозначения достижения конца контейнера (рис. 1).


Рис.1. Методы begin(), end() и текущая позиция

   
Операторы ++ и -- объекта итератора производят перемещения на следующий или преды-дущий элемент соответственно. Доступ к элементу, на который указывает итератор, можно получить при помощи операции разыменования *. Например:

QVector<QString> vec;
vec <<"Turbo Pascal" << "Lisp" << "Библиотека Qt";
QVector<QString>::iterator it = vec.begin();
for (; it != vec.end(); ++it) {
    qDebug() << "Элемент:"<< *it;
}

   На экране будет отображено:

Элемент: "Turbo Pascal"
Элемент: "Lisp"
Элемент: "Библиотека Qt"

   
Обратите внимание, что для увеличения итератора it в цикле используется операция преинкрементации, т. е. ++it. Это позволяет избежать, при каждом витке цикла, сохранения старого значения, как скрытно делается в инкрементации, что делает цикл более эффективным.

   
При прохождении элементов в обратном порядке при помощи оператора -- необходимо помнить, что он не симметричен с прохождением при помощи оператора ++. Поэтому цикл должен в этом случае выглядеть следующим образом:

QVector<QString>::iterator it = vec.end();
for (;it != vec.begin();) {
    --it;
    qDebug() << "Элемент:" << *it;
}

   
На экране будет отображено:

Элемент: "Библиотека Qt"
Элемент: "Lisp"
Элемент: "Turbo Pascal"

   
Если вы собираетесь только получать значения элементов, не изменяя их, то гораздо эффективнее будет использовать константный итератор const_iterator. При этом нам нужно будет пользоваться (вместо begin() и end()) методами constBegin() и constEnd(). Таким образом, наш пример примет следующий вид:

QVector<QString> vec;
vec << "Turbo Pascal" << "Lisp" << "Библиотека Qt";
QVector<QString>::const_iterator it = vec.constBegin();
for (; it != vec.constEnd(); ++it) {
    qDebug() << "Элемент:" << *it;
}

   
Примечательно также то, что эти итераторы можно использовать со стандартными алгоритмами STL, определенными в заголовочном файле algorithm. Например, для сортировки вектора посредством STL-алгоритма sort() можно поступить следующим образом:

QVector<QString> vec;
vec << "Turbo Pascal" << "Lisp" << "Библиотека Qt";
std::sort(vec.begin(), vec.end());
qDebug() <<  vec;

   
На экране будет отображено:

QVector("Turbo Pascal", "Lisp", "Библиотека Qt")

   
Ключевое слово foreach

   
В языке C++ нет такого ключевого слова, оно было создано искусственно, посредством препроцессора, и представляет собой разновидность цикла, предназначенного для перебора всех элементов контейнера. Этот способ является альтернативой константному итератору. Например:

QList<QString> list;
list << "Turbo Pascal" << "Lisp" << "Библиотека Qt";
foreach(QString str, list) {
     qDebug() << "Элемент:" << str;
}

   
В foreach, как и в циклах, можно использовать ключевые слова break, continue, а также вкладывать циклы друг в друга.

   
Qt делает копию контейнера при входе в цикл foreach, поэтому если вы будете менять значение элементов в цикле, то на оригинальном контейнере это никак не отразится.

   
На следующем шаге рассмотрим последовательные контейнеры.

Предыдущий шаг
Содержание
Следующий шаг



Вы можете оставить комментарий, или Трекбэк с вашего сайта.

Оставить комментарий