Библиотека контейнеров Qt5 является универсальной коллекцией шаблонов классов и алгоритмов, позволяющих программистам легко реализовывать общие структуры данных, такие как очереди, списки и стеки.
Контейнеры в Qt5
Контейнеры — это классы общего назначения, которые хранят в себе значения заданного типа. В языке С++ имеется свой набор контейнеров, который называется STL.
Стоит отметить, что идеологии контейнеров Qt и STL, при всей своей схожести, отличаются в следующем:
Фреймворк Qt5 предоставляет максимально богатый интерфейс, а копирование контейнера происходит за константное время, но цена этому — дополнительные проверки при доступе к разделяемым данным (implicit data sharing).
В свою очередь, STL предоставляет ограниченный интерфейс и следует идеологии уменьшения необоснованного расхода процессорного времени, памяти, ненужной нагрузки и пр.
Мы же, программируя на Qt, можем использовать как Qt-контейнеры, так и STL. Если вы не знакомы с STL или предпочитаете работать «только с Qt», то можете использовать классы Qt вместо классов STL.
На этом уроке мы рассмотрим следующие контейнеры:
QVector
;
QList
;
QStringList
;
QSet
;
QMap
.
Есть 2 типа контейнеров:
Последовательные — элементы хранятся друг за другом (последовательно). Примерами последовательных контейнеров являются QList, QVector, QLinkedList.
Ассоциативные — элементы хранятся в виде пары ключ-значение. Примерами ассоциативных контейнеров являются QMap и QHash.
Контейнер QVector
QVector — это шаблонный класс, который представляет динамический массив. Данный контейнер хранит свои элементы в соседних участках памяти, а доступ к ним осуществляется при помощи индексов. Для векторов с большим количеством элементов вставка значений в начало или в середину вектора может происходить довольно медленно, потому что это может сопровождаться перемещением большого количества элементов в памяти на одну позицию, поэтому в подобных случаях рекомендуется использовать QList вместо QVector.
Следующий пример показывает работу с вектором, элементами которого являются целые числа:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#include <QVector> #include <QTextStream> int main(void) { QTextStream out(stdout); // Создаем вектор, содержащий целочисленные значения QVector<int> vals = {1, 2, 3, 4, 5}; // С помощью метода size() возвращаем размер вектора (количество элементов, содержащихся в нем) out << "The size of the vector is: " << vals.size() << endl; out << "The first item is: " << vals.first() << endl; // получаем первый элемент вектора out << "The last item is: " << vals.last() << endl; // получаем последний элемент вектора vals.append(6); // вставляем новый элемент в конец вектора vals.prepend(0); // вставляем новый элемент в начало вектора out << "Elements: "; // Перебираем элементы вектора и выводим их на экран for (int val : vals) { out << val << " "; } out << endl; return 0; } |
Результат выполнения программы:
Контейнер QList
QList — это контейнер, который предназначен для создания списка элементов. QList во многом похож на QVector. Он хранит список значений указанного типа и обеспечивает быстрый доступ к ним с помощью индексов, а также вставку и удаление элементов. Это один из наиболее часто используемых контейнеров в Qt.
Пример использования контейнера QList:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#include <QTextStream> #include <QList> #include <algorithm> int main(void) { QTextStream out(stdout); // Создаем контейнер QList, в котором будем хранить имена писателей QList<QString> authors = {"Balzac", "Tolstoy", "Gulbranssen", "London"}; // Перебираем каждый элемент массива и выводим на экран for (int i=0; i < authors.size(); ++i) { out << authors.at(i) << endl; // метод at() возвращает элемент с указанным индексом } // С помощью оператора << вставляем в список 2 новых элемента authors << "Galsworthy" << "Sienkiewicz"; out << "***********************" << endl; // С помощью метода sort() сортируем список в порядке возрастания std::sort(authors.begin(), authors.end()); // Выводим на экран отсортированный список out << "Sorted:" << endl; for (QString author : authors) { out << author << endl; } } |
Результат выполнения программы:
Контейнер QStringList
QStringList — это контейнер, который предоставляет список строк. Он удобен прежде всего тем, что имеет дополнительные методы для фильтрации данных и легко взаимодействует со стандартными компонентами Qt, «понимающими» строки QString. Он также поддерживает быстрый доступ к элементам, их вставку и удаление.
В следующем примере мы создадим список строк из исходной строки и выведем их на экран:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include <QTextStream> #include <QList> int main(void) { QTextStream out(stdout); QString string = "coin, book, cup, pencil, clock, bookmark"; // С помощью метода split() разделяем строку на подстроки QStringList items = string.split(","); // Это константный итератор в Java-стиле для QStringList QStringListIterator it(items); // С помощью созданного итератора выводим элементы списка на экран while (it.hasNext()) { out << it.next().trimmed() << endl; // с помощью метода trimmed() удаляем пробелы из строки } } |
Результат выполнения программы:
Контейнер QSet
QSet предоставляет однозначный (без повторений) математический набор с возможностью быстрого поиска элементов. Значения хранятся в неопределенном порядке.
В следующем примере QSet используется для хранения значений цветов. Как уже было сказано, данный контейнер является однозначным, поэтому нет смысла указывать одно и то же значение цвета несколько раз:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
#include <QSet> #include <QList> #include <QTextStream> #include <algorithm> int main(void) { QTextStream out(stdout); // Создаем 2 набора цветов QSet<QString> cols1 = {"yellow", "red", "blue"}; QSet<QString> cols2 = {"blue", "pink", "orange"}; // С помощью метода size() возвращаем размер набора out << "There are " << cols1.size() << " values in the set" << endl; // С помощью метода insert() вставляем новый элемент cols1.insert("brown"); out << "There are " << cols1.size() << " values in the set" << endl; // Метод unite() выполняет объединение двух наборов cols1.unite(cols2); out << "There are " << cols1.size() << " values in the set" << endl; // Перебираем все элементы набора cols1 и выводим их на экран for (QString val : cols1) { out << val << endl; } // Создаем отдельный список из набора элементов cols1 для их сортировки QList<QString> lcols = cols1.values(); // метод values() возвращает новый QList, содержащий элементы набора std::sort(lcols.begin(), lcols.end()); out << "*********************" << endl; out << "Sorted:" << endl; for (QString val : lcols) { out << val << endl; } return 0; } |
Результат выполнения программы:
Контейнер QMap
QMap — это ассоциативный массив (словарь), который хранит пары ключ-значение. Он обеспечивает быстрый поиск значения, ассоциированного с ключом. В следующем примере у нас есть два словаря, в которых мы сопоставляем ключ в виде строки целочисленному значению:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#include <QTextStream> #include <QMap> int main(void) { QTextStream out(stdout); // Создаем QMap, содержащий 2 пары элементов QMap<QString, int> items = { {"coins", 5}, {"books", 3} }; // С помощью метода insert() добавляем новую пару значений items.insert("bottles", 7); // Получаем все значения словаря и выводим их на экран QList<int> values = items.values(); // метод values() возвращает список значений словаря out << "Values:" << endl; for (int val : values) { out << val << endl; } // Аналогично выводим все ключи словаря QList<QString> keys = items.keys(); // метод keys() возвращает список, содержащий все ключи в словаре out << "Keys:" << endl; for (QString key : keys) { out << key << endl; } // Создаем итератор для QMap в Java-стиле QMapIterator<QString, int> it(items); // этот итератор может использоваться для итерации по элементам QMap out << "Pairs:" << endl; // С помощью итератора перебираем все элементы QMap while (it.hasNext()) { it.next(); out << it.key() << ": " << it.value() << endl; // метод key() возвращает текущий ключ, а метод value() возвращает текущее значение } } |
Результат выполнения программы:
Сортировка пользовательских классов
В следующем примере мы выполним сортировку объектов пользовательского класса в QList.
Ниже представлен заголовочный файл book.h для нашего пользовательского класса Book (книга):
1 2 3 4 5 6 7 8 9 10 |
class Book { public: Book(QString, QString); QString getAuthor() const; QString getTitle() const; private: QString author; QString title; }; |
А здесь реализация класса Book (у нас есть два геттеры для доступа к элементам класса):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <QString> #include "book.h" Book::Book(QString auth, QString tit) { author = auth; title = tit; } QString Book::getAuthor() const { return author; } QString Book::getTitle() const { return title; } |
Далее мы создадим несколько объектов класса Book и отсортируем их с помощью алгоритма std::sort():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#include <QTextStream> #include <QList> #include <algorithm> #include "book.h" bool compareByTitle(const Book &b1, const Book &b2) { return b1.getTitle() < b2.getTitle(); } int main(void) { QTextStream out(stdout); QList<Book> books = { Book("Jack London", "The Call of the Wild"), Book("Honoré de Balzac", "Father Goriot"), Book("Leo Tolstoy", "War and Peace"), Book("Gustave Flaubert", "Sentimental education"), Book("Guy de Maupassant", "Une vie"), Book("William Shakespeare", "Hamlet") }; std::sort(books.begin(), books.end(), compareByTitle); for (Book book : books) { out << book.getAuthor() << ": " << book.getTitle() << endl; } } |
CompareByTitle() — это функция сравнения, используемая алгоритмом сортировки:
1 2 3 |
bool compareByTitle(const Book &b1, const Book &b2) { return b1.getTitle() < b2.getTitle(); } |
Алгоритм std:: sort() сортирует книги в списке по заголовку:
1 |
std::sort(books.begin(), books.end(), compareByTitle); |
Результат выполнения программы:
На этом всё! До следующего урока.
Чисто для лучшего познания QSet контейнера. При каждой компиляции компонент brown до сортировки помещается в разные позиции контейнера, то есть он может быть в 0 месте, может в 1 и так далее. Чем это можно объяснить?
Существуют неупорядоченные контейнеры, которые не гарантируют определённую позицию элемента в себе. Не помню с чем это связано, но вроде за счёт этого они быстрее предоставляют доступ к своему содержимому. По факту QSet просто для других целей, если нужен порядок следует использовать QVector или QList.
И несколько не по теме.
Роман Льва Николаевича Толстого "Война и мiр" на современном языке — это "Война и общество". Таким образом: "War and society".
Интересное замечание, но сейчас официальным переводом является именно "War and Peace". Если официально примут название "War and Society", тогда можно будет менять, а пока в статье всё ок))
Здравствуйте! Было бы неплохо чуть подробнее описать когда правильнее использовать контейнеры stl, когда Qt.
Есть правило: "When in Rome… Do as the Romans Do".
Если вы работаете с Qt лучше использовать Qt контейнеры.
Например , подумайте, что будет если вам надо будет передать
данные в функцию Qt. Вам придётся писать код по преобразованию stl контейнеров в Qt контейнеры и наоборот.
Больше информации тут.
https://stackoverflow.com/questions/1668259/stl-or-qt-containers
Спасибо за уроки и за урок в частности.
Небольшое замечание по поводу QList. В уроке указано следующее по поводу QList:"Он хранит список значений указанного типа и обеспечивает быстрый доступ к ним с помощью индексов, а также быстрые вставку и удаление элементов."
В документации же на qt (ссылка https://doc.qt.io/qt-5/qlist.html#details от 26.11.2019) написано " It stores items in a list that provides fast index-based access and index-based insertions and removals.", что можно перевести как " Он хранит элементы в списке, который обеспечивает быстрый доступ по индексу и, основанные на индексах, вставку и удаление." Быстрой вставки и удаления в произвольное место QList не обеспечивает потому, что он представляет собой массив, в котором содержатся ссылки на хранимые в нем объекты. Согласно статье "Container Class" (https://doc.qt.io/qt-5/containers.html#algorithmic-complexity от 26.11.2019) сложность вставки для QList соответствует O(n), что соответствует сложности вставки для QVector.
В Qt5 объект, который обеспечивает "быструю" (сложностью O(1)) вставку и/или удаление в произвольное место обеспечивает контейнер QLinkedList.
Спасибо за замечание. В ближайшее время внесем в статью соответствующие правки. 🙂
Исправили)
Классный урок !!!
Спасибо 🙂