简介这是一份面向数据结构课程实验与C语言编程练习的银行排队系统实验报告围绕多窗口排队场景展开帮助读者理解队列结构、随机事件模拟与客户平均逗留时间统计的实现思路。压缩包仅含1个doc文件大小约191KB正文覆盖设计要求、实验目的、仪器与编译环境、设计思路、程序分析及运行界面说明并给出主菜单、客户到达、客户离开、业务查询、排队查询和系统查询等模块的代码解析。内容结合VC 6.0与stdio.h、malloc.h、stdlib.h、conio.h、time.h等头文件展示VIP与普通客户分流、时间记录、队列初始化及入队出队操作的具体做法便于对照理解C语言如何把现实排队问题转化为程序流程。已有904人学习适合需要参考实验报告结构、梳理队列应用或完成同类课程设计的学生与自学者。1. 三个窗口、一条等待队伍先把需求咬准银行排队系统实验报告这道题十个人里有八个第一版写成随机数发生器一个 for 循环灌进几十个客户跑完打印个平均数就交差。我第一版也这么干答辩时被问一句就哑了——现在让 3 号窗口空出来演示一下队首客户补位程序里根本没这个入口。问题不在队列算法而在于这份实验报告的交付物是一个能被现场操作、能被观察的模拟器不是一段跑完就结束的计算。所以这套方案把客户到达和离开都做成手动触发的事件窗口用定长数组映射柜台号等待客户用链式队列每位客户进门打一个时间戳离开时结算逗留时长。它适合正在做数据结构课程设计、需要交一份能当面演示、能讲清每个函数职责的同学也适合把这套骨架改造成叫号机、食堂取餐、窗口业务受理这类同类模拟的人。2. 系统总体设计定长窗口数组与链式等待队列的分工整套程序的骨架其实就两句话正在办理业务的人待在一个长度固定的数组里等着办理业务的人排在一个可以无限延伸的链表里。这两句话决定了后续所有函数的形态——数组负责位置链表负责顺序二者通过一次离开时补位的动作咬合在一起。下面把业务模型、结构选型、模块划分三层拆开讲这一层想清楚了后面的代码基本是照着写。2.1 业务模型抽象窗口、客户、事件三要素真实网点里能观察到的对象只有三个柜台窗口、客户、以及发生在客户身上的事件。窗口数量在一开始就定死通常是 3 个左右对应代码里的宏WINDOW_NUM客户用一个自增编号唯一标识普通客户从 1 开始递增VIP 客户走另一条认证通道单独计数事件只有两类——到达和离开其中离开是唯一能推动队列前移的动作。这个抽象看似简单但它把时间这个连续量切成了离散的事件序列才有了后面用time()打点做统计的可能。VIP 的处理方式值得单独说一句。原方案里 VIP 不走等待队列认证通过后直接进入 VIP 区办理只累加计数不影响普通队列。这个设计避免了插队带来的队列重排问题也符合多数网点把贵宾业务物理隔离的现实做法。如果你的题目要求 VIP 也要排队可以把等待队列做成两条补位时优先从 VIP 队列出队改动量只在Leave函数的一处判断上。事件驱动带来的另一个好处是可复现。随机生成客户看着高级但每次跑出来的结果都不一样答辩现场没法解释为什么这次平均是 47 秒上次是 62 秒。手动触发虽然要多敲几次键盘但每一次状态变化都能对应到一个明确的输入出了问题能立刻定位是哪一步操作引起的这在被追问细节时非常关键。2.2 数据结构选型为什么窗口用数组、等待用链表窗口用数组理由有三条。第一窗口数量固定且很小数组的随机访问能让你用柜台号直接定位到负责的客户cust_id[3]就是 3 号柜台正在办的人不用遍历。第二数组的每个槽位天然对应一个物理窗口状态用 0 表示空闲语义直观打印业务办理查询时一个循环就能输出全部窗口。第三数组没有动态分配的开销不会因为 malloc 失败让整个程序崩在演示现场。等待区用链式队列同样有三条理由。第一等待人数不可预知顺序队列必须预先开一个够大的数组开小了假溢出开大了浪费内存而链表按需申请来一个挂一个。第二链式队列的出队只动头结点后面的一个指针没有元素搬移队首客户被叫号时是真正的 O(1)。第三链表结构能直接支撑查看排队情况这个功能——从头结点往后走一遍一边打印一边计数天然就得到了你前面还有几个人。这里有个常见误区有人觉得既然窗口只有三个等待队列也开个定长数组得了反正一天也来不了几百人。这种想法在演示阶段会翻车——老师往往会让你连续点几十次顾客到达一旦超过预设长度要么报错退出要么覆盖已有数据。链表没有这个上限你能一直点下去程序一直稳定。数据结构教材里讲循环队列是为了解决顺序存储的假溢出但那是针对长度已知且可估算的场景排队模拟恰恰不属于这一类。结构存储方式容量典型操作在本系统里的角色窗口集定长数组固定为窗口数按柜台号随机访问、置空、补位记录谁正在办理等待队列带头结点的链式队列无上限入队、出队、遍历记录谁在等到达时刻表以客户号为下标的数组与客户号上限一致写入、读取结算逗留时长2.3 模块划分与六个功能入口主函数本身不承担业务它只做一件事把键盘输入翻译成函数调用。菜单用switch分发每个case对应一个业务入口输入完成后清屏回到主菜单形成闭环。这种写法虽然朴素但把界面和逻辑彻底隔开了——界面里只有printf和scanf逻辑全在独立函数里任何一个函数都可以被单独拿出来测试。函数职责依赖的数据结构InitWindows/InitQueue开场初始化清空窗口、建立空队列窗口集、等待队列Arrive处理到达事件有空位直接占没空位入队两者Leave处理离开事件清空窗口并从队首补位两者ShowWindows打印各窗口正在办理的客户号窗口集ShowQueue遍历队列打印等待客户并给出前方人数等待队列ShowStat输出办理总数、总时长、平均时长、系统运行时长统计变量菜单项里通常还藏一个不公开的入口用于工作人员查询累计数据。原方案用输入 6 触发配合一个固定的卡号和密码校验。这个设计的意图是区分客户视角和管理视角实现上不必复杂但一定要有——它让整份报告多了一个可以讲解的层次同一套数据对外只暴露排队信息对内才看得到统计口径。主流程的控制用一个大while(1)包住退出项直接return。这里不建议用exit(0)因为它会跳过后续可能存在的资源释放也不建议用break跳出switch就当退出那样只会回到菜单是个很典型的翻车点——演示时按了退出没反应只能强行关窗口。int main(void) { WindowSet win; LinkQueue que; int choice, cust_id 0; InitWindows(win); /* 开场先把所有柜台置为空闲 */ InitQueue(que); /* 建立带头结点的空等待队列 */ while (1) { ShowMenu(); /* 打印菜单顺带显示当前时刻 */ scanf(%d, choice); switch (choice) { case 1: /* 顾客到达普通客户取号递增VIP 走认证 */ cust_id; Arrive(win, que, cust_id); break; case 2: /* 顾客离开按客户号查找并补位 */ printf(请输入离开客户的编号); scanf(%d, cust_id); Leave(win, que, cust_id); break; case 3: ShowWindows(win); break; case 4: ShowQueue(que); break; case 5: return 0; /* 退出必须用 return别用 break */ default: printf(输入有误请重新选择\n); } } }这段主函数里有两个参数需要注意。cust_id在case 1里自增后传给Arrive所以客户编号是连续分配的和取号机吐号的体验一致但在case 2里它又被scanf覆盖成了用户输入的离开编号这两个语义共用了一个变量属于典型的偷懒写法。工程上更稳妥的做法是把下一个待分配的号和本次操作的号拆成两个变量避免离开操作影响了后续发号。ShowMenu内部调用一次time()打印当前时刻让界面有真实感但它不参与统计统计靠的是到达和离开各自打的时间戳。3. 队列的落地实现初始化、入队、出队与补位算法数据结构课上学队列重点往往在先进先出这四个字上真正写起来的时候翻车点全在指针上。链式队列只要有一个指针没接对第一次演示就会出现排队的客户凭空消失或者程序直接退出。这一章把结构定义、入队出队、到达离开四个动作的代码摊开逐段说明每个指针在做什么以及哪些地方必须做防御。3.1 结构定义与初始化头结点的作用链式队列用带头结点的形式头结点不存数据只用来统一空队和非空队的操作。有了它入队时不需要判断是不是第一个元素出队时也不需要单独处理删掉的是不是唯一元素代码能少一半分支。代价是多一次 malloc但换来的是逻辑清晰非常划算。#define WINDOW_NUM 3 /* 窗口数量按业务规模调整 */ #define MAX_CUST 10000 /* 客户号上限用于到达时刻表 */ typedef struct { int cust_id[WINDOW_NUM 1];/* 下标 1..WINDOW_NUM 对应柜台号0 表示空闲 */ int len; /* 当前占用窗口的客户数 */ } WindowSet; typedef struct Lnode { int data; /* 排队客户的编号 */ struct Lnode *next; } Lnode; typedef struct { Lnode *front; /* 永远指向头结点 */ Lnode *rear; /* 指向最后一个数据结点 */ } LinkQueue;WINDOW_NUM 1这个写法是为了让柜台号从 1 开始下标 0 空着不用。很多同学图省事从 0 开始结果打印时输出0 号窗口和现实对不上答辩时容易被挑。多开一个int的成本可以忽略换来的是编号和柜台一一对应。InitQueue只做一件事申请头结点让 front 和 rear 都指向它next置空。这一步必须检查 malloc 的返回值虽然课设环境里几乎不会失败但写上去体现的是工程习惯而且真失败时不检查会直接段错误比报错退出更难排查。void InitWindows(WindowSet *W) { int i; for (i 1; i WINDOW_NUM; i) W-cust_id[i] 0; /* 0 代表该窗口当前为空闲 */ W-len 0; } void InitQueue(LinkQueue *Q) { Q-front Q-rear (Lnode *)malloc(sizeof(Lnode)); if (Q-front NULL) { /* 分配失败要立刻退出别带着空指针往下跑 */ printf(内存分配失败\n); exit(1); } Q-front-next NULL; /* 头结点的 next 为空即为空队 */ }3.2 入队与出队两个必须处理的边界入队简单申请结点、填数据、挂到rear后面、rear后移四步一气呵成。出队麻烦一些因为它有两个边界队列为空时怎么办以及出队后队列变空时尾指针怎么处理。前者如果不判会解引用空指针直接崩溃后者如果不处理rear会指向一块已经free掉的内存下一次入队就往野指针上挂程序在某个不确定的时刻莫名其妙地死掉。void EnQueue(LinkQueue *Q, int cust_id) { Lnode *s (Lnode *)malloc(sizeof(Lnode)); if (s NULL) { printf(内存分配失败\n); return; } s-data cust_id; s-next NULL; Q-rear-next s; /* 挂到当前队尾之后 */ Q-rear s; /* 尾指针后移到新结点 */ } int DeQueue(LinkQueue *Q) { Lnode *p; int id; if (Q-front Q-rear) { /* 头尾重合说明队空 */ printf(队列为空当前无人等待\n); return -1; /* 用 -1 表示无人可出比 exit 温和得多 */ } p Q-front-next; /* p 指向第一个数据结点 */ id p-data; Q-front-next p-next; /* 头结点越过 p */ if (Q-rear p) /* 出队的是最后一个结点尾指针必须回退 */ Q-rear Q-front; free(p); return id; }DeQueue返回 -1 而不是直接exit(1)是个很实际的取舍。原方案里队列空就退出程序演示时只要在没人排队的时候点一次离开整个窗口就没了前面的数据全丢。改成返回哨兵值后调用方拿到 -1 就知道没人可补窗口老老实实空着程序继续跑。这个改动看着小但在现场演示时能救一次场。顺带说一句长度统计。要让你前面还有几个人这句话准确可以在队列结构里加一个int count入队加一出队减一O(1) 拿到长度也可以临时遍历一遍数结点个数O(n) 但不用维护。排队规模不大时两种写法差别可以忽略我一般选后者少一个需要同步的状态就少一个出错的地方。3.3 到达算法有空位就坐没空位就排到达事件的处理逻辑可以写得很短从头扫一遍窗口数组找到第一个值为 0 的槽位就把客户塞进去扫完一圈没找到说明三张柜台全满客户入队。这个先找空位再入队的顺序不能反反了就会出现窗口明明空着、客户却在排队的荒唐情况。void Arrive(WindowSet *W, LinkQueue *Q, int cust_id) { int slot; for (slot 1; slot WINDOW_NUM; slot) { if (W-cust_id[slot] 0) { /* 找到空闲窗口直接占用 */ W-cust_id[slot] cust_id; W-len; record_arrive(cust_id); /* 记录到达时刻供逗留统计使用 */ printf(客户 %d 请到 %d 号窗口办理\n, cust_id, slot); return; } } EnQueue(Q, cust_id); /* 三张台全忙进等待队列 */ record_arrive(cust_id); printf(窗口已满客户 %d 取号排队前方 %d 人\n, cust_id, QueueLength(Q)); }record_arrive这一行是整套统计的基础它的实现就是往一个以客户号为下标的浮点数组里写当前时间。之所以要在到达时立刻记而不是等到离开时倒推是因为离开时已经没有任何信息能还原出这位客户是什么时候进门的。原方案在这里踩了个大坑——它在离开时累加A[0]而A数组的下标 0 从来没被赋过值恒为 0所以平均逗留时间永远是 0跑一百次都是 0。这个 bug 在界面上不报错只在统计栏显示一个漂亮的0.00非常具有欺骗性。3.4 离开算法按客户号查窗口再让队首补位离开是整个系统里唯一会让状态发生连锁变化的动作。它要做三件事在窗口数组里找到这位客户、结算他的逗留时长并把槽位置空、如果队列非空就从队首叫下一位补进刚空出来的窗口。第三步的补进同一个槽位是关键不能补到别的窗口去否则窗口和客户就对不上了。void Leave(WindowSet *W, LinkQueue *Q, int cust_id) { int slot, next_id; for (slot 1; slot WINDOW_NUM; slot) { if (W-cust_id[slot] cust_id) { /* 按客户号查找不是按下标查找 */ finish_stat(cust_id); /* 结算逗留时间并累加统计 */ W-cust_id[slot] 0; W-len--; next_id DeQueue(Q); /* 尝试从队首叫下一位 */ if (next_id ! -1) { /* -1 表示队列为空窗口保持空闲 */ W-cust_id[slot] next_id; W-len; printf(客户 %d 请到 %d 号窗口办理\n, next_id, slot); } printf(客户 %d 办理完毕\n, cust_id); return; } } printf(没有找到客户 %d请核对编号\n, cust_id); }这里最容易写错的是查找条件。原方案用if (x L.len)判断编号是否合法逻辑上把客户号当成了数组下标只要客户号大于当前在办人数就报错。实际上客户号是自增的流水号第 8 位客户完全可能对应数组里第 1 个槽位用长度去卡编号必然误判。正确做法是老老实实比cust_id[slot] cust_id找不到再提示。另一个细节是补位之后要不要重新计算平均时长。严格来说补位进窗口的那位客户他的到达时刻早就记下了逗留时间是从进门到真正开始办理再到办完离开中间排队的时间也算在内。这个口径其实就是题目要的客户在银行逗留的平均时间别把它算成业务办理时长两者差着整个排队时间答辩时一定会被问到区别。4. 时间与统计这条线逗留时长、平均用时与柜台评分排队模拟里最难的不是队列是时间这个东西怎么表达。真实世界里时间连续流动程序里只能靠打点求差。打的点选在哪里、差值怎么累加、分母用什么每一步都会影响最后那个平均数的含义。这一章把时间采集、平均时长计算、柜台满意度累计三块讲透顺带说清楚哪些统计口径是会被人当场问倒的。4.1 时间戳采集time、difftime 与秒级精度标准库给了time()和difftime()这一对函数用法极简进门前调一次time(NULL)存起来离开时再调一次两个值相减就是逗留秒数。返回值类型是time_t转成double交给difftime才稳妥直接用整数相减在某些平台上会因为类型宽度出问题。#include time.h static double arrive_time[MAX_CUST]; /* 以客户号为下标存到达时刻 */ static double total_stay 0.0; /* 所有已离开客户的逗留时长之和 */ static int finished_count 0; /* 已办理完成的客户数作为分母 */ void record_arrive(int cust_id) { arrive_time[cust_id] (double)time(NULL); /* 进门立刻打点单位秒 */ } void finish_stat(int cust_id) { double stay (double)time(NULL) - arrive_time[cust_id]; double avg; total_stay stay; finished_count; avg (finished_count 0) ? total_stay / finished_count : 0.0; printf(客户 %d 本次逗留 %.0f 秒当前平均 %.2f 秒\n, cust_id, stay, avg); }秒级精度在演示时会带来一个尴尬你手再快从点到达到点离开也隔不了一秒算出来全是 0 秒平均时长依旧是 0只不过这次是因为真没花时间而不是因为 bug。解决办法有两个一是把时间单位换成毫秒用clock()取 CPU 计时二是干脆用逻辑时间每完成一次操作就把全局时钟推进若干个单位。后者更好讲——你可以说本系统以操作步为单位推进时间每步代表 30 秒统计结果就是有意义的数值而且完全可复现。arrive_time数组开多大要有依据MAX_CUST就是客户号的上限。如果演示时客户号一路点到超过这个上限数组越界写入会污染内存里的其他变量症状是统计数字随机跳变极其难查。稳妥的做法是在发号前加一句判断超过上限就提示今日号源已发完把边界挡在入口。4.2 平均逗留时长的口径分母到底取谁题目说的是一天中客户在银行逗留的平均时间这句话至少有两种读法。读法一所有进过门的客户包括还在排队的他们的等待时长也算进去那么分母是总到达人数。读法二只统计已经办理完离开的客户分母是finished_count。课程设计里通常取后者因为它有完整的时间区间可算前者对还在排队的客户只能算到现在为止会随时间不断变化没法给一个稳定答案。选读法二还有个现实理由finished_count是随着离开动作自然累加的不会出现总人数有 50、完成人数只有 30、平均值忽大忽小的情况。结算逻辑集中在finish_stat一个函数里分母同步递增账目始终对得上。如果按读法一就需要额外维护当前总人数和已等待时长两个状态都要在到达和离开时更新出错面翻倍。系统运行时长是另一个常被问到的指标算法是拿程序启动时刻和当前时刻求差和客户逗留时长完全无关。原方案把两个统计混在一个变量里allsum既想当总办理时长又想在系统查询里当运行时长结果两边都不对。拆成两个独立变量各算各的是最省心的做法。4.3 柜台满意度分柜台累计而不是全局累计评分模块的设计要点不在算法在账本怎么记。五个柜台每个柜台有独立的累计分数和评价人数每次评分只更新对应柜台的那一组变量平均值随用随算。原方案用了五行几乎一样的case分支每行改个数字虽然啰嗦但逻辑是对的——它保证了 1 号柜台的分数不会跑到 2 号柜台去。static double score_sum[WINDOW_NUM 1] {0}; /* 各柜台累计得分 */ static int score_cnt[WINDOW_NUM 1] {0}; /* 各柜台累计评价人数 */ void RateWindow(int slot, int score) { if (slot 1 || slot WINDOW_NUM) { /* 柜台号越界直接拒绝 */ printf(柜台号不存在请重新选择\n); return; } if (score 1 || score 5) { /* 评分只接受 1 到 5 */ printf(评分无效请输入 1 到 5 的整数\n); return; } score_sum[slot] score; score_cnt[slot]; printf(%d 号柜台平均满意度 %.2f共 %d 人评价\n, slot, score_sum[slot] / score_cnt[slot], score_cnt[slot]); }用数组替代五个独立的case好处是加窗口只改宏定义不用再复制一段代码也避免了case 5 里手滑写成 ave4这类复制粘贴事故。两个边界检查必须写在函数入口而不是在外面靠调用方自觉把校验和业务放一起任何一处调用都不会漏掉校验。评分分值用 1 到 5 的整数界面上把每个分值的含义打出来比让用户凭空输一个数字友好得多。输入非法时不要用exit也不要直接return当作已处理正确做法是提示之后让用户重输用do-while循环住直到拿到合法值为止。这一小段交互往往占了演示时间的大头做得顺不顺直接影响观感。5. 避坑清单六个在演示现场最容易翻车的点代码能跑通和能在人前跑通是两码事。下面这六条都是从实际调试和现场演示里抠出来的共同特征是编译不报警、运行不崩溃、结果就是不对属于典型的黑匣子问题。按现象、原因、解决三段式列出来对着自查一遍能省掉大量返工。5.1 平均逗留时长恒为 0.00现象无论怎么操作系统查询里的平均用时永远显示 0.00没有任何波动。原因累加语句写成了allsum A[0]而A[0]这个位置从头到尾没有被赋过值初值 0 一直没变。离开时清零的是窗口数组对应槽位跟A[0]没有关系所以加来加去都是 0。解决把到达时刻单独存在arrive_time[cust_id]里离开时用当前时间减去它得到单次时长再累加。不要在离开时才去窗口数组里找时间信息那时候数据已经被清掉了等于事后找后悔药。5.2 队列空时点离开整个窗口直接消失现象在没有任何人排队的情况下点一次离开程序窗口瞬间关闭之前所有数据全没了。原因出队函数在队空时调用了exit(1)把整个进程终止了。演示时随手多点几下就触发。解决出队函数返回哨兵值 -1 表示无人可出调用方判断后跳过补位即可。程序退出只保留在菜单的退出项里其他地方一律不许调用exit。5.3 排队客户凭空消失或者重复出现现象明明看着排了三个人点两次离开之后队里只剩一个或者同一个人被叫了两次号。原因出队后没有处理尾指针回退队列变空时rear仍指向已释放的结点下一次入队挂在野指针上或者补位时把客户同时写进了窗口数组和队列造成一人两处。解决出队函数里加一句如果出队的是最后一个结点rear回退到front补位时严格保证同一位客户只存在于窗口或队列中的一处Arrive里先判断空窗口再决定是否入队绝不两边都写。5.4 密码框输入五位程序花屏或者卡死现象VIP 认证时按提示输密码敲到第三位界面就开始乱码或者程序停在那里再也不响应。原因密码缓冲区只开了三个字符用while(ch[k-1] ! )做终止条件这个条件既没有真正的结束符也没有长度上限输入超过缓冲区长度就直接踩内存。解决缓冲区开到 16 个字符以上用长度计数控制循环读到换行或者达到上限就停校验时先把输入统一成字符串再比对不要逐位判断四个字符那样改动一处就要改四处。5.5 客户号校验用了数组长度合法编号被拒现象输入一个明明存在的客户号程序却提示输入有误请重新输入而且输几次都过不去。原因校验条件写成了if (x L.len)把客户流水号当成了数组下标来比较。窗口数组的元素个数最多是 3客户号可能已经到 20用长度去卡编号必然误判。解决把查找逻辑改成按值遍历cust_id[slot] cust_id命中才处理遍历完还没命中再提示不存在。合法性校验要基于值的范围不是基于容器的长度。5.6 菜单退出项按了没反应现象按 5 想退出界面刷新一下又回到主菜单只能手动关窗口。原因switch的case 5里写的是break它只会跳出switch外层的while(1)继续转于是又打印一遍菜单。解决退出分支改用return 0直接结束主函数或者在case 5里置一个标志位让while的条件不再成立。两条路都行关键是别用break。6. 验收与答辩把功能清单变成一张能现场过的表答辩现场的时间通常只有五到八分钟评审看的不是你写了多少行而是这套东西在被人随机操作时会不会崩、统计口径能不能自圆其说。我的建议是提前把验收拆成一张表自己先把每一项都跑一遍跑通了再去现场。这张表的颗粒度要细到点哪个键、界面应该出现什么粗了检查不出问题细了又记不住十来项刚好。验收项操作步骤通过标准空场启动编译运行不做任何操作主菜单正常显示当前时刻正确窗口未满连续点三次到达三位客户分到 1、2、3 号窗口无人排队进入排队第四次到达提示窗口已满前方 1 人补位正确让 2 号窗口客户离开队首客户被叫到 2 号窗口队列长度减一空队保护队列为空时点离开提示无人等待程序不退出非法编号输入一个不存在的客户号提示核对编号其他状态不变统计自洽办理若干人后查系统信息平均时长大于 0且等于总时长除以完成人数评分隔离给 1 号柜台打 5 分、2 号打 1 分两柜台平均值互不影响这张表里最值得反复跑的是统计自洽那一项。答辩时最常被问的问题就是你这个平均数是怎么算的、分母是谁你能当场指着屏幕说总时长 186 秒完成 6 人平均 31 秒数字对得上这个环节就过了。反过来如果平均时长显示 0或者总时长和平均数除不尽评审立刻会怀疑整个统计模块是摆设。还有个答辩技巧主动演示一次异常路径。比如故意输一个不存在的客户号或者队列空的时候点离开让评审看到程序给出提示而不是崩溃。主动暴露并处理好边界比等着被问出来强得多因为前者显得你清楚自己的边界在哪后者只显得你没测过。代码讲解控制在三处就够窗口数组和等待队列的分工、离开时的补位动作、到达时刻怎么打点。这三处讲清楚了其余全是重复调用。讲的时候不要念代码指着屏幕上的一行说这一行做的是把队首客户塞进刚空出来的槽位比读一遍变量名有效十倍。我后来形成的习惯是任何一份带交互的课设交付前都必须由别人来操作一遍我在旁边只看不说。自己点的时候总会下意识避开那些会出问题的地方换个人上手三分钟就能把所有的边界踩一遍。这套流程从那以后我每次交东西都强制走一遍比自测十遍管用。希望帮到你。本文还有配套的精品资源点击获取