На этом шаге рассмотрим итераторы.
Наверняка вам потребуется перемещаться по элементам контейнера. Для этих целей предназначен итератор. Итераторы позволяют абстрагироваться от структуры данных контейнеров, т. е., если в какой-либо момент вы решите, что применение другого типа контейнера было бы гораздо эффективнее, то все, что вам нужно будет сделать, — это просто заменить тип контейнера нужным. На остальном коде, использующем итераторы, это никак не отразится.
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. Эти итераторы являются константными, а это значит, что изменение значений элементов, их вставка и удаление не возможны.
Перемещает итератор на начало списка | |
Перемещает итератор на конец списка | |
Возвращает значение true, если итератор не находится в конце списка | |
Возвращает значение следующего элемента списка и перемещает итератор на следующую позицию | |
Просто возвращает следующее значение без изменения позиции итератора | |
Возвращает значение true, если итератор не находится в начале списка | |
Возвращает значение предыдущего элемента списка и перемещает итератор на предыдущую позицию | |
Просто возвращает предыдущее значение без изменения позиции итератора | |
Поиск заданного элемента в прямом направлении | |
Поиск заданного элемента в обратном направлении |
Если необходимо производить изменения в процессе прохождения итератором элементов, то для этого следует воспользоваться изменяющимися (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, поэтому если вы будете менять значение элементов в цикле, то на оригинальном контейнере это никак не отразится.
На следующем шаге рассмотрим последовательные контейнеры.