Разработка графического интерфейса с помощью библиотеки Qt3 | ||
---|---|---|
Пред. | Глава 11. Контейнерные классы. | След. |
Кроме STL-подобных контейнеров, Qt предоставляет еще целый ряд контейнерных классов. Они были разработаны в начале 90-х годов прошлого века для Qt 1.0, еще до того, как STL стала частью C++, и потому имеют свой характерный синтаксис. Поскольку эти классы оперируют указателями на объекты, их часто называют контейнерами указателей (pointer-based containers), в противоположность более современным контейнерам значений (value-based containers) Qt и STL. В Qt 4 контейнеры указателей еще останутся, для сохранения совместимости, но их использование не будет приветствоваться.
Контейнеры указателей сохраняют свою актуальность лишь благодаря тому, что в Qt 3 еще имеется ряд немаловажных функций, которые работают с ними. Один пример мы приводили в Главе 3, когда выполняли итерации по виджетам, второй -- в Главе 6, когда выполняли итерации по окнам в MDI-приложении.
Основными контейнерами указателей являются классы QPtrVector<T>, QPtrList<T>, QDict<T>, QAsciiDict<T>, QIntDict<T> и QPtrDict<T>.
Класс QPtrVector<T> предназначен для хранения вектора указателей. Ниже приводится пример создания QPtrVector<Film> с пятью элементами:
QPtrVector<Film> films(5);
films.setAutoDelete(true);
films.insert(0, new Film(4812, "A Hard Day's Night", 85));
films.insert(1, new Film(5051, "Seven Days to Noon", 94));
films.insert(2, new Film(1301, "Day of Wrath", 105));
films.insert(3, new Film(9227, "A Special Day", 110));
films.insert(4, new Film(1817, "Day for Night", 116));
Класс QPtrVector<T> не имеет
функции append(), поэтому приходится явно
указывать индекс для добавляемых элементов. В этом примере использована
первая версия класса Film, которая содержит
переменную-член -- числовой идентификатор фильма.Контейнеры указателей в Qt обладают одним замечательным свойством -- "auto-delete" (автоматическое удаление). Если автоудаление разрешено, Qt становится владельцем всех объектов, вставляемых в контейнер и удаляет их автоматически, когда удаляется контейнер (или при вызове методов remove() и clear()).
Для исключения элемента из вектора, должна вызываться функция remove(), с указанием индекса удаляемого элемента:
films.remove(2);
Эта функция не изменяет размер вектора, она просто обнуляет
указатель с заданным индексом. Если разрешено автоудаление, то
автоматически удаляется объект, на который указывал элемент
вектора.Чтобы обойти все элементы вектора в цикле, можно просто использовать индексы:
for (int i = 0; i < (int)films.count(); ++i) {
if (films[i])
cerr << films[i]->title().ascii() << endl;
}
В данном примере сначала выполняется проверка указателя
(указатель не должен быть пустым), а затем выполняются все необходимые
действия над указателем.Класс QPtrList<T> предназначен для хранения списка указателей. Добавление новых элементов в QPtrList<T> производится функциями append(), prepend() и insert():
QPtrList<Film> films;
films.setAutoDelete(true);
films.append(new Film(4812, "A Hard Day's Night", 85));
films.append(new Film(5051, "Seven Days to Noon", 94));
Список указателей имеет "текущий" элемент, значение
которого изменяется функциями навигации по списку, такими как
first(), next(), prev() и last(). Один из способов выполнения прохода по
списку:
Film *film = films.first();
while (film) {
cerr << film->title().ascii() << endl;
film = films.next();
}
Однако списки допускают доступ к элементам по индексу:
for (int i = 0; i < (int)films.count(); ++i)
cerr << films.at(i)->title().ascii() << endl;
Третий возможный вариант обхода списка, заключается в
использовании QPtrListIterator<T>.Классы QDict<T>, QAsciiDict<T>, QIntDict<T> и QPtrDict<T> являются близкими эквивалентами map<K, T>. Эти классы так же хранят пары "ключ-значение". Ключ в них может быть представлен одним из четырех типов: QString, const char *, int и void *, в зависимости от типа используемого класса. Поскольку все четыре класса предоставляют одинаковую функциональность, мы рассмотрим только один из них -- QIntDict<T>.
Для демонстрации воспользуемся второй версией класса Film, которая использовалась ранее, совместно с классом map<K, T>.
QIntDict<Film> films(101);
films.setAutoDelete(true);
Конструктору передается число, используемое классом для
определения количества памяти, которую нужно выделить под элементы
словаря. Для улучшения производительности, это число должно быть
простым и немного больше, чем количество элементов, которое
предполагается вставить в словарь. Список простых чисел, меньших 10
000, вы найдете по адресу: http://doc.trolltech.com/3.2/primes.html.Вставка нового элемента выполняется функцией insert(), которой передаются ключ и значение:
films.insert(4812, new Film("A Hard Day's Night", 85));
films.insert(5051, new Film("Seven Days to Noon", 94));
Для доступа к элементу словаря можно использовать функцию
find() или оператор "[ ]". Для
удаления элемента -- функцию remove(). Для
изменения значения, ассоциированного с заданным ключом --
replace().Если функция insert() вызывается несколько раз с одним и тем же ключом, доступ будет иметься только к значению, которое было вставлено последним. При вызове remove(), элементы удаляются в обратном порядке. Чтобы избежать вставки нескольких значений с одим и тем же ключом, используйте replace() вместо insert().
Обход элементов контейнера может быть выполнен с помощью итератора:
QIntDictIterator<Film> it(films);
while (it.current()) {
cerr << it.currentKey() << ": "
<< it.current()->title().ascii() << endl;
++it;
}
Текущий ключ итератора может быть получен вызовом currentKey(), а текущее значение -- функцией
current(). Порядок следования элементов в
словаре не определен.Для хранения элементов базовых типов языка C++ (int, double и т.п) и структур, Qt предоставляет специальный, вектор-подобный класс QMemArray<T>. В некоторых приложениях он может использоваться напрямую, однако, чаще используются два производных класса QByteArray (QMemArray<char>) и QPointArray (QMemArray<QPoint>). Мы уже использовали их несколько раз в предыдущих главах.
Ниже приводится пример создания QByteArray:
QByteArray bytes(4);
bytes[0] = 'A';
bytes[1] = 'C';
bytes[2] = 'D';
bytes[3] = 'C';
При создании экземпляра QMemArray<T>, необходимо либо сразу указать
начальный размер будущего массива, либо вызвать функцию resize() после создания. Доступ к элементам массива
выполняется с помощью оператора "[ ]":
for (int i = 0; i < (int)bytes.size(); ++i)
cerr << bytes[i] << endl;
Поиск элемента в массиве осуществляется с помощью функции
QMemArray<T>::find():
if (bytes.find( A ) != -1)
cerr << "Found" < endl;
Иногда программисты забывают об одной особенности класса
QMemArray<T> и его производных -- они
используют то, что называется explicitly shared (явное совместное
использование данных). Это означает, что созданные копии объекта (с
помощью конструктора копирования или оператором присваивания) ссылаются
на одни и те же данные. Когда данные модифицируются с помощью одного
объекта, изменения будут видны в другом. Не следует путать явное
совместное использование данных (explicitly shared) с неявным совместным
использованием данных (implicitly shared), которое лишено данной
проблемы.Избежать описанной проблемы несложно, для этого достаточно выполнить полное копирование объекта вызовом copy():
duplicate = bytes.copy();
Теперь два объекта будут ссылаться на различные наборы
данных.Скорее всего, в Qt 4, предпочтение будет отдано классу QValueVector<T>, а классы QByteArray и QPointArray станут его производными.
Пред. | В начало | След. |
Словари (map). | На уровень выше | Классы QString и QVariant. |