C++数据结构(队列)

Posted N3ptune

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++数据结构(队列)相关的知识,希望对你有一定的参考价值。

队列是先进先出的线性表

顺序队列

顺序存储,使用一段连续的内存空间去依次存储队列中的数据

代码实现:

#include <iostream>

#define MaxSize 10

template <typename T>
class SeqQueue 
public:
    SeqQueue();
    ~SeqQueue();

public:
    bool EnQueue(const T& e);
    bool DeQueue(T &e);
    bool GetHead(T &e);
    void ClearQueue();
    
    void DispList();
    int ListLength();
    bool IsEmpty();
    bool IsFull();

private:
    T *m_data;
    int m_front;
    int m_rear;
;

template <typename T>
SeqQueue<T>::SeqQueue() 
    m_data = new T[MaxSize];
    m_front = 0;
    m_rear = 0;


template <typename T>
SeqQueue<T>::~SeqQueue() 
    delete[] m_data;


template <typename T>
bool SeqQueue<T>::EnQueue(const T &e) 
    if (IsFull() == true) 
        std::cout << "SeqQueue Full" << std::endl;
        return false;
    
    m_data[m_rear] = e;
    m_rear++;
    return true;


template <typename T>
bool SeqQueue<T>::DeQueue(T &e) 
    if (IsEmpty() == true) 
        std::cout << "SeqQueue Empty" << std::endl;
        return false;
    
    e = m_data[m_front];
    m_front++;
    return true;


template <typename T>
bool SeqQueue<T>::GetHead(T &e) 
    if (IsEmpty() == true) 
        std::cout << "SeqQueue Empty" << std::endl;
        return false;
    
    e = m_data[m_front];
    return true;


template <class T>
void SeqQueue<T>::DispList() 
    for (int i = m_front; i < m_rear; i++) 
        std::cout << m_data[i] << " ";
    
    std::cout << std::endl;


template <class T>
int SeqQueue<T>::ListLength() 
    return m_rear - m_front;


template <class T>
bool SeqQueue<T>::IsEmpty() 
    if (m_front == m_rear) 
        return true;
    
    return false;


template <class T>
bool SeqQueue<T>::IsFull() 
    if (m_rear >= MaxSize) 
        return true;
    
    return false;


template <class T>
void SeqQueue<T>::ClearQueue() 
    m_front = m_rear = 0;


int main(void) 
    SeqQueue<int> seqobj;
    seqobj.EnQueue(150);
    seqobj.EnQueue(200);
    seqobj.EnQueue(300);
    seqobj.EnQueue(400);
    seqobj.DispList();

    return 0;

链式队列

如果长度不确定,那么可以使用链式队列

#include <iostream>

template <typename T>
struct QueueNode 
    T data;
    QueueNode<T> *next;
;

template <typename T>
class LinkQueue 
public:
    LinkQueue();
    ~LinkQueue();
public:
    bool EnQueue(const T &e);
    bool DeQueue(T &e);
    bool GetHead(T &e);

    void DispList();
    int ListLength();
    bool IsEmpty();
private:
    QueueNode<T> *m_front;
    QueueNode<T> *m_rear;
    int m_length;
;

template <typename T>
LinkQueue<T>::LinkQueue() 
    m_front = new QueueNode<T>;
    m_front->next = nullptr;
    m_rear = m_front;
    m_length = 0;


template <typename T>
LinkQueue<T>::~LinkQueue() 
    QueueNode<T> *pnode = m_front->next;
    QueueNode<T> *ptmp;
    while (pnode != nullptr) 
        ptmp = pnode;
        pnode = pnode->next;
        delete ptmp;
    
    delete m_front;
    m_front = m_rear = nullptr;
    m_length = 0;


template <typename T>
bool LinkQueue<T>::EnQueue(const T &e) 
    QueueNode<T> *node = new QueueNode<T>;
    node->data = e;
    node->next = nullptr;

    m_rear->next = node;
    m_rear = node;
    m_length++;

    return true;


template <typename T>
bool LinkQueue<T>::DeQueue(T &e) 
    if (IsEmpty() == true) 
        std::cout << "LinkQueue Empty" << std::endl;
        return false;
    
    QueueNode<T> *p_willdel = m_front->next;
    e = p_willdel->data;

    m_front->next = p_willdel->next;
    if (m_rear == p_willdel) 
        m_rear = m_front;
    

    delete p_willdel;
    m_length--;
    return true;


template <typename T>
bool LinkQueue<T>::GetHead(T &e) 
    if (IsEmpty() == true) 
        std::cout << "Link Queue Empty" <<std::endl;
        return false;
    
    e = m_front->next->data;
    return true;


template <class T>
void LinkQueue<T>::DispList() 
    QueueNode<T> *p = m_front->next;
    while (p != nullptr) 
        std::cout << p->data << " ";
        p = p->next;
    
    std::cout << std::endl;


template <class T>
int LinkQueue<T>::ListLength() 
    return m_length;


template <class T>
bool LinkQueue<T>::IsEmpty() 
    if (m_front == m_rear) 
        return true;
    
    return false;


int main(void) 
    LinkQueue<int> lnobj;
    lnobj.EnQueue(150);

    int eval2 = 0;
    lnobj.DeQueue(eval2);
    lnobj.EnQueue(200);
    lnobj.EnQueue(700);
    lnobj.DispList();

    return 0;

c++缓冲池循环队列实现

承接上周任务,目的效果是写线程将数据写入缓冲区,读线程获取缓冲区中的数据。

缓冲区

数据结构

所谓缓冲区,就是开辟一段内存空间来保存数据,主要包括的属性为储存数据的内存空间,缓冲区长度,已使用的长度。对应的方法为将数据写入缓冲区,从缓冲区中读入数据,设置已写入的缓冲区长度。所建立的数据结构为:

class Buffer {
private:
    USHORT* buffer;    // 缓冲区
    int maxSize;    // 缓冲区最大长度
    int effectiveSize;    // 已经使用长度
public:
    Buffer(int bufferSize);        // 设置缓冲区大小

    void setEffectiveSize(int size);    // 设置缓冲区已用数据

    void write(std::function<int(USHORT*, int)> const& writeBuffer);    // 将数据写入缓冲区

    void read(std::function<void(USHORT*, int, int)> const& readBuffer);    // 从缓冲区中读取数据

    ~Buffer();
};

这里write方法和read方法接受的参数为c++11定义的lambda表达式。例std::function<int(USHORT*, int)> const& writeBuffer代表传入一个lambda表达式,接受参数为USHORT*, int

具体实现

  1. 构造函数实现:
    将缓冲区大小作为参数传递给构造函数,在构造函数中申请内存空间,并设置相应属性。
  2. writeread函数实现
    将缓冲区数据和缓冲区长度作为参数传入lambda表达式参数并调用。
Buffer::Buffer(int size) {
    this->maxSize = size;
    this->effectiveSize = 0;
    this->buffer = new USHORT[size];
}

void Buffer::setEffectiveSize(int size) {
    this->effectiveSize = size;
}

/**
* writeBuffer: lambda表达式,接受参数为  USHORT*:缓冲区数据 int:缓冲区最大长度, 返回int:写入数据的长度
*/
void Buffer::write(std::function<int(USHORT*, int)> const& writeBuffer) {
    this->effectiveSize = writeBuffer(this->buffer, this->maxSize);
}

/**
* readBuffer: lambda表达式,接受参数为  USHORT*:缓冲区数据 int:缓冲区有效长度 int:缓冲区最大长度  返回void
*/
void Buffer::read(std::function<void(USHORT*, int, int)> const& readBuffer) {
    readBuffer(this->buffer, this->effectiveSize, this->maxSize);
}

对于同一个缓冲区,writeread操作因该是互斥的,否则就会导致数据错乱。因此需要信号量的实现来保证writeread互斥。

信号量实现

关于c++信号量可参考这篇文章:C++ 并发编程(六):信号量(Semaphore)

class Semaphore {
private:
    std::mutex mutex;    // 互斥量
    std::condition_variable cv;    // 条件变量
    int count;    // 可用资源数
public:
    Semaphore(int count = 0);

    void singal();    // 释放一个资源
        
    void wait();    // 等待一个资源
};

Semaphore::Semaphore(int count) {
    if (count < 0) { throw "可用资源不能小于0"; }
    this->count = count;
}

/**
* 释放资源
*/
void Semaphore::singal() {
    std::unique_lock<std::mutex> lock(this->mutex);
    ++this->count;
    this->cv.notify_one();
}

/**
* 申请资源
*/
void Semaphore::wait() {
    std::unique_lock<std::mutex> lock(this->mutex);
    this->cv.wait(lock, [=] { return count > 0; });        // reutrn true时往下执行
    --this->count;
}

完善缓冲区

在缓冲区数据结构中加入信号量:

class Buffer {
private:
    ......
    Semaphore* sem;        // 使用信号量保证缓冲区使用互斥
};

writeread方法中使用信号量实现互斥。

Buffer::Buffer(int size) {
    .......
    this->sem = new Semaphore(1);
}

/**
* 将数据写入缓冲区,互斥操作
* writeBuffer: lambda表达式,接受参数为  USHORT*:缓冲区数据 int:缓冲区最大长度, 返回int:写入数据的长度
*/
void Buffer::write(std::function<int(USHORT*, int)> const& writeBuffer) {
    this->sem->wait();
    this->effectiveSize = writeBuffer(this->buffer, this->maxSize);
    this->sem->singal();
}

/**
* 读取缓冲区数据,互斥操作
* readBuffer: lambda表达式,接受参数为  USHORT*:缓冲区数据 int:缓冲区有效长度 int:缓冲区最大长度  返回void
*/
void Buffer::read(std::function<void(USHORT*, int, int)> const& readBuffer) {
    this->sem->wait();
    readBuffer(this->buffer, this->effectiveSize, this->maxSize);
    this->sem->singal();
}

缓冲池

缓冲池实际上就是缓冲区的集合。通过缓冲池来分配缓冲区。

数据结构

/**
* 缓冲池定义,存储并分配缓冲区
*/
class BufferPool {
private:
    int head, tail;    // 头尾指针
    Buffer** buffers;    // 缓冲池
    int total, lenth;    // 缓冲区总个数和以使用个数

public:
    BufferPool(int count = 10, int bufferSize = DEFAULT_BUFFER_SIZE);  // 构造函数  count:缓冲区个数  bufferSize:缓冲区大小

    Buffer* getBuffer();    // 获取一个缓冲区

    Buffer* popBuffer();    // 取得头缓冲区并弹出

    bool empty();        // 缓冲池是否为空

    bool full();        // 缓冲池是否已满

    ~BufferPool();

};

具体实现

要保证按数据写入的顺序读出,应该把缓冲池设计为队列,保证读取时缓冲区总是最先写入的,同时写入时获得的缓冲区是最末尾的缓冲区。同时为了保证缓冲区循环利用,将缓冲池设计为循环队列。

对于循环队列,当头指针和尾指针相等时,有两种情况。一种是队列为空(未分配缓冲区),另一种是队列已满(所有缓冲区都被分配)。解决方法一般有两种,第一种是牺牲一个存储空间,当尾指针指向的下一位为头指针时,即队列为满。另一种是增加标志位来判断当头尾指针相同时,当前队列的状态。

由于本项目一个缓冲区设置空间较大,所以采用第二种方法,增加lenth属性表示当前使用的缓冲区个数,用来判断队列为空或满。

BufferPool::BufferPool(int count, int bufferSize) {
    this->head = 0;
    this->tail = 0;
    this->lenth = 0;
    this->total = count;

    this->buffers = new Buffer*[count];
    for (int i = 0; i < count; i ++) {
        this->buffers[i] = new Buffer(bufferSize);
    }
}

/**
* 获取一个缓冲区 当缓冲池已满时,覆盖旧数据
*/
Buffer* BufferPool::getBuffer() {
    Buffer* buffer = this->buffers[this->tail];
    // tail指针指向下一个缓冲区,如果当前缓冲池已满,头指针下移
    this->tail = (this->tail + 1) % this->total;
    this->lenth++;
    if (this->lenth > this->total) {
        this->head = (this->head + 1) % this->total;
        this->lenth = this->total;
    }
    return buffer;
}

/**
* 获取头缓冲区并弹出
*/
Buffer* BufferPool::popBuffer() {
    if (this->lenth == 0) { throw "缓冲池为空"; }
    Buffer* buffer = this->buffers[this->head];
    this->head = (this->head + 1) % this->total;
    this->lenth--;
    return buffer;
}

BufferPool::~BufferPool() {
    for (int i = 0; i < this->total; i ++) {
        delete this->buffers[i];
        this->buffers[i] = NULL;
    }
    delete this->buffers;
    this->buffers = NULL;
}

bool BufferPool::empty() {
    return this->lenth == 0;
}

bool BufferPool::full() {
    return this->lenth == this->total;
}

测试

按照上次汇报,从缓冲池中获取缓冲区,并将数据写入缓冲区。

// 用缓冲池来保存数据
BufferPool* bufferPool = new BufferPool();
// 获取一个缓冲区并将AD数据写入
bufferPool->getBuffer()->write([&](USHORT* buffer, int maxSize) {
    if (!ACTS1000_ReadDeviceAD(hDevice, buffer, maxSize, &nRetSizeWords, &nAvailSampsPoints, 5.0)) // 采集数据,将数据保存到ADBuffer中,nRetSizeWords代表实际共读取了多少个点
    {
        printf("ReadDeviceDmaAD error...\\n");
        _getch();
    }
    return nRetSizeWords;
});

获取缓冲区数据:

while (!bufferPool->empty()) {
    bufferPool->popBuffer()->read([&](USHORT* ADBuffer, int eff, int maxSize) {
        for (int Index = 0; Index < 2; Index++)
        {
            printf("%d:%hu", Index, ADBuffer[Index]);
        }
    });
}

以上是关于C++数据结构(队列)的主要内容,如果未能解决你的问题,请参考以下文章

C++数据结构——顺序队列(基本代码实现与案例)

怎么定义队列类模板(c++)

剑指offer(C++)-JZ9:用两个栈实现队列(数据结构-队列 & 栈)

C++ 多线程框架:消息队列

用C++的类做三种优先队列的实现

在 C++ 中使用队列构造二叉树