C++ STL库_队列queue

一、C++ 队列(queue)的核心性质

队列是 C++ STL 中的容器适配器 (不是原生容器),它封装了底层容器(默认是deque),并提供符合 "先进先出(FIFO, First In First Out)" 规则的操作接口。核心性质如下:

1. 底层实现

  • queue本身不存储数据,而是依赖其他容器作为 "底层存储",属于适配器模式
  • 默认底层容器是std::deque(双端队列),也可以手动指定为std::list(只要满足back()front()push_back()pop_front()empty()size()接口的容器都可以)。
  • 选择deque作为默认的原因:兼顾了数组的随机访问优势和链表的高效插入 / 删除优势,且避免了vector扩容时的内存拷贝开销。

2. 访问 / 操作规则(核心)

  • 访问限制 :只能访问队首 (第一个入队的元素)和队尾 (最后一个入队的元素),不支持随机访问 (比如通过下标q[0]访问),也没有迭代器 (无法用for循环遍历)。
  • 操作限制 :只能在队尾插入 元素,只能在队首删除元素,完全符合 "排队" 的逻辑。

3. 其他特性

  • 不支持排序、反转等操作(因为 FIFO 规则不允许);
  • 线程不安全(多线程操作需手动加锁)。

二、queue 的常用接口(附代码示例)

包含头文件:#include <queue>

std::queue的核心设计目标是严格遵循先进先出(FIFO) 规则,它的接口是为这个规则量身定制的:

  • 允许的操作:队尾插入 (push/emplace)、队首删除(pop);
  • 禁止的操作:删除队尾、删除中间元素、随机删除 ------ 这些操作会破坏 FIFO 的核心逻辑,因此标准库刻意隐藏了这些接口。

1. 构造函数

构造方式 说明
queue<T> q; 默认构造,底层为deque<T>,空队列
queue<T, Container> q; 指定底层容器(如list<T>
queue<T> q2(q1); 拷贝构造,复制 q1 的所有元素到 q2

2. 元素访问(仅 2 个接口)

接口 说明 注意事项
q.front() 返回队首元素的引用(可修改) 队列空时调用会导致未定义行为
q.back() 返回队尾元素的引用(可修改) 队列空时调用会导致未定义行为

3. 容量判断

接口 说明
q.empty() 判断队列是否为空,返回bool(空返回true
q.size() 返回队列中元素的个数,类型为size_t

4. 修改操作(核心)

接口 说明 注意事项
q.push(val) 在队尾插入元素val(拷贝 / 移动构造) -
q.emplace(args...) 在队尾直接构造 元素(无需先创建对象),比push更高效 C++11 及以上支持
q.pop() 删除队首元素,无返回值 队列空时调用会导致未定义行为
q.swap(q2) 交换两个队列的内容(底层容器也会交换) -
cpp 复制代码
#include <iostream>
#include <queue>
#include <list>  // 用于指定底层容器
using namespace std;

// 定义一个简单的结构体,演示emplace的用法
struct Person {
    string name;
    int age;
    // 构造函数
    Person(string n, int a) : name(n), age(a) {}
};

int main() {
    // ========== 1. 构造队列 ==========
    // 默认构造(底层deque)
    queue<int> q1;
    // 指定底层容器为list
    queue<int, list<int>> q2;

    // ========== 2. 插入元素 ==========
    q1.push(10);    // 队尾插入10
    q1.push(20);    // 队尾插入20
    q1.emplace(30); // 直接构造30并插入队尾(效果同push,但更高效)
    
    // 对自定义类型使用emplace(直接传构造参数,无需先创建Person对象)
    queue<Person> q_person;
    q_person.emplace("张三", 20);  // 直接构造
    q_person.push(Person("李四", 25)); // 先创建对象再拷贝

    // ========== 3. 访问元素 ==========
    cout << "队首元素:" << q1.front() << endl; // 输出10
    cout << "队尾元素:" << q1.back() << endl;  // 输出30
    // 修改队尾元素
    q1.back() = 35;
    cout << "修改后队尾元素:" << q1.back() << endl; // 输出35

    // ========== 4. 容量判断 ==========
    if (!q1.empty()) {
        cout << "队列大小:" << q1.size() << endl; // 输出3
    }

    // ========== 5. 删除元素 ==========
    q1.pop(); // 删除队首的10
    cout << "pop后队首元素:" << q1.front() << endl; // 输出20

    // ========== 6. 交换队列 ==========
    queue<int> q3;
    q3.push(100);
    q1.swap(q3); // 交换q1和q3的内容
    cout << "交换后q1的队首:" << q1.front() << endl; // 输出100
    cout << "交换后q3的队首:" << q3.front() << endl; // 输出20

    return 0;
}

编译运行说明

  • 编译命令(g++):g++ -std=c++11 queue_demo.cpp -o queue_demo-std=c++11用于支持emplace);

  • 运行:./queue_demo

  • 输出结果:

    复制代码
    队首元素:10
    队尾元素:30
    修改后队尾元素:35
    队列大小:3
    pop后队首元素:20
    交换后q1的队首:100
    交换后q3的队首:20

三、常见易错点提醒

  1. pop () 无返回值 :很多新手误以为pop()会返回被删除的队首元素,实际需要先调用front()获取值,再调用pop()删除;
  2. 空队列操作 :调用front()back()pop()前,必须用empty()检查队列非空,否则会导致程序崩溃(未定义行为);
  3. 迭代器缺失queue没有迭代器,无法用for (auto& e : q)遍历,若需要遍历,可先逐个front()+pop()取出元素(会清空队列),或改用deque/list

四、队列的底层实现

queue本身不直接实现数据存储和基础操作,它是一种 "适配器(Adapter)"------ 本质是对现有容器的接口进行 "包装和限制",只暴露符合 FIFO(先进先出)规则的操作,隐藏底层容器的其他功能。

可以用通俗的比喻理解:

  • 底层容器(如 deque/list)是 "多功能工具箱",有各种工具(随机访问、首尾操作、中间插入等);
  • queue适配器是 "定制化外壳",只留两个开口:"从尾部放东西""从头部取东西",把其他工具都盖住,只保留符合排队逻辑的功能。

默认底层容器:std::deque(双端队列)

1. 为什么选择 deque 作为默认?

C++ 标准委员会选择deque而非vector/list,是综合性能、内存效率的最优解:

容器 优点 缺点 适配 queue 的问题
vector 随机访问快、内存连续 队首删除(pop_front)效率极低(需移动所有元素);扩容时内存拷贝 不符合 queue "高效删除队首" 的需求
list 首尾插入 / 删除效率 O (1) 无随机访问、内存碎片化、额外指针开销 性能尚可,但内存效率不如 deque
deque 首尾插入 / 删除 O (1);内存分段连续(无整体扩容拷贝);支持随机访问 中间插入 / 删除效率低(但 queue 用不到) 完美匹配 queue 的操作需求
2. deque 的底层结构(简化版)

deque不是单一连续数组,而是由:

  • 中控器:一个存储 "分段数组指针" 的连续数组;
  • 分段缓冲区:多个固定大小的连续数组(称为 "缓冲区")。

这种结构让deque既能像list一样高效地在首尾插入 / 删除(只需操作缓冲区的首尾,无需移动大量元素),又能像vector一样接近连续的内存访问(减少缓存失效)。

3. queue 对 deque 的接口映射

queue的所有操作,本质都是调用底层deque的对应接口,仅做了 "功能筛选":

queue 接口 底层 deque 的接口 说明
push(val) push_back(val) 队尾插入 → deque 尾部插入
emplace() emplace_back() 队尾构造 → deque 尾部构造
pop() pop_front() 队首删除 → deque 头部删除
front() front() 访问队首 → 访问 deque 头部
back() back() 访问队尾 → 访问 deque 尾部
empty() empty() 判断空 → 复用 deque 判断
size() size() 获取大小 → 复用 deque 大小

自定义底层容器(以 list 为例)

你可以手动指定queue的底层容器,只要该容器满足以下最小接口要求

  • 支持empty()size()
  • 支持front()back()
  • 支持push_back()pop_front()
示例:用 list 作为 queue 的底层容器
cpp 复制代码
#include <iostream>
#include <queue>
#include <list>
using namespace std;

int main() {
    // 显式指定底层容器为std::list<int>
    queue<int, list<int>> q;

    // 操作逻辑和默认queue完全一致,底层实际调用list的接口
    q.push(10);
    q.push(20);
    q.push(30);

    cout << "队首:" << q.front() << endl; // 10,调用list::front()
    cout << "队尾:" << q.back() << endl;  // 30,调用list::back()

    q.pop(); // 调用list::pop_front(),删除队首
    cout << "pop后队首:" << q.front() << endl; // 20

    cout << "队列大小:" << q.size() << endl; // 2,调用list::size()

    return 0;
}

运行结果

复制代码
队首:10
队尾:30
pop后队首:20
队列大小:2

用deque模拟实现简易版 queue 适配器

cpp 复制代码
#include <iostream>
#include <deque>
using namespace std;

// 简易版queue适配器
template <typename T, typename Container = deque<T>>
class MyQueue {
private:
    Container c; // 底层容器,默认是deque

public:
    // 判空:直接调用底层容器的empty()
    bool empty() const { return c.empty(); }

    // 大小:直接调用底层容器的size()
    size_t size() const { return c.size(); }

    // 访问队首:调用底层容器的front()
    T& front() { return c.front(); }
    const T& front() const { return c.front(); }

    // 访问队尾:调用底层容器的back()
    T& back() { return c.back(); }
    const T& back() const { return c.back(); }

    // 队尾插入:调用底层容器的push_back()
    void push(const T& val) { c.push_back(val); }

    // 队首删除:调用底层容器的pop_front()
    void pop() { c.pop_front(); }
};

int main() {
    MyQueue<int> q;
    q.push(1);
    q.push(2);
    cout << q.front() << endl; // 1
    q.pop();
    cout << q.front() << endl; // 2
    return 0;
}

用list模拟实现简易版 queue 适配器

因为list本身支持高效的首尾插入 / 删除操作(均为 O (1) 时间复杂度),完全满足queue的适配要求,核心思路是:

  1. 定义模板类MyQueue,底层固定使用std::list作为存储容器;
  2. queue的核心接口(empty()/size()/front()/back()/push()/pop())直接映射到list的对应接口;
  3. 仅暴露符合 FIFO 规则的操作,隐藏list的其他接口(如中间插入、随机访问等),体现适配器的 "封装 + 限制" 本质。
cpp 复制代码
#include <iostream>
#include <list>  // 作为底层容器
using namespace std;

// 以list为底层容器的简易版queue适配器
template <typename T>  // 模板参数:队列存储的元素类型
class MyQueue {
private:
    // 私有成员:底层存储容器(std::list)
    list<T> container;

public:
    // ========== 1. 容量相关 ==========
    // 判断队列是否为空:调用list的empty()
    bool empty() const {
        return container.empty();
    }

    // 获取队列元素个数:调用list的size()
    size_t size() const {
        return container.size();
    }

    // ========== 2. 元素访问 ==========
    // 访问队首元素:调用list的front(),返回引用(支持修改)
    T& front() {
        // 空队列访问队首属于未定义行为,这里简单判断并提示
        if (empty()) {
            throw runtime_error("MyQueue is empty, cannot access front!");
        }
        return container.front();
    }

    // 常量版本的front(只读,不能修改)
    const T& front() const {
        if (empty()) {
            throw runtime_error("MyQueue is empty, cannot access front!");
        }
        return container.front();
    }

    // 访问队尾元素:调用list的back(),返回引用(支持修改)
    T& back() {
        if (empty()) {
            throw runtime_error("MyQueue is empty, cannot access back!");
        }
        return container.back();
    }

    // 常量版本的back(只读)
    const T& back() const {
        if (empty()) {
            throw runtime_error("MyQueue is empty, cannot access back!");
        }
        return container.back();
    }

    // ========== 3. 修改操作 ==========
    // 队尾插入元素:调用list的push_back()
    void push(const T& value) {
        container.push_back(value);
    }

    // 队首删除元素:调用list的pop_front()(无返回值)
    void pop() {
        if (empty()) {
            throw runtime_error("MyQueue is empty, cannot pop!");
        }
        container.pop_front();
    }
};

// 测试简易版MyQueue
int main() {
    try {
        MyQueue<int> q;

        // 1. 测试空队列判断
        cout << "队列是否为空:" << (q.empty() ? "是" : "否") << endl; // 输出:是

        // 2. 插入元素(队尾)
        q.push(10);
        q.push(20);
        q.push(30);
        cout << "插入3个元素后,队列大小:" << q.size() << endl; // 输出:3

        // 3. 访问队首/队尾
        cout << "队首元素:" << q.front() << endl; // 输出:10
        cout << "队尾元素:" << q.back() << endl;   // 输出:30

        // 4. 修改队尾元素(通过引用)
        q.back() = 35;
        cout << "修改后队尾元素:" << q.back() << endl; // 输出:35

        // 5. 删除队首元素
        q.pop();
        cout << "pop后队首元素:" << q.front() << endl; // 输出:20
        cout << "pop后队列大小:" << q.size() << endl;  // 输出:2

        // 6. 继续pop直到空
        q.pop();
        q.pop(); // 此时队列已空,再pop会抛异常
        // q.pop(); // 取消注释会触发异常:MyQueue is empty, cannot pop!
    } catch (const exception& e) {
        cout << "异常信息:" << e.what() << endl;
    }

    return 0;
}
相关推荐
cui_ruicheng2 小时前
C++ 继承(下):多继承、菱形继承与虚继承
开发语言·c++
wangjialelele2 小时前
万字整理计算机网络知识点
linux·c语言·网络·c++·计算机网络·php
草莓熊Lotso2 小时前
Qt文件操作:QFile读写全解析
运维·开发语言·c++·人工智能·qt
D_evil__2 小时前
【Effective Modern C++】第六章 lambda表达式:34. 考虑lambda而非bind
c++
水木姚姚2 小时前
string类(C++)
开发语言·c++·windows·vscode·开发工具
方便面不加香菜2 小时前
C++ 类和对象(一)
开发语言·c++
浅念-2 小时前
C++ STL list 容器
开发语言·数据结构·c++·经验分享·笔记·算法·list
WW_千谷山4_sch2 小时前
MYOJ_7788:(洛谷P3387)【模板】缩点(有关强连通分量)
c++·算法·深度优先·动态规划·图论·拓扑学
枫叶丹42 小时前
【Qt开发】Qt界面优化(六)-> Qt样式表(QSS) 伪类选择器
c语言·开发语言·c++·qt