前言:大家用过队列吗?普通队列的性能瓶颈是什么呢? "假溢出" 和频繁内存移动的问题成为了普通队列绕不开的性能瓶颈。而环形队列凭借 "空间复用 + O (1) 读写" 的特性,成为解决这一问题的最优解 。无论是中断服务函数(ISR)中的数据缓存,还是网络 IO 的数据包处理,几乎都能看到它的身影。今天,我们从原理到实例,解读一下C++环形队列。
目录
一、什么是环形队列
环形队列 ,又称循环队列,它是一种++线性数据结构++ 。它基于普通的队列(先进先出,FIFO),用**数组模拟环状结构,**在存储结构上表现就像一个"环形"。

为什么需要它?
来看看普通顺序队列的缺陷:
它存在两个指针,队头(front)与队尾指针(rear)。队头指针始终指向队头元素,队尾指针始终指向队尾元素的下一位置。

当rear等于队列总长时,说明队列已经 "满" 了。此时若再执行入队操作,便会出现队满 "溢出"。然而,由于在此之前可能已经执行了若干次出队操作,因而队列的前面部分可能还有很多闲置的空间,所以这种溢出并非是真的没有可用的存储空间,故这种溢出现象称之为 "假溢出"。
而环形队列通过将数组的首尾相连,解决了假溢出问题。当尾指针移动到数组末尾时,如果数组前端有空位,它可以"绕"回头,重新利用之前的内存空间,从而极大地提高了内存利用率。

二、如何实现环形
环形队列通常基于一段连续的内存空间(如数组或 std::vector)实现。实现"环形"的关键在于指针(索引)的移动方式。
我们不能通过简单地让指针 +1来实现,因为当指针指向数组末尾时,再加一就越界了。这里,我们需要利用取模运算(%)。
指针移动公式:
bash
next_index = (current_index + 1) % capacity
这样,当 current_index 达到 capacity - 1 时,下一位置会自动计算为 0,从而实现循环。
例如:

当rear在索引为4的位置的时候,若再次入队,则 rear = (4+1)%5 = 0,自动回到了索引0的位置,实现了内存重新利用。
三、难点
环形队列实现中最棘手的问题是:判空与判满
对于普通队列,当队头指针front == 队尾指针rear时,代表队列为空。然而,对于环形队列,front == rear的时候,不一定表示队空,还有可能表示队满。
常见的解决方案:
- 牺牲一个存储单元
- 队列为空:
front == rear。 - 队列为满:
(rear + 1) % capacity == front。 - 这意味着如果数组大小为 N,最多只能存储 N-1 个元素。
- 增加一个计数器
- 增加一个整数变量 count 记录当前元素个数。
- 队列为空:count
== 0。 - 队列为满:count
== capacity。 - 这种方法逻辑清晰,且能充分利用数组空间。
推荐大家使用第二种方式。
四、实例演示
下面我们采用增加计数器的方法,通过模板实现通用环形队列。
cpp
#include <iostream>
#include <vector>
#include <stdexcept>
template <typename T>
class CircularQueue {
private:
std::vector<T> data;
int front; // 头指针,指向队首元素
int rear; // 尾指针,指向队尾元素的下一个位置
int capacity; // 容量
int count; // 当前元素数量
public:
// 构造函数
CircularQueue(int cap) : capacity(cap), front(0), rear(0), count(0) {
if (cap <= 0) throw std::invalid_argument("Capacity must be positive");
data.resize(capacity);
}
// 入队
bool enqueue(const T& value) {
if (isFull()) {
std::cout << "Queue is Full! Cannot enqueue " << value << std::endl;
return false;
}
data[rear] = value;
rear = (rear + 1) % capacity; // 循环移动
count++;
return true;
}
// 出队
bool dequeue(T& value) {
if (isEmpty()) {
std::cout << "Queue is Empty! Cannot dequeue." << std::endl;
return false;
}
value = data[front];
front = (front + 1) % capacity; // 循环移动
count--;
return true;
}
// 获取队首元素
T& peek() {
if (isEmpty()) throw std::runtime_error("Queue is empty");
return data[front];
}
bool isEmpty() const {
return count == 0;
}
bool isFull() const {
return count == capacity;
}
int size() const {
return count;
}
};
int main() {
// 1. 创建容量为 5 的环形队列
CircularQueue<int> cq(5);
// 2. 测试入队
std::cout << "--- Enqueue Operations ---" << std::endl;
for (int i = 1; i <= 6; ++i) {
cq.enqueue(i * 10);
}
// 3. 测试出队
std::cout << "\n--- Dequeue Operations ---" << std::endl;
int val;
for (int i = 0; i < 3; ++i) {
if (cq.dequeue(val)) {
std::cout << "Dequeued: " << val << std::endl;
}
}
// 4. 测试循环利用空间
std::cout << "\n--- Enqueue Again (Circular Reuse) ---" << std::endl;
cq.enqueue(60);
cq.enqueue(70);
cq.enqueue(80); // 此时队列应再次变满
// 5. 打印最终状态
std::cout << "\n--- Final Queue Status ---" << std::endl;
std::cout << "Current Size: " << cq.size() << std::endl;
while (!cq.isEmpty()) {
cq.dequeue(val);
std::cout << "Remaining Element: " << val << std::endl;
}
return 0;
}
代码运行输出:
bash
--- Enqueue Operations ---
Queue is Full! Cannot enqueue 60
--- Dequeue Operations ---
Dequeued: 10
Dequeued: 20
Dequeued: 30
--- Enqueue Again (Circular Reuse) ---
(空间被循环利用,成功入队 60, 70, 80)
--- Final Queue Status ---
Current Size: 5
Remaining Element: 40
Remaining Element: 50
Remaining Element: 60
Remaining Element: 70
Remaining Element: 80