The OpenNET Project / Index page

[ новости /+++ | форум | wiki | теги | ]

Каталог документации / Раздел "Программирование, языки" / Оглавление документа

11.4. Контейнеры указателей.

Кроме 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 станут его производными.




Спонсоры:
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

Закладки на сайте
Проследить за страницей
Created 1996-2022 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру