上节说到,循环队列: 它是队列的一个特殊,队头和队尾在逻辑上是连通的,环形队列同时也是非常高效的一种队列实现。在很多场景都非常有用。
本节将结合策略模式、工厂模式介绍环形队列的实现使用。
策略模式:处理一个问题时有多种算法,这些算法的输入输出参数一致,那么可以使用策略模式封装,并通过绑定不同的策略对象来执行算法,而这些对象的创建,您可以直接手动new出来,也可以设计“工厂模式”将创建对象,与使用对象分离开来,达到解耦的目的,但是,除非您的系统中要创建的对象很多,否则慎用“工厂模式;
工厂模式:当实现多态时,如果要创建派生类对象并赋值给抽象类指针,那么,我们需要知道派生类类名,这样,创建对象时,对象所属类(派生类)的名字就会硬编码到程序中的各个地方,导致要更换派生类对象的时候,需要在创建对象的地方修改其名称。如果使用工厂模式,只需要添加一个抽象工厂的派生类就可以扩展可用的派生类集合,因此,实现了解耦,且遵循开闭原则(对已有代码只读,可扩展更多的派生类来使用。)注意:不要在派生类数目已经确定的场景中使用”工厂模式“,否则,就是鸡肋。
定义抽象类和测试函数
抽象类描述了环形队列的接口和一些公共实现;接口都以纯虚函数实现;测试函数作为其友元函数,可以访问抽象类的成员方法,完成对接口功能测试。
circular_buffer.hpp
#include<iostream>
//entry
template<typename T_data>
struct queue_entry
{
T_data _data;
queue_entry(const T_data& data):_data(data){}
};
//node
template<typename T_data>
struct queue_node
{
queue_entry<T_data> _entry;
queue_node* _next;
queue_node(const queue_entry<T_data>& entry,queue_node* next) :_entry(entry),_next(next) {}
};
//抽象队列定义
template<typename T_data>
class abstract_queue
{
public:
abstract_queue(size_t capacity);
virtual ~abstract_queue();
size_t size() { return _size; }
size_t capacity() { return _capacity; }
bool empty() { return _size == 0; }
virtual void clear() = 0;//不再是直接销毁队列,而是将队列中元素全部改为无效。
virtual bool enqueue(const queue_entry<T_data>& entry) = 0;
virtual bool dequeue() = 0;
virtual queue_entry<T_data>* front() = 0;
virtual queue_entry<T_data>* rear() = 0;
private:
virtual void print() = 0;
friend void test_queue();
protected:
size_t _size, _capacity;
};
template<typename T_data>
inline abstract_queue<T_data>::abstract_queue(size_t capacity):
_size(0),_capacity(capacity)
{
std::cout << "abstract_queue" << std::endl;
}
template<typename T_data>
inline abstract_queue<T_data>::~abstract_queue()
{
std::cout << "~abstract_queue" << std::endl;
}
extern void test_queue();
二、测试函数
在测试函数中,抽象类指针分别绑定策略对象circular_queue_array和circular_queue_link的实例,然后调用抽象类中定义的接口。
circular_buffer.cpp
#include "circular_queue_array.hpp"
#include "circular_queue_link.hpp"
//测试
void test_queue()
{
abstract_queue<int>* queue = new circular_queue_array<int>(12);
for (size_t i = 0; i < 12; i++)
{
queue_entry<int> entry(i);
queue->enqueue(entry);
}
std::cout << "\nqueue_array size after enqueue " << queue->size()
<< ",front:" << queue->front()->_data
<< ",rear:" << queue->rear()->_data << std::endl;
queue->print();
size_t i;
for (i = 0; i < 4; i++)
{
queue->dequeue();
}
std::cout << "\nqueue_array size after dequeue " << queue->size()
<< ",front:" << queue->front()->_data
<< ",rear:" << queue->rear()->_data << std::endl;
queue->print();
for (; i > 0; i--)
{
queue_entry<int> entry(i);//4,3,2,1
queue->enqueue(entry);
}
std::cout << "\nqueue_array size after enqueue " << queue->size()
<< ",front:" << queue->front()->_data
<< ",rear:" << queue->rear()->_data << std::endl;
queue->print();
queue->clear();
std::cout << "\nqueue_array size after clear " << queue->size() << std::endl;
queue->print();
delete queue;
std::cout << "\n";
//circular_queue_link<int> link_queue;
//abstract_queue<int>* queue = &link_queue;//复习:绑定了编译期对象,对象销毁可以知道对象类型。
//queue->dequeue();
//复习:绑定了运行期对象,编译的时候无法知道基类指针指向对象类型,因此,delete时调用的是基类类型虚函数,
// 若基类使用虚析构函数,会结合虚表调用指向对象的析构函数,才能正确析构掉对象。
abstract_queue<int>* queue_link = new circular_queue_link<int>(12);
for (size_t i = 0; i < 12; i++)
{
queue_entry<int> entry(i);
queue_link->enqueue(entry);
}
std::cout << "\nqueue_link size after enqueue " << queue_link->size() << ",front:" << queue_link->front()->_data << ",rear:" << queue_link->rear()->_data << std::endl;
queue_link->print();
for (i = 0; i < 3; i++)
{
queue_link->dequeue();
}
std::cout << "\nqueue_link size after dequeue " << queue_link->size() << ",front:" << queue_link->front()->_data << ",rear:" << queue_link->rear()->_data << std::endl;
queue_link->print();
for (; i > 0; i--)
{
queue_entry<int> entry(i);
queue_link->enqueue(entry);
}
std::cout << "\nqueue_link size after enqueue " << queue_link->size() << ",front:" << queue_link->front()->_data << ",rear:" << queue_link->rear()->_data << std::endl;
queue_link->print();
queue_link->clear();
std::cout << "\nqueue_link size after clear " << queue_link->size() << std::endl;
queue_link->print();
delete queue_link;
}
三、派生出的策略对象
继承抽象队列,以数组形式实现环形队列,并且预先分配队列空间,当有空闲位置enqueue会成功,没有就抛出溢出异常,您需要捕获这个异常,否则有可能导致使用者的数据遭到覆盖。注意,在派生类构造函数体中,使用基类protected或者public成员时,若基类时模板,那么需要用this引用,或者以基类的作用域限定。
circular_queue_array.hpp
#pragma once
#include"circular_buffer.hpp"
template<typename T_data>
class circular_queue_array:public abstract_queue<T_data>
{
public:
static constexpr size_t MAX_CAPACITY = 128;
public:
circular_queue_array(size_t capacity = MAX_CAPACITY);
virtual ~circular_queue_array();
virtual void clear() override;
virtual bool enqueue(const queue_entry<T_data>& entry)override;
virtual bool dequeue()override;
queue_entry<T_data>* front()override;
queue_entry<T_data>* rear()override;
private:
virtual void print() override;
private:
int64_t _front, _rear;
std::shared_ptr<queue_entry<T_data>>* _array;
};
template<typename T_data>
inline circular_queue_array<T_data>::circular_queue_array(size_t capacity):
abstract_queue<T_data>(capacity),_front(-1),_rear(-1)
{
//基类是模板,需要用this来访问基类protected或者public成员。
if (this->_capacity > MAX_CAPACITY)
{
this->_capacity = MAX_CAPACITY;
}
_array = new std::shared_ptr<queue_entry<T_data>>[this->_capacity];
std::cout << "circular_queue_array\n";
}
template<typename T_data>
inline circular_queue_array<T_data>::~circular_queue_array()
{
std::cout << "~circular_queue_array\n";
delete[] _array;
}
template<typename T_data>
inline void circular_queue_array<T_data>::clear()
{
this->_size = 0;
}
template<typename T_data>
inline bool circular_queue_array<T_data>::enqueue(const queue_entry<T_data>& entry)
{
if (this->_size == this->_capacity)
{
//溢出异常,你不捕获,就崩给你看
throw std::runtime_error("the circular_buf is full warning!!!");
return false;
}
if (_rear == this->_capacity - 1)
{
_rear = 0;//数组末尾
}
else
{
_rear++;
}
_array[_rear] = std::make_shared<queue_entry<T_data>>(entry);
if (this->_size == 0)//首元素,队列空,_front无效
{
_front = _rear ;//_front指向首元素位置
}
this->_size++;
return true;
}
template<typename T_data>
inline bool circular_queue_array<T_data>::dequeue()
{
if (this->_size > 0)
{
if (_front == this->_capacity - 1)//最后一个下标
{
_front = 0;
}
else
{
_front++;
}
this->_size--;
}
return true;
}
template<typename T_data>
inline queue_entry<T_data>* circular_queue_array<T_data>::front()
{
if (this->_size > 0)
{
std::shared_ptr<queue_entry<T_data>>& ptr_entry = _array[_front];
return ptr_entry.get();
}
return nullptr;
}
template<typename T_data>
inline queue_entry<T_data>* circular_queue_array<T_data>::rear()
{
if (this->_size > 0)
{
std::shared_ptr<queue_entry<T_data>>& ptr_entry = _array[_rear];
return ptr_entry.get();
}
return nullptr;
}
template<typename T_data>
inline void circular_queue_array<T_data>::print()
{
int64_t idx = _front;
size_t count = this->_size;
while (count>0)//_front,_rear有效
{
std::cout << _array[idx]->_data << ",";
//下一个位置
if (idx == this->_capacity - 1)
{
idx = 0;
}
else
{
idx++;
}
count--;
}
}
继承抽象队列,以链表形式实现环形队列。链表容量刚开始是0,当增长到设置容量后,容量不在增长,当有空闲位置enqueue会成功,没有就抛出溢出异常,您需要捕获这个异常,否则有可能导致使用者的数据遭到覆盖。注意,在派生类构造函数体中,使用基类protected或者public成员时,若基类时模板,那么需要用this引用,或者以基类的作用域限定。
circular_queue_link.hpp
#pragma once
#include "circular_buffer.hpp"
template<typename T_data>
class circular_queue_link : public abstract_queue<T_data>
{
public:
static size_t constexpr MAX_CAPACITY = 1024 * 10;
public:
circular_queue_link(size_t capacity = MAX_CAPACITY);
virtual ~circular_queue_link();
virtual void clear() override;
virtual bool enqueue(const queue_entry<T_data>& entry) override;
virtual bool dequeue() override;
queue_entry<T_data>* front() override;
queue_entry<T_data>* rear() override;
private:
virtual void print() override;
private:
queue_node<T_data>* _front, * _rear;
size_t _cur_capacity;
};
//容量初始化0
template<typename T_data>
inline circular_queue_link<T_data>::circular_queue_link(size_t capacity):
abstract_queue<T_data>(capacity),_cur_capacity(0), _front(nullptr),_rear(nullptr)
{
std::cout << "circular_queue_link\n";
}
template<typename T_data>
inline circular_queue_link<T_data>::~circular_queue_link()
{
if (this->_cur_capacity > 0)
{
while (_front)
{
queue_node<T_data>* next = _front->_next;
this->_cur_capacity--;
if (next == _front)
{
delete next;
break;
}
else
{
_front->_next = next->_next;
delete next;
}
}
}
std::cout << "~circular_queue_link\n";
}
template<typename T_data>
inline void circular_queue_link<T_data>::clear()
{
this->_size = 0;
}
template<typename T_data>
inline bool circular_queue_link<T_data>::enqueue(const queue_entry<T_data>& entry)
{
//当前容量一直增加直到等于设置的容量
if (_cur_capacity < this->_capacity)
{
queue_node<T_data>* node = new queue_node<T_data>(entry, nullptr);
if (_rear == nullptr)
{
node->_next = node;
_front = node;//首元素
}
else
{
node->_next = _rear->_next;
_rear->_next = node;
}
_rear = node;
_cur_capacity++;
}
else
{//容量稳定后
if (this->_size == _cur_capacity)
{
//溢出异常,你不捕获,就崩给你看
throw std::runtime_error("the circular_buf is full warning!!!");
return false;
}
//插入位置
_rear = _rear->_next;
_rear->_entry = entry;
if (this->_size == 0)
{
_front = _rear;//首元素
}
}
this->_size++;
return true;
}
template<typename T_data>
inline bool circular_queue_link<T_data>::dequeue()
{
if (this->_size > 0)
{
_front = _front->_next;
this->_size--;
return true;
}
return false;
}
template<typename T_data>
inline queue_entry<T_data>* circular_queue_link<T_data>::front()
{
if (this->_size > 0)
{
return &_front->_entry;
}
return nullptr;
}
template<typename T_data>
inline queue_entry<T_data>* circular_queue_link<T_data>::rear()
{
if (this->_size > 0)
{
return &_rear->_entry;
}
return nullptr;
}
template<typename T_data>
inline void circular_queue_link<T_data>::print()
{
queue_node<T_data>* p_node = _front;
while (this->_size > 0)//_front,_rear有效
{
std::cout << p_node->_entry._data << ",";
if (p_node == _rear)
{
break;
}
//打印下一个节点
p_node = p_node->_next;
}
}
四、执行测试
#include <iostream>
#include "circular_buffer.hpp"
int main()
{
test_queue();
}
五、结果
虽然线性结构及算法使用简单,方便,但对于搜索,数组、链表的时间复杂度达到O(N),而栈、队列不支持搜索;插入时间复杂度方面,数组、链表也都是O(N),而数组插入最坏情况下,精确来说步骤在2N,但差别还是在同一数量级O(N),而栈、队列的插入效率极高,在O(1),但不支持随机插入。
因此,线性数据结构的缺憾导致我们必须研究一些二维的数据结构,比如二维表(树、图),来满足对大数据量输入时,能够高效,并随即插入删除查找。