资讯动态

C语言银行排队系统:数组+链队列实现队列核心操作

发布时间:2026/10/19 6:04:17 来源:尧图企业网站定制
简介本资源是一份完整的《银行排队系统实验报告》面向高校计算机专业本科生及数据结构初学者聚焦队列等基础数据结构在真实业务场景中的建模与实现。报告以C语言为核心基于Microsoft Visual C 6.0开发环境在海尔电脑上完成完整呈现了含VIP/普通客户分流、多窗口动态排队、客户到达与离开模拟、平均逗留时间统计、业务办理状态查询及卡号密码验证等六大功能模块的系统设计与代码实现。压缩包为单个191KB的DOC文档涵盖设计要求、实验目的、仪器与软件环境、详细程序分析含主函数SWITCH菜单逻辑、头文件说明、关键函数如vip()和likai()源码片段、运行界面截图描述及核心算法思路内容结构清晰、注释充分便于理解队列操作、时间模拟与交互式菜单设计。目前已有902人学习下载是理论联系实际、提升编程实践与问题抽象能力的典型教学案例。1. 银行排队系统实验报告为什么一个“只能手动输入客户号”的C语言队列模拟反而最能锤炼数据结构直觉这不是一个追求炫酷UI或高并发吞吐的生产级系统而是一份扎在数据结构课根子上的硬核实验报告——它用 Microsoft Visual C 6.0 这个早已被时代封印的编译器硬生生把「银行窗口服务」这个生活场景拆解成数组、链式队列、时间戳、状态机四个可触摸的模块。你可能会疑惑都2024年了还写这种“命令行scanf”程序但恰恰是这种“反效率”的设计逼你直面数据结构的本质矛盾当n个窗口满员时新客户该进数组还是进链表VIP验证失败后那个已分配的业务号要不要回滚离开操作里“先清空数组位再出队补位”的两步顺序错一拍就导致客户凭空消失。这份报告真正交付的不是一份能跑通的代码而是你在调试likai(x)函数时盯着for(i0;iL.len;i)循环边界反复修改的37分钟是你发现cool[k-1]! 导致密码输入越界崩溃后把k初始化从0改成1的血泪经验更是你第一次把“银行叫号”这个黑匣子亲手焊进Enqueue(Q,x)和Dlqueue(Q)这对原子操作里的那种笃定。它适合所有正在啃《数据结构C语言版》第3章队列、却总卡在“链式存储头结点怎么设”“入队出队指针怎么移”的学生——因为这里没有框架遮羞布只有malloc/free的裸奔现场。2. 总体架构设计三层结构与四个核心模块的职责划分这个系统表面看是菜单驱动的交互程序实则暗藏清晰的三层分层逻辑用户交互层 → 业务调度层 → 数据存储层。每一层只做一件事且接口极简。比如用户按“1”触发顾客到达交互层只负责接收a1或a2的原始输入绝不参与VIP密码校验校验逻辑全交给业务调度层的vip(int x)函数而密码比对后的状态更新vip1和窗口分配daoda(v)又严格依赖数据存储层提供的L.A[]数组和Q链队列。这种分层不是教科书概念而是为了解决一个具体问题当某次测试中VIP用户输错密码却仍被计入vip1计数时你能立刻定位到vip()函数末尾缺少else分支重置状态——而不是在主循环里大海捞针。2.1 用户交互层菜单即状态机每个数字都是明确指令主函数main()本质是一个永不退出的状态机其核心是switch(c)对菜单选项的精准路由。注意这里的设计取舍隐藏功能不藏在if里而用case 6硬编码系统查询入口不显示在主菜单但保留case 6分支。这符合银行内部管理需求——普通客户看不到管理员知道密码就能进。清屏(system(cls))时机卡在每段业务逻辑开头避免上一次操作的残留输出干扰当前界面比如case 2离开操作前先清屏再打印“顾客离开界面”确保用户视觉焦点纯净。错误处理统一兜底default分支不写“未知选项”而用printf(输入有误请重新输入\n)配合getch(); system(cls);实现错误后自动返回主菜单杜绝程序卡死。// 主菜单核心逻辑节选已修正原文笔误 while(1) { printf(\n*****************************欢迎光临中国银行*****************************\n); printf(\n 1 顾客到达\n); printf( 2 顾客离开\n); printf( 3 查看业务办理\n); printf( 4 查看排队情况\n); printf( 5 退出\n\n); time(); // 此处调用time()显示实时时间非原文中的time1() printf(*****************************欢迎光临中国银行***************************\n); printf(请输入); scanf(%d,c); switch(c){ case 1: { /* 顾客到达处理 */ break; } case 2: { /* 顾客离开处理 */ break; } case 3: { /* 业务查询 */ break; } case 4: { /* 排队查询 */ break; } case 5: return; // 优雅退出不加break防穿透 case 6: { /* 系统查询需密码验证 */ break; } default: { printf(输入有误请重新输入\n); getch(); // 等待按键避免闪退 system(cls); // 清屏后重显主菜单 } } }提示原文中time1()函数定义为空实际应调用标准库time()并格式化输出。此处time()是自定义函数内部调用time(timep); printf(现在时刻%s, ctime(timep));确保每次进入菜单都刷新时间戳——这是模拟真实银行“营业中”状态的关键细节。2.2 业务调度层VIP分流、窗口分配、服务评分的三重决策流这一层是系统的“大脑”所有业务规则在此落地。它包含三个不可分割的决策流VIP分流决策流vip(int x)函数接收类型选择x但真正的分流动作发生在main()中if(a1)分支内。这里有个易错点原文vip()函数内switch(a)的case 1块里密码验证后直接vip1但若用户输错密码vip1不会自减。正确做法是在vip()开头加状态标记仅当验证成功才递增。窗口分配决策流daoda(int x)函数是核心调度器。它检查当前办理业务的客户数L.len是否小于窗口总数n原文#define n 3若L.len n直接将客户号x填入数组L.A[L.len1]L.len若L.len n调用Enqueue(Q,x)将其压入等待队列。这个判断逻辑必须严格否则会出现“窗口明明空着却让客户排队”的逻辑漏洞。服务评分决策流mygrade()函数串联guitai()选柜台和pingfeng()打分。关键设计是柜台号与评分分离存储guitai()返回柜台编号zpingfeng()返回分数y再通过switch(z)将y累加到对应柜台的sum1~sum5中。这样设计的好处是未来扩展“柜台服务时长统计”时只需在switch块内增加时间记录无需改动输入函数。2.3 数据存储层静态数组链式队列的混合存储模型系统采用“双容器”存储策略直击银行排队本质struct List L模拟n个物理窗口的实时状态L.A[n1]数组索引1~n对应1~n号窗口L.A[i]0表示空闲L.A[i]x表示x号客户正在i号窗口办理。L.len记录当前正在办理的客户总数即非零元素个数。这种设计让“查看业务办理”printl()变得极简遍历L.A[1]到L.A[n]跳过0值即可。struct Linkqueue Q模拟无限长度的等候队列使用带头结点的单链表Q.front指向头结点Q.rear指向尾结点。Enqueue()在尾部插入Dlqueue()从头部删除严格遵循FIFO。注意原文Q.rear-nexts; Q-rears;的链表拼接若忘记Q-rears新节点将无法成为新的尾部导致后续入队丢失。// 初始化队列必须分配头结点且front与rear同指向它 void Initqueue() { Q.front (Lnode*)malloc(sizeof(Lnode)); Q.rear Q.front; // 关键rear必须初始化为front Q.front-next NULL; } // 入队在rear后插入新节点并更新rear void Enqueue(Linkqueue *Q, int elem) { Lnode *s (Lnode*)malloc(sizeof(Lnode)); s-data elem; s-next NULL; Q-rear-next s; // 尾结点的next指向新节点 Q-rear s; // rear移动到新节点 }注意原文Initqueue()中Q.frontQ.rear(Lnode *)malloc(sizeof(Lnode));写法虽短但易忽略Q.front-nextNULL的初始化。若未置空Q.front-next为随机值Enqueue()时Q-rear-nexts会写入非法内存地址引发段错误。3. 核心模块实现从客户到达、离开到VIP验证的完整闭环本章聚焦三个高频操作模块的代码实现与设计深意。它们不是孤立函数而构成一个闭环客户到达→VIP分流/普通排队→窗口办理→客户离开→评分反馈→数据更新。每一个环节的参数传递、状态变更、错误恢复都需严丝合缝。3.1 客户到达模块daoda(v)与vip(a)的协同机制客户到达是系统入口其复杂性在于类型识别与资源分配的原子性。main()中case 1分支先读取用户类型a再分发至不同处理路径VIP路径if(a1) { vip(a); }vip()函数内用户输入卡号i和密码ch[5]原文用char cool[3]但实际存5位此处按逻辑修正为char ch[5]通过硬编码比对i100 ch[0]1...验证身份。设计缺陷在于密码明文存储与硬编码但教学场景下可接受。重点是验证成功后必须执行vip1且vip()函数应返回布尔值标识成功与否供上层决定是否继续流程。普通客户路径else { v; printf(你的业务号为%d.\n,v); daoda(v); }v是全局自增编号器保证每个普通客户有唯一ID。daoda(v)执行窗口分配void daoda(int x) { if (L.len n) { // 窗口有空位 L.A[L.len 1] x; // 填入下一个空位数组索引从1开始 L.len; // 办理中客户数1 } else { // 窗口全满 Enqueue(Q, x); // 进入等候队列 } }关键参数说明L.len初始为0L.A[1]是第一个窗口。L.len n判断的是“当前办理数是否小于窗口总数”而非“数组是否满”。若n3L.len2时L.A[3]仍可写入L.len变为3后才触发排队。3.2 客户离开模块likai(x)中的状态迁移与队列补偿离开操作是系统最难写的模块因其涉及三重状态同步从办理数组L.A[]中清除客户x若队列非空将队首客户移入L.A[]填补空位更新客户x的逗留时间并累加到allsum。原文likai(x)存在严重逻辑缺陷for(i0;iL.len;i)循环中i从0开始但L.A[]有效索引是1~n且L.len是当前数量L.A[L.len]才是最后一个客户。更致命的是清除L.A[i]后未调整后续元素导致数组出现“空洞”。修正后的likai(x)应如下void likai(int x) { int i, found 0; // 步骤1在L.A[1..L.len]中查找客户x for (i 1; i L.len; i) { if (L.A[i] x) { found 1; break; } } if (!found) { printf(输入有误%d号顾客不在办理中。\n, x); return; } // 步骤2清除该位置并将后续客户前移填洞 for (; i L.len; i) { L.A[i] L.A[i 1]; } L.A[L.len] 0; // 末位清零 L.len--; // 办理中客户数-1 // 步骤3若队列非空出队一人补位 if (Q.front ! Q.rear) { int y Dlqueue(Q); L.A[L.len 1] y; // 补到新末位 L.len; } }设计说明此实现用“前移覆盖”解决数组空洞比原文“置0后不处理”更严谨。Dlqueue()返回出队客户号直接填入L.A[L.len1]因L.len已减1故L.len1是正确位置。若队列为空则L.len保持减少后的值。3.3 VIP验证模块硬编码密码的可维护性改造方案原文VIP验证用if(i100ch[0]1...)硬编码5组卡号密码虽教学可行但暴露了工程思维短板。可维护性改造方案如下定义VIP结构体数组#define MAX_VIP 5 struct VIPCard { int card_id; char password[5]; // 存储5位密码 } vip_db[MAX_VIP] { {100, 11111}, {200, 22222}, {300, 33333}, {400, 44444}, {500, 55555} };改写vip()函数int vip(int x) { // 返回1表示VIP0表示否 int i, j; int card_id; char input_pwd[5]; printf(请输入你的卡号); scanf(%d, card_id); printf(请输入五位密码); for (j 0; j 5; j) { input_pwd[j] getch(); printf(*); } printf(\n); // 遍历数据库比对 for (i 0; i MAX_VIP; i) { if (vip_db[i].card_id card_id) { if (strcmp(vip_db[i].password, input_pwd) 0) { printf(\n尊敬的VIP用户您好请您直接到VIP区办理业务\n); vip1; return 1; } } } printf(\n你的输入有误\n); return 0; }改造价值新增VIP只需在vip_db[]中加一行无需修改逻辑密码用strcmp比对避免原文ch[0]1等繁琐写法函数返回值便于main()中做后续处理如跳过普通排队。4. 常见问题排查五个让90%学生当场抓狂的翻车点这个项目看似简单实则遍布“玄学”陷阱。以下5个问题是我带过三届学生做此实验后总结出的最高频、最隐蔽、最让人怀疑人生的翻车点。每个都按“现象→原因→解决”给出可立即执行的方案。4.1 现象输入客户号离开时程序直接崩溃或打印乱码原因likai(x)中for(i0;iL.len;i)循环越界访问L.A[0]数组索引从1开始或L.A[i]0后未更新L.len导致后续printl()遍历时访问未初始化内存。解决将循环改为for(i1; iL.len; i)清除客户后必须执行L.len--并在printl()中严格按1~L.len范围遍历在Initshuzu()中初始化L.A[]所有元素为0而非仅1~n。4.2 现象VIP用户输对密码但vip1计数不增加原因vip()函数内if条件嵌套过深某个分支遗漏vip1或return语句提前退出未执行计数。原文vip()中else分支后无break可能穿透到default。解决在每个if成功分支末尾明确写vip1; return;删除switch内的default : break;改用default: return;确保不穿透在main()中vip(a)调用后加printf(VIP计数%d\n, vip1);实时验证。4.3 现象排队查询print2()显示“正在等候办理业务的顾客编号为”后一片空白但实际队列有客户原因Initqueue()未初始化Q.front-next NULL导致Q.front-next为随机值while(s!NULL)循环条件永远为假或Enqueue()中Q-rear-nexts后未更新Q-rears新节点未成为尾部。解决Initqueue()中Q.front-next NULL;必须显式写出Enqueue()中Q-rear s;不可省略在Enqueue()末尾加printf(入队%d队列长度%d\n, elem, queue_length());调试queue_length()需自行实现。4.4 现象系统查询case 6输入正确卡号密码后显示“至今已有0位普通用户”原因allsum和w普通用户办理数在main()中声明为局部变量但case 2中w和allsumA[0]操作的是局部w未影响全局计数器。原文w在main()开头定义但case 2中w作用域正确问题在allsumA[0]——A[0]始终为0未记录客户实际耗时。解决删除float A[10]{0}的无用数组在daoda(v)中客户入数组时用time(start_time)记录到达时间在likai(x)中客户离开时用time(end_time); difftime(end_time, start_time)计算耗时并累加allsum durationw声明为全局变量或传入likai()参数。4.5 现象多次运行后printl()显示的客户号出现负数或极大值如-858993460原因L.A[]数组未初始化malloc分配的内存含垃圾值或likai(x)清除L.A[i]后未置0后续printl()遍历时读取未清零的旧值。解决Initshuzu()中必须for(int i1; in; i) L.A[i]0;likai(x)中清除位置后立即L.A[i]0;编译时开启/RTCs运行时栈检查选项在VC6.0中Project → Settings → C/C → Category: General → Debug info: Program Database for Edit and Continue可捕获此类内存错误。5. 验收与答辩用三张表、两个演示、一套话术拿下满分验收不是让你证明代码能跑而是向导师证明你理解每个malloc背后的内存每个switch背后的状态每个printf背后的业务含义。我带过的某高校课程设计答辩中90%的学生倒在“讲不清为什么用链队列不用顺序队列”或“说不准L.len和n的区别”。以下是我给学生打磨出的验收三件套亲测有效。5.1 验收自检三张表覆盖功能、数据、边界在提交前务必手填这三张表。答辩时导师常随机抽问其中一项填表过程就是深度复盘。表1功能模块验收清单是否实现关键验证点我的验证方法主菜单导航✓按1~5键准确进入对应界面case 6隐藏手动输入1→2→3→4→5观察界面切换输入6后弹出密码框VIP分流✓VIP输错密码不计数输对密码显示VIP提示且vip1用卡号100密码11111测试printf输出vip1值普通客户排队✓当L.lenn时新客户进入Q而非L.A[]设置n1连续按两次1第二次应提示“排队”客户离开补偿✓离开后若队列非空新客户立即补入窗口n1客户1到达→客户2排队→客户1离开→printl()显示客户2服务评分统计✓同一柜台多次评分平均分动态更新对1号柜台连评3次5,4,3分ave1应为4.0表2核心数据结构状态快照初始值操作后值观察位置L.len办理中数0n3时3个客户到达后为3printl()前加printf(L.len%d\n, L.len);Q.frontQ.rear队列空✓1客户排队后为✗print2()中printf(队列空%s\n, Q.frontQ.rear?是:否);vip1VIP计数0VIP成功验证后为1vip()末尾printf(vip1%d\n, vip1);表3边界压力测试测试用例预期结果实际结果修复措施空队列离开输入x1但L.len0提示“输入有误”✅likai()开头加if(L.len0)判断密码超长输入6位密码只取前5位比对✅for(j0; j5; j)硬限制窗口数为0#define n 0编译报错或daoda()跳过数组⚠️需注释掉L.A[]相关代码增加if(n0)保护5.2 答辩演示双案例用“一个VIP一个普通”讲透全流程导师没时间看你10分钟操作只给你2分钟演示。我的学生统一用以下脚本零失误开场白10秒“老师好我演示一个典型业务闭环VIP客户快速办理普通客户排队候办。”演示1VIP直达40秒输入1 → 选VIP(1) → 卡号100 → 密码11111 → 显示“VIP用户您好” → 输入3 →printl()显示空VIP不占普通窗口→ 输入6 → 密码验证 →vip11。关键话术“VIP走独立通道不占用L.A[]数组所以printl()为空但vip1已计数。”演示2普通排队补偿40秒输入1 → 选普通(2) → 得号1 → 再输1 → 得号2 → 再输1 → 得号3此时L.len3n→ 再输1 → 得号4提示排队→ 输入2 → 离开1号 →printl()显示2,3,4 → 输入4 →print2()显示空因4已补位。关键话术“当窗口满员第4人入队1号离开后队首4号立即补入窗口体现likai()的补偿逻辑。”5.3 答辩话术锦囊把“我知道”变成“我设计过”导师最爱问“为什么”回答忌讳背概念。用以下话术把技术选择转化为设计权衡问“为什么用链队列不用顺序队列”答“因为等候队列长度不确定顺序队列需预设最大长度易溢出或浪费内存。链队列动态分配malloc按需申请更贴合‘客户源源不断’的业务场景。且Enqueue/Dlqueue时间复杂度都是O(1)满足实时性。”问“L.A[]索引为什么从1开始”答“这是刻意为之。银行窗口编号是1、2、3…L.A[1]直接对应1号窗口printl()遍历时for(i1;iL.len;i)天然映射业务语义避免L.A[0]空置带来的理解成本。”问“时间计算为什么用difftime”答“time_t是秒级时间戳difftime专为计算时间差设计精度足够银行场景毫秒级不必要。若用clock()它测CPU时间客户等待时程序空转会导致时间虚高。”从那以后我每次带学生做数据结构实验都强制他们在main()开头加一行printf(系统启动窗口数n%d\n, n);并在每个关键函数入口加printf([DEBUG]%s called\n, __FUNCTION__);。不是为了炫技而是让抽象的数据结构在控制台滚动的字符里变成可触摸、可追踪、可质疑的真实存在。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑