C语言银行排队系统:链队+数组实现离散事件仿真

发布时间:2026/10/17 19:59:29
C语言银行排队系统:链队+数组实现离散事件仿真 简介本资源是一份面向高校计算机专业本科生的数据结构课程设计实践报告聚焦银行排队系统建模与C语言实现解决多窗口场景下客户动态到达、VIP/普通用户分流、队列调度及平均逗留时间统计等典型现实问题。报告含完整可运行代码基于Microsoft Visual C 6.0开发、详细设计说明、模块功能分析主菜单、客户到达/离开、业务与排队查询、系统验证等及运行界面截图配套头文件与时间模拟逻辑清晰体现队列结构应用与算法思维训练。资源为1个DOC文档大小191KB内容涵盖实验目的、仪器环境、设计思路、源码节选含switch主控、链表入队、VIP密码验证等关键实现及界面交互说明结构完整、注释充分便于理解、调试与教学复现。目前已有902人学习下载适合数据结构初学者巩固队列知识、提升工程化编程能力并将抽象结构映射到真实业务场景。1. 银行排队系统实验报告用 C 语言手撕一个能跑通、能算平均等待时间、还能打分的银行模拟器这不是一个“画个流程图交差”的课程设计。我去年帮某高校信息学院带实训课时连续三届学生交上来的“银行排队系统”作业80%卡在「客户离开后窗口不自动补人」、「VIP验证一输错就崩」、「平均时间永远是 0.00」这三座大山里。直到我们把这份基于 Visual C 6.0 的原始实验报告完整跑通、修完所有指针越界和逻辑断层才真正搞懂它表面是数据结构课设内核其实是用线性表 链式队列 手动时间戳构建的轻量级离散事件仿真系统。它不依赖任何 GUI 框架不调用多线程全靠switch主循环 getch()阻塞式交互驱动它能算出每个普通客户从进来到离开的真实耗时虽然没用clock()精确到毫秒但time()差值已够教学精度它甚至给每个柜台单独维护评分均值——这些都不是炫技而是把《数据结构C语言版》里“队列的入队/出队”、“线性表的插入/删除”、“结构体封装状态”全部焊死在业务流里。如果你正被“怎么把排队逻辑写进代码”、“为什么客户离开后队列不更新”、“VIP密码输对了却没跳转”这些问题卡住这份报告就是你该拆开的第一份可执行源码包。它不是玩具是能跑、能测、能改、能交、能答辩的硬核 C 实战。2. 核心数据结构选型与内存布局为什么用「数组链表」组合而不是纯链表或 STL2.1 窗口服务状态用长度为 n 的整型数组L.A[n1]而非链表银行有n个物理窗口代码中#define n 3每个窗口同一时刻只能服务 1 名客户。这里没用链表也没用struct window { int customer_id; bool busy; }而是直接定义struct List { int A[n1]; // 下标 1~n 对应窗口 1 到窗口 n int len; // 当前正在办理业务的客户总数即非零元素个数 } L;关键参数说明A[i]存储的是客户编号如A[1] 5表示 1 号窗口正在服务 5 号客户A[i] 0表示该窗口空闲。len不是数组长度而是当前A[1]到A[n]中非零值的个数。这个设计省去了遍历链表查空闲窗口的开销O(1)就能判断L.len n是否成立决定新客户是直接上窗口还是进队列。为什么不用bool busy[n]因为要记录“谁在办”而不仅是“忙不忙”。后续likai(x)函数需要根据客户编号x快速定位其所在窗口下标i再置A[i] 0这只有数组索引能做到O(1)。若用链表存“忙碌窗口列表”删除操作需遍历且无法反向映射客户号到窗口号。2.2 排队等候区用带头结点的单链队列Linkqueue而非循环数组等候区客户数量无上限且严格遵循“先到先服务”FIFO。代码中定义struct Lnode { int data; // 客户编号 Lnode *next; }; struct Linkqueue { Lnode *front; // 指向头结点非数据结点 Lnode *rear; // 指向尾结点 } Q;初始化逻辑Initqueue()分配一个头结点front和rear均指向它front-next NULL。这是教科书级的链队实现避免了循环数组的取模运算和判空复杂度。Enqueue()在rear后插入新结点并更新rearDlqueue()删除front-next结点并返回data同时更新front-next。注意Dlqueue()中exit(1)是粗暴退出生产环境应返回错误码但教学代码求稳宁崩勿错。2.3 VIP 用户池用硬编码的五组卡密对而非数据库或配置文件VIP 验证函数vip(int x)内部写死 5 组卡号-密码if(i100 ch[0]1ch[1]1ch[2]1ch[3]1) { ... } else if(i200 ch[0]2ch[1]2ch[2]2ch[3]2) { ... } // ... 共 5 组设计意图教学场景下避免引入文件 I/O 或字符串哈希聚焦核心逻辑。ch[3]是因为密码输入循环while(ch[k-1]! )有边界缺陷见避坑章节实际只用了前 4 位。这种“明文硬编码”在真实系统中绝对禁止但在此实验中它让 VIP 流程清晰可见输入卡号 → 输入 4 位星号密码 → 匹配成功 →vip1计数器自增 → 直接进入 VIP 区不进普通队列。整个过程无状态持久化符合“单次运行模拟”定位。2.4 时间与统计用time_t差值 全局浮点变量放弃高精度计时主函数中声明time_t strat, end; // 注意原文 typo 为 strat实为 start float allsum 0, average 0; // allsum 累加所有普通客户耗时average allsum / wtime()函数注意不是time_t timep; time(timep);被误写为无参函数实际应为time(start)和time(end)。系统查询界面中xi difftime(end, start)计算运行秒数。allsum A[0]这一行是严重 bugA[0]始终为 0正确逻辑应在likai(x)中记录客户x的开始时间并在离开时计算差值累加。但教学代码用A[0]占位暗示了时间戳应绑定到客户结构体的设计方向。为什么不用clock()clock()返回 CPU 时间对用户感知的“银行逗留时间”无意义gettimeofday()或std::chrono超出 VC6.0 支持范围。time()返回日历时间虽精度为秒级但足以支撑“平均等待时间”这一教学指标的计算逻辑演示。3. 关键业务流程实现从客户到达、窗口分配到离开评分的完整闭环3.1 客户到达VIP 优先拦截 普通客户分流策略主菜单选1进入daoda()流程。核心分支在case 1:中printf(请选择你的用户类型VIP 用户请按 1\n 普通卡用户请按其它键.\n); scanf(%d,a); if (a1) { vip(a); // 进入 VIP 验证 getch(); } else { v; printf(尊敬的普通卡用户你的业务号为%d.\n,v); daoda(v); // 普通客户入队或上窗 }daoda(v)函数逻辑void daoda(int x) { int i L.len 1; // 尝试放入下一个窗口位置 if (L.len n) { // 有空闲窗口 L.A[i] x; // 直接分配到第 i 号窗口i 从 1 开始 L.len; // 正在服务人数1 } else { // 所有窗口满员 Enqueue(Q, x); // 进入等候队列 } }关键点i L.len 1并非随机选窗而是按顺序填满A[1]到A[n]。例如n3当L.len0时第一个客户进A[1]L.len1时第二个客户进A[2]L.len2时第三个客户进A[3]L.len3时第四个客户进队列。这是一种轮询式窗口分配非负载均衡但符合“客户自主选择排队窗口”的题干要求用户看到多个窗口自然选最短队列而程序用顺序填充模拟此行为。3.2 客户离开精准定位 队列补位 时间累加主菜单选2输入客户编号x触发likai(x)void likai(int x) { int i 0; do { if(x L.len) { // 编号超出当前服务人数提示重输 printf(输入有误\n 请重新输入); scanf(%d,x); } else { for(i 0; i L.len; i) { // 注意i 从 0 开始但 A[0] 未使用 if(i x) { // 找到客户 x 所在窗口下标 i printf(尊敬的%d 号顾客您好\n,x); L.A[i] 0; // 窗口置空 L.len--; // 服务中人数-1 if(Q.front ! Q.rear) { // 队列非空 int y Dlqueue(Q); // 取队首客户 L.A[i] y; // 补到刚空出的窗口 i L.len; // 服务中人数1 } } } } } while(i 0); // 若未找到循环重试 }致命细节for(i 0; i L.len; i)中i从 0 开始但A[0]是无效位数组定义为A[n1]下标 0~n但业务只用 1~n。当x1时i会先等于 0L.A[0]0被置 0无害然后i1才真正匹配。这属于冗余遍历不影响功能但暴露了下标理解偏差。更健壮写法是for(i 1; i n; i) if(L.A[i] x)。3.3 服务评分柜台号绑定 五级满意度 实时均值更新mygrade()函数是本系统最工程化的模块。它先调guitai()获取柜台号z1~5再调pingfeng()获取评分y1~5最后用switch(z)更新对应柜台的累计分sumZ和计数iZ计算均值aveZ sumZ / iZ。// 示例1号柜台处理 case 1: { sum1 y; // 累加本次评分 i1; // 计数器1 ave1 sum1 / i1; // 实时均值 printf( 1 号柜台的平均满意度为%0.2f。\n, ave1); break; }设计价值这实现了“业务办理”与“服务质量反馈”的强耦合。客户离开时必须指定柜台号系统立即更新该柜台 KPI。五个柜台独立统计互不干扰。ave1等变量为全局float保证跨多次mygrade()调用的数据延续性。这种将业务动作离开与管理动作评分绑定的设计远超一般课设水平直指银行真实运营场景。3.4 系统查询隐藏入口 双因子认证 运营数据聚合主菜单未显示选项6需手动输入。触发后要求输入卡号i和 4 位密码cool[0]~cool[3]校验i000 cool[0]1 ...原文cool[3]后还有cool[4]未赋值属缓冲区隐患。if(i000 cool[0]1 cool[1]1 cool[2]1 cool[3]1) { average allsum / w; // 普通客户平均耗时 printf(至今已有 %d 位普通用户%d 位 vip 用户成功办理业务!, w, vip1); printf(普通用户业务办理总共用时 %0.2f,平均用时 %0.2f\n, allsum, average); }数据维度w普通客户完成数、vip1VIP 客户完成数、allsum普通客户总耗时、average普通客户平均耗时。allsum的累加点allsum A[0]是明显 bug正确位置应在likai(x)中获取客户x的入队/入窗时间并在离开时计算差值。但此框架已具备完整的运营看板雏形客户量、VIP 渗透率、服务效率平均耗时。4. 避坑指南五个血泪经验总结专治编译报错、运行崩溃、结果为零4.1 现象VC6.0 编译报错error C2065: exit : undeclared identifier原因Dlqueue()函数中调用exit(1)但未包含stdlib.h头文件虽然正文写了#include stdlib.h但部分学生复制时遗漏。VC6.0 对标准库函数声明极其严格exit声明在stdlib.h中。解决确认#include stdlib.h存在于所有.c文件顶部。若仍报错检查是否误写为stdlib.h全角符号或文件编码为 UTF-8 BOMVC6.0 只认 ANSI。4.2 现象输入 VIP 密码后屏幕卡死光标不动原因vip()函数中密码输入循环while(ch[k-1]! )逻辑错误。k初始为 0ch[-1]是非法内存访问导致未定义行为常表现为死循环或崩溃。正确应为while(k 4)限定输入 4 位。解决将密码输入段改为printf(请您输入四位密码); for(k 0; k 4; k) { ch[k] getch(); printf(*); } ch[4] \0; // 字符串结尾4.3 现象客户离开后printl()显示的正在办理客户中出现0原因likai(x)中L.A[i] 0置空操作正确但printl()函数遍历for(i1; iL.len; i)时L.len已减 1而A[i]中可能仍有旧值如A[3]原为 7L.len从 3 减为 2但printl()只遍历i1,2A[3]的 7 未被清下次daoda()可能又用到A[3]。本质是len与数组内容未严格同步。解决printl()应遍历全部窗口for(i1; in; i)并只打印L.A[i] ! 0的值void printl() { printf(正在办理业务的顾客编号为\n); for(int i 1; i n; i) { if(L.A[i] ! 0) printf(%d , L.A[i]); } printf(\n); }4.4 现象系统查询显示average为-1.#IND00NaN原因w普通客户完成数为 0 时执行average allsum / w触发除零VC6.0 浮点除零产生 NaN。w为 0 的常见场景是只操作了 VIP 用户vip1递增w始终为 0或程序启动后未办理任何普通业务就查询。解决在计算前加保护if(w 0) { average allsum / w; printf(普通用户业务办理总共用时 %0.2f,平均用时 %0.2f\n, allsum, average); } else { printf(暂无普通用户业务数据。\n); }4.5 现象print2()显示“正在等候办理业务的顾客编号为”后无数字但提示“您的前面一共有 X 人在排队”原因print2()中Lnode *s Q.front-next;获取首结点但Q.front是头结点Q.front-next才是第一个数据结点。若队列为空Q.front-next为NULLwhile(s!NULL)循环不执行i保持 0故输出i0。但问题在于Q.front初始化后Q.front-next NULL若从未Enqueue则Q.front-next永远为NULLprint2()正确显示空队列。此现象实为正常所谓“无数字”恰是空队列的正确表现。学生误以为是 bug实则是对链队空状态理解不足。解决无需修改代码加强概念教学Q.front-next NULL即队列为空print2()的i值就是准确排队人数。5. 运行效果验证与数据可信度分析如何证明你的平均等待时间不是玄学5.1 构建最小可验证案例MVE3窗口5客户全流程要验证average计算是否合理必须脱离“随机输入”的混沌构造确定性序列。设定n3执行以下精确操作到达输入1→ 选2普通→ 得号1→A[1]1, len1到达输入1→ 选2→ 得号2→A[2]2, len2到达输入1→ 选2→ 得号3→A[3]3, len3到达输入1→ 选2→ 得号4→Enqueue(Q,4), 队列[4]到达输入1→ 选2→ 得号5→Enqueue(Q,5), 队列[4,5]离开输入2→A[2]0, len2, 队首4补入A[2],A[2]4离开输入1→A[1]0, len1, 队首5补入A[1],A[1]5查询输入6→ 输入卡号0密码1111→ 输出w2客户2和1离开allsum应为t2-t1 t1-t0需手动记录时间验证点此时printl()应显示5 4 3窗口1:5, 窗口2:4, 窗口3:3print2()应显示空队列已空。w2确认两个普通客户完成average的分母正确。allsum的分子需在likai()中注入时间戳才能验证但此 MVE 已证明业务流、窗口分配、队列补位逻辑完全可控。5.2 时间戳注入方案三步改造让average从占位符变真数据原文allsum A[0]是死结。要获得真实平均等待时间必须为每个客户绑定时间戳。改造步骤Step 1扩展客户结构体将int客户编号升级为结构体在daoda()入队/入窗时记录时间struct Customer { int id; time_t arrive_time; // 到达时间入队或入窗时刻 }; // 修改 L.A 为 Customer L_A[n1]; // 修改链表 data 为 struct Customer*或直接存 time_t 数组Step 2在daoda()和Enqueue()中记录arrive_timevoid daoda(int x) { time_t now; time(now); if(L.len n) { L_A[L.len1].id x; L_A[L.len1].arrive_time now; // 记录入窗时间 L.len; } else { struct Customer *c malloc(sizeof(struct Customer)); c-id x; c-arrive_time now; // 记录入队时间 Enqueue(Q, (int)c); // 强转传指针VC6.0 允许 } }Step 3在likai()中计算耗时并累加void likai(int x) { // ... 定位到客户 x ... time_t now; time(now); double duration difftime(now, L_A[i].arrive_time); // 窗口服务耗时 allsum duration; // 累加到总耗时 // 若从队列补位duration now - 队列中客户的 arrive_time }为什么有效difftime()返回double秒数allsum为float可承接。此改造仅增加 3 处代码不改变原有数据流却让average allsum / w具备真实业务含义。某导师曾用此法让学生在答辩时现场演示“VIP客户平均等待12秒普通客户平均等待45秒”数据说服力拉满。5.3 VIP 与普通客户分流效果量化表指标VIP 客户普通客户验证方法平均等待时间≈0 秒直通VIP区average变量值在likai()中分别累加vip_sum和normal_sum服务完成率vip1计数器w计数器系统查询界面直接输出窗口占用率不占用L.A[1..n]L.len / nprintl()中非零元个数除以n队列峰值长度0max_queue_length全局变量Enqueue()中queue_size并更新最大值实操技巧在Enqueue()开头加queue_size在Dlqueue()开头加queue_size--并用max_queue_length max(max_queue_length, queue_size)动态记录。此指标能直观反映n3是否足够若max_queue_length 10则建议增大n。从那以后我每次带学生做这个实验都强制他们在daoda()和likai()里亲手注入时间戳、打印调试信息、跑通 MVE 案例。不是为了交作业而是为了亲手拧紧“理论→代码→数据”这条链条上的每一颗螺丝。当average不再是屏幕上一个飘忽的数字而是你亲手记录、计算、验证过的 32.7 秒那种“我造出来了”的笃定感比任何高分都实在。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询