Qt 队列容器 QQueue 的详解与示例

Qt 队列容器 QQueue 的详解与示例

  • [一、QQueue 的详解](#一、QQueue 的详解)
    • 1、引言
    • [2、 QQueue 的核心特性](#2、 QQueue 的核心特性)
    • [3、 基本用法与接口详解](#3、 基本用法与接口详解)
      • [3.1 、包含头文件与声明](#3.1 、包含头文件与声明)
      • [3.2、 核心成员函数](#3.2、 核心成员函数)
    • [4、 基础操作示例](#4、 基础操作示例)
      • [4.1 、入列与出列](#4.1 、入列与出列)
      • [4.2 、查看队头元素](#4.2 、查看队头元素)
    • [5、 实际应用场景](#5、 实际应用场景)
      • [5.1 、任务调度系统](#5.1 、任务调度系统)
      • [5.2 、消息队列实现](#5.2 、消息队列实现)
      • [5.3 、广度优先搜索(BFS)](#5.3 、广度优先搜索(BFS))
    • 6、高级用法与技巧
      • [6.1、 与 STL 算法结合](#6.1、 与 STL 算法结合)
      • [6.2、 自定义对象队列](#6.2、 自定义对象队列)
    • 7、性能考虑与最佳实践
      • [7.1、 性能特点](#7.1、 性能特点)
      • [7.2、 最佳实践](#7.2、 最佳实践)
    • [8、 与 std::queue 的比较](#8、 与 std::queue 的比较)
    • [9、 常见问题与解决方案](#9、 常见问题与解决方案)
      • [Q1: `QQueue` 为空时调用 `dequeue()` 会怎样?](#Q1: QQueue 为空时调用 dequeue() 会怎样?)
      • [Q2: 如何清空队列但保留容量?](#Q2: 如何清空队列但保留容量?)
      • [Q3: `QQueue` 是否线程安全?](#Q3: QQueue 是否线程安全?)
      • [Q4: 如何实现优先级队列?](#Q4: 如何实现优先级队列?)
    • 10、总结
  • 二、代码示例

一、QQueue 的详解

1、引言

在 Qt 框架中,QQueue 是一个基于模板的容器类,它实现了先进先出(FIFO)的队列数据结构。作为 QList 的子类,QQueue 提供了专为队列操作优化的接口,使得在 Qt 应用中处理任务队列、消息传递、广度优先搜索等场景变得异常简单和高效。

本文将深入探讨 QQueue 的核心特性、常用接口,并通过丰富的 C++ 代码示例展示其在实际开发中的应用。

2、 QQueue 的核心特性

QQueue<T> 本质上是对 QList<T> 的封装,专门提供了队列的标准操作。其主要特性包括:

  • 先进先出(FIFO):元素从队尾入列,从队头出列。
  • 基于 QList :继承自 QList,因此拥有 QList 的所有优点,如快速的随机访问和隐式共享。
  • 线程安全 :对于只读访问,多个线程可以同时访问一个 QQueue。但如果需要修改,则需要外部同步机制。
  • 隐式共享:采用写时复制(Copy-on-Write)技术,在传递值时避免不必要的深拷贝,提升性能。

3、 基本用法与接口详解

3.1 、包含头文件与声明

要使用 QQueue,首先需要包含相应的头文件。

cpp 复制代码
#include <QQueue>
#include <QDebug>

// 声明一个整型队列
QQueue<int> intQueue;

// 声明一个字符串队列
QQueue<QString> stringQueue;

3.2、 核心成员函数

QQueue 提供了一组简洁而强大的成员函数来操作队列:

函数 说明 时间复杂度
void enqueue(const T &value) 将元素 value 添加到队尾 O(1) 平均
T dequeue() 移除队头元素并返回它 O(1)
T &head() 返回队头元素的引用(不移除) O(1)
const T &head() const 返回队头元素的常量引用 O(1)
bool isEmpty() const 判断队列是否为空 O(1)
int size() const 返回队列中元素的数量 O(1)
void clear() 清空队列中的所有元素 O(n)
bool contains(const T &value) const 检查队列是否包含某个值 O(n)
int count(const T &value) const 返回特定值在队列中出现的次数 O(n)

4、 基础操作示例

4.1 、入列与出列

这是 QQueue 最基本的使用场景。

cpp 复制代码
#include <QQueue>
#include <QDebug>

int main() {
    QQueue<QString> taskQueue;
    
    // 入列操作
    taskQueue.enqueue("处理用户登录");
    taskQueue.enqueue("加载配置文件");
    taskQueue.enqueue("初始化数据库连接");
    
    qDebug() << "队列大小:" << taskQueue.size(); // 输出: 3
    
    // 出列操作(先进先出)
    while (!taskQueue.isEmpty()) {
        QString task = taskQueue.dequeue();
        qDebug() << "正在执行:" << task;
    }
    // 输出顺序:
    // 正在执行: "处理用户登录"
    // 正在执行: "加载配置文件"
    // 正在执行: "初始化数据库连接"
    
    return 0;
}

4.2 、查看队头元素

有时我们需要查看下一个要处理的元素,但不立即移除它。

cpp 复制代码
QQueue<int> numberQueue;
numberQueue.enqueue(10);
numberQueue.enqueue(20);
numberQueue.enqueue(30);

// 查看队头元素
qDebug() << "队头元素:" << numberQueue.head(); // 输出: 10

// 队头元素仍然存在
qDebug() << "队列大小:" << numberQueue.size(); // 输出: 3

// 现在移除它
int first = numberQueue.dequeue();
qDebug() << "移除的元素:" << first; // 输出: 10

5、 实际应用场景

5.1 、任务调度系统

在需要顺序处理任务的系统中,QQueue 是理想的选择。

cpp 复制代码
#include <QQueue>
#include <QThread>
#include <QDebug>

class Task {
public:
    QString name;
    int priority;
    
    Task(const QString &n, int p) : name(n), priority(p) {}
};

class TaskScheduler {
private:
    QQueue<Task> taskQueue;
    
public:
    void addTask(const Task &task) {
        taskQueue.enqueue(task);
        qDebug() << "任务已添加:" << task.name;
    }
    
    void processTasks() {
        while (!taskQueue.isEmpty()) {
            Task currentTask = taskQueue.dequeue();
            qDebug() << "正在处理任务:" << currentTask.name 
                     << "(优先级:" << currentTask.priority << ")";
            
            // 模拟任务处理时间
            QThread::msleep(100);
        }
        qDebug() << "所有任务处理完成!";
    }
    
    int pendingTasks() const {
        return taskQueue.size();
    }
};

// 使用示例
int main() {
    TaskScheduler scheduler;
    
    scheduler.addTask(Task("备份数据库", 1));
    scheduler.addTask(Task("发送日报", 2));
    scheduler.addTask(Task("清理临时文件", 3));
    
    qDebug() << "等待处理的任务数:" << scheduler.pendingTasks();
    scheduler.processTasks();
    
    return 0;
}

5.2 、消息队列实现

QQueue 可以用于实现简单的消息队列,这在事件驱动架构中非常有用。

cpp 复制代码
#include <QQueue>
#include <QString>
#include <QDebug>

class Message {
public:
    QString type;
    QVariant data;
    qint64 timestamp;
    
    Message(const QString &t, const QVariant &d) 
        : type(t), data(d), timestamp(QDateTime::currentMSecsSinceEpoch()) {}
};

class MessageQueue {
private:
    QQueue<Message> messages;
    QMutex mutex; // 用于线程安全
    
public:
    void postMessage(const Message &msg) {
        QMutexLocker locker(&mutex);
        messages.enqueue(msg);
        qDebug() << "消息已发布:" << msg.type << "at" << msg.timestamp;
    }
    
    Message getNextMessage() {
        QMutexLocker locker(&mutex);
        if (messages.isEmpty()) {
            return Message("EMPTY", QVariant());
        }
        return messages.dequeue();
    }
    
    bool hasMessages() const {
        QMutexLocker locker(&mutex);
        return !messages.isEmpty();
    }
};

5.3 、广度优先搜索(BFS)

在图论和树遍历算法中,QQueue 是实现 BFS 的天然选择。

cpp 复制代码
#include <QQueue>
#include <QVector>
#include <QDebug>

class Graph {
private:
    QVector<QVector<int>> adjacencyList;
    
public:
    Graph(int vertices) : adjacencyList(vertices) {}
    
    void addEdge(int from, int to) {
        adjacencyList[from].append(to);
    }
    
    QVector<int> bfs(int startVertex) {
        QVector<int> traversalOrder;
        QVector<bool> visited(adjacencyList.size(), false);
        QQueue<int> queue;
        
        visited[startVertex] = true;
        queue.enqueue(startVertex);
        
        while (!queue.isEmpty()) {
            int current = queue.dequeue();
            traversalOrder.append(current);
            
            for (int neighbor : adjacencyList[current]) {
                if (!visited[neighbor]) {
                    visited[neighbor] = true;
                    queue.enqueue(neighbor);
                }
            }
        }
        
        return traversalOrder;
    }
};

// 使用示例
int main() {
    Graph g(6);
    g.addEdge(0, 1);
    g.addEdge(0, 2);
    g.addEdge(1, 3);
    g.addEdge(1, 4);
    g.addEdge(2, 4);
    g.addEdge(3, 5);
    g.addEdge(4, 5);
    
    QVector<int> bfsResult = g.bfs(0);
    qDebug() << "BFS遍历顺序:" << bfsResult;
    // 输出: 0, 1, 2, 3, 4, 5
    
    return 0;
}

6、高级用法与技巧

6.1、 与 STL 算法结合

由于 QQueue 继承自 QList,它可以与 Qt 和 STL 算法无缝协作。

cpp 复制代码
#include <QQueue>
#include <QDebug>
#include <algorithm>

int main() {
    QQueue<int> queue;
    queue.enqueue(5);
    queue.enqueue(2);
    queue.enqueue(8);
    queue.enqueue(1);
    queue.enqueue(9);
    
    // 使用 std::find 查找元素
    auto it = std::find(queue.begin(), queue.end(), 8);
    if (it != queue.end()) {
        qDebug() << "找到元素 8";
    }
    
    // 使用 Qt 算法计算总和
    int sum = std::accumulate(queue.begin(), queue.end(), 0);
    qDebug() << "队列元素总和:" << sum;
    
    // 使用范围for循环遍历
    qDebug() << "队列内容:";
    for (int value : queue) {
        qDebug() << value;
    }
    
    return 0;
}

6.2、 自定义对象队列

QQueue 可以存储任何可拷贝的类型,包括自定义类。

cpp 复制代码
#include <QQueue>
#include <QDebug>

class Customer {
private:
    QString name;
    int customerId;
    
public:
    Customer(const QString &n, int id) : name(n), customerId(id) {}
    
    QString getName() const { return name; }
    int getId() const { return customerId; }
    
    // 重载输出操作符便于调试
    friend QDebug operator<<(QDebug debug, const Customer &c) {
        debug << "Customer[" << c.customerId << ":" << c.name << "]";
        return debug;
    }
};

int main() {
    QQueue<Customer> customerQueue;
    
    customerQueue.enqueue(Customer("张三", 1001));
    customerQueue.enqueue(Customer("李四", 1002));
    customerQueue.enqueue(Customer("王五", 1003));
    
    // 处理客户队列
    while (!customerQueue.isEmpty()) {
        Customer nextCustomer = customerQueue.dequeue();
        qDebug() << "正在服务:" << nextCustomer;
    }
    
    return 0;
}

7、性能考虑与最佳实践

7.1、 性能特点

  • 入列/出列操作:平均 O(1) 时间复杂度
  • 随机访问 :由于基于 QList,支持 O(1) 的随机访问
  • 内存使用:使用连续内存存储,缓存友好
  • 大规模数据:对于极大规模队列,考虑内存碎片问题

7.2、 最佳实践

  1. 预分配空间 :如果知道队列的大致大小,可以使用 reserve() 预分配内存。

    cpp 复制代码
    QQueue<QString> largeQueue;
    largeQueue.reserve(10000); // 预分配空间
  2. 避免频繁拷贝:对于大型对象,考虑使用指针或共享指针。

    cpp 复制代码
    QQueue<QSharedPointer<LargeObject>> objectQueue;
  3. 线程安全:在多线程环境中使用队列时,务必添加适当的同步机制。

    cpp 复制代码
    QQueue<Data> sharedQueue;
    QMutex queueMutex;
    
    // 线程1:生产者
    {
        QMutexLocker locker(&queueMutex);
        sharedQueue.enqueue(newData);
    }
    
    // 线程2:消费者
    {
        QMutexLocker locker(&queueMutex);
        if (!sharedQueue.isEmpty()) {
            Data data = sharedQueue.dequeue();
            // 处理数据
        }
    }
  4. 异常安全dequeue() 在队列为空时会构造一个默认对象返回,这可能不是期望的行为。始终先检查 isEmpty()

    cpp 复制代码
    if (!queue.isEmpty()) {
        auto value = queue.dequeue();
        // 安全使用 value
    }

8、 与 std::queue 的比较

特性 QQueue<T> std::queue<T>
底层容器 QList<T> 默认为 std::deque<T>,可指定
随机访问 支持(继承自 QList) 不支持
隐式共享 支持 不支持
Qt 集成 完美集成 Qt 类型系统 需要适配
序列化 支持 QDataStream 需要自定义
调试输出 支持 QDebug 流输出 需要重载 operator<<

选择建议:

  • 在纯 Qt 项目中使用 QQueue,享受更好的集成和隐式共享带来的性能优势。
  • 在跨平台或标准库优先的项目中考虑 std::queue

9、 常见问题与解决方案

Q1: QQueue 为空时调用 dequeue() 会怎样?

A: 会构造一个默认的 T 对象并返回。这可能导致难以发现的逻辑错误。最佳实践是始终先检查 isEmpty()

Q2: 如何清空队列但保留容量?

A: QQueue 没有直接的方法,但可以通过 QList 的接口实现:

cpp 复制代码
queue.clear();          // 清空元素,可能释放内存
queue.reserve(capacity); // 重新保留容量

Q3: QQueue 是否线程安全?

A: 对于只读操作,多个线程可以同时访问。对于修改操作,需要外部同步(如使用 QMutex)。

Q4: 如何实现优先级队列?

A: QQueue 是严格的 FIFO 队列。如果需要优先级队列,可以使用 QPriorityQueuestd::priority_queue

10、总结

QQueue 是 Qt 框架中一个简单而强大的队列容器,它:

  1. 提供了标准的 FIFO 队列接口
  2. 基于 QList,支持快速随机访问和隐式共享
  3. 与 Qt 生态系统完美集成
  4. 适用于任务调度、消息传递、BFS 等多种场景

通过合理使用 QQueue,您可以编写出更清晰、更高效的 Qt 应用程序。记住始终考虑线程安全和异常情况,特别是在生产环境中使用队列时。

二、代码示例

1、运行结果

2、示例代码

cpp 复制代码
#include <QCoreApplication>
#include <QQueue>
#include <QDebug>
#include <QList>

// 自定义测试结构体(验证自定义数据存储)
struct MsgTask
{
    int id;
    QString info;
    MsgTask(int i, const QString& s) : id(i), info(s) {}
    // 重载打印
    friend QDebug operator<<(QDebug dbg, const MsgTask& t)
    {
        dbg << "Task{id:" << t.id << ", info:" << t.info << "}";
        return dbg;
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    qDebug() << "========= 1. 基础入队 enqueue() ==========";
    QQueue<QString> strQueue;
    strQueue.enqueue("A");
    strQueue.enqueue("B");
    strQueue.enqueue("C");
    qDebug() << "队列全部元素:" << strQueue;
    qDebug() << "队列长度 size():" << strQueue.size();
    qDebug() << "是否为空 isEmpty():" << strQueue.isEmpty();

    qDebug() << "\n========= 2. 获取队头/队尾 front() / back() =========";
    qDebug() << "队首 front():" << strQueue.front();
    qDebug() << "队尾 back():" << strQueue.back();

    qDebug() << "\n========= 3. 出队 dequeue()(先进先出FIFO) =========";
    QString out1 = strQueue.dequeue();
    qDebug() << "弹出元素:" << out1 << " 剩余队列:" << strQueue;

    qDebug() << "\n========= 4. 下标访问 at() =========";
    qDebug() << "at(0):" << strQueue.at(0);

    qDebug() << "\n========= 5. 遍历方式:范围for / STL迭代器 =========";
    qDebug() << "范围for 遍历:";
    for (const auto& item : strQueue)
        qDebug() << "  " << item;
    qDebug() << "迭代器遍历:";
    for (auto it = strQueue.begin(); it != strQueue.end(); ++it)
        qDebug() << "  " << *it;

    qDebug() << "\n========= 6. 拷贝构造 / 赋值运算符 =========";
    QQueue<QString> copyQueue = strQueue;
    copyQueue.enqueue("D");
    qDebug() << "原队列:" << strQueue;
    qDebug() << "拷贝队列:" << copyQueue;

    qDebug() << "\n========= 7. 清空队列 clear() =========";
    copyQueue.clear();
    qDebug() << "清空后长度:" << copyQueue.size();

    qDebug() << "\n========= 8. 存储自定义结构体 =========";
    QQueue<MsgTask> taskQueue;
    taskQueue.enqueue(MsgTask(1, "读取传感器"));
    taskQueue.enqueue(MsgTask(2, "上报数据"));
    qDebug() << "自定义结构体队列:" << taskQueue;
    qDebug() << "弹出任务:" << taskQueue.dequeue();

    qDebug() << "\n========= 9. Qt6 无 push/pop,统一 enqueue/dequeue =========";
    QQueue<int> stlQueue;
    stlQueue.enqueue(100);
    stlQueue.enqueue(200);
    qDebug() << "队首 front:" << stlQueue.front();
    stlQueue.dequeue();
    qDebug() << "dequeue后队列:" << stlQueue;

    qDebug() << "\n========= 10. 容器互转 QQueue <-> QList =========";
    QQueue<double> numQueue;
    numQueue.enqueue(1.1);
    numQueue.enqueue(2.2);
    // QQueue 转 QList
    QList<double> list = numQueue.toList();
    qDebug() << "转QList:" << list;
    // 低版本Qt6无fromList,手动遍历填充
    QQueue<double> fromListQueue;
    for(double val : list)
    {
        fromListQueue.enqueue(val);
    }
    qDebug() << "从List构建队列:" << fromListQueue;

    qDebug() << "\n========= 11. 原位构造替代 emplace(Qt6无emplace) =========";
    QQueue<QString> moveQueue;
    moveQueue.enqueue(QString("原位构造字符串"));
    qDebug() << "入队元素:" << moveQueue.front();

    qDebug() << "\n========= 12. 过滤、查找 contains() =========";
    QQueue<QString> filterQueue;
    filterQueue.enqueue("apple");
    filterQueue.enqueue("banana");
    filterQueue.enqueue("orange");
    filterQueue.enqueue("apple");
    qDebug() << "是否包含 banana:" << filterQueue.contains("banana");
    qDebug() << "是否包含 grape:" << filterQueue.contains("grape");

    qDebug() << "\n========= 13. 交换队列 swap() =========";
    QQueue<int> q1;
    q1.enqueue(1); q1.enqueue(2); q1.enqueue(3);
    QQueue<int> q2;
    q2.enqueue(10); q2.enqueue(20);
    q1.swap(q2);
    qDebug() << "交换后 q1:" << q1 << " q2:" << q2;

    qDebug() << "\n========= 14. 边界异常演示(空队列禁止front/dequeue) =========";
    QQueue<int> emptyQ;
    if (!emptyQ.isEmpty())
    {
        emptyQ.front();
        emptyQ.dequeue();
    }
    else
    {
        qDebug() << "空队列禁止调用 front/dequeue,必须先判空";
    }

    qDebug() << "\n========= 15. 常量队列 const QQueue =========";
    QQueue<QString> tempConstQueue;
    tempConstQueue.enqueue("X");
    tempConstQueue.enqueue("Y");
    const QQueue<QString> constQ = tempConstQueue;
    qDebug() << "const队列 front:" << constQ.front();
    // constQ.enqueue("Z"); // 编译报错,常量队列不可修改

    return a.exec();
}
相关推荐
ACP广源盛139246256731 小时前
GSV6155@ACP# Type-C 视频信号转换芯片:技术架构、工程痛点适配与落地场景分析
大数据·c语言·开发语言·人工智能·分布式·嵌入式硬件·架构
程序员-Benothing2 小时前
Java集合:List实现类深度对比
java·开发语言·后端·面试·list
酷在前行2 小时前
【R论文复刻】Nature Communications 冲积图进阶:状态转移、流带排序与多面板排版(保姆级教程)
开发语言·r语言
减瓦2 小时前
用 Git 为本地文件打造一台时光机
开发语言·git·编辑器
冻柠檬飞冰走茶2 小时前
PTA基础编程题目集 7-37 整数分解为若干项之和(C语言实现)
c语言·开发语言·数据结构·算法
开开心心就好2 小时前
文件批量重命名工具简单好用支持规则改名
java·开发语言·b树·ocr·excel·音视频·kmeans
末代iOS程序员华仔3 小时前
OPC + Flutter + Swift:跨平台应用上架与专业知识点获客实战指南
开发语言·flutter·swift
脚踏实地皮皮晨3 小时前
003004002_UniformGrid控件的使用方法
开发语言·c#·.net·wpf·visual studio
j7~4 小时前
【Linux】二十六.线程篇三《Linux多线程编程:线程控制、线程ID以及进程地址空间分布、线程局部存储__thread以及clone系统调用》---详解
linux·运维·服务器·开发语言·c++·多线程编辑·线程的控制