QT: о контейнерах – просто
Более краткое, чем эта статья введение в тему контейнеров QT – для чего они предназначены и какие основные возможности имеют. На самом деле, тему вряд ли имеет смысл рассматривать отдельно от действий с QStringList и обеими группами стандартных виджетов, рассчитанных на работу с контейнерными данными.
Задача любых контейнеров, в сущности, проста – это организация обработки групп элементов. Мы кратко расскажем обо всех основных контейнерах QT и приведём примеры, такие же, как везде, только сделаем их законченными мини-приложениями.
Для демонстрации примеров нам будет достаточно консольного приложения QT с совсем простым шаблоном. Выберем в QT Creator меню Файл, Новый файл или проект…, затем шаблон "Консольное приложение QT" и проделаем те же шаги, что в этой заметке, не будет только 4-го шага, так как нам не нужен отдельный класс-виджет.
Всё содержимое файла main.cpp
можно удалить, заменив его на такое:
#include <QtCore> int main() { //Сюда вставлять код return 0; }
В заголовочном файле QtCore
все контейнеры уже прописаны, а "остановку" перед завершением консольного приложения QT обеспечит сам. Весь вывод будем выполнять с помощью стандартной функции qDebug()
, например, так:
#include <QtCore> int main() { int n=5; QVector <int> v(n,100); qDebug() << n << v; return 0; }
На экран выведется
5 QVector(100, 100, 100, 100, 100)
что очень удобно – функция qDebug()
принимает любые типы данных, а для составных типов, таких, как вектор, ещё и печатает имя класса.
Но мы немного забежали вперёд, пройдёмся сначала по последовательным контейнерам, представляющим собой упорядоченные наборы элементов какого-либо типа данных.
1. Вектор QVector похож на обычный массив, но можно узнать количество элементов внутри вектора (размерность) методом size()
, а также динамически расширять вектор. Вектор экономнее по памяти и ресурсам, чем другие виды контейнеров. Добавление элементов в конец вектора делается методом push_back()
, а обращение к отдельным элементам – оператором []
или с помощью итератора:
QVector<int> vec; vec.push_back(10); //push_back сам увеличит размерность с 0 до 1 vec.resize(2); vec[1] = 20; //А квадратные скобки - нет, сначала зарезервировали место qDebug() << vec;
2. Массив байт QByteArray, в отличие от вектора, не является шаблоном, в нём допускается хранение только элементов, имеющих размер в один байт. Объекты типа QByteArray
можно использовать везде, где требуется промежуточное хранение данных. Количество элементов массива можно задать в конструкторе, а доступ к ним получать при помощи оператора []
.
QByteArray arr(3,'0'); //Массив байт QByteArray arr[0] = 0x23; arr[2]=65; qDebug() << arr.data(); //#0A
К данным объектов класса QByteArray
можно также применить операцию сжатия и обратное преобразование. Это достигается при помощи двух глобальных функций qCompress()
и qUncompress().
Сожмём и разожмем данные из строки char *
:
QByteArray a = "Some text..."; QByteArray aCompressed = qCompress(a); a = qUncompress(aCompressed); qDebug() << a;
3. QBitArray – это булев массив, каждое из значений которого занимает 1 бит, не расходуя лишней памяти. Этот тип используется для хранения большого количества переменных типа bool
.
QBitArray arr(3); arr[0] = arr[1] = true; arr[2] = false; qDebug() << arr;
4. QList – это базовый шаблон списка, имеющий множество методов для работы с его элементами, например:
- move() — перемещает элемент с одной позиции на другую;
- removeFirst() — удаление первого элемента списка;
- removeLast() — удаление последнего элемента списка;
- swap() — меняет местами два элемента списка на указанных позициях;
- takeAt() — возвращает элемент на указанной позиции и удаляет его из списка;
- takeFirst() — возвращает первый элемент и удаляет его из списка;
- takeLast() — возвращает последний элемент и удаляет его из списка;
- toSet() — возвращает контейнер QSet с данными, содержащимися в списке;
- toStdList() — возвращает стандартный список STL std::List с элементами из списка;
- toVector() — возвращает вектор QVector с данными содержащимися в списке.
Если вы не собираетесь менять значения элементов, из соображений эффективности не рекомендуется использовать оператор []
. Вместо него есть метод at()
, так как он возвращает константную ссылку на элемент.
Самая распространённая задача со списком – его обход для последовательного получения значений каждого элемента:
QList <int> list; list << 10 << 20 << 30; QList <int>::iterator it = list.begin(); while (it != list.end()) { qDebug() << "Element: " << *it++; }
5. Стек QStack – это класс-наследник QVector
, он реализует структуру данных, работающую по принципу LIFO (Last In, First Out — последним пришёл, первым ушёл). То есть, из стека первым удаляется элемент, который был вставлен позже всех остальных.
Процесс помещения элементов в стек обычно называется "проталкиванием" (pushing), а извлечение из него верхнего элемента — "выталкиванием" (poping). Каждая операция проталкивания увеличивает размер стека на 1, а каждая операция выталкивания уменьшает его на 1. Для этих операций в классе QStack
определены функции push()
и pop()
.
QStack <QString> stack; stack.push("Hedgehog"); stack.push("Woodpecker"); stack.push("Fox"); while (!stack.empty()) { qDebug() << "Element: " << stack.pop(); }
6. Очередь QQueue реализует структуру данных, работающую по принципу — FIFO (First In, First Out — первым пришёл, первым ушёл). Класс очереди QQueue
унаследован от QList
.
QQueue <QString> q; //Очередь QQueue q.enqueue("Str1"); q.enqueue("Str 2"); while (!q.empty()) qDebug() << q.dequeue(); qDebug() << q.size();
7. Множества QSet удобны тем, что поддерживают уникальность ключей и стандартные операции над множествами - объединение, пересечение, разность. Контейнер QSet
можно использовать в качестве неупорядоченного списка для быстрого поиска данных.
QSet <int> set1, set2; set1 << 1 << 2 << 3; set2 << 3 << 4 << 2 << 5; qDebug() << set1.unite(set2); // set1 U set2 = (1, 3, 2, 5, 4)
8. Словари QMap, QMultiMap – контейнеры, действительно похожи на обычные словари, они хранят элементы одного и того же типа, индексируемые ключевыми значениями. Основное достоинство словарей в том, что они позволяют быстро получать значение, сопоставленное (ассоциированное) с заданным ключом. В QMap
при этом ключи должны быть уникальными, а QMultiMap
допускает дубликаты ключей, то есть, одному ключу может быть сопоставлено несколько значений. И ключами, и элементами могут выступать значения любого типа данных.
При создании объекта QMap
нужно указать, какого типа данных будут ключи и значения.
QMap <QString,QString> map; //Словарь с ключом-строкой и значением-строкой map["Piggy"]="+79131234567"; //ключ-имя,значение-телефон map["Kermit"]="+79539990203"; qDebug() << map;
Частый способ обращения к элементам словаря - использование ключа в операторе []
. Можно обойтись и без этого, так как ключ и значение можно получить через методы итератора key()
и value()
, например:
QMap <QString,QString> map; map["Piggy"]="+79131234567"; map["Kermit"]="+79539990203"; QMap <QString,QString>::iterator it; for (it = map.begin(); it != map.end(); ++it) { qDebug() << "Name:" << it.key() << "Phone:" << it.value(); }
Если же одному имени можно сопоставлять несколько телефонных номеров, поможет QMultiMap
:
QMultiMap <QString,QString> mmap; //Словарь, допускающий дублирование ключей mmap.insert("Mike","6400508"); mmap.insert("Sopha","3332702"); mmap.insert("Sopha","+79531170409"); qDebug() << mmap;
9. Хэши QHash и QMultiHash, в принципе, похожи на QMap
и QMultiMap
, но вместо сортировки по ключу они используют специальную хэш-таблицу, что позволяет искать ключи гораздо быстрее, чем на QMap
.
Как и в случае с QMap
, нужно соблюдать осторожность при использовании оператора индексации []
, так как задание ключа, для которого элемент не существует, приведет к тому, что элемент будет создан. Важно всегда проверять наличие элемента, привязанного к ключу при помощи метода contains()
контейнера.
QHash <int,QString> array; //Здесь ключами служат целые числа array[0]="Peppa"; array[10]="Kermit"; if (array.contains(10)) array[10]="TheFrog"; //Проверка, есть ли ключ 10 в хэше qDebug() << array;
Класс QMultiHash
унаследован от QHash
. Он позволяет размещать значения с одинаковыми ключами и, в целом, похож на QMultiMap
, но учитывает специфику своего родительского класса.
В завершение реализуем пример, который везде обсуждается, но нигде не приведён полностью :)
Если нужно разместить в QHash
объекты собственных классов, потребуется реализовать в классе оператор сравнения ==
и функцию qHash()
. Функция qHash()
при этом возвращает число, которое должно быть уникальным для каждого находящегося в хэше элемента.
#include <QtCore> class MyClass { //Класс с фамилией и именем QString _firstName,_secondName; public: MyClass(QString firstName=0,QString secondName=0) { //Конструктор _firstName=firstName; _secondName=secondName; } QString firstName() { return _firstName; } QString secondName() { return _secondName; } QString printable() { //строка для вывода объекта return _firstName+" "+_secondName; } bool operator== (MyClass &e2) { //Оператор сравнения return _firstName == e2.firstName() && _secondName == e2.secondName(); } uint qHash(MyClass key, uint seed) { //Функция qHash() return qHash(key.firstName(),seed) ^ qHash(key.secondName(),seed); } MyClass & operator = (MyClass *e2) { //Оператор присваивания _firstName = e2->firstName(); _secondName = e2->secondName(); return *this; } }; int main() { QHash <QString,MyClass> lst; //Ключом будет строка, элементом - объект класса lst["Piggy"] = new MyClass("Peppa","Piggy"); lst["Kermit"] = new MyClass("Kermit","TheFrog"); QHash <QString,MyClass>::iterator it = lst.begin(); for (;it != lst.end(); ++it) { qDebug() << "Key: " << it.key() << "Value:" << it.value().printable(); } return 0; }
Все примеры проверялись в консоли QT 5.X.
22.03.2018, 11:32 [10559 просмотров]