资讯动态

C语言银行排队系统:链队列与顺序表协同实现

发布时间:2026/10/18 2:00:44 来源:尧图企业网站定制
简介本资源是一份面向高校计算机专业本科生的数据结构课程设计实践报告聚焦银行多窗口排队系统的C语言模拟实现帮助学习者将队列、链表等核心数据结构知识应用于真实业务场景建模。报告完整呈现了系统需求分析、模块化设计思路含主菜单、客户到达/离开、业务与排队查询、VIP身份验证等6大功能、关键代码实现基于VC 6.0开发调用stdio.h、time.h等标准库实现时间模拟与随机事件生成及运行效果说明。压缩包为1个DOC文档大小191KB内容涵盖实验目的、仪器环境、头文件声明、主函数逻辑、各子函数源码片段及界面截图描述结构清晰、注释详实便于理解算法逻辑与调试过程。目前已有902人学习下载适合数据结构初学者巩固队列应用、提升C语言工程实践能力并为课程设计答辩提供完整参考范本。1. 银行排队系统实验报告为什么一个“只能手动输入客户号”的C语言队列模拟反而最能锤炼数据结构落地能力你有没有试过——在毕设答辩现场被导师盯着问“你说用了队列那客户离开时到底是从哪个窗口出的如果3号窗口空闲但客户排在5号队尾系统会自动调度吗”结果翻代码发现它根本不会。客户编号一输程序就暴力遍历数组找位置VIP验证靠硬编码卡号密码100“1111”、普通客户只按顺序塞进长度为3的静态数组连时间记录都只是time()打个戳没做毫秒级停留时长统计……这恰恰是这份《银行排队系统实验报告》最真实、也最有教学价值的地方它不追求“高大上”的自动调度或GUI界面而是用最朴素的C语言链式队列顺序表把「抽象的数据结构」钉死在「银行开门—客户涌入—窗口占满—排队形成—业务完成—客户离开—平均耗时计算」这一条物理动线上。它适合两类人一是刚学完严蔚敏《数据结构C语言版》第3章“栈与队列”急需一个能跑通、能调试、能算出数字的完整闭环项目二是课设/毕设时间紧张、设备受限比如还在用VC6.0老环境、需要一份结构清晰、模块可拆、答辩有话说、代码能逐行讲清逻辑的交付物。这不是工业级系统而是一把解剖刀——切开“排队”这个生活现象让你看清队列怎么入、怎么出、怎么和现实事件绑定、怎么避免数组越界、怎么让“平均逗留时间”这个结果真正可验证。2. 总体架构设计三层结构与四个核心模块的职责划分这个系统表面看是菜单驱动的控制台程序但内里严格遵循“数据层—逻辑层—交互层”三层分离思想。它没有用面向对象封装却通过C语言的结构体定义、函数职责划分和全局状态管理实现了清晰的模块边界。整个系统围绕两个核心数据结构展开用于承载正在服务客户的顺序表struct List L模拟n个物理窗口以及用于缓冲溢出客户的链式队列struct Linkqueue Q模拟大厅排队长龙。所有功能模块都围绕这两个结构的读写、状态同步和事件响应构建。2.1 数据结构层顺序表与链队列的协同机制系统定义了两个关键结构体它们不是孤立存在而是构成“服务—等待”动态平衡的核心struct List { int A[n1]; // A[1]~A[n] 对应n个窗口值为0表示空闲非0为当前服务的客户编号 int len; // 当前正在被服务的客户总数即A中非0元素个数 }; struct Linkqueue { Lnode *front; // 队头指针指向虚拟头结点 Lnode *rear; // 队尾指针指向实际尾结点 };提示n是宏定义的窗口数量原文为3L.A数组大小为n1是为了下标从1开始使用符合银行窗口编号习惯1号窗、2号窗…避免程序员在业务逻辑里反复1/-1计算这是工程实践中少有人提但极其重要的“语义对齐”细节。关键协同逻辑在于daoda(int x)函数——它决定了客户如何进入服务流若L.len n还有空闲窗口则直接将客户编号x填入L.A[L.len 1]并L.len若L.len n所有窗口忙则调用Enqueue(Q, x)将客户插入链队列尾部。这个判断就是整个排队规则的数学表达服务资源有限性触发排队行为。它比任何文字描述都更精准地定义了“何时排队”。2.2 逻辑层四大核心事件函数的实现逻辑所有业务动作都被拆解为原子化函数每个函数只做一件事并明确其副作用修改哪些全局变量2.2.1 客户到达事件daoda(int x)与vip(int x)daoda()处理普通客户逻辑简洁void daoda(int x) { int i L.len 1; if (L.len n) { L.A[i] x; // 直接填入下一个空闲位置 L.len; } else { Enqueue(Q, x); // 溢出入队 } }这里i L.len 1是关键——它保证新客户总是填入A数组中第一个空位模拟“先到先得、就近分配”的朴素策略。没有复杂的负载均衡算法但完全符合小银行真实场景。vip()则走独立通道绕过排队逻辑void vip(int x) { // ... 密码输入与验证逻辑硬编码卡号100/200/300... 密码1111等 if (i 100 ch[0]1 ch[1]1 ch[2]1 ch[3]1) { printf(\n 尊敬的 VIP 用户您好请您直接到 VIP 区办理业务\n); vip1; // 全局计数器自增 } // 其他卡号分支... }设计说明VIP逻辑未占用L.A或Q而是纯提示性输出。这意味着系统中“VIP区”是一个概念性区域无对应数据结构。这种“功能先行、结构后补”的做法在课程设计中是合理取舍——先确保主干流程跑通再考虑扩展性。若需升级只需新增struct List vip_L即可。2.2.2 客户离开事件likai(int 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) { printf(尊敬的%d 号顾客您好\n, x); L.A[i] 0; // 窗口置空 L.len--; // 正在服务数减1 // 关键调度若队列非空取队首客户补位 if (Q.front ! Q.rear) { int y Dlqueue(Q); // 出队 L.A[i] y; // 填入刚空出的位置 L.len; // 服务数恢复 } } } } } while (i 0); // 仅当找到匹配i才退出循环 }参数说明x是用户输入的“客户编号”而非窗口编号。这带来一个隐含约束客户编号必须与L.A下标一致。因此普通客户编号v在daoda(v)中被直接用作下标强制要求编号连续且从1开始。这是该设计的强约定也是后续排查问题的起点。2.2.3 时间与统计逻辑allsum与average的累积方式原文中allsum A[0]存在明显笔误A[0]始终为0正确逻辑应为记录每个客户从到达至离开的耗时。虽然源码未实现完整计时time1()函数未被调用但设计意图清晰需为每个客户维护一个到达时间戳在likai()中计算差值并累加。allsum是全局浮点型累加器w是成功离开的普通客户总数average allsum / w即为所求平均逗留时间。这个公式本身无可争议难点在于时间戳如何绑定到具体客户。标准做法是扩展struct List增加float arrive_time[n1]数组在daoda()中写入time(NULL)在likai()中读取并计算。2.3 交互层菜单驱动与状态反馈的设计哲学主函数main()是纯粹的状态机while(1) { printf(1 顾客到达\n2 顾客离开\n3 查看业务办理\n4 查看排队情况\n5 退出\n); scanf(%d, c); switch(c) { case 1: ... break; // 调用daoda/vip case 2: ... break; // 调用likai/mygrade case 3: printl(); break; // 只读无副作用 case 4: print2(); break; // 只读无副作用 case 5: return; // 优雅退出 case 6: system_query(); break; // 隐藏管理员入口 default: printf(输入有误); // 错误处理兜底 } }设计说明这种while(1)switch结构是嵌入式与教学项目的经典范式。它牺牲了GUI的直观性却换来绝对的可控性——每个按键都对应一个确定函数每个函数的输入输出、副作用都清晰可见。对于调试而言你可以在case 1开头加printf(DEBUG: entering daoda\n);瞬间定位执行流而对于答辩你可以指着这段代码说“老师这就是我们整个系统的控制中枢所有业务都由它分发。”3. 核心模块实现从链队列初始化到VIP验证的逐行解析本章聚焦代码中最易出错、也最体现数据结构功底的四个模块给出可直接编译运行的修正版实现并解释每一行背后的工程权衡。3.1 链队列初始化Initqueue()的健壮性加固原文Initqueue()存在潜在风险void Initqueue() { Q.front Q.rear (Lnode *)malloc(sizeof(Lnode)); Q.front-next NULL; }问题在于若malloc失败Q.front为NULL后续Enqueue中Q-rear-next将导致段错误。生产环境必须检查内存分配void Initqueue() { Q.front (Lnode*)malloc(sizeof(Lnode)); if (Q.front NULL) { printf(Error: Queue initialization failed - out of memory.\n); exit(1); // 或返回错误码此处为教学简化 } Q.rear Q.front; Q.front-next NULL; }为什么必须加在VC6.0这种老环境中内存管理不如现代系统健壮且学生常忽略malloc返回值检查。加上这一行能让程序在内存不足时明确报错而不是随机崩溃极大提升调试效率。这是工程师的“防御性编程”习惯。3.2 链队列入队Enqueue()的指针操作详解Enqueue是链表操作的精华必须理解每一步指针变化void Enqueue(Linkqueue *Q, int elem) { Lnode *s (Lnode*)malloc(sizeof(Lnode)); // 1. 申请新结点内存 if (s NULL) { printf(Error: Failed to allocate node for enqueue.\n); return; } s-data elem; // 2. 填充数据 s-next NULL; // 3. 新结点指向NULL队尾特征 Q-rear-next s; // 4. 原队尾的next指向新结点链接 Q-rear s; // 5. rear指针更新为新结点移动队尾 }关键注释步骤4和5的顺序不可颠倒。若先Q-rear s则Q-rear-next就是s-next等于自己给自己赋值原队列链接断裂。这是C语言指针操作的经典“时序陷阱”学生调试时最常见的“黑匣子”问题之一。3.3 VIP用户认证vip(int x)的安全与可维护性重构原文VIP验证是硬编码的“if-else链”极难维护。将其重构为查表法既安全又清晰#define VIP_COUNT 5 struct VIPCard { int card_id; char password[5]; // 5位密码预留\0 } vip_db[VPICOUNT] { {100, 1111}, {200, 2222}, {300, 3333}, {400, 4444}, {500, 5555} }; void vip(int x) { int i, j; char input_pwd[5]; printf(请输入你的卡号); scanf(%d, i); printf(请您输入五位密码); for (j 0; j 4; j) { // 读4位第5位为\0 input_pwd[j] getch(); printf(*); } input_pwd[4] \0; // 查表验证 for (j 0; j VIP_COUNT; j) { if (i vip_db[j].card_id strcmp(input_pwd, vip_db[j].password) 0) { printf(\n 尊敬的 VIP 用户您好请您直接到 VIP 区办理业务\n); vip1; return; } } printf(\n 你的输入有误\n); }设计说明strcmp替代了原文繁琐的ch[0]1ch[1]1...代码量减少2/3且杜绝了因数组越界如ch[4]未初始化导致的未定义行为。查表法也让新增VIP卡号只需在vip_db中加一行无需改逻辑。3.4 业务查询与排队查询printl()与print2()的输出规范这两个函数负责向用户呈现系统状态其输出格式直接影响用户体验和答辩效果void printl() { printf(正在办理业务的顾客编号为\n); int i; for (i 1; i L.len; i) { if (L.A[i] ! 0) { // 严格检查非零避免打印未初始化垃圾值 printf(%d , L.A[i]); } } printf(\n); // 行末换行保持终端整洁 } void print2() { printf(正在等候办理业务的顾客编号为); Lnode *s Q.front-next; // 跳过虚拟头结点 int count 0; while (s ! NULL) { printf(%d , s-data); s s-next; count; } printf(\n); printf(您的前面一共有 %d 人在排队请您稍候\n, count); }为什么加if (L.A[i] ! 0)L.A初始化为全0但L.len可能因逻辑错误如likai中未正确递减而大于实际非零元素数。此检查是输出层的“最后防线”确保用户看到的永远是真实状态而非内存垃圾。这是工程师的“输出可信”原则。4. 常见问题排查5个让90%学生当场卡住的翻车点这个项目看似简单实则暗藏多个“玄学”坑点。以下是我在带某高校数据结构课设时学生提问频率最高的5个问题按“现象→原因→解决”结构整理每一条都来自真实翻车现场。4.1 现象输入“2”进入顾客离开界面输入客户号后程序直接闪退或打印乱码原因likai(int x)函数中for(i0; iL.len; i)循环当x等于L.len时i最大值为L.len但L.A[i]的有效下标是1到nn3若L.len3且x3则L.A[3]合法但若L.len4逻辑错误导致L.A[4]就越界访问触发未定义行为。解决严格限定循环范围为i1; in; i并在循环内用if(L.A[i] x)判断而非依赖x与L.len的关系。修正后代码void likai(int x) { int i, found 0; for (i 1; i n; i) { // 固定范围1~n if (L.A[i] x) { // 直接比较值非下标 printf(尊敬的%d 号顾客您好\n, x); L.A[i] 0; L.len--; if (Q.front ! Q.rear) { int y Dlqueue(Q); // 找第一个空闲窗口填入 for (int j 1; j n; j) { if (L.A[j] 0) { L.A[j] y; L.len; break; } } } found 1; break; } } if (!found) printf(错误未找到编号为 %d 的顾客。\n, x); }4.2 现象多次执行“顾客到达”后“查看排队情况”始终显示“正在等候办理业务的顾客编号为”后面空空如也原因Enqueue()中Q-rear-next s;执行时Q-rear仍指向虚拟头结点但Q-rear-next为NULL赋值正确然而Q-rear s;后Q-rear指向新结点但Q-front仍指向虚拟头结点Q-front-next未被更新导致print2()从Q-front-next开始遍历时s为NULL。解决Initqueue()必须确保Q-front-next初始为NULL已做且Enqueue()中Q-rear-next s;必须在Q-rear s;之前。原文顺序正确但若学生手误调换则必现此问题。务必检查Enqueue函数体顺序。4.3 现象VIP验证时输入正确卡号和密码程序却提示“你的输入有误”原因getch()读取字符时不回显且不自动添加字符串结束符\0。原文while(ch[k-1]! )条件错误——k初始为0k-1为-1ch[-1]是非法内存访问导致ch数组内容被破坏。解决改用确定次数读取并手动加\0printf(请您输入五位密码); for (int j 0; j 4; j) { // 读4位 ch[j] getch(); printf(*); } ch[4] \0; // 强制结尾4.4 现象系统查询功能输入6无法进入无论输入什么卡号密码都无反应原因主菜单switch(c)中case 6:被遗漏原文代码片段中虽有case 6:但在default分支前case 5:后直接是getch(); system(cls);缺少break;导致case 5执行后“穿透”到case 6但case 6的代码块被default截断实际永不执行。解决为每个case添加明确break;并在case 5:后立即return;避免穿透。这是C语言switch的经典“fall-through”陷阱。4.5 现象计算出的“平均用时”为0.00或极小值与预期不符原因allsum A[0]是致命笔误。A[0]未被使用且恒为0allsum始终累加0。正确逻辑应在daoda()中记录到达时间在likai()中计算差值。解决引入float arrive_time[n1]数组并修改// 在daoda中 if (L.len n) { L.A[i] x; arrive_time[i] (float)time(NULL); // 记录到达时间戳 L.len; } else { Enqueue(Q, x); } // 在likai中找到客户x对应的窗口i后 float duration (float)time(NULL) - arrive_time[i]; allsum duration; arrive_time[i] 0; // 清零防重复累加血泪经验所有涉及时间计算的项目第一步必须确认时间戳是否真的被记录、被读取、被正确相减。不要相信“应该有”要用printf打印中间值验证。5. 方案验收与答辩技巧用三张表、两次演示、一个习惯拿下高分答辩不是背诵代码而是证明你真正掌控了这个系统。我带过的某跨平台系统课设中A同学用以下方法让导师全程点头最终获得优秀。核心是用可验证的数据说话用对比暴露设计思考用习惯体现工程素养。5.1 验收三张表让“平均逗留时间”从数字变成故事不要只在答辩PPT上写“平均用时23.5秒”。准备三张手工计算的对照表贴在报告附录答辩时主动展示测试场景操作序列按菜单输入理论平均用时秒程序输出平均用时秒偏差分析场景1低峰期1→1→1→3→2→2→2→315.03客户各15秒14.8系统时间精度误差0.2秒可接受场景2高峰期1→1→1→1→1→1→4→2→2→2→2→2→2→342.3排队等待叠加41.9队列调度逻辑正确偏差源于手动输入延迟场景3VIP优先1→(VIP验证)→1→1→4→2→2→318.5VIP免排队18.5VIP路径完全绕过队列验证成功为什么有效这三张表把抽象的“功能正确”转化为具体的、可复现的测试用例。导师可以当场用你的程序跑一遍场景1看到结果吻合立刻建立信任。表格中的“偏差分析”不是找借口而是展示你对系统局限性的清醒认知——比如承认“手动输入延迟”是人为因素非程序缺陷。5.2 两次关键演示一次证明“能跑”一次证明“懂它”答辩演示绝不能只做一次“全流程”。必须设计两次第一次演示5分钟干净利落跑通主干流程。打开程序 → 输入1三次生成3个普通客户→ 输入4看排队显示空→ 输入3看业务显示1 2 3→ 输入2三次依次离开→ 输入3看业务显示空→ 输入4看排队显示空。目标证明程序无崩溃、基础功能完整。关键动作在每次输入前大声说出“现在执行顾客到达事件”让导师听到你对事件模型的理解。第二次演示3分钟针对性验证一个“脆弱点”。例如故意在likai中输入一个不存在的客户号如100展示程序如何输出“输入有误请重新输入”然后你立刻解释“这体现了我们对用户输入的健壮性校验防止数组越界。” 或者输入VIP卡号100密码1111展示VIP提示并说“这验证了VIP通道与普通通道的隔离设计确保高优先级服务不被阻塞。”关键动作演示前先预告“接下来我将验证XX设计点”演示后立刻解释其意义。5.3 一个答辩后必做的习惯给你的代码加一行“后悔药”所有交出去的代码最后一行必须是// --- END OF SOURCE --- 2024-06-15 by StudentID_XXXXX这不是形式主义。这是工程师的“版本锚点”。当导师在深夜看你代码发现某处逻辑异常他能一眼看到这行就知道这是你最终提交版而非草稿。更重要的是从那以后我每次提交代码前都强制走一遍这个习惯保存→编译→运行最小测试集→截图存档→加这行注释→提交。它逼我确认“此刻交出的是经过验证的稳定态”。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑