БлогNot. Qt: делаем кольцевой буфер

Qt: делаем кольцевой буфер

В библиотеке Tulip, как и в stl, есть практически все нужные контейнеры для реализации типовых динамических структур данных. Конечно, иногда приходится изменять или расширять функционал контейнера, и самый естественный путь сделать это - написать класс-потомок, модифицирующий или дополняющий базовый функционал класса-родителя.

В качестве примера сделаем из стандартной очереди QQueue кольцевой буфер CQueue, отличающийся тем, что ёмкость очереди ограничена заданным натуральным значением count. Если при добавлении элементов предельная ёмкость превышена, из буфера удаляется самый старый элемент.

Чтобы решение было достаточно универсальным, реализуем шаблон класса CQueue, полный исходник с комментариями - ниже:

#include <QCoreApplication>
#include <QQueue>
#include <QDebug>
#include <iostream>
using namespace std;

//Кольцевой буфер на основе стандартной очереди
template <class T>
class CQueue : public QQueue <T> {
 //как наследоваться от стандартного контейнера
 //для любого типа данных T
 private:
  int count; //предельный размер
 public:
  inline CQueue (int cnt) : QQueue<T>(),count(cnt) {
   //конструктор
  }
  inline void enqueue (const T &t) {
   //перегрузка метода постановки в очередь
   if (count==QQueue<T>::count()) {
    QQueue<T>::dequeue();
    //удалить старейший элемент по заполнении
   }
   QQueue<T>::enqueue(t);
  }
};

int main() {
 const int size=5; //размер буфера
 CQueue <int> q(size);
 for (int i=0; i<2*size; i++) q.enqueue(i+1);
  //заносим в буфер вдвое больше элементов,
  //чем он вмещает
 qDebug() << "Size=" << q.size();
 for (int i=0; i<size; i++) qDebug() << q.dequeue();
  //осталась только последняя половина элементов
 cin.sync(); cin.get(); return 0;
}

Как видно из кода, нам пришлось модифицировать только конструктор и метод постановки в очередь родительского класса.

P.S. Показанный ниже шаблон кольцевого буфера пригодился мне при "ручном" решении задачи, аналогичной поставленной в статье, поэтому добавлю листинг сюда.

Конструктору основного класса queue должны быть переданы размерность буфера _cnt (больше единицы) и значение _unacceptable типа T, которое будет возвращаться при невозможности получить элемент (например, при попытке извлечь значение из уже пустого буфера или получить значение по недопустимой позиции в буфере).

Показана работа с буфером из целых чисел и буфером из объектов демо-класса Data.

В классе должны быть определены конструктор по умолчанию и оператор new. Оператор << определён просто для удобства вывода объектов класса в консоль.

Проверено в актуальных сборках "Студии" 2019/2022.

#include <iostream>
#include <cstdlib>
using namespace std;

//Основной шаблон
template <class T> class queue {
 size_t cnt, head, tail, len;
 T* data;
 T unacceptable;
 size_t next(size_t pos) {
  return (pos + 1) % cnt;
 }
public:
 queue(size_t _cnt, T _unacceptable) {
  if (_cnt > 1) {
   cnt = _cnt; data = new T[cnt];
  }
  else {
   cnt = 0; data = nullptr;
  }
  unacceptable = _unacceptable;
  head = cnt - 1;
  tail = len = 0;
 }
 ~queue() { if (data) delete[] data; }
 bool empty () { return len == 0; }
 bool full() { return len == cnt; }
 size_t length () { return len; }
 T get() {
  if (!empty()) {
   T t = data[tail];
   tail = next(tail);
   len--;
   return t;
  }
  return unacceptable;
 }
 void put(const T& t) {
  if (data) {
   head = next (head);
   data[head] = t;
   if (full()) tail = next(tail);
   else len++;
  }
 }
 T at(int i) {
  if (data) {
   if (i > -1 && i < cnt) return data[i];
   else return unacceptable;
  }
  return unacceptable;
 }
};

//Просто класс для демонстрации
class Data {
 int n;
public:
 Data(int _n = 0) { n = _n; }
 void* operator new (size_t n) {
  return new Data [n];
 }
 friend ostream& operator << (ostream& out, Data d) {
  return out << d.n;
 }
};

int main(void) {
 //Кольцевой буфер n целых чисел
 const int n = 5;
 queue <int> d(n,0);
 for (int i = 1; i <= 2*n; i++) d.put(i);
 cout << "After adding of " << 2*n << " int item(s) in buffer from " 
  << n << " position(s)" << endl;
 for (int i = 0; i < n; i++) cout << d.at(i) << ' ';
 cout << endl << "Getting of " << n << " item(s):" << endl;
 for (int i = 0; i < n; i++) cout << d.get() << ' ';
 cout << endl << "After retrieving of " << n << " items length = " << 
  d.length() << endl;
 d.put(11);
 d.put(12);
 cout << "Put 2 items, 11 and 12" << endl;
 cout << "Get 1 item: " << d.get() << endl;
 cout << "Length = " << d.length() << endl;

 //Буфер из m объектов класса Data
 Data a(1),b(2),c(3);
 const int m = 2;
 Data bad(-1);
 queue <Data> d2(m, bad);
 d2.put(a); d2.put(b); d2.put(c);
 cout << endl << "Put 3 Data items, 1, 2 and 3 " << 
  "in buffer from 2 positions" << endl;
 for (int i = 0; i < m; i++) cout << d2.at(i) << ' ';
 cout << endl << "Length of d2 = " << d2.length() << endl;
 cout << "Get item " << d2.get() << endl;
 cout << "Length of d2 = " << d2.length() << endl;
 cout << "Get item " << d2.get() << endl;
 cout << "Length of d2 = " << d2.length() << endl;
 d2.put(11);
 cout << "Put 1 item, 11" << endl;
 cout << "Length of d2 = " << d2.length() << endl;
 cout << "Trying to extract another 2 elements" << endl;
 Data t = d2.get();
 t = d2.get();
 cout << "Last result is " << t <<  endl;
 cout << "Length of d2 = " << d2.length() << endl;

 return 0;
}

Вывод этого кода в консоль:

After adding of 10 int item(s) in buffer from 5 position(s)
6 7 8 9 10
Getting of 5 item(s):
6 7 8 9 10
After retrieving of 5 items length = 0
Put 2 items, 11 and 12
Get 1 item: 11
Length = 1

Put 3 Data items, 1, 2 and 3 in buffer from 2 positions
3 2
Length of d2 = 2
Get item 2
Length of d2 = 1
Get item 3
Length of d2 = 0
Put 1 item, 11
Length of d2 = 1
Trying to extract another 2 elements
Last result is -1
Length of d2 = 0

Разумеется, на основе стандартной очереди queue из STL мы тоже могли сделать кольцевой буфер с меньшими усилиями, как и для Qt в начале статьи.

Здесь мы тоже создаём класс-наследник от стандартной очереди и модифицируем только метод постановки в очередь с учётом ограниченного размера буфера.

#include <iostream>
#include <queue>
using namespace std;

//Кольцевой буфер на основе стандартной очереди
template <class T> class cqueue : public queue <T> {
 //как наследоваться от стандартного контейнера для любого типа данных T
private:
 int count; //предельный размер
public:
 cqueue(int cnt) : queue<T>(), count(cnt) {
  //конструктор
 }
 void push(const T& t) { //перегрузка метода постановки в очередь
  if (count == queue<T>::size()) {
   queue<T>::pop(); //удалить старейший элемент по заполнении
  }
  queue<T>::push(t);
 }
};

int main() {
 const int size = 5; //размер буфера
 cqueue <int> q(size);
 for (int i = 0; i < 2 * size; i++) {
  q.push(i + 1);
  //заносим в буфер вдвое больше элементов, чем он вмещает
 }
 cout << "Size=" << q.size() << endl;
 for (int i = 0; i < size; i++) { //осталась только последняя половина элементов
  cout << q.front() << ' ';
  q.pop();
 }
 //буфер пуст, добавим ещё 2 элемента и проверим размер:
 q.push(11);
 q.push(12);
 cout << endl << "Size=" << q.size() << endl;
 return 0;
}

/*
Вывод этого кода в консоль:

Size=5
6 7 8 9 10
Size=2
*/

25.03.2016, 14:40 [9149 просмотров]


теги: qt список c++ алгоритм studio

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