Qt. Ітератори. Контейнери. Частина 2
Для обходу елементів, що зберігаються в контейнері, можна використовувати один з двох типів ітераторів: ітератори в стилі Java та ітератори в стилі STL. Ітератори в стилі Java легше використовувати і вони надають високорівневу функціональність, тоді як ітератори в стилі STL трохи ефективніші і можуть використовуватися разом з базовими алгоритмами Qt і STL.
Ітератори надають однакові засоби доступу до елементів контейнера. Класи контейнерів Qt надають два типи ітераторів: ітератори у стилі Java та ітератори у стилі STL.
Ітератори в стилі Java
Ітератори в стилі Java є новими Qt 4 і є стандартними, використовуваними в додатках Qt. Вони зручніші у використанні, ніж ітератори в стилі STL, але вони трохи менш ефективні. Їх API зроблено за зразком класів-ітераторів Java.
Для кожного класу-контейнера визначено два типи ітераторів у стилі Java: один з них надає доступ лише для читання, а інший надає доступ для читання-запису.
Контейнери
Ітератори тільки для читання
Ітератори для читання-запису
У цьому обговоренні ми зосередимося на QList та QMap. Типи ітераторів для QLinkedList, QVector і QSet мають такий самий інтерфейс, як і ітератори QList; аналогічно, типи ітераторів для QHash мають той самий інтерфейс, як і ітератори QMap.
На відміну від ітераторів у стилі STL (розглянутих нижче), ітератори в стилі Java вказують на комірку пам'яті між елементами, а не самі елементи. Тому вони вказують або початок контейнера (перед першим елементом), або на кінець контейнера (після останнього елемента), або між двома елементами. На діаграмі нижче червоними стрілками показані можливі позиції ітератора у списку,містить чотири елементи:
Ось типовий приклад циклу для перебору всіх елементів QList по порядку та виведення їх у консоль:
qDebug() , це елемент типу QString.
Ось як перебрати елементи QList у зворотному порядку:
qDebug() використовуючи QMutableListIterator:
Виклик next() здійснюється у кожній ітерації циклу. Він перестрибує через наступний елемент у списку. Функція remove() видаляє останній елемент, через який ми перестрибнули у списку. Виклик remove() не робить ітератор недійсним і залишається придатним для подальшого використання. Це працює так само і при переборі елементів у зворотному порядку:
if (i.previous() % 2 != 0)
Якщо ви бажаєте змінити значення існуючого елемента, то можете використовувати функцію setValue(). У коді нижче ми замінюємо будь-яке значення більше ніж 128, на 128:
Так само, як і remove(), setValue() працює з останнім елементом, який ми перестрибнули. Якщо ви перебираєте елементи в прямому напрямку, то це елемент, розташований прямо перед ітератором, якщо ви перебираєте елементи у зворотному порядку, це елемент, розташований відразу за ітератором.
Функція next() повертає неконстантне посилання елемент списку. Для простих операцій нам навіть не потрібно setValue():
Як було сказано вище, ітератори класів QLinkedList, QVector і QSet мають такий самий API як і у QList. Тепер звернемося до QMapIterator, який дещо відмінний, оскільки служить для перебору пар (ключ, значення).
Як QListIterator, QMapIterator надає toFront(), toBack(), hasNext(), next(), peekNext(), hasPrevious(), previous() і peekPrevious(). Компоненти ключ і значення можуть бути отримані викликом key() та value() для об'єкта, повернутий next(),peekNext(), previous() або peekPrevious().
У наступному прикладі видаляються всі пари (столиця, держава), у яких назва столиці закінчується на "City":
map.insert("Guatemala City", "Guatemala");
map.insert("Mexico City", "Mexico");
Якщо ми хочемо перебрати всі елементи, що містять те саме значення, то ми можемо використовувати findNext() або findPrevious(). Ось приклад, де ми видаляємо всі елементи із заданим значенням:
Ітератори в стилі STL
Ітератори в стилі STL стали доступними, починаючи з версії Qt 2.0. Вони сумісні з базовими алгоритмами Qt та STL та оптимізовані за швидкістю.
Для кожного контейнерного класу є два типи ітераторів у стилі STL: один з них надає доступ лише для читання, а інший доступ для читання-запису. Ітератори для читання повинні використовуватися скрізь, де це тільки можливо, оскільки вони швидше, ніж ітератори для читання-запису.
Контейнери
Ітератори тільки для читання
Ітератори для читання-запису
API ітераторів у стилі STL зроблено за зразком покажчиків у масиві. Наприклад, оператор ++ переміщає ітератор до наступного елемента, а оператор повертає елемент, на який позиціонований ітератор. Фактично, для QVector і QStack, що зберігають свої елементи в суміжних осередках пам'яті, тип iterator - це лише typedef для T*, а тип const_iterator - всього лише typedef для const T*.
У цьому обговоренні ми зосередимося на QList та QMap. Типи ітераторів для QLinkedList, QVector і QSet мають такий самий інтерфейс, як і ітератори QList; аналогічно, типи ітераторів для QHash мають той самий інтерфейс, як і ітератори QMap.
Ось типовий приклад циклу для перебору всіх елементів QList по порядку та конвертуванняїх у нижній регістр:
list::iterator i;
for (i = list.begin(); i != list.end(); ++i)
На відміну від ітераторів у стилі Java, ітератори у стилі STL вказують прямо на елемент. Функція контейнера begin() повертає ітератор, що вказує перший елемент контейнера. Функція контейнера end() повертає ітератор, що вказує на уявний елемент, що знаходиться в позиції, що йде за останнім елементом контейнера. end() позначає неіснуючу позицію; він ніколи не повинен розіменовуватись. Зазвичай, він використовується як умова виходу із циклу. Якщо список порожній, begin() дорівнює end(), тому цикл ніколи не виконається.
На діаграмі нижче червоними стрілками показані можливі позиції ітератора у списку, що містить чотири елементи:
При переборі елементів у зворотному порядку за допомогою ітераторів у стилі STL потрібно, щоб оператор декремента використовувався перед зверненням до елемента. Від потрібний цикл while:
list:: iterator i = list.end();
while (i != list.begin())
У цих фрагментах коду ми використовували унарний оператор * для відновлення значення елемента (типу QString), що зберігається в деякій позиції ітератора, а потім для нього викликали QString::toLower(). Більшість компіляторів C++ (але не всі) також дозволяють писати i->toLower()().
Для доступу до елементів лише для читання можна використовувати const_iterator, constBegin() і constEnd(). Наприклад:
QList :: const_iterator i;
for (i = list.constBegin(); i != list.constEnd(); ++i)
QMap :: const_iterator i;
for (i = map.constBegin(); i != map.constEnd(); ++i)
qDebug() sizes = splitter->sizes();
QList :: const_iterator i;
for (i = sizes.begin(); i != sizes.end(); ++i)
QList::const_iterator i;
for (i = splitter->sizes().begin();
i != splitter->sizes().end(); ++i)
Ця проблема не повинна виникати при використанні функцій, які повертають константний або неконстантний покажчик контейнера.
Неявне спільне використання даних має інший вплив на використання ітераторів у стилі STL: ви не повинні робити копії контейнера, якщо для нього активні неконстантні ітератори. Ітератори в стилі Java не страждають таким обмеженням.
Голосів: 2 Голосувати