Перебор значений QMap

Автор: | 23 июля, 2019

QMap — это отсортированная по ключу ассоциативная коллекция из Qt, реализованная как сбалансированное дерево. Очень часто требуется обойти все её элементы — по ключам, по значениям, или по парам ключ–значение. В этой статье мы рассмотрим различные способы перебора значений в QMap, включая плюсы, минусы и советы по применению.


1. Классический перебор через итераторы

QMap<QString, int> map;
map["Alice"] = 10;
map["Bob"] = 20;
map["Carol"] = 30;

QMap<QString, int>::iterator i;
for (i = map.begin(); i != map.end(); ++i) {
    qDebug() << i.key() << ":" << i.value();
}

Плюсы:

  • Полный контроль над итерацией (можно изменять элементы).
  • Высокая производительность (по сравнению с .keys() и .values()).

Минусы:

  • Не очень удобочитаемо.
  • Требуется аккуратно обращаться с типами итераторов.

2. Использование const_iterator

Если вы не собираетесь изменять значения, лучше использовать const_iterator:

QMap<QString, int>::const_iterator i;
for (i = map.constBegin(); i != map.constEnd(); ++i) {
    qDebug() << i.key() << ":" << i.value();
}

Плюсы:

  • Безопаснее: гарантирует, что вы не измените карту.
  • Производительный и стильный способ для чтения.

3. Использование foreach (устаревший, но всё ещё встречается)

foreach (QString key, map.keys()) {
    qDebug() << key << ":" << map.value(key);
}

Минусы:

  • map.keys() создаёт временный QList<QString>, что требует дополнительных затрат.
  • value(key) делает поиск по ключу — это О(log N) операция, и если коллекция большая, производительность может резко упасть.

🚫 Qt официально не рекомендует использовать foreach с контейнерами Qt в новых проектах (начиная с Qt 5.7).


4. Современный способ: range-based for

for (auto it : map.toStdMap()) {
    qDebug() << QString::fromStdString(it.first) << ":" << it.second;
}

Или через итератор напрямую:

for (auto it = map.constBegin(); it != map.constEnd(); ++it) {
    qDebug() << it.key() << ":" << it.value();
}

Плюсы:

  • Удобочитаемость.
  • Поддерживает const, auto, и structured bindings в C++17.

5. Использование std::for_each

std::for_each(map.constBegin(), map.constEnd(), [](const QMap<QString, int>::value_type& pair) {
    qDebug() << pair.first << ":" << pair.second;
});

Удобно для использования с лямбда-выражениями и функциональным стилем.

6. Перебор только ключей или значений

for (const QString &key : map.keys()) {
    qDebug() << key;
}

for (int value : map.values()) {
    qDebug() << value;
}

Оба метода создают временные QList, что может быть затратным для больших карт.

7. Перебор с structured bindings (C++17)

Если вы работаете со std::map или конвертируете QMap в std::map:

for (const auto& [key, value] : map.toStdMap()) {
    qDebug() << QString::fromStdString(key) << ":" << value;
}

8. Какой способ выбрать?

СпособИзменение данныхБыстродействиеРекомендуется?
iteratorДа✅ Высокое✅ Да
const_iteratorНет✅ Высокое✅ Да
foreach map.keys()Нет❌ Низкое🚫 Нет
range-based forНет✅ Высокое✅ Да (современный)
map.keys() / .values()Нет❌ Среднее–низкое⚠️ Ограниченно
std::for_eachНет✅ Высокое✅ Для C++-стиля

Практический совет

Если вам нужно часто перебирать элементы и производительность критична — используйте const_iterator или range-based for. Избегайте map.keys() и value(key) в цикле, особенно на больших объемах данных.

Пример на практике: фильтрация элементов

for (auto it = map.constBegin(); it != map.constEnd(); ++it) {
    if (it.value() > 50) {
        qDebug() << "Найдено:" << it.key() << ":" << it.value();
    }
}

Заключение

QMap предоставляет гибкие возможности для перебора, и выбор подхода зависит от ваших целей: удобство, безопасность, производительность. Не забывайте учитывать стоимость операций и использовать const там, где это возможно.