简介本资源是同济大学20级计算机科学与技术专业《数据结构》课程设计成果——基于Qt开发的医院排队叫号系统面向高校学生开展课程设计、毕业设计、大创项目及工程实训等实践场景解决医疗场景下患者有序候诊、医生动态调度、队列实时管理等典型数据结构应用问题。压缩包共30个文件含6个核心cpp源码如patient.cpp、PatientQueue.cpp、5个头文件含B_scan.h、doctor.h等模块化定义、1个UI界面文件widget.ui、1个Qt工程配置文件.pro及12张界面截图与流程图jpg/jpeg/png/gif另附实验报告PDF、README说明文档与资源映射qrc文件整体体积仅6MB结构清晰、开箱即用。已有134人学习下载提供完整可运行工程、规范的类封装与队列逻辑实现含优先级调度与多窗口交互代码注释充分便于理解链表/队列等数据结构的实际落地并支持二次扩展功能。1. 为什么一个课程设计级的 QT 排队叫号系统反而成了我带新人绕不开的「结构教学锚点」去年帮某高校实验室做嵌入式 GUI 课设评审时翻到一份标题平平无奇的压缩包“基于QT的医院排队叫号系统同济大学20级计科数据结构课程设计.zip”。本以为又是套界面拖拽简单信号槽的练手项目结果打开源码发现它用QQueue封装了真实挂号逻辑用QTimer实现非阻塞叫号节拍用QStandardItemModel动态维护三栏视图待叫/正在叫/已过号连「窗口最小化后叫号音效不中断」这种细节都靠QSoundEffect 独立线程做了隔离。这不是玩具——这是把数据结构课的「队列」从课本定义里拽出来按真实业务流一帧帧喂给 QT 框架跑通的完整闭环。它解决的不是“能不能显示号码”而是「如何让抽象数据结构在 GUI 事件驱动模型里不丢状态、不卡主线程、不崩 UI 响应」。适合两类人刚学完链表栈队列但写不出实际用途的新手以及想快速验证 QT 多线程与模型视图协同边界的中级开发者。别被“课程设计”四个字骗了——里面埋的坑比很多商用医疗插件还典型。2. 从解压到可运行三步还原原始开发环境与最小可执行路径这个项目本质是 QT Widgets 应用不依赖 QML 或 WebAssembly因此复现门槛极低。但必须严格匹配其隐含的编译约束它用的是 QT 5.12.12非最新 LTS 版且未使用 CMakeLists.txt 而是传统.pro工程文件。直接用 QT 6.x 打开会报QSoundEffect: No backend available这类底层模块缺失错误——因为 QT 6 默认移除了部分多媒体后端。2.1 环境准备精准复刻编译链而非盲目升级提示不要试图用 QT 6 编译本项目所有信号槽连接、定时器行为、模型刷新逻辑均基于 QT 5.12 的 QObject 元对象机制升级后需重写 30% 以上核心逻辑。# Ubuntu 20.04 下安装指定版本 QT其他系统请查对应包名 sudo apt update sudo apt install qt5-default qt5-qmake qtbase5-dev qttools5-dev-tools libqt5multimedia5-dev # 验证版本必须输出 5.12.x qmake -v | grep QMake versionqt5-default确保系统默认调用 QT 5.12 分支libqt5multimedia5-dev是关键QSoundEffect依赖此包缺它则叫号音效静音且不报错黑匣子式失败不要装qt6-base-dev二者共存会导致 qmake 混淆编译时提示Unknown module(s) in QT: multimedia2.2 工程加载识别 .pro 文件中的隐藏依赖项解压后进入根目录你会看到hospital_queue/ ├── hospital_queue.pro # 主工程文件 ├── main.cpp # 入口仅初始化 QApplication ├── mainwindow.h/.cpp # 核心窗口类 ├── queue_logic.h/.cpp # 队列管理逻辑重点 ├── sound_player.h/.cpp # 音效封装类 └── resources/ # 号码语音 WAV 文件1.wav ~ 100.wav打开hospital_queue.pro注意这三行QT core widgets gui multimedia # 必须包含 multimedia TARGET hospital_queue TEMPLATE app若你本地 QT 安装了 multimedia 模块但编译仍报错检查/usr/lib/x86_64-linux-gnu/qt5/mkspecs/modules/下是否存在qt_lib_multimedia.pri文件若不存在说明libqt5multimedia5-dev未正确安装重装并执行sudo ldconfig2.3 一键构建与运行跳过 IDE 直接命令行验证cd hospital_queue qmake # 生成 Makefile注意不是 cmake make -j$(nproc) # 并行编译通常 3 秒内完成 ./hospital_queue # 启动窗口左上角应显示「同济大学 计科20级 数据结构课设」水印成功标志点击「取号」按钮后右侧「当前队列」列表实时新增编号如「A001」且顶部状态栏显示「当前等待人数1」失败常见现象窗口弹出即崩溃 → 检查libqt5multimedia5-dev是否安装点击无响应 →qmake未执行或.pro中QT multimedia被注释3. 核心逻辑拆解队列结构如何与 QT 事件循环共生而不阻塞 UI本项目最值得深挖的不是界面有多炫而是它用最朴素的QQueueQString实现了「业务队列」与「GUI 响应」的解耦。很多新手误以为“用队列线程安全”结果在queue.enqueue()后直接model-appendRow()导致多线程访问QStandardItemModel崩溃。而本项目通过信号驱动 单线程模型更新规避了全部风险。3.1 队列管理类queue_logic.h的设计哲学// queue_logic.h class QueueLogic : public QObject { Q_OBJECT public: explicit QueueLogic(QObject *parent nullptr); void addNumber(const QString number); // 业务层调用取号 void callNext(); // 业务层调用叫号 int waitingCount() const; // 查询当前等待数 signals: void numberAdded(const QString number); // 通知 UI 新增号码 void numberCalled(const QString number); // 通知 UI 叫号成功 void queueEmpty(); // 通知 UI 队列空了 private: QQueueQString m_queue; // 真实存储队列纯数据无 GUI 依赖 };关键设计m_queue是私有成员所有修改操作都封装在公有方法中外部无法直接访问QQueue实例信号numberAdded/numberCalled是唯一与 UI 通信的出口避免跨线程调用QStandardItemModel::appendRow()waitingCount()返回m_queue.size()不触发任何 UI 更新纯计算函数3.2 UI 层如何安全响应队列信号// mainwindow.cpp 中连接信号 connect(m_queueLogic, QueueLogic::numberAdded, this, MainWindow::onNumberAdded); connect(m_queueLogic, QueueLogic::numberCalled, this, MainWindow::onNumberCalled); void MainWindow::onNumberAdded(const QString number) { // 此函数运行在主线程UI 线程可安全操作 model QStandardItem *item new QStandardItem(number); item-setData(waiting, Qt::UserRole); // 标记状态 m_model-appendRow(item); updateStatus(); } void MainWindow::onNumberCalled(const QString number) { // 找到第一个 waiting 状态的 item改为 called for (int i 0; i m_model-rowCount(); i) { QStandardItem *item m_model-item(i); if (item item-data(Qt::UserRole).toString() waiting) { item-setData(called, Qt::UserRole); item-setText(▶ number); // 视觉标识 break; } } updateStatus(); }onNumberAdded和onNumberCalled是槽函数由 QT 事件循环自动调度到主线程执行所有QStandardItemModel操作都在主线程内完成彻底规避QObject: Cannot create children for a parent that is in a different thread错误Qt::UserRole存储业务状态waiting/called/done而非依赖文本内容判断——这是防止「文本含▶符号导致状态误判」的血泪经验3.3 叫号节拍控制QTimer 如何实现「准实时」而非「死等」叫号不能一秒叫一个太慢也不能瞬间刷完失去排队意义。本项目用QTimer实现可配置节拍// mainwindow.cpp 初始化定时器 m_callTimer new QTimer(this); connect(m_callTimer, QTimer::timeout, this, MainWindow::onCallTimeout); m_callTimer-setInterval(3000); // 默认 3 秒一叫 m_callTimer-start(); void MainWindow::onCallTimeout() { if (m_queueLogic-waitingCount() 0) { m_queueLogic-callNext(); // 触发信号由槽函数更新 UI } else { m_callTimer-stop(); // 队列空则停 QMessageBox::information(this, 提示, 当前无等候患者); } }setInterval(3000)是硬编码值但实际部署时建议改为配置文件读取如QSettingsonCallTimeout内只做轻量判断和信号触发绝不在此处执行耗时操作如播放音频—— 音频播放交给sound_player.h的独立槽函数处理若需动态调整节拍如高峰期缩短为 2 秒暴露setCallInterval(int ms)方法并重新start()即可无需重启4. 避坑指南五个让开发者当场沉默的典型问题与根因修复这个项目表面简单但因混合了 GUI、音频、定时器、模型视图极易触发 QT 的隐性约束。以下是我在带教 12 名新人时高频复现的 5 个坑按「现象→原因→解决」结构整理每条都附可验证的代码片段。4.1 现象点击「取号」按钮后号码显示在列表里但状态栏「等待人数」始终为 0原因updateStatus()函数未被调用或调用位置错误。原项目中该函数需在每次m_model-appendRow()后手动触发但新手常删掉这行或移到信号连接前。解决确认onNumberAdded槽函数末尾存在updateStatus()调用并检查updateStatus()内部是否正确读取m_model-rowCount()void MainWindow::updateStatus() { int count 0; for (int i 0; i m_model-rowCount(); i) { if (m_model-item(i)-data(Qt::UserRole).toString() waiting) { count; } } ui-statusLabel-setText(QString(当前等待人数%1).arg(count)); }4.2 现象叫号时无声音控制台无报错QSoundEffect对象创建成功但play()无效原因QSoundEffect必须在QApplication创建之后、QMainWindow显示之前初始化否则音频后端未就绪。原项目在main.cpp中顺序正确但若你重构入口易破坏此顺序。解决确保QSoundEffect实例化在QApplication a(argc, argv);之后且a.exec()之前int main(int argc, char *argv[]) { QApplication a(argc, argv); SoundPlayer player; // 必须在此处构造 MainWindow w; w.show(); return a.exec(); // player 生命周期覆盖整个事件循环 }4.3 现象窗口最小化后定时器停止叫号中断但恢复窗口后不自动续叫原因QTimer默认受窗口可见性影响Qt::TimerType默认为Qt::CoarseTimer在窗口最小化时可能被系统暂停。解决显式设置定时器类型为Qt::PreciseTimer并启用setSingleShot(false)m_callTimer new QTimer(this); m_callTimer-setTimerType(Qt::PreciseTimer); // 关键 m_callTimer-setInterval(3000); m_callTimer-setSingleShot(false); connect(m_callTimer, QTimer::timeout, this, MainWindow::onCallTimeout);4.4 现象连续快速点击「取号」10 次列表出现重复号码如 A001 出现两次原因addNumber()方法未做防重校验且QQueue允许重复入队。原项目假设前端操作可控但真实场景需防御性编程。解决在QueueLogic::addNumber()中增加去重逻辑按业务需求此处用简单字符串比对void QueueLogic::addNumber(const QString number) { // 防止同一号码重复加入如用户手抖连点 if (!m_queue.contains(number)) { m_queue.enqueue(number); emit numberAdded(number); } }4.5 现象编译通过运行时报QMetaObject::connectSlotsByName: No matching signal found原因.ui文件中按钮的objectName被手动修改如将pushButton改为btnTakeNumber但connectSlotsByName()仍按默认名on_pushButton_clicked()查找槽函数。解决两种方案任选其一① 恢复.ui文件中控件的objectName为pushButton/pushButton_2等默认名② 在mainwindow.h中声明对应槽函数private slots: void on_btnTakeNumber_clicked(); // 名称必须与 objectName 严格匹配 void on_btnCallNext_clicked();5. 进阶落地从课程设计到可部署系统的三个关键改造点这个项目的价值远不止于交作业。我把它用在某社区卫生服务中心的临时分诊屏上只做了三处改动就满足了真实场景需求支持多科室分流、断电续号、后台远程监控。下面说具体怎么做不讲虚的。5.1 支持多科室用 QMap 替代单队列实现逻辑隔离原项目只有一条队列但真实医院有内科、外科、儿科等。改造思路用QMapQString, QQueueQString按科室名索引队列UI 上增加科室下拉框。// queue_logic.h 新增 QMapQString, QQueueQString m_departmentQueues; // 添加取号方法带科室参数 void addNumberForDepartment(const QString department, const QString number); // 修改 addNumberForDepartment 实现 void QueueLogic::addNumberForDepartment(const QString department, const QString number) { if (!m_departmentQueues.contains(department)) { m_departmentQueues[department] QQueueQString(); } m_departmentQueues[department].enqueue(number); emit numberAddedForDepartment(department, number); // 新信号 }UI 层QComboBox *deptCombo绑定科室列表取号时传deptCombo-currentText()优势不破坏原有单队列逻辑新老模式可并存QMap查找 O(log n)百科室内无性能压力5.2 断电续号用 QSettings 持久化队列状态轻量级方案要求关机重启后未叫号的患者编号不丢失。不用数据库用 QT 内置QSettings序列化。// queue_logic.cpp 中保存逻辑 void QueueLogic::saveQueueState() { QSettings settings(HospitalQueue, Config); QStringList savedNumbers; for (const auto dept : m_departmentQueues.keys()) { const auto queue m_departmentQueues[dept]; for (const QString num : queue) { savedNumbers dept | num; // 拼接科室号码 } } settings.setValue(pending_numbers, savedNumbers); } // 构造函数中恢复 QueueLogic::QueueLogic(QObject *parent) : QObject(parent) { QSettings settings(HospitalQueue, Config); auto saved settings.value(pending_numbers).toStringList(); for (const QString item : saved) { int sep item.indexOf(|); if (sep 0) { QString dept item.left(sep); QString num item.mid(sep 1); addNumberForDepartment(dept, num); } } }QSettings默认存到~/.config/HospitalQueue/Config.confLinux或注册表Windows无需额外配置每次addNumberForDepartment()后调用saveQueueState()保证实时落盘QSettings::sync()自动触发注意QSettings不适合海量数据10 万条但医院日均取号 rarely 超过 5000完全够用5.3 远程监控暴露 HTTP 接口获取实时队列长度零依赖方案运维需要看各科室排队数又不想装 Web 服务器。用 QT 的QTcpServer开个极简端口返回 JSON// 在 mainwindow.h 中添加 QTcpServer *m_monitorServer; // 初始化mainwindow.cpp 构造函数 m_monitorServer new QTcpServer(this); connect(m_monitorServer, QTcpServer::newConnection, this, MainWindow::handleMonitorConnection); m_monitorServer-listen(QHostAddress::Any, 8080); // 监听 8080 端口 void MainWindow::handleMonitorConnection() { QTcpSocket *socket m_monitorServer-nextPendingConnection(); connect(socket, QTcpSocket::readyRead, []() { // 读请求忽略内容只返回状态 QByteArray response QString({\departments\:{) .toUtf8(); bool first true; for (const QString dept : m_queueLogic-getDepartments()) { if (!first) response ,; int count m_queueLogic-waitingCountForDepartment(dept); response \ dept.toUtf8() \: QByteArray::number(count); first false; } response }}; socket-write(HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n); socket-write(response); socket-close(); }); }运维用curl http://192.168.1.100:8080即可获取 JSON接入 Zabbix 或 Prometheus 零成本无第三方库依赖纯 QT 原生实现编译时无需加-lssl等额外链接我坚持把这套改法用在所有新人培训里——不是因为它多高大上而是它用最直白的代码告诉你所谓工程能力就是把课本里的队列、信号、定时器焊进真实世界的电压、网络、断电场景里还能不冒烟。每次看到学员自己加上科室分流后眼睛亮起来我就知道数据结构这门课终于活了。希望帮到你。本文还有配套的精品资源点击获取