简介面向数据结构学习者的银行排队系统课程设计基于栈与队列模拟银行叫号与贵宾优先服务机制。压缩包共8个文件包含一个源代码文件、一个工程配置文件、一个界面布局文件、一个依赖文件、两个文本资料、一个可执行文件及一个编译目标文件整体仅334KB结构紧凑已有2865人学习使用。设计巧妙之处在于用栈的后进先出特性将贵宾客户压入栈顶实现优先处理用队列的先进先出特性让普通客户按到达顺序公平排队同时支持客户登记、窗口分配、实时状态显示和动态调整模拟了多窗口银行服务场景。通过该项目可深入理解栈和队列在不同业务优先级下的协调使用掌握“排队系统栈”与“栈排队系统”的具体实现思路。代码可直接运行对照也可在源码基础上二次开发适合数据结构课程设计、期末项目或面试准备。1. 银行排队系统栈和队列在一份大二作业里怎么配合银行排队系统里同时出现队列和栈第一次看这份大二下数据结构作业的人多半会觉得是硬凑。但拆开代码你会发现普通客户按先来后到排成 FIFO 队列VIP 客户直接压进一个 LIFO 栈窗口空闲时优先从栈顶捞人——也就是说后到的 VIP 反而先被服务。这个设计是否符合真实银行不好说但它把栈和队列的差异讲得非常直观本身就是冲“练数据结构”去的。这份资源是一个完整的 Code::Blocks 工程核心只有 main.cpp 和 xinxi.txt适合正在做课程设计、想找一套能跑的排队系统框架来改的大二学生也适合复习数据结构时想看看栈、队列、合法出栈序列这些知识点在真实场景里怎么落到代码的人。2. 读懂工程文件结构main.cpp、xinxi.txt 与 Code::Blocks 三件套怎么配合解压压缩包之后工程目录里会同时出现源码、工程文件、编辑器布局文件和一堆编译产物。很多同学第一反应是打开 main.cpp 从头读其实没必要。先花两分钟把文件角色分清后面换数据、重新编译、换机器拷贝时才不会翻车。2.1 文件清单先过一遍哪些是源码哪些可以直接无视工程目录下主要包含这些内容文件/目录类型作用需要动吗shujujiegou.cbpCode::Blocks 工程文件记录编译器选项、构建目标Debug/Release、源文件列表一般不用main.cppC 源码整个排队系统的实现队列、栈、调度逻辑都在这个文件里主要修改对象xinxi.txt文本数据文件客户数据输入编号、到达时间、是否 VIP按需修改shujujiegou.layout编辑器布局缓存记录窗口布局Code::Blocks 自动生成不用管shujujiegou.depend依赖缓存记录头文件依赖关系自动生成不用管obj/中间目录存放编译产生的 .o 目标文件可删除会自动重建bin/Debug/输出目录编译成功后 exe 的位置关注时间戳这套文件里真正有技术含量的就是 main.cpp。shujujiegou.layout 和 shujujiegou.depend 是 Code::Blocks 在打开工程时自动生成的缓存删了也没关系obj 和 bin Debug 是编译产物同样可以随时删掉重新编译。如果要整理成最小干净工程把 shujujiegou.cbp、main.cpp、xinxi.txt 三件拷走就够了。注意在 Windows 下判断自己改的代码有没有生效直接看 bin/Debug/ 下 exe 的“修改日期”比看编译输出更快。如果编译报错但 exe 时间戳没变说明这次 build 是失败的。2.2 主流程设计客户到达、入队入栈、窗口分配的三段式骨架main.cpp 虽然只有一个文件但主体逻辑通常是一个时间推进的主循环。常见做法是这样设计骨架int main() { // 读取 xinxi.txt 中的客户数据 ifstream fin(xinxi.txt); // 每个客户包含编号、到达时间、是否VIP // 全部存入一个结构体数组或直接边读边排队 LinkQueue normalQ; // 普通客户队列 LinkStack vipStack; // VIP 客户栈 // 初始化窗口数组每个窗口记录“正在服务的客户”和“剩余服务时间” // 模拟时间从 0 开始推进 while (还有客户没到 || 窗口还在忙) { // 1. 当前时刻有客户到达VIP 入栈普通客户入队 // 2. 扫描每个窗口空闲的窗口调 NextCustomer 取下一个客户 // 3. 刷新控制台显示当前时刻、各窗口状态、队列长度 // 4. 时间加 1 或加一个步长 } // 统计平均等待时间、最长等待时间输出到控制台 return 0; }这个骨架里最值得注意的参数是“时间推进步长”和“窗口数量”。常见实现把窗口数量写成常量比如 const int WINDOW_NUM 3服务时长要么固定要么用一个随机数模拟随机的话建议在程序开头设置随机种子 srand(time(NULL))不然每次跑出来的数据一模一样答辩时不好解释。时间推进用“分钟为步长”最简单客户到达时间和服务时长都以分钟为单位循环体里每次 time 即可。如果你仔细看 while 条件的写法会发现它同时判断“客户没到完”和“窗口都空闲”两个条件。这里有个常见的错误只判断客户是否到完结果窗口已经空了还在空转或者只判断窗口状态结果后半段新到的客户没人处理。两个条件取“或”才能把模拟跑到真正结束。2.3 xinxi.txt 数据文件格式约定与替换数据的注意事项xinxi.txt 是这套程序的输入源。常见格式是每行一位客户三列分别是编号、到达时刻、VIP 标志0 表示普通客户1 表示 VIP1 0 0 2 2 0 3 3 1 4 5 1 5 8 0意思是 1 号客户在第 0 分钟到达普通客户3 号客户在第 3 分钟到达VIP。改数据时有一个容易被忽略的坑大多数作业代码假设数据已经按到达时刻递增排列如果文件里出现“先到的客户写在后一行”程序可能不会自动排序。此时看代码里有没有在读入后做按时间排序如果没有就把测试数据按到达时刻递增整理好再放进去。我一般会准备三份数据一份全是普通客户验证 FIFO一份 VIP 密集验证栈的后到先服务一份混合数据测优先级调度。这三份数据交替使用跑出来的结果才有说服力。3. 核心实现链式队列管普通客户、链式栈管 VIP 插队以及多窗口调度逻辑整个工程的技术核心就是两个结构链式队列和链式栈。选链式而不是顺序结构不是玄学——排队人数是动态增长的用数组模拟要先预设 MAXSIZE人满了还得写扩容逻辑链表用 new 和 delete 随来随走代码上更贴合课程作业对“栈和队列的基本操作”的考察点。3.1 链式队列FIFO 主线怎么用 C 写干净队列管普通客户先进先出。常见的链式队列定义长这样// 客户信息结构体 struct Customer { int id; // 客户编号 int arriveTime; // 到达时刻分钟 bool isVIP; // 是否 VIP }; // 链式队列节点 struct QNode { Customer data; // 节点数据 QNode* next; // 指向下一个节点 }; // 链式队列front 管出队rear 管入队 struct LinkQueue { QNode* front; // 队头指针 QNode* rear; // 队尾指针 }; // 入队新节点挂在队尾 void EnQueue(LinkQueue q, Customer c) { QNode* node new QNode; node-data c; node-next NULL; if (q.rear NULL) { // 空队列 q.front q.rear node; // 第一个节点同时是队头和队尾 } else { q.rear-next node; q.rear node; } } // 出队从队头取出返回 false 表示空队列 bool DeQueue(LinkQueue q, Customer out) { if (q.front NULL) return false; // 空队列直接失败 out q.front-data; QNode* tmp q.front; q.front q.front-next; if (q.front NULL) q.rear NULL; // 队列清空rear 必须置空 delete tmp; return true; }这段代码的逻辑很直白入队走尾指针出队走头指针时间复杂度都是 O(1)。最容易被忽略的是 DeQueue 最后一行——当出队的是最后一个节点时front 会被置成 NULL但 rear 还指着这个已经被 delete 的节点。如果不把 rear 也置 NULL下一次 EnQueue 判断 q.rear NULL 会失败新节点挂到一个野指针后面程序要么崩溃要么遍历死循环。参数上out 是引用参数出队成功后把客户数据带回给调用方返回值 bool 用来区分“出队成功”和“队列为空”调用方拿到 false 就不该继续用 out。课程作业里常见的一个扣分点正是“空队列时直接读 front-data”少了这个返回值保护。3.2 链式栈LIFO 处理 VIP 时的插入与取出逻辑VIP 客户用栈管理后到先服务。链式栈的实现比队列更短// 链式栈节点 struct SNode { Customer data; // 节点数据 SNode* next; // 指向前一个更靠近栈底的节点 }; // 链式栈只需要一个 top 指针 struct LinkStack { SNode* top; // 栈顶指针NULL 表示空栈 }; // 压栈新节点放到栈顶 void Push(LinkStack s, Customer c) { SNode* node new SNode; node-data c; node-next s.top; // 新节点指向原来的栈顶 s.top node; // 更新栈顶 } // 弹栈取栈顶元素返回 false 表示空栈 bool Pop(LinkStack s, Customer out) { if (s.top NULL) return false; out s.top-data; // 取出栈顶数据 SNode* tmp s.top; s.top s.top-next; // 栈顶下移 delete tmp; return true; }VIP 到达时执行 Push窗口空闲时执行 Pop最后一个来的 VIP 最先被服务这就是“合法出栈序列判定”那套逻辑的现场版本。如果你在复习王道 408 或者对照严蔚敏《数据结构》C 语言版看栈这一章会发现这里就是基础操作题的翻版只是数据从“数字”换成了“客户结构体”。写这一段时最容易翻车的点是搞混“栈顶”的方向。Push 里新节点必须指向旧 top再把 top 更新为新节点如果顺序写反top 指向了新节点的 next链表在第一次 Push 后就断了。拿纸笔画一遍指针指向再动手比 debug 猜半天快。3.3 多窗口调度VIP 栈和普通队列的优先级怎么控制有了队列和栈核心问题变成窗口空闲时从哪边取人常见实现是一个统一的取人函数// 窗口空闲时决定下一个服务谁 bool NextCustomer(LinkQueue normalQ, LinkStack vips, Customer next) { // 优先处理 VIP 栈 if (vips.top ! NULL) { Pop(vips, next); return true; } // VIP 栈空了从普通队列队头取 if (normalQ.front ! NULL) { DeQueue(normalQ, next); return true; } return false; // 两边都空没人可服务 }调度逻辑的优先级非常直接先查 VIP 栈再查普通队列。每轮遍历窗口数组对每个空闲窗口调用一次 NextCustomer就把“多窗口并行”模拟出来了。窗口数量、服务时长这些参数都在主循环里控制跟这个函数无关这也便于答辩时单独解释“调度策略”这一段。如果想让程序更像真实银行可以在这里做“动态调整”。常见做法是记录普通队列的等待人数当 VIP 连续被服务多次且普通队列长度超过阈值时下一轮强制从普通队头取一个客户。这就是摘要里“动态调整服务策略”落地的加分点代码上只是在 NextCustomer 前面加一个计数器判断逻辑不难但回答“为什么普通客户不会饿死”这个问题时非常好用。3.4 顺序结构 vs 链式结构这份作业为什么选链式有些同学会问用数组模拟队列和栈不是更简单吗确实顺序结构代码量更小但在这份作业的背景下链式通常更合理。两者的差异可以用一张表说清对比项顺序结构数组链式结构链表最大容量需要预设 MAXSIZE满了要扩容不限制动态分配队头出队需要移动后续元素时间 O(n)或用循环队列指针后移O(1)代码量少边界条件也少稍多多出节点和指针管理内存释放静态数组不用管new 和 delete 要一一对应容易泄漏答辩暴露点容易被问“容量满了怎么办”容易被问“内存怎么释放空结构怎么判断”课程作业选链式一方面是排队人数不可预知链式天然适配另一方面是老师在问“栈和队列的区别”时链式实现能引出内存管理、指针操作、空结构判断这些进阶话题展示的内容比顺序结构多得多。顺序结构更适合“容量确定、性能要求高”的场景比如嵌入式里受限环境下的环形队列。提示链式实现写完一定要自查 new 和 delete 是否成对。作业代码规模不大常见的泄漏点就是 Pop 和 DeQueue 里 delete 了节点但 Push 和 EnQueue 里 new 出来的节点在程序结束时没清空。做不做析构清理一般不影响课程作业跑分但会在答辩时被问住。4. 避坑排查调试银行排队系统最常见的五个翻车点这份作业我从工程层面、代码层面、数据层面拆过一遍也见过不少同学踩同样的坑整理五条血泪经验。每条都按现象、原因、解决三步写基本覆盖了从拿到压缩包到答辩前的大部分问题。4.1 改了源码跑出来的还是旧程序现象在 Code::Blocks 里改了 main.cpp点 Run 之后控制台输出跟改之前一模一样加的中文提示也没出现。原因最常见的有三种——编译没通过但没看编译输出栏工程配置的是 Release 目标而 Run 用的是 Debug改完的代码编译到了另一个目录更隐蔽的情况是直接用资源管理器双击了 bin/Debug 里残留的旧 exe根本没走 Code::Blocks 的编译流程。解决先看 Code::Blocks 下方编译输出面板有没有 “error:” 开头的行有就先把报错修完然后在主界面确认右上角 Build target 选的是 Debug再点一次 Build最后到 bin/Debug 下看 exe 的修改日期是否和刚才编译时间一致。我一般习惯每次编译完都顺手看一眼 exe 时间戳比背编译快捷键更靠谱。4.2 控制台中文全部乱码现象程序能跑但窗口里输出的中文提示变成一屏乱码或者“烫烫烫”。原因Code::Blocks 自带 GCC 链在 Windows 上对源码的编码处理和 Windows 控制台默认代码页不一致。源码是 GBK 保存的MinGW 默认按 UTF-8 解析或者反过来中文字符全部解码失败。解决最省事的办法是把 main.cpp 另存为 UTF-8 编码带 BOM并在程序开头加一句 SetConsoleOutputCP(CP_UTF8)需要包含 windows.h。不想引入 Windows 头文件的话直接把所有中文提示改成英文代码里只剩 ASCII 字符就不会有编码问题。课程作业用英文提示完全没问题答辩时口头解释即可。4.3 队列还剩一个元素出队后再次入队就崩溃现象DeQueue 把队列清空后程序没有退出而是继续运行。后面某个新客户入队程序在 EnQueue 或者下一次遍历队列时崩溃要么访问野指针要么死循环。原因这就是 3.1 节里说的那个坑——出队最后一个节点后front 被置成 NULL但 rear 还指向已释放节点。下一次 EnQueue 判断 q.rear NULL 时不成立于是把新节点挂到了野指针的 next 上。解决DeQueue 里在 q.front q.front-next 之后立即补一句 if (q.front NULL) q.rear NULL;。写完这道逻辑顺便把 EnQueue 里空队列分支的初始化也检查一遍保证 front 和 rear 始终是“要么都 NULL要么都指向有效节点”。4.4 VIP 源源不断普通客户排队被饿死现象测试数据里 VIP 较多窗口优先服务完一个 VIP栈顶立刻又有新的 VIP。跑完模拟普通客户等待时间高得离谱甚至到模拟结束还没来得及服务。原因调度函数 NextCustomer 每次都先查 VIP 栈只要栈不为空普通队列永远拿不到服务机会。从数据结构角度这符合 LIFO但从模拟真实排队场景的角度这个策略不公平。解决在调度里加一个“饥饿保护”计数。每连续服务 k 个 VIPk 自己定比如 3 个下一轮强制从普通队列取一个人然后把计数清零。这样既保留了栈的插入特性又解释了“动态调整”的设计来源。改完这步答辩时老师问“VIP 和普通客户怎么平衡”就有了明确答案。4.5 xinxi.txt 换个目录就读不到了现象项目在原来电脑上跑得好好的拷到笔记本或者直接从 bin/Debug 里双击 exe程序启动后提示找不到文件或者客户数据全为 0。原因代码里用的是相对路径而程序的工作目录变了。Code::Blocks 里 Run 是在工程目录下启动的所以相对路径 xinxi.txt 能读到直接双击 bin/Debug 的 exe工作目录变成了 bin/Debug工程根目录下的 xinxi.txt 自然读不到。解决两个办法任选——把 xinxi.txt 复制一份到 bin/Debug 目录或者在 main.cpp 里用绝对路径写死但换机器又要改。我一般做法是工程根目录和输出目录各放一份代码里先用 ifstream 的 is_open() 判断打不开就输出当前工作目录排查起来最快。5. 从跑通到答辩验证步骤、实验数据与自查清单能跑通只是及格线答辩现场讲得清楚才算真正消化。拿到这份资源后别急着改代码先按下面三步把“栈和队列在这套系统里到底怎么配合”验证一遍实验报告的数据也就有了着落。5.1 三组必测用例第一组全普通客户所有人入队按到达顺序出队验证 FIFO。第二组全 VIP所有人进栈后到的先被服务验证 LIFO 和合法出栈序列。第三组混合数据VIP 约占 10%验证优先级调度和饥饿保护是否生效。这三组用例每个跑完记录平均等待时间就是实验报告的核心数据。5.2 实验数据怎么设计设计 20 组客户反复跑VIP 占比分别取 0%、10%、30%开启饥饿保护前后各记录一次得到的结果就可以画对比表VIP 占比平均等待时间分钟是否开启饥饿保护0%6.2否10%5.1是30%8.7是等待时间统计的是客户从到达时刻到服务开始时刻的差值。VIP 占比越高后到的 VIP 越容易插到前面普通客户平均等待时间反而上升这个现象本身就是答辩时可以展开的话题。5.3 答辩前自查清单最后花十分钟过一遍空队列和空栈判断是否齐全每个 new 是否有对应的 delete窗口数组在每轮循环是否有状态刷新时间统计口径是否统一引入随机数时是否设置了种子。每一项都能和栈或队列的某个特性挂钩被问到任何一项都能接得住。我自己大二那次就栽在只测常规用例上。答辩现场老师给了一组 VIP 连续到达的数据普通客户排到最后一分钟还没被服务我盯着屏幕半天说不清这是 LIFO 的必然结果。“栈的优先级和公平性怎么平衡”这个问题我直到实际改了饥饿保护才彻底想明白。从那以后每次写完这类模拟程序我都强制自己先跑极端数据再跑混合数据两头逻辑都对才敢拿出手。希望帮到你。本文还有配套的精品资源点击获取