Топ контрибуторов
loading
loading
Знаете ли Вы, что

После прохождения теста можно комментировать вопросы теста, а Ваши комментарии увидят модераторы теста и пользователи, которым когда-либо эти вопросы попадались.

Лента обновлений
ссылка Oct 22 23:24
Комментарий от alexcei88:
В вопросе переменная s даже не выводиться, а выводитьс...
ссылка Oct 22 17:46
Комментарий от AlexFurm:
Это UB, так можно вызывать только статические функции ч...
ссылка Oct 22 17:43
Комментарий от AlexFurm:
Любые битовые операции с signed это UB
ссылка Oct 21 20:30
Комментарий от yoori:
Любой вариант скомпилируется если компилировать не в конеч...
ссылка Oct 21 16:53
Добавлен вопрос в тест QA (Quality Assurance)
Статистика

Тестов: 153, вопросов: 8596. Пройдено: 443367 / 2177507.

Qt. Итераторы. Контейнеры. Часть 2

head tail Статья
категория
C++
дата28.09.2012
авторSemenovMS
голосов2

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

Итераторы предоставляют одинаковые средства доступа к элементам контейнера. Классы контейнеров Qt предоставляют два типа итераторов: итераторы в стиле Java и итераторы в стиле STL.

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

Итераторы в стиле Java являются новыми в Qt 4 и является стандартными, используемыми в приложениях Qt. Они более удобны в использовании, чем итераторы в стиле STL, но они немного менее эффективны. Их API сделан по образцу классов-итераторов Java.

Для каждого класса-контейнера определено два типа итераторов в стиле Java: один из них предоставляет доступ только для чтения, а другой предоставляет доступ для чтения-записи.

Контейнеры

Итераторы только для чтения

Итераторы для чтения-записи

QList<T>, QQueue<T>

QListIterator<T>

QMutableListIterator<T>

QLinkedList<T>

QLinkedListIterator<T>

QMutableLinkedListIterator<T>

QVector<T>, QStack<T>

QVectorIterator<T>

QMutableVectorIterator<T>

QSet<T>

QSetIterator<T>

QMutableSetIterator<T>

QMap<Key, T>, QMultiMap<Key, T>

QMapIterator<Key, T>

QMutableMapIterator<Key, T>

QHash<Key, T>, QMultiHash<Key, T>

QHashIterator<Key, T>

QMutableHashIterator<Key, T>

В этом обсуждении мы сконцентрируемся на QList и QMap. Типы итераторов для QLinkedList, QVector и QSet имеют точно такой же интерфейс, что и итераторы QList; аналогично, типы итераторов для QHash имеют тот же интерфейс, что и итераторы QMap.

В отличие от итераторов в стиле STL (рассматриваемых ниже), итераторы в стиле Java указывают на ячейку памяти между элементами, а не на сами элементы. Поэтому они указывают либо на начало контейнера (перед первым элементом), либо на конец контейнера (после последнего элемента), либо между двумя элементами. На диаграмме ниже красными стрелками показаны возможные позиции итератора в списке, содержащем четыре элемента:

Вот типичный пример цикла для перебора всех элементов QList<QString> по порядку и вывода их в консоль:

 QList<QString> list;

 list << "A" << "B" << "C" << "D";

 

 QListIterator<QString> i(list);

 while (i.hasNext())

     qDebug() << i.next();

Он работает следующим образом: чтобы перебрать элементы, QList помещается в конструктор QListIterator. В этот момент итератор позиционирован на начало первого элемента в списке (перед элементом "A"). Потом мы вызываем hasNext() для проверки, существует ли какой-либо элемент после позиции итератора. Если есть, то мы вызываем next(), чтобы перепрыгнуть через него. Функция next() возвращает элемент, через который перепрыгнул итератор. Для QList<QString>, это элемент типа QString.

Вот как перебрать элементы QList в обратном порядке:

 QListIterator<QString> i(list);

 i.toBack();

 while (i.hasPrevious())

     qDebug() << i.previous();

Этот код симметричен перебору в прямом порядке, за исключения того, что мы начинаем с вызова toBack() для перемещения итератора на позицию, после последнего элемента в списке.

Диаграмма, приведенная ниже, показывает эффект от вызовов функций итератора next() и previous():

В следующей таблице подводится итог API QListIterator:

Функция

Поведение

toFront()

Перемещает итератор в начало списка (перед первым элементом)

toBack()

Перемещает итератор в конец списка (после последнего элемента)

hasNext()

Возвращает true, если итератор не в конце списка

next()

Возвращает следующий элемент и перемещает итератор на одну позицию вперед

peekNext()

Возвращает следующий элемент без перемещения итератора

hasPrevious()

Возвращает true, если итератор не в начале списка

previous()

Возвращает предыдущий элемент и перемещает итератор на одну позицию назад

peekPrevious()

Возвращает предыдущий элемент без перемещения итератора

QListIterator не предоставляет функций для вставки или удаления элементов перебираемого списка. Для того, чтобы сделать это, вы должны использовать QMutableListIterator. Вот пример, где мы удаляем все элементы с нечетными значениями из QList<int> используя QMutableListIterator:

 QMutableListIterator<int> i(list);

 while (i.hasNext()) {

     if (i.next() % 2 != 0)

         i.remove();

 }

Вызов next() осуществляется в каждой итерации цикла. Он перепрыгивает через следующий элемент в списке. Функция remove() удаляет последний элемент, через который мы перепрыгнули в списке. Вызов remove() не делает итератор недействительным, и он остается пригодным для дальнейшего использования. Это работает точно также и при переборе элементов в обратном порядке:

 QMutableListIterator<int> i(list);

 i.toBack();

 while (i.hasPrevious()) {

     if (i.previous() % 2 != 0)

         i.remove();

 }

Если вы желаете лишь изменить значение существующего элемента, то можете использовать функцию setValue(). В коде ниже, мы заменяем любое значение большее чем 128, на 128:

 QMutableListIterator<int> i(list);

 while (i.hasNext()) {

     if (i.next() > 128)

         i.setValue(128);

 }

Точно также, как и remove(), setValue() работает с последнем элементом, который мы перепрыгнули. Если вы перебираете элементы в прямом направлении, то это элемент, расположенный прямо перед итератором, если вы перебираете элементы в обратном порядке, то это элемент, расположенный сразу за итератором.

Функция next() возвращает неконстантную ссылку на элемент списка. Для простых операций нам даже не требуется setValue():

 QMutableListIterator<int> i(list);

 while (i.hasNext())

     i.next() *= 2;

Как было сказано выше, итераторы классов QLinkedList, QVector и QSet имеют совершенно такой же API как и у QList. Теперь обратимся к QMapIterator, который несколько отличен, так как служит для перебора пар (ключ, значение).

Каки QListIterator, QMapIterator предоставляет toFront(), toBack(), hasNext(), next(), peekNext(), hasPrevious(), previous() и peekPrevious().Компоненты ключ и значение могут быть получены вызовом key() и value() для объекта, возвращенного next(), peekNext(), previous() или peekPrevious().

В следующем примере удаляются все пары (столица, государство), в которых название столицы оканчивается на "City":

 QMap<QString, QString> map;

 map.insert("Paris", "France");

 map.insert("Guatemala City", "Guatemala");

 map.insert("Mexico City", "Mexico");

 map.insert("Moscow", "Russia");

 ...

 

 QMutableMapIterator<QString, QString> i(map);

 while (i.hasNext()) {

     if (i.next().key().endsWith("City"))

         i.remove();

 }

QMapIterator также предоставляет функции key() и value(), которые работают напрямую с итератором и возвращают ключ и значение последнего элемента, который перепрыгнул итератор. Например, в следующем коде производится копирование содержимого QMap в QHash:

 QMap<int, QWidget *> map;

 QHash<int, QWidget *> hash;

 

 QMapIterator<int, QWidget *> i(map);

 while (i.hasNext()) {

     i.next();

     hash.insert(i.key(), i.value());

 }

Если мы хотим перебрать все элементы, содержащие одно и то же значение, то мы можем использовать findNext() или findPrevious(). Вот пример, где мы удаляем все элементы с заданным значением:

 QMutableMapIterator<int, QWidget *> i(map);

 while (i.findNext(widget))

     i.remove();

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

Итераторы в стиле STL стали доступны, начиная с версии Qt 2.0. Они совместимы с базовыми алгоритмами Qt и STL и оптимизированы по скорости.

Для каждого контейнерного класса есть два типа итераторов в стиле STL: один из них предоставляет доступ только для чтения, а другой - доступ для чтения-записи. Итераторы только для чтения должны использоваться везде, где это только возможно, так как они быстрее, чем итераторы для чтения-записи.

Контейнеры

Итераторы только для чтения

Итераторы для чтения-записи

QList<T>, QQueue<T>

QList<T>::const_iterator

QList<T>::iterator

QLinkedList<T>

QLinkedList<T>::const_iterator

QLinkedList<T>::iterator

QVector<T>, QStack<T>

QVector<T>::const_iterator

QVector<T>::iterator

QSet<T>

QSet<T>::const_iterator

QSet<T>::iterator

QMap<Key, T>, QMultiMap<Key, T>

QMap<Key, T>::const_iterator

QMap<Key, T>::iterator

QHash<Key, T>, QMultiHash<Key, T>

QHash<Key, T>::const_iterator

QHash<Key, T>::iterator

API итераторов в стиле STL сделан по образцу указателей в массиве. Например, оператор ++ перемещает итератор к следующему элементу, а оператор * возвращает элемент, на который позиционирован итератор. Фактически, для QVector и QStack, хранящих свои элементы в смежных ячейках памяти, тип iterator - это всего лишь typedef для T *, а тип const_iterator - всего лишь typedef для const T *.

В этом обсуждении мы сконцентрируемся на QList и QMap. Типы итераторов для QLinkedList, QVector и QSet имеют точно такой же интерфейс, что и итераторы QList; аналогично, типы итераторов для QHash имеют тот же интерфейс, что и итераторы QMap.

Вот типичный пример цикла для перебора всех элементов QList<QString> по порядку и конвертирования их в в нижний регистр:

 QList<QString> list;

 list << "A" << "B" << "C" << "D";

 

 QList<QString>::iterator i;

 for (i = list.begin(); i != list.end(); ++i)

     *i = (*i).toLower();

В отличие от итераторов в стиле Java, итераторы в стиле STL указывают прямо на элемент. Функция контейнера begin() возвращает итератор, указывающий на первый элемент контейнера. Функция контейнера end() возвращает итератор, указывающий на воображаемый элемент, находящийся в позиции, следующей за последним элементом контейнера. end() обозначает несуществующую позицию; он никогда не должен разыменовываться. Обычно, он используется, как условие выхода из цикла. Если список пуст, то begin() равен end(), поэтому цикл никогда не выполнится.

На диаграмме ниже красными стрелками показаны возможные позиции итератора в списке, содержащем четыре элемента:

При переборе элементов в обратном порядке с помощью итераторов в стиле STL, требуется, чтобы оператор декремента использовался перед обращения к элементу. Воттребуемыйцикл while:

 QList<QString> list;

 list << "A" << "B" << "C" << "D";

 

 QList<QString>::iterator i = list.end();

 while (i != list.begin()) {

     --i;

     *i = (*i).toLower();

 }

В этих фрагментах кода, мы использовали унарный оператор * для восстановления значения элемента (типа QString), хранящегося в некоторой позиции итератора, а затем для него вызывали QString::toLower(). Большинство компиляторов C++ (но не все) также позволяют писать i->toLower()().

Для доступа к элементам только для чтения, можно использовать const_iterator, constBegin() и constEnd(). Например:

 QList<QString>::const_iterator i;

 for (i = list.constBegin(); i != list.constEnd(); ++i)

     qDebug() << *i;

В следующей таблице подводится итог API итераторов в стиле STL:

Выражение

Поведение

*i

Возвращает текущий элемент

++i

Перемещает итератор к следующему элементу

i += n

Перемещает итератор вперед на n элементов

--i

Перемещает итератор на один элемент назад

i -= n

Перемещает итератор назад на n элементов

i - j

Возвращает количество элементов, находящихся между позицией итератора i и j

Оба оператора ++ и -- могут использоваться и как префиксные (++i, --i) и как постфиксные (i++, i--) операторы. Префиксная версия изменяет итератор, и возвращает ссылку на измененный итератор; постфиксная версия, берет копию итератора перед его изменением, и возвращает эту копию. В выражениях, в которых возвращаемое значение игнорируется, мы рекомендуем использовать префиксную версию (++i, --i), так как она несколько быстрее.

Для неконстантных итераторов, возвращаемое значение унарного оператора *, может быть использовано с левой стороны от оператора присваивания.

Для QMap и QHash, оператор * возвращает компонент значения элемента. Если вы хотите извлечь ключ, вызовите key() для итератора. Для симметрии, типы итераторов предоставляют также функцию value(), извлекающую значение. Например, здесь показано, как мы можем напечатать все элементы в QMap в консоль:

 QMap<int, int> map;

 ...

 QMap<int, int>::const_iterator i;

 for (i = map.constBegin(); i != map.constEnd(); ++i)

     qDebug() << i.key() << ":" << i.value();

Благодаря неявному совместному использованию данных, использование значений контейнера весьма недорого. API Qt содержит множество функций, возвращающих QList или QStringList со значениями (например, QSplitter::sizes()). Если вы хотите перебрать эти значения с помощью итератора в стиле STL, то вы всегда должны иметь копию контейнера и перебирать ее элементы. Например:

 // ПРАВИЛЬНО

 const QList<int> sizes = splitter->sizes();

 QList<int>::const_iterator i;

 for (i = sizes.begin(); i != sizes.end(); ++i)

     ...

 

 // НЕПРАВИЛЬНО

 QList<int>::const_iterator i;

 for (i = splitter->sizes().begin();

         i != splitter->sizes().end(); ++i)

     ...

Эта проблема не должна возникать при использовании функций, которые возвращают константный или неконстантный указатель на контейнер.

Неявное совместное использование данных имеет и другое влияние на использование итераторов в стиле STL: вы не должны делать копии контейнера, если для него активны неконстантные итераторы. Итераторы в стиле Java не страдают таким ограничением.

Если Вам понравилась статья, проголосуйте за нее

Голосов: 2  loading...
ingwarsmith   partigiano