
简介这份银行排队系统实验报告面向数据结构课程的学习者以及需要完成课程设计的在校学生。它用 C 语言在 Microsoft Visual C 6.0 环境下模拟多窗口银行排队场景重点训练队列、链表等数据结构的实际运用并统计客户在银行内的平均逗留时间。报告完整保留了设计要求、实验目的、实验仪器与设计思路等章节程序分析部分逐段讲解主函数菜单顾客到达时对 VIP 用户和普通用户的分流与认证顾客离开时的出队与用时累加业务办理查询各窗口排队情况查询以及按卡号检索排队状态等功能。头文件部分也说明了 stdio.h、malloc.h、time.h 等库在输入输出、内存分配与时间模拟中的分工可直接对照理解随机到达与事件处理的思路。资源包共 1 个 doc 文件约 191KB篇幅紧凑便于打印或随堂参考。目前已有 904 人学习适合作为课程实验的对照范本与改写入门材料。1. 三个窗口、一条链队列这个控制台程序到底在模拟什么期末周的自习室里最常见的画面是一个同学盯着黑框框反复输入 1、2、3屏幕上跳出一行「正在办理业务的顾客编号」。这套银行排队系统就是数据结构课设里被反复翻牌的那类题它不是真的做一套银行业务系统而是把「n 个窗口 一条等候队伍」抽象成两种存储结构用菜单驱动的方式手工触发事件再把状态打印出来最后算一个客户在店里平均待了多久。窗口用定长数组因为柜台数固定、要能按编号随机定位等候队伍用带头结点的链式队列因为人数不定、只在一头进一头出。实验报告里写的是「学以致用」落到实处其实就是数组、链表、队列三样东西的一次组合演练。它适合正在做课程设计、想找一个能编译能跑、又能顺手改成自己版本的同学也适合已经工作、想回头复习顺序存储与链式存储差异的人。2. 结构选型与工程骨架数组存窗口、链队列存等候、全局变量记账2.1 窗口为什么用定长数组等候为什么用链式队列先看柜台这一侧。原文用#define n 3把窗口数钉死成 3然后开了一个长度n1的整型数组下标 0 空着不用1 到 3 分别对应三个柜台。这个选择的理由很实在柜台是固定资源编号就是下标要查「2 号柜台在服务谁」一句L.A[2]就出来了O(1)不需要遍历。如果这里也上链表那打印窗口状态就得从头走到尾纯属给自己找麻烦。#define n 3 // 窗口数量改这里就能模拟 5 个、8 个柜台 struct List { int A[n 1]; // 下标 1..n 对应 n 个窗口A[0] 是哨兵位不使用 int len; // 当前正在办理业务的客户数不是窗口数 } L;len这个字段容易看岔。它不是「有几个窗口」而是「此刻有几个窗口在忙」。因为程序允许窗口空着所以len只增到n为止后面的客户全部被推去排队。这个设计让「有没有空窗口」的判断退化成一次L.len n的整数比较非常省事。再看等候这一侧。客户什么时候来、来多少程序里是人工按键触发的人数完全没有上限用顺序表就得考虑扩容和整体搬移。链式队列的入队出队都是 O(1)不预估容量正好对上这个场景。struct Lnode { // 链表结点只存一个业务号 int data; Lnode *next; }; struct Linkqueue { // 带头结点的链式队列 Lnode *front; // 永远指向头结点不存有效数据 Lnode *rear; // 指向最后一个有效结点 } Q;带头结点是这套代码里少数几个「有工程味」的细节。没有头结点的话出队时如果队列只剩一个元素你得特判front rear并把两个指针一起置空有了头结点出队永远是「摘掉front-next」空队列的判断统一成front rear代码少一大截分支。教材上反复强调这一点落到键盘上才发现确实能省事。两种结构的分工在查询模块里体现得最清楚3 号菜单打印数组4 号菜单打印队列同一个界面风格底层一个是按下标遍历、一个是顺着next走链正好是一份现成的对照实验材料。2.2 全局数据结构与两个初始化函数这份代码是单文件风格所有状态都摊在全局区函数之间靠全局变量传数据。放到工程里当然不规范但对一个几百行的课设来说它省掉了大量参数传递读起来反而直接。int vip1 0; // VIP 客户累计人数 int y, z; // 评分、柜台号的临时全局量 float sum1 0, sum2 0, sum3 0; // 各柜台评分总分 float i1 0, i2 0, i3 0; // 各柜台被评分次数 float ave1 0, ave2 0, ave3 0; // 各柜台平均分注意这里一口气开到了sum5 / i5 / ave5也就是准备了 5 个柜台的计数器但n只有 3。这个不一致后面会变成一处实打实的坑第 5 章会展开。写的时候觉得「多准备两个又不花钱」改的时候就知道什么叫牵一发动全身。初始化分成两个函数职责切得很干净void Initshuzu() // 初始化窗口数组 { for (int i 1; i n; i) L.A[i] 0; // 0 表示该窗口空闲 L.len 0; } void Initqueue() // 初始化链式队列 { Q.front Q.rear (Lnode *)malloc(sizeof(Lnode)); Q.front-next NULL; // 头结点的 next 置空代表空队列 }Initshuzu里L.A[i] 0是个约定俗成的哨兵值业务号从 1 开始发所以 0 天然可以表示「没人」。这个约定在打印函数里被直接依赖printl不做任何判空直接把 1 到len的数全打出来。约定一旦被打破比如业务号真的从 0 开始发打印就会出现幽灵客户。Initqueue里front和rear同时指向新申请的头结点这是带头结点队列的标准起手式。到这一步为止malloc只调用了一次后面每次入队再加一个结点。2.3 六个菜单分支对应的模块边界主函数是一个while(1)套switch输入数字进不同分支。菜单上只印了 5 项但代码里藏着第 6 项这个后面单独说。先把六个分支和它们背后的函数、数据结构对起来菜单项界面主要函数操作的数据结构副作用1顾客到达vip/daoda数组 L、队列 QVIP 计数加一、发业务号2顾客离开likai/mygrade数组 L、队列 Q出队补位、评分累加、w 自增3业务办理查询printl数组 L无4排队情况查询print2队列 Q无5退出无无return结束进程6系统查询隐藏内联代码全局统计量无这张表值得先记住因为整份代码的调用关系就是它。case 1内部还要再分一次 VIP 还是普通卡case 2内部要先删人再评分这两处是逻辑最厚的地方。主循环的骨架大概长这样int main(void) // 原文是 void main跨编译器会报警告 { int c, x, v 0, w 0; // c 菜单选择x 离开编号v 业务号w 完成人数 float allsum 0, average 0; Initshuzu(); Initqueue(); while (1) { /* 打印菜单 */ scanf(%d, c); switch (c) { case 1: /* 到达 */ break; case 2: /* 离开 */ break; case 3: /* 查业务 */ break; case 4: /* 查排队 */ break; case 5: return 0; case 6: /* 隐藏的员工查询 */ break; default: /* 提示重输 */ break; } } }v是全局递增的业务号发号器w是「走过离开流程的人数」allsum本该是所有人逗留时间之和。三个变量摆在一起看就能大致猜到作者想干什么发号、走人、累加时长、最后求平均。可惜最后那步没接上这是这份代码最需要动手补的地方。3. 核心链路拆解一次「到达—排队—叫号」是怎么走通的3.1 到达事件VIP 校验与普通客户的业务号分配按下 1 之后程序先进一个二级选择按 1 走 VIP 通道按其他键走普通卡。普通卡这条路很短发个号就完事case 1: { int a; printf(VIP 用户请按 1普通卡用户请按 2.\n); scanf(%d, a); if (a 1) { vip(a); // 走 VIP 校验校验通过后 vip1 getch(); } else { v; // 业务号全局自增从 1 开始 printf(尊敬的普通卡用户你的业务号为 %d.\n, v); daoda(v); // 交给到达算法决定进窗口还是进队列 } break; }这里的关键是「发号」和「入座」被拆成了两步。v只负责产生一个唯一编号daoda(v)才决定这个号是直接占窗口还是排到队尾。拆开的好处是将来要加「预约客户」「对公业务」这类新类型只要保证发号唯一入座逻辑可以完全复用。VIP 那条路就复杂多了原文写成了五个else if的硬编码白名单void vip(int x) { int i; char ch[8]; // 原文是 ch[3]装 5 位密码会越界 int k 0; printf(请输入你的卡号); scanf(%d, i); printf(请您输入五位密码); while (k 5) { // 原文靠空格字符判断结束容易死循环 ch[k] getch(); // 键入不回显 k; printf(*); // 用星号遮盖 } ch[5] \0; if (i 100 strcmp(ch, 11111) 0) { printf(\n尊敬的 VIP 用户您好请您直接到 VIP 区办理业务\n); vip1; // 只计数没有插队逻辑 } else if (i 200 strcmp(ch, 22222) 0) { /* 同样处理 */ } else { printf(\n你的输入有误\n); } }卡号和密码的对应规则是「卡号 100 配 11111、200 配 22222」一眼就能看出是测试数据。教学代码这么写没问题但它暴露了一个设计取向VIP 身份校验不查表、不查文件纯靠if堆。真要扩到几百个会员这段就得改成「读文件到数组再遍历比对」这也是二次开发里性价比最高的一个改造点。还有个容易被忽略的细节VIP 校验通过后只是vip1并没有真的把客户塞进某个「VIP 窗口」。也就是说 VIP 不走队列也不占窗口纯粹是个计数器。原文说要「进入 VIP 区办理业务」但代码里没有 VIP 区这个数据结构。想做完整得单独开一个 VIP 队列或者把窗口数组再分一段出来这是课设里常见的「文档说得比代码多」。3.2daoda的容量判断与Dlqueue的摘链细节到达事件真正的分流在这儿void daoda(int x) { int i L.len 1; // 下一个空位的下标数组是紧凑排列的 if (L.len n) { // 还有空窗口 L.A[i] x; L.len; } else { Enqueue(Q, x); // 窗口满了排到队尾 } }L.len 1这个写法依赖于「数组前len个位置一定是连续占用的」这个不变量。只要删除时也用尾部压缩的方式维护它就成立一旦删除时在中间留了洞这个下标就会指到已经被清空的位置上。原文的删除函数恰好没有严格保证这一点第 5 章细说。入队是标准的尾插void Enqueue(Linkqueue *Q, int elem) { Lnode *s (Lnode *)malloc(sizeof(Lnode)); s-data elem; s-next NULL; Q-rear-next s; // 挂到当前队尾后面 Q-rear s; // 队尾指针后移 }三步走申请、赋值、接链。因为头结点恒定存在Q-rear永远不会是空指针所以不需要判空分支。malloc的返回值在这里没有检查课设规模下不会出问题但如果要循环跑几万个客户最好补一句判空不然内存耗尽时会直接段错误。出队要稍微小心一点int Dlqueue(Linkqueue *Q) { Lnode *t; int x; if (Q-front Q-rear) { // 空队列 printf(队列为空\n); exit(1); // 直接终止进程比较粗暴 } t Q-front-next; // 要摘掉的第一个有效结点 Q-front-next t-next; // 头结点越过它 x t-data; free(t); // 释放避免泄漏 return x; }注意Q-rear在这里没有动。如果队列里本来只有一个元素摘完之后front-next变成 NULL但rear还指着那个已经被free掉的结点——这是个悬空指针。下一次入队时Q-rear-next s就往已释放的内存里写属于典型的 use-after-free。正确做法是在摘链后补一句if (Q-front-next NULL) Q-rear Q-front;。这个 bug 在单次运行里不一定暴发但连续跑「最后一个客户离开、又来一个新客户」就会翻车。3.3 离开事件likai的空位回填逻辑按 2 进入离开界面输入客户编号然后走这段void likai(int x) { int i 0; do { if (x L.len) { printf(输入有误请重新输入); scanf(%d, x); } else { for (i 0; i L.len; i) { // 从 0 开始A[0] 是无效位 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; // 补上后 len 恢复 } } } } } while (i 0); }思路是「原位替换」客户走了窗口空出来如果队里有人就让队首那位直接坐进去窗口总数不变队列长度减一。这样数组永远不会出现中间空洞daoda里L.len 1的假设也就成立了。这个设计比「整体前移」省事得多也不用搬数据。但循环边界写得很微妙。for (i 0; i L.len; i)从 0 开始而 0 号位是从来不用的哨兵一旦用户输入 0就会匹配到A[0]——一个从未初始化的值然后触发一次莫名其妙的「离场」。另外for循环里没有break删除完成后i继续加L.len又刚刚变化过循环条件会重新求值可能多跑几轮。代码没崩是因为补位后的A[i]一般不等于x属于靠数据巧合躲过去的写法。do-while (i 0)这个退出条件也很别扭。它的本意大概是「一直重试直到输入合法」但i在for循环结束后已经被改成了L.len 1所以实际上只跑一轮就退出了。如果用户一开始输入了一个大于len的编号if分支会让他重输重输之后i还是 0才能进入for。这段控制流能跑通但读起来要花点时间属于典型的「调试时改出来的结构」不是设计出来的。4. 查询与评分模块三个 print、一个 switch 和平均分的算法4.1printl与print2数组窗口表与链队列的遍历两个查询函数都很短但正好是两种存储结构的写法对照。先看窗口void printl() { int i; printf(正在办理业务的顾客编号为\n); for (i 1; i L.len; i) { // 从 1 开始跳过哨兵位 printf(%d , L.A[i]); } printf(\n); }按下标遍历边界是len不检查A[i]是不是 0。也就是说如果前面删除时留下了空洞这里会老老实实打出一个 0看起来像有个编号为 0 的客户在办业务。想稳妥一点可以改成if (L.A[i] ! 0) printf(...)代价是失去「第几个窗口对应第几个位置」的对齐效果。课设展示的时候对齐其实挺重要所以原作者选了不加判断可以理解。再看队列void print2() { int i 0; printf(正在等候办理业务的顾客编号为); Lnode *s Q.front-next; // 跳过头结点 while (s ! NULL) { printf(%d , s-data); s s-next; i; // 顺手统计人数 } printf(\n您的前面一共有 %d 人在排队请您稍候\n, i); }用一个游标指针顺着next走走完顺便把人数数出来一次遍历干两件事。注意这里是从front-next起步如果把头结点也打出来屏幕上就会多一个不存在的编号这是新手写链队列最常见的错误之一。这两个函数都没有做system(cls)以外的额外处理调用方在case 3/case 4里清屏、打印、getch()暂停、再清屏节奏很统一。getch()在这里的作用是「让用户看清楚再走」没有它的话清屏太快屏幕上什么都留不下。4.2mygrade柜台满意度的累加平均离开流程里客户走完还要给柜台打分。这段用一个两层函数guitai()收柜台号pingfeng()收分数mygrade()负责分发和算平均。int pingfeng() { int y 0; printf(请输入你的评分1-5\n); printf(1 分……非常不满意\n2 分……比较不满意\n); printf(3 分……一般满意\n4 分……比较满意\n5 分……非常满意。\n); scanf(%d, y); if (y 1 || y 5) { // 只重试一次第二次不再校验 printf(你输入评分有误请重新输入\n); scanf(%d, y); } printf(你的评分为 %d.\n, y); return y; }输入校验只做了一轮用户要是连着输错两次第二次的非法值会被直接返回然后进入累加。写课设图省事可以理解但如果要把这个模块拿去当正式功能得改成while循环直到合法为止。重头戏在mygrade的分发void mygrade() { z guitai(); // 柜台号 y pingfeng(); // 分数 switch (z) { case 1: sum1 y; i1; // 计数加一 ave1 sum1 / i1; // 每次重算平均不做除法保护 printf(1 号柜台的平均满意度为 %0.2f。\n, ave1); break; case 2: /* 同样处理 sum2 / i2 / ave2 */ break; case 3: /* 同样处理 sum3 / i3 / ave3 */ break; default: printf(你的输入有误请重新输入\n); } }算法本身没问题总分 / 次数就是算术平均每次重新算一遍避免维护「上次平均值」这种容易出错的中间量。i1用float而不是int说明作者考虑过除法的类型转换虽然int也能自动提升但显式写成浮点读起来更清楚。问题出在边界上。guitai()的提示写的是「请输入你所办理业务的柜台号1-3」但校验写成了if (y 1 || y 5)等于放行了 4 和 5。switch里确实有case 4和case 5全局变量也备了sum4、sum5看起来是打算做 5 个柜台。可窗口数组只有 3 个位置所以 4 号、5 号柜台永远不会有人办业务这两个分支统计到的分数永远是空的。这就是典型的「需求改了、代码改了一半」。4.3 隐藏的 6 号分支员工查询界面的取巧与风险菜单上只印了 5 项但输入 6 能进一个内部界面case 6: { system(cls); printf(**************** 系统查询界面 ****************\n); char cool[3]; int i 0, k 0; printf(请输入你的卡号); scanf(%d, i); printf(请您输入五位密码); while (cool[k - 1] ! ) { // 结束条件依赖空格用户按回车不会停 cool[k] getch(); k; printf(*); } if (i 0 cool[0] 1 cool[1] 1 cool[2] 1 cool[3] 1) { average allsum / w; // w 可能为 0除零风险 printf(至今已有 %d 位普通用户%d 位 vip 用户成功办理业务!\n, w, vip1); printf(普通用户业务办理总共用时 %0.2f平均用时 %0.2f\n, allsum, average); } getch(); system(cls); break; }用「隐藏菜单项」做权限隔离在课设里是个很讨巧的做法不印出来就当作不存在。但它不是权限控制任何人在主菜单敲 6 都能进只是多了一道卡号密码。真要用于展示至少得把提示语句藏起来或者改成进入前先让用户输一遍凭证。这段还有两个硬伤。一是char cool[3]只能装 3 个字符循环却往里写 5 个越界写栈内存二是while (cool[k - 1] ! )在k 0时读的是cool[-1]完全未定义的行为。第一次跑可能碰巧没崩换个编译器就未必了。改成定长循环while (k 5)是最省事的修法。另外average allsum / w没有判断w 0。刚启动程序就敲 6w还是 0浮点除零会得到inf或nan屏幕上就打出个1.#INF00之类的怪东西。5. 编译运行与常见问题排查从 VC 6.0 到 gcc 的六个翻车点5.1 最小可运行步骤这份代码是单文件、纯控制台、无第三方依赖跑起来不难。原文用的是一台装了 VC 6.0 的机器现在你完全可以用更现代的工具链只是要改几处。新建一个bank.c把全部源码贴进去然后按编译器选命令# Windows MinGW / 现代 gcc gcc bank.c -o bank.exe -stdc99 bank.exe # Linux / macOS需要先处理 conio.h 和 system(cls) gcc bank.c -o bank -stdc99 ./bank如果用的是 Dev-C 或者 Code::Blocks直接新建 Console Application把代码粘进main.c点编译运行即可。用 Visual Studio 的话记得把scanf的警告压掉或者按它建议的scanf_s改否则会报 C4996。跑通之后的操作顺序建议这样走一遍能把主要分支都覆盖到先连按 3 次 1普通卡让三个窗口坐满再按 1 一次这次会进队列按 4 看排队情况按 2 输入一个编号让人离开观察队首是否补位最后按 6 输入卡号0、密码1111看统计输出。5.2 六个高频翻车点现象、原因、解决第一坑time重名编译直接报错。现象是编译到time(timep)那一行报「too many arguments to function」或者报类型不匹配。原因是文件里自己定义了一个void time()把time.h里的标准库time给遮住了调用time(timep)时编译器看到的是那个无参版本。血泪经验是给自定义函数起名前先在标准库里搜一遍。解决办法很简单把void time()改名成showNow()函数体里的ctime调用保持不变。第二坑getch()在 Linux 上找不到。现象是fatal error: conio.h: No such file or directory。conio.h是 DOS/Windows 控制台专用的Linux 和 macOS 没有。快速绕过的方法是自己写一个#ifdef _WIN32 #include conio.h #else #include termios.h #include unistd.h static int getch(void) { // 关掉行缓冲读一个字符不回显 struct termios oldt, newt; int ch; tcgetattr(STDIN_FILENO, oldt); newt oldt; newt.c_lflag ~(ICANON | ECHO); tcsetattr(STDIN_FILENO, TCSANOW, newt); ch getchar(); tcsetattr(STDIN_FILENO, TCSANOW, oldt); return ch; } #endifICANON关掉行缓冲ECHO关掉回显读完再把终端属性还原不然程序退出后终端会变得很奇怪。第三坑system(cls)在 Linux 下刷屏刷不动。现象是屏幕上菜单越堆越多根本不清屏。cls是 Windows 命令Linux 下要clear。懒人写法是包一层宏#ifdef _WIN32 #define CLEAR cls #else #define CLEAR clear #endif /* 调用处统一写成 system(CLEAR); */第四坑混合声明在老编译器上报错。现象是在gcc -stdc89下Initshuzu(); Initqueue(); double sum 0;这三行报「ISO C90 forbids mixed declarations and code」。C89 要求所有变量声明必须写在语句之前而示例代码是先调用两个函数、再声明sum。VC 6.0 把它当 C 编译所以不报换成 C 编译器就翻车。解决方法是把double sum 0;挪到函数开头或者直接加-stdc99。我一般选后者因为 c99 的for (int i ...)写起来顺手。第五坑平均逗留时间永远是 0。现象是按 6 查询不管走多少个客户「普通用户业务办理总共用时」都显示0.00。原因在case 2里w; allsum A[0]; // A 是 main 的局部数组全 0且从没被赋过值A[0]是一个在main里定义、初始化成全 0、之后再没被写过的数组元素。真正记录客户编号的是全局的L.A[]两者名字像但不是一回事。所以allsum一直加 0平均值自然也是 0。这个坑藏得深因为程序不报错、流程照走只有盯着输出看才会发现不对。修法见第 6 章。第六坑删到队列只剩一个人时下一个客户到达就崩。现象是先让两个客户排队再连续两次让人离开第二次离开后立刻按 1 加一个客户程序偶发闪退或打出乱码编号。原因就是 3.2 节说的Dlqueue摘掉最后一个结点后没有把Q-rear拉回头结点rear变成了悬空指针下次Enqueue往已释放的内存里写。补上一句就好int Dlqueue(Linkqueue *Q) { Lnode *t; int x; if (Q-front Q-rear) { printf(队列为空\n); return -1; // 比 exit(1) 温和调用方可以判断 } t Q-front-next; Q-front-next t-next; x t-data; if (Q-front-next NULL) // 关键一行队列空了尾指针归位 Q-rear Q-front; free(t); return x; }这六个点里前四个是环境适配后两个是逻辑缺陷。环境问题改一次就完了逻辑问题得盯着输出慢慢对后者才是真正花时间的地方。6. 二次开发把「假的平均时间」改成真的逗留时长统计前面那个永远输出 0 的平均时间是这份代码最值得动手的地方也是最能体现「数据结构课设」和「能跑的软件」差距的地方。想算准就得在客户进店的那一刻把时间戳记下来出店的时候做差。现有的结点只存了一个int data装不下时间得扩结构。#include time.h struct Lnode { int data; // 业务号 time_t arrive; // 到达时刻入队时写入 Lnode *next; }; /* 入队时顺带打时间戳 */ void Enqueue(Linkqueue *Q, int elem) { Lnode *s (Lnode *)malloc(sizeof(Lnode)); s-data elem; s-arrive time(NULL); // 秒级精度课设够用 s-next NULL; Q-rear-next s; Q-rear s; }窗口那一侧也一样struct List里再加一个time_t busy[n 1]daoda成功占窗口时记录L.busy[i] time(NULL)。然后离开时把差值累加/* 在 likai 里客户离场那一刻 */ time_t now time(NULL); if (L.busy[x] ! 0) { allsum difftime(now, L.busy[x]); // 返回 double单位秒 L.busy[x] 0; w; // 只有真的走了才计数 }difftime返回的是double秒数比手写now - arrive更稳跨平台也不会有精度上的意外。w一定要挪到「确认删除成功」之后否则用户输错编号也会被算成一次成功办理统计就虚高了。改完之后怎么验证我一般会跑一个固定剧本启动程序立刻让 1 号客户到达并记下当前秒数等 3 秒让他离开再看查询界面是不是显示 3 秒左右。误差在 ±1 秒内就算通过因为time(NULL)是秒级精度。想更精确就换clock_gettime(CLOCK_MONOTONIC, ...)但课设演示没必要。顺手还能做两个小改造成本都不高。一是把 VIP 白名单从五个if改成一个小数组卡号和密码做成结构体加会员只要往数组里加一行二是把n从 3 改成 5同时把guitai的提示和校验统一到1..n让柜台数和计数器数量真正对齐。这两处改完代码的「可配置性」会上一个台阶答辩的时候也更好讲。还有个习惯值得养成不要在main里留一个和全局数组同名的局部数组。float A[10]和L.A[]名字撞车是这次统计失效的直接原因。从那以后我每次写这种课设代码都强制自己把「数据」和「统计量」分两个前缀命名比如g_和stat_宁可多敲几个字符也不想再花半小时找一个永远为 0 的变量。希望帮到你。本文还有配套的精品资源点击获取