Шаг 58.
Библиотека Qt.
Итераторы

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

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

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

    В качестве альтернативы существует вариант обхода элементов при помощи ключевого слова 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, поэтому если вы будете менять значение элементов в цикле, то на оригинальном контейнере это никак не отразится.

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




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