БлогNot. QT: о контейнерах – просто

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 [9949 просмотров]


теги: учебное список программирование c++ qt

К этой статье пока нет комментариев, Ваш будет первым