简介《银行排队系统实验报告》面向数据结构课程学习者与C语言课程设计实践者围绕模拟多窗口银行排队场景展开将队列结构、随机事件生成与时间管理落实到可运行的C程序设计中。文档完整记录实验目的、实验仪器与编译环境Microsoft Visual C 6.0、设计思路及程序分析并拆解主菜单、顾客到达与离开、业务办理查询、排队状态查询、卡号查询等功能模块同时给出主函数、VIP认证等关键源码片段便于对照理解队列初始化、入队出队和平均逗留时间统计的实现。资源包共1个doc文档约191KB体量轻便适合直接查阅或打印研读。目前已有904人学习可作为课程实验报告撰写、课程设计复盘与C语言综合练习的参考样本帮助读者把握从实际问题抽象到算法与代码表达的完整路径。1. 银行排队仿真为什么用数组加链队列而不是一路 malloc 到底银行排队系统这道题几乎所有数据结构课都会遇到n 个窗口对外接待客户不断进入人多时要在窗口前排队最后要算一天里客户在银行逗留的平均时间。很多人第一反应是“队列嘛一个链队列全搞定”真写起来才发现窗口数固定、排队人数不定全用链表反而把“哪个窗口空着”“谁在办业务”这些查询搞复杂了。这份实验报告给出的是一个更稳的组合窗口用定长数组表示等候队伍用链队列表示到店时间用time()打点离开时结算逗留时长。它适合正在做数据结构课程设计、要交一份能跑能讲清的报告的学生也适合想拿一个最小可运行模型理解“离散事件仿真”思路的开发者。整套东西不依赖图形库一台装了编译器的机器就能演示完整流程。2. 需求与数据结构选型三个实体、两种存储、一套事件流2.1 先把业务对象定清楚窗口、客户、等候队列这类仿真最容易翻车的地方不是代码是需求没定死就开始写。我一般会先列出系统里到底有哪几类东西各自的生命周期是什么。窗口是固定资源。题目说“n 个窗口”n 在编译期就能确定报告里用#define n 3固定成 3 个窗口实际答辩时改成 5 也不影响逻辑。窗口的状态只有空和忙两种忙的时候挂着一个正在办业务的客户编号。这种“数量固定、按下标访问”的特征天然适合数组。客户是流动对象。普通客户到达时系统分配一个递增编号编号就是他排队的凭证VIP 客户走独立通道验证卡号密码后直接进 VIP 区不占普通窗口的排队位置。客户身上要记的信息其实只有编号和到达时刻其余都是派生量。等候队列是缓冲。窗口满了后来的客户进队列等着有窗口空出来队头客户出队补位。这里用链队列是因为排队人数无法预知链式存储不用担心数组开小了溢出。整个系统的实体关系可以这样描述一个长度为 n 的数组承载服务台一个带头结点的链队列承载等待区两者通过“到达”和“离开”两个事件互相搬运客户编号。提示报告里把窗口数组定义成int A[n1]下标从 1 开始用第 0 位空着不用这样“1 号窗口”和A[1]直接对应读代码时少一层换算答辩时也不容易被问懵。2.2 为什么窗口用数组、队列用链表这个选型值得单独讲因为它是整份报告的技术骨架也是老师最爱追问的点。窗口用数组的理由是访问模式决定的。系统运行期间会频繁执行“找出一个空闲窗口”“把某个窗口的客户清掉”“打印所有窗口当前客户”这三类操作全部是按下标定位的随机访问。数组的 O(1) 访问正好匹配而且窗口数最多十几个空间浪费可以忽略。反过来如果用链表存窗口每次查看业务办理情况都要从头遍历代码量和出错概率都上去了。队列用链表的理由是容量不确定。仿真过程中客户到达是持续的谁也不知道峰值会排多少人。顺序队列要预留MAXSIZE开小了溢出开大了浪费还要处理假溢出循环队列虽然能缓解但长度仍然是死的。链队列每次入队 malloc 一个结点出队 free 掉容量跟着实际需求走正好对上“人数众多时排队”这个业务描述。两种结构的分工可以用一张表说清楚实体存储结构关键操作时间复杂度选型理由服务窗口定长数组L.A[n1]按下标查询、置空、赋值O(1)数量固定随机访问频繁等候队列带头结点链队列Q入队、出队、遍历打印O(1) 入出队人数不可预知避免溢出客户记录整型编号 时间打点到达记录、离开结算O(1)信息量小无需额外结构这套组合还有个隐性好处数组长度L.len天然就是“当前正在办业务的人数”链队列遍历计数天然就是“当前排队人数”两个指标不用额外维护计数器查询界面直接拿来用。2.3 事件流到达、离开、查询三条主线系统本质是一个事件驱动的循环。主函数用while(1)撑起菜单用switch分发五类操作报告里把这条主线叫“银行排队系统导航图”落到代码就是一个死循环加分支。到达事件的判定逻辑很短但包含了一个关键判断窗口有空位就直接占位没空位才入队。这段是整个仿真里最该背下来的十行void daoda(int x) // 顾客到达事件先看窗口再决定排队 { int i L.len 1; // 下一个可用窗口下标 if (L.len n) { // 还有空窗口 L.A[i] x; // 客户编号直接落到窗口 i; L.len; // 在办人数加一 } else { Enqueue(Q, x); // 窗口满进等候队列 } }L.len在这里一物两用既是数组里有效元素的个数也是“已占用窗口数”。因为客户总是从下标 1 开始连续填len1永远是下一个空位。参数x是调用方分配的客户编号普通客户靠全局计数器v自增产生。这个函数没有返回值成功与否通过窗口数组和队列的状态体现查询界面一打印就能看出来。离开事件的逻辑比到达复杂因为它要处理“窗口空出来后从队列补人”的连锁反应。报告里的做法是先校验编号合法性再在数组里找到对应位置清空然后立刻检查队列是否非空非空就出队补位。这里有个顺序问题必须注意先L.len--再判断队列补位成功后再L.len一减一加之间长度不变但窗口里的人和队列里的人都换了。这个细节如果写反会出现“队列有人但窗口显示空闲”的诡异状态答辩演示时特别容易被抓。3. 模块实现从菜单分发到评分统计的完整链路3.1 主菜单与状态机switch 分发五类操作主函数是整个系统的外壳结构很朴素清屏、打印菜单、读入选项、switch 分支、清屏回到循环。菜单项 1 到 5 分别是顾客到达、顾客离开、查看业务办理、查看排队情况、退出第 6 项系统查询被刻意隐藏只有知道的人才会输入。void main() { int c, x, v 0, w 0; // v:普通客户计数器 w:离开人数 Initshuzu(); // 窗口数组清零 Initqueue(); // 链队列初始化带头结点 while (1) { // ... 打印菜单、显示当前时刻 ... scanf(%d, c); switch (c) { case 1: /* 顾客到达选 VIP 或普通 */ break; case 2: /* 顾客离开读编号、结算、评分 */ break; case 3: printl(); break; // 查看在办业务 case 4: print2(); break; // 查看排队情况 case 5: return; // 退出 case 6: /* 隐藏的系统查询 */ break; default: /* 输入有误 */ break; } } }v和w两个计数器的职责要分清v只在普通客户到达时自增保证编号从 1 开始连续w在每次成功离开时自增用来算平均时间。菜单循环里每次操作后都system(cls)清屏是为了让演示界面干净但这也意味着所有历史输出都不留痕所以关键结果必须在离开时当场打印出来。注意switch里每个 case 结尾都要break报告原始代码中case 5的return之后还跟着getch()和system(cls)属于不可达代码虽然不影响运行但答辩时被指着问“这两行有什么用”会很难解释建议直接删掉。3.2 到达与 VIP 认证两条通道的分流实现普通客户的到达路径很短分配编号、打印业务号、调用daoda决定占窗口还是排队。VIP 客户的路径多一道认证报告里用vip(int x)函数实现内部是一个 switch 加一串if-else比对卡号和密码。void vip(int x) // VIP 认证卡号与密码成对匹配 { int i; char ch[6] {0}; int k 0; printf(请输入你的卡号); scanf(%d, i); printf(请您输入五位密码); while (k 5) { // 逐位读入并回显星号 ch[k] getch(); k; printf(*); } // 卡号 100/200/300... 对应密码 1111/2222/3333... if ((i 100 ch[0]1 ch[1]1 ch[2]1 ch[3]1) || (i 200 ch[0]2 ch[1]2 ch[2]2 ch[3]2)) { printf(\n尊敬的 VIP 用户您好请您直接到 VIP 区办理业务\n); vip1; // VIP 计数加一 } else { printf(\n你的输入有误\n); } }这段代码的教学价值在于“读密码不回显”这个交互细节用getch()而不是scanf每读一位手动打印一个星号。参数x实际只用来判断是否进入 VIP 分支switch(a)里只有一个case 1写法上有点绕理解成“进门开关”就行。认证通过后 VIP 计数vip1自增这个计数最终会出现在系统查询界面里作为 VIP 客流量的统计口径。需要提醒的是原始代码里密码缓冲是char ch[3]却要读 5 位属于数组越界演示时可能不崩但埋着雷。改成ch[6]并显式置零再把循环条件从“等空格”改成“读满 5 位”逻辑才闭合。这类小坑在照抄代码时最容易带进去。3.3 离开、出队与补位一趟操作三处状态变更离开函数是整份报告里状态变更最密集的地方一次调用会同时改动窗口数组、队列和统计量。逻辑顺序是先校验、再清位、再补位。void likai(int x) // 顾客离开清窗口 - 队列补位 { int i 0; do { if (x L.len) { // 编号超出在办范围 printf(输入有误请重新输入); scanf(%d, x); } else { for (i 0; i L.len; i) { if (i x) { // 找到该客户所在窗口 printf(尊敬的%d号顾客您好\n, x); L.A[i] 0; // 清空窗口 L.len--; if (Q.front ! Q.rear) { // 队列非空则补位 int y Dlqueue(Q); L.A[i] y; L.len; } } } } } while (i 0); }三个参数要点x是客户编号也是数组下标这种“编号即下标”的约定贯穿全系统Q.front ! Q.rear是带头结点链队列判空的标准写法不能写成Q.front-next ! NULL之外的花样Dlqueue返回出队客户的编号直接回填到刚清空的窗口位置保证窗口下标连续占用。补位之后评分流程紧接着执行。mygrade()先调guitai()让客户选择柜台号再调pingfeng()读入 1 到 5 的评分然后用 switch 把分数累加到对应柜台的sum和i上算出该柜台的平均满意度。五个柜台对应五组全局变量写法重复但直观适合课程设计阶段阅读。提示柜台号校验函数guitai()里提示写的是“1-3”判断条件却是y 1 || y 5两处不一致。照抄时要么把提示改成 1-5要么把判断收紧到 1-3别让老师和演示观众同时发现这个矛盾。3.4 三个查询界面打印数组、遍历队列、隐藏的系统统计查询类功能实现都不长但覆盖了两种存储结构的不同遍历方式是报告里最适合当“结构对比”讲的部分。printl()打印窗口数组从下标 1 遍历到L.len把正在办业务的客户编号一次输出。因为数组是连续占用循环边界直接用len不会打印到空位。print2()打印等候队列从头结点的下一个结点开始沿next走边走边计数走完顺便输出“你前面还有几人”。这个计数逻辑天然就是队列长度比额外维护一个 size 变量更可靠。系统查询被藏在菜单第 6 项进门要卡号和密码。原始代码的验证条件写成了i 000 cool[0]1 ...000在 C 里就是 0密码只比对了 4 位第 5 位读进来却没用上。这块的正确做法是把卡号和密码都当字符串处理逐位比对。查询结果输出三类数据普通客户数w、VIP 客户数vip1、普通客户办理业务总时长和平均时长。平均值的算法是average allsum / w分母是成功离开的人数因此没离开的客户不参与统计这个口径要在报告里写明白否则算出来的数对不上。3.5 时间打点与平均逗留时长difftime 的正确用法题目要的最终指标是“一天中客户在银行逗留的平均时间”这就必须有时间打点。报告里引入了time.h用time_t存时间点用difftime求差。正确的用法是每个客户到达时记一个开始时间离开时记一个结束时间差值就是这个客户的逗留时长累加后再除以离开人数。原始代码里时间变量的命名和赋值比较乱出现了time difftime(end,start);这种把函数名当变量用的写法编译能过但语义混乱。整理后的骨架应该是#include time.h time_t t_start, t_end; double stay_seconds, total_stay 0.0; int leave_count 0; /* 客户到达时 */ time(t_start); /* 客户离开时 */ time(t_end); stay_seconds difftime(t_end, t_start); // 单位秒 total_stay stay_seconds; leave_count; /* 任意时刻求平均 */ double avg (leave_count 0) ? total_stay / leave_count : 0.0;参数说明time_t是秒级时间戳difftime返回double类型的秒差跨平台一致。分母要做零判断否则第一位客户还没离开时查询会除零。如果想让演示更直观可以把秒换算成分钟显示但内部统计建议统一用秒避免多次换算累积误差。实际演示时有个现实问题真实等待几十秒才有数据答辩现场等不起。常见的处理是把“到达”和“离开”都由人工触发两个操作之间隔几秒就能产出一个有效样本报告里的手动菜单模式正好支持这种节奏。这也是这套设计比全自动随机仿真更适合课堂的原因——每一步都可控、可讲解、可复现。4. 避坑与排查这类仿真最容易翻车的五个点4.1 数组越界编号、下标、窗口数三者没对齐现象是程序运行到某次到达或离开后输出乱码或者窗口里冒出莫名其妙的编号。原因通常是L.A定义成int A[n]却按 1 到 n 使用或者客户编号自增后超过数组容量。解决方式很固定数组一律开n1下标 0 弃用客户编号由独立计数器产生和数组下标解耦所有写数组的地方前面加一次范围判断。我一般会在daoda和likai入口各加一行断言式的if越界就打印提示并返回宁可少一个客户也不要让内存被踩。4.2 链队列判空写错补位逻辑静默失效现象是客户离开后窗口空了但队列里明明有人却不补位查询界面显示“正在办理”人数变少、排队人数不变。原因是判空条件写成了Q.front ! NULL之类带头结点的队列永远是front非空条件永真或永假补位分支根本不进。正确判空只有一个写法Q.front Q.rear表示空队。排查时在likai里临时打印Q.front、Q.rear两个指针地址退出前后各打一次指针没动说明根本没进出队问题就定位了。4.3 密码缓冲区开小getch 循环读飞现象是 VIP 认证界面输入几位密码后程序行为异常甚至直接崩。原因是缓冲区按 3 字节开循环却按“读到空格为止”控制5 位密码必然越界写。解决是把缓冲区按实际密码长度加一开循环条件改成计数上限读满即停。更稳的做法是读完后手动补\0后续比较统一按字符串处理别一位一位比字符。4.4 时间变量命名撞车编译过了逻辑错了现象是平均逗留时间算出来是 0 或者巨大值。原因是把time这个函数名当成了变量名或者start、end在多个客户之间共用一份前一个客户的结束时间被后一个覆盖。解决是时间变量按“每个客户一份”的思路设计或者干脆在离开时立刻结算并累加只保留累计量和计数两个全局变量。命名上避开time、clock这类标准库标识符加前缀区分。4.5 评分统计口径不一致柜台平均分忽高忽低现象是同一个柜台前几次评分后平均分正常后面突然跳变。原因是sum和计数变量i1、i2在不同分支里被复用或者柜台号校验放行了 4、5 号但只有 1 到 3 号有统计分支导致分数加进了没有对应计数的变量。解决是把五个柜台的累加器和计数器写成数组sum[6]、cnt[6]柜台下标直接索引switch里五个重复分支立刻塌缩成三行既短又不会漏。5. 验收与答辩怎么用一张对照表把实验讲圆这类课程设计的验收老师通常不看你写了多少行而是按“需求是否覆盖、结构是否用对、数据是否可信”三条线问。我的建议是提前准备一张功能对照表把菜单项、对应函数、用到的数据结构、可现场演示的输入输出一一对上问到哪里指哪里。验收项对应实现数据结构演示要点多窗口接待daoda 窗口数组定长数组连续到达 4 人前 3 人进窗口、第 4 人进队列排队与补位likaiDlqueue链队列让 1 号离开队列首位移入 1 号窗口逗留时长统计timedifftime时间戳隔几秒到达再离开系统查询看平均值非零VIP 分流vip认证字符串比对正确卡密进 VIP 区错误卡密被拒服务评分mygradeguitai全局累加器同一柜台连评两次平均分正确刷新隐藏系统查询菜单 6卡密验证输对卡密输出客户数与平均时长演示顺序也有讲究。我一般先跑“三到达一排队”让窗口全满、队列有人这个画面最能体现数组加链队列的分工再跑一次离开补位展示队列头进窗口最后进隐藏查询把累计人数和平均时长念出来。三步走完结构选型、事件流、统计口径全讲到了中间不用碰代码。答辩时最容易被问的是“为什么不用循环队列”“平均时间为什么除以离开人数而不是到达人数”。前一个问题按 3.2 节的容量论证回答后一个问题要讲清统计口径——没离开的客户逗留时长还没确定算进去就是错的所以分母只能取已完成样本。把这层因果关系说清楚比背代码有用得多。从那以后我每次带这类仿真作业都强制自己先把“谁是分母、谁是分子”写在纸上再动手希望帮到你。本文还有配套的精品资源点击获取