简介:面向高校软件工程课程设计与项目答辩场景的停车场管理系统报告书,围绕车辆停放管理需求,完整呈现需求分析、概要设计、详细设计、编码实现与测试的分析过程,可帮助读者理解软件工程方法如何应用到小型管理系统中。系统以 Windows XP 为平台,采用 VC++6.0 开发、Visio 2007 建模,功能覆盖车辆信息记录、车位动态分配、停车费用结算、车位使用状况查看与退出登录,并涉及单门进出、便道排队等待和按时长计费等业务逻辑。资源包仅含 1 个 doc 文件,约 1.7MB,内含任务书、章节设计说明、测试分析与参考文献,报告框架、模块划分和测试用例组织较完整。已有 4750 人学习,适合需要撰写课程设计报告、梳理设计文档结构或准备答辩的读者参考。
1. 停车场管理系统:一段只有两百行的 C 代码,撑起半个软件工程课程
我见过不少课设压缩包,解开之后是一个.doc、一个.cpp、一个截图文件夹,看着寒酸,但真把代码跑起来读一遍,会发现里面把“栈”和“队列”两个基础结构用在了最该用的地方。这套停车场管理系统就是这么个东西:一个只有大门、单向进出的狭长停车场,车按到达顺序从最里面往门口停,中间有车要走,后面的车必须依次退出来让路,走完再按原顺序开回去。这个物理规则本身就是后进先出,所以停车场天然是一枚栈;而门口便道上排队的车是先来先走,天然是一支队列。项目规模不大,技术栈也就是 C 语言加控制台菜单,开发环境是 Windows XP 配 VC++6.0,但需求分析、概要设计、详细设计、编码、测试这条链路全走了一遍。放到今天,这类作业在互联网上依然是搜“停车场管理系统 C 语言”出现频率最高的资源之一。适合谁看:刚学完结构体和指针、想找一个能把栈队列真正用起来的小项目的同学,以及想拿它当二次开发起点、把控制台程序改成带持久化存储的小工具的开发者。
2. 骨架拆解:从 main 到 menu 的调用链与三个结构体的选型
这套程序的整体结构比想象中要“软件工程”一些,虽然实现只有两百行上下,但模块划分是清楚的:一个入口、一个菜单、四个功能模块,全部靠全局数组共享状态。理解它的第一步不是看功能怎么实现,而是先把调用链和数据结构摸清,否则后面读任何一个函数都会被一堆全局变量绕晕。
2.1 main 与 menu:一个伪装成事件循环的递归调用
原程序的入口极其简短,短到第一次看会以为是没写完:
void main() /* 主函数 */ { menu(); /* 菜单函数 */ }真正的中枢在menu()。它用一个do-while做输入合法性校验,用switch分发到初始化、车辆进入、车辆离开、退出四个分支:
void menu() { int n, w; do { puts("\t\t**************MENU**************\n\n"); puts("\t\t\t 1.初始化"); puts("\t\t\t 2.有车进入"); puts("\t\t\t 3.有车离开"); puts("\t\t\t 4.退出"); puts("\n\n\t\t*********************************\n"); printf("Please choice your number(1-4): [ ]\b\b"); scanf("%d", &n); if (n < 1 || n > 4) { /* 非法输入:置标志位,重新循环 */ w = 1; getchar(); /* 吃掉残留的换行符,防止下次 scanf 直接读到 */ } else { w = 0; } } while (w == 1); switch (n) { case 1: chushi(); break; /* 初始化:清空场内与便道 */ case 2: jinru(); break; /* 车辆进入:分配车位或进便道 */ case 3: likai(); break; /* 车辆离开:让路、计费、补位 */ case 4: exit(0); /* 退出 */ } }这里要说清两件事。一是w这个标志位的用法:它不是在判断“输入对不对”,而是在控制“要不要再来一次”,getchar()那一行是血泪经验,不带它的话,输入一个字母会让scanf失败并把字符留在缓冲区,后面的循环会疯狂空转,屏幕上刷满菜单。二是每个功能函数(比如chushi())执行完最后一行都写着menu();,也就是靠函数递归回到菜单,而不是返回到main里再循环。这个写法在课设里非常常见,好处是每个模块都能独立跳回界面、不用层层 return,坏处是每操作一次就压一层栈帧,递归深度等于操作次数。
提示:递归式菜单是这个项目最需要优先改造的点,后面第 5 章会专门讲它为什么会把程序跑崩。
2.2 三个结构体:车、场内车位、便道车位
数据设计是这套代码里最值得抄的部分,它把“实体”和“位置”拆成了两类结构。车辆信息单独一个结构体数组,车位状态用两个只存 0/1 的结构体数组表示:
#define N 100 /* 预设停车场有 100 个车位 */ #define M 100 /* 预设便道有 100 个车位 */ #define D 1.2 /* 预设停车费为 1.2 元每小时 */ #define CIXUHAO 1000 /* 预设车辆次序号上限,从 0 开始 */ struct car { /* 车辆信息 */ int car_num; /* 车辆次序号 */ int car_arr; /* 车辆到达时间 */ int car_lef; /* 车辆离开时间 */ int car_stay; /* 车辆停放位置 */ } car[CIXUHAO]; struct changnei { /* 场内车道信息 */ int neikong; /* 0 = 空,1 = 占用 */ } chn[N - 1]; struct changwai { /* 场外便道信息 */ int waikong; } chw[M - 1];这套设计的关键在于用下标表达物理顺序。chn[0]是停车场最里面那个位,chn[N-2]是紧挨大门的位;车停进来的顺序就是下标递增的顺序,这正是“先到的停最里面”这条规则的代码映射。car[]数组则是一个按次序号索引的信息表,用户输入次序号a,程序就用car[a]取这辆车的到达时间、离开时间、停在哪一位。四个宏定义分别控制容量、便道容量、单价和最大车辆数,改N就能把停车场从 100 位扩到 200 位,这是这套代码里少见的、考虑过“可配置”的地方。
不过这里有个明显的坑:chn[N-1]声明的数组长度是N-1,合法下标是0到N-2,可后面的循环写的是for (i = 0; i < N; i++),会访问到chn[N-1],数组越界一个元素。初学阶段这种错误很隐蔽,因为多访问的那一格通常恰好是别的全局变量,程序不崩,只是数据莫名其妙被改。修正方式很简单,要么把声明改成chn[N],要么把循环上界改成N-1,但两者必须一起改,只改一处等于换个地方越界。
2.3 为什么停车场是栈、便道必须是队列
把物理规则翻译成数据结构,是这个项目最核心的一次建模决策。停车场只有一个大门,车从门口进、从门口出,最先到的车停在最里面,最后到的车停在门口,要开走中间某辆车,它后面所有车都得先退出去让路,等它走了再按原顺序回来——这就是标准的后进先出,栈顶在门口,栈底在最里面。原文用“以堆栈来模拟停车场”一句话概括了,但实现上没有真的写push/pop,而是用chn[]数组加 0/1 标记来承担栈的职责,靠下标顺序维持结构。
便道则是另一种情况:门口排队的车,先来的先能进场,谁也不能插队,这是先进先出,必须用队列。原文的chw[]数组虽然定义了,但通篇没有任何入队、出队操作,jinru()在停车场满的时候只打印一句“停车场已停满,请停在便道上”,然后就把人踢回菜单,便道上的车实际上是一笔糊涂账,全靠操作者在likai()里手动回答“便道上现在有车吗”。这是原始版本最大的功能缺口,也是二次开发最值得先补的地方。
写到这里可以总结一下调用链的全貌:main只做一次跳转,menu负责输入校验和分发,四个功能函数各自读写三份全局数据,结束后递归回到menu。整条链路上没有参数传递、没有返回值、没有封装,这种写法在小体量程序里够用,但一旦要加功能,全局变量就会变成最难缠的部分。
3. 车辆进场链路:初始化、找空位与便道排队的真实实现
进场这条链路包含两个函数:初始化和车辆进入。前者看着简单,实际上藏着原始代码里最典型的一个复制粘贴错误;后者是整个程序里逻辑最密的一段,能不能正确分配车位,取决于一个叫h的计数器怎么被使用。
3.1 初始化:把车位状态清零的两行循环并不简单
需求上,初始化就是“每天开始营业前把场内和便道的车位全部置空”。原始实现如下:
void chushi() /* 初始化函数 */ { int i, j; for (i = 0; i <= N - 1; i++) chn[i].neikong = 0; /* 将场内车道设置为空 */ for (j = 0; j <= M - 1; j++) chw[i].waikong = 0; /* 注意:这里下标写的是 i,不是 j */ printf("\n\n\t\t 已初始化\n\n"); menu(); /* 初始化完成后递归回菜单 */ }第二个循环里chw[i]的i是个硬伤。第一层循环结束后i停在N-1(也就是 99),所以这段代码实际效果是:把chw[99].waikong反复赋 0 共 100 次,便道上前 99 个位置一个都没清。如果上一次运行在便道上留下了等待车辆,初始化之后它们仍然“在”,后续进场逻辑就会读到脏状态。这类错误编译器不会报,运行也不崩,只能靠肉眼看或者写测试对照。稳妥的写法是用memset一次性抹平,既避开手写下标,也省掉两层循环:
#include <string.h> void chushi(void) { memset(chn, 0, sizeof(chn)); /* 场内全部置空 */ memset(chw, 0, sizeof(chw)); /* 便道全部置空 */ memset(car, 0, sizeof(car)); /* 车辆信息一并清掉,避免上一轮的残留时间参与计费 */ printf("\n\n\t\t 已初始化\n\n"); }memset的第三个参数是字节数,用sizeof(数组名)拿到的就是整个数组的字节大小,对int成员的结构体数组同样成立,因为全零字节就代表整数 0。这里顺手把car[]也清了,原因在于原始版本只清车位不清车辆信息,第二轮操作时如果输入了一个上一轮用过的次序号,car[a].car_arr还是旧值,计费会算出一个荒唐的负数。
3.2 车辆进入:h 既是计数器又是下标,这一步最容易翻车
进场函数是整套代码里逻辑密度最高的地方,原文实现如下:
void jinru() /* 车辆进入函数 */ { int i, a; int h = 0; printf("\n\n 请输入该车次序号(从 0 号开始):"); scanf("%d", &a); for (i = 0; i < N; i++) { if (chn[i].neikong == 0); /* 无车标记 0,这里多了个分号 */ if (chn[i].neikong == 1) /* 有车标记 1 */ h = h + 1; } if (h == N) { printf("停车场已停满,请停在便道上\n"); } else { car[a].car_stay = h; /* 把 h 当成“下一个空位下标” */ chn[h].neikong = 1; /* 把第 h 位标记为占用 */ printf("该车应该停在停车场第%d 道(从 0 道开始记)\n", h); printf("请输入该车进停车场时刻(24 小时整点计时):\n"); scanf("%d", &car[a].car_arr); } menu(); }逐段拆。第一段是统计场内已有多少辆车,遍历整个chn[],遇到neikong == 1就h++。这里if (chn[i].neikong == 0);后面那个分号是空语句,虽然不影响最终结果(因为真正干活的是下面那个if),但它暴露了一个思维习惯问题:写判断的时候没想清楚这个分支要做什么。第二段判断h == N表示“车位数等于车辆数”,即停满。第三段是关键,h在这里被赋予了第二个语义——下一个可用车位的下标。
问题就出在这个双语义上。h数的是“有几辆车”,而不是“空位在哪”。如果停车场停了 0 号、1 号、3 号三位,h统计出来是 3,程序就会把新车塞进chn[3],而 3 号位本来就有车,等于直接覆盖。物理上这对应于“中间有车开走了,来了辆新车,结果停到了别人车上”。原始版本的离开函数没有真正把车位标记清 0,所以这个 bug 在正常流程里不容易触发,一旦你补上“离开即清位”,它立刻就会冒出来。
正确的做法是维护一个栈顶指针,只增不减地记录“下一个可停的位置”,同时保证离开时按栈规则处理:
int top = 0; /* 栈顶指针:0 表示空场,也等于“下一辆车的停放下标” */ int jinru(int seq, int arrive_hour) { if (top >= N) { /* 场内已满 */ if (enqueue_bian(seq)) /* 进便道队列 */ printf("停车场已满,次序号 %d 的车进入便道等待\n", seq); else printf("便道也满了,无法接纳\n"); return 0; } car[seq].car_arr = arrive_hour; /* 记录到达时刻 */ car[seq].car_stay = top; /* 该车停在 top 号位 */ chn[top].neikong = 1; /* 标记占用 */ printf("次序号 %d 的车停入场内第 %d 道\n", seq, top); top++; /* 栈顶上移,指向下一个空位 */ return 1; }top的语义被收敛成唯一一个:栈内元素个数,同时也是下一个空位下标。这样进场是push,离开是pop,中间不会出现“数出来 3 但 3 号位有人”的尴尬。参数上,seq是车辆次序号(对应原版的a),arrive_hour是到达时刻(对应car_arr),返回 0/1 表示是否成功入场,调用方可以据此决定要不要提示用户。把scanf从业务函数里挪出去,是让这段逻辑可测试的前提——你可以不接键盘,直接构造参数跑一百组用例。
3.3 车场满了之后:便道队列该怎么接
原始版本在“满场”分支里只打印一句提示,便道上到底有谁、排第几、什么时候来的,一概没记。真要做完整,得给便道配一套队列操作。环形缓冲区是最省事的实现,head指向队首(最先来、下一个能进场的),tail指向队尾的下一个空槽:
int bhead = 0, btail = 0, bcount = 0; int enqueue_bian(int seq) /* 入便道队尾 */ { if (bcount >= M) return 0; /* 便道满 */ bianq[btail] = seq; btail = (btail + 1) % M; /* 取模实现环形 */ bcount++; return 1; } int dequeue_bian(void) /* 从便道队首取一辆车 */ { int seq; if (bcount == 0) return -1; /* 便道空 */ seq = bianq[bhead]; bhead = (bhead + 1) % M; bcount--; return seq; }bianq[]只存车辆次序号,不存完整车辆信息,因为详细信息已经在car[seq]里了,队列只负责维持顺序。取模% M是让tail走到数组末尾后自动绕回开头,避免数组用一段就废弃一段。bcount单独存一个计数值,比用head == tail判断空满要可靠得多——环形队列在满的时候head和tail会重合,光靠两个指针分不清是空是满。这段代码里M是便道容量,seq是车辆次序号,enqueue_bian返回 1 表示成功入队,dequeue_bian返回 -1 表示便道已空,调用方不需要额外查状态。
4. 车辆离场链路:让路临时栈、跨位出栈与小时计费
离场是这套程序里最容易写错的部分,因为它同时涉及三个动作:把挡路的车挪走、算钱、让便道上的车补位。原始版本把这三个动作拆成了“提示用户手动操作”,等于把最难的逻辑甩给了操作者,这也是它作为课程设计报告书、而不是可用软件的根本原因。
4.1 原始实现的计费逻辑与它的边界
先看原文的离开函数核心:
void likai() { int i, k, choi, time; double fee; printf("请输入离开车辆次序号[ ],并将此车之后的车先全部退出停车场!有车离开"); scanf("%d", &i); printf("\n 请输入离开车辆的离开时刻(24 小时整点计时):\n"); scanf("%d", &car[i].car_lef); fee = D * (car[i].car_lef - car[i].car_arr); /* 时长 × 单价 */ time = car[i].car_lef - car[i].car_arr; printf("次序号为%d 的车停车时间%d 小时,应收费%f 元\n", i, time, fee); printf("\n\n\n 请让场退出的车再依次进场!\n\n\n"); printf("便道上现在有车吗?(请选择 1 或 2)\n1.有\n2.没有\n"); scanf("%d", &choi); if (choi == 2) menu(); if (choi == 1) { printf("请让便道上的第一辆车进场\n"); printf("该车次序号为:\n"); scanf("%d", &k); printf("请输入该车进场时间:\n"); scanf("%d", &car[k].car_arr); menu(); } }计费公式本身没问题:D是每小时单价 1.2 元,car_lef - car_arr是在场小时数,两者相乘就是费用。但这段代码埋了三个能在验收现场直接翻车的点。
第一,跨天为负。时间只有“24 小时整点”这一个维度,没有日期。一辆车 22 点进场、次日 6 点离场,6 - 22 = -16,程序会打出一句“停车时间 -16 小时,应收费 -19.2 元”,负费用直接从钱包里往外掏钱。第二,让路完全靠人。注释写着“请让此车之后的车先全部退出停车场”,但代码里没有任何退出、入栈的操作,全靠操作者自己记住顺序。第三,便道只是个判断题。choi等于 1 就手动输入便道车的次序号和进场时间,那便道队列的顺序、便道车到底等了多久,全都无从校验。次序号k甚至可以是场内的某辆车,同一辆车被记两次到达时间。
4.2 让路临时栈:把“退出来再开回去”翻译成 push 和 pop
物理规则要求:要让第k位(0 是最里面)的车开走,必须先把它后面所有车(k+1到top-1)按从门口往里的顺序退出来,等目标车走了,再按原来的顺序倒回去。这正好是两段连续的出栈和入栈,临时栈就是干这个的:
#define CAP 100 int tmpstack[CAP]; /* 让路临时栈,只存车辆次序号 */ int ttop = 0; int leave(int seq, int leave_hour) { int pos = car[seq].car_stay; /* 该车停在第几位 */ int t, fee, hours; /* 第一段:把 pos 之后的车全部倒进临时栈,从门口往里依次退出 */ for (t = top - 1; t > pos; t--) { tmpstack[ttop++] = find_seq_at(t); /* 按位置反查车辆次序号 */ chn[t].neikong = 0; /* 腾空该车位 */ } /* 第二段:结算目标车 */ hours = leave_hour - car[seq].car_arr; if (hours < 0) hours += 24; /* 简化版跨天:整点时制补 24 */ fee = (int)(D * hours * 100 + 0.5) / 100.0; /* 保留两位小数 */ printf("次序号 %d 的车停车 %d 小时,应缴 %d.%02d 元\n", seq, hours, (int)fee, (int)((fee - (int)fee) * 100 + 0.5)); chn[pos].neikong = 0; /* 目标车位腾空 */ top--; /* 栈顶下移 */ /* 第三段:临时栈里的车按原顺序倒回场内 */ while (ttop > 0) { int s = tmpstack[--ttop]; car[s].car_stay = top; chn[top].neikong = 1; top++; } car[seq].car_lef = leave_hour; return fee; }第一段是出栈循环,t从top - 1往下走到pos + 1,正好是“从门口往最里面”的顺序,每退一辆就把它的车位标记清 0,次序号压进tmpstack。第二段算钱,hours为负时补 24 小时是一种粗糙但够用的跨天处理,小说里那种“停了两天”就得老老实实加日期字段了。fee那行用* 100 + 0.5再取整的方式是控制浮点误差的老办法,避免printf("%f")打出3.5999999这种数字。第三段是入栈,注意while里用的是--ttop,从栈顶往下取,取出来的顺序正好是第一段压入顺序的逆序,也就是“原来离门口最近的先回去”,物理上和人工让路的动作完全一致。反查函数find_seq_at(pos)需要你遍历car[]找car_stay == pos的那辆,返回它的次序号。
4.3 补位:便道车进场要发生在让路之后
目标车走了、临时栈的车也回位了,此时场内容量空出一格(top减了 1),便道队首的车就可以进场。顺序必须是“先让路、再结算、最后补位”,如果把补位放在让路前面,便道车会抢到临时栈还没回位的车位上,直接撞车。补位代码:
int seq_from_bian = dequeue_bian(); /* 便道队首出队,没有则返回 -1 */ if (seq_from_bian >= 0 && top < N) { car[seq_from_bian].car_arr = leave_hour; /* 进场时间 = 前车离场的时刻 */ car[seq_from_bian].car_stay = top; chn[top].neikong = 1; top++; printf("便道上的次序号 %d 进场,停第 %d 道\n", seq_from_bian, top - 1); }car_arr赋成leave_hour表示“前车刚走它就进场了”,这样便道上的等待时长不会被计进停车费里,符合“便道上不收费”的业务规则。如果业务上要求便道排队超过一定时间要额外收费,那就在car结构里再加一个bian_arr字段单独记入队时间,计费时分开算。
5. 避坑与排查:这套课设代码里五个真实的坑
代码不长,但坑的密度不低。下面这几条都是照着原样编译、运行、对着输出看一遍就能复现的问题,遇到相似现象可以先对号入座。
5.1 初始化后便道状态没清干净
现象:点了“初始化”,界面提示“已初始化”,但便道上残留的等待车辆依然能在后续操作里被引用,甚至出现同一辆车既在场内又在便道的怪状态。原因:for (j = 0; j <= M - 1; j++) chw[i].waikong = 0;里下标写成了i而不是j,i在上一层循环结束后固定为N-1,循环 100 次只是在反复清同一个格子。解决:换成memset(chw, 0, sizeof(chw)),或者把下标改成j;同时建议把car[]一起清掉,避免旧时间参与新计费。
5.2 数组越界却毫无提示
现象:程序能跑,但偶尔某个车位的状态会莫名其妙变成 1,或者某个车辆的到达时间变成不合理的值。原因:chn[N - 1]声明的长度是N-1(合法下标0到N-2),而遍历写的是for (i = 0; i < N; i++),多访问了一格;chw[M - 1]同理。越界那一格通常恰好是相邻的全局变量,写进去就改坏了别人的数据。解决:声明和循环上界必须成对修改。建议统一成chn[N]+i < N,语义更直观:容量是 N,下标 0 到 N-1。
5.3 中间车位空出来后新车覆盖已有车辆
现象:先让三辆车进场(占 0、1、2 号位),再让 1 号位的车离场,然后来一辆新车,它被分配到了 3 号位以外的位置,或者直接覆盖了 2 号位的车。原因:h被同时当作“已停车数量”和“下一个空位下标”使用。数量是 2(还剩 0 号和 2 号位有车),但下一个可用下标可能是 1,两者不相等。解决:引入独立的top栈顶指针,只表示“下一个可停放的位置”,离场时执行top--,入场时执行top++,不要用计数去推下标。
5.4 跨天停车算出负费用
现象:晚上 10 点进场、次日早上 6 点离场,输出“停车时间 -16 小时,应收费 -19.20 元”。原因:时间字段只有整点小时数,没有日期,减法结果天然可能为负。解决:短期可以用if (hours < 0) hours += 24;兜住一天内的跨天;正经做法是把时间字段换成time_t,用difftime求秒差再换算小时,跨月跨年都不会错。
5.5 菜单递归导致长时间运行后崩溃
现象:连续操作几十次后程序突然退出,或者弹出栈溢出的错误框。原因:每个功能函数结尾都调用menu(),而menu()又会调用功能函数,形成递归。每操作一次压一层栈帧,栈帧里还带着局部变量,深度到几千层就撑不住了。解决:把递归改成循环。main里写while (1) { menu(); },功能函数执行完直接return,由外层循环重新拉起菜单。这个改法只动两处,收益最大。
6. 二次开发:从“能演示”走到“能真正跑起来”
如果只打算交作业,原版跑通截图就够了;但如果想把它当成一个能继续长的项目,我建议按依赖顺序做四件事,每一件都能独立验证,不用一次全改完。
第一步,把全局数组收进一个结构体,做成“停车场对象”。把chn[]、top、bianq[]、bhead、btail、bcount全塞进struct ParkingLot,函数签名改成int pl_enter(struct ParkingLot *pl, int seq, time_t t)这种形式。好处是同一份代码可以同时开两个停车场做对比测试,也方便以后换成动态分配。
第二步,把时间换成time_t。字段car_arr、car_lef的类型从int改成time_t,进场时time(&car[seq].car_arr),计费时double hours = difftime(lef, arr) / 3600.0;再按分钟向上取整。这一步能一次性消灭跨天、跨月、跨年三个问题,代价是所有printf的格式符要从%d换成对应的日期格式化函数。
第三步,补上持久化。每次进场、离场之后,把car[]和两个队列 dump 到一个文本文件,启动时读回来。格式不用复杂,一行一辆车,用空格分隔字段即可,fscanf直接能读。这一步做完,程序就有了“关掉再打开数据还在”的能力,也是从课设走向可用工具的分水岭。
第四步,写一组边界用例,跑完再交付。我一般会固定测这几组:空场直接离场(应该提示找不到车)、场满后再进第十一辆(应该进便道)、便道有车时场内离场(便道队首应该补位且进场时间等于离场时间)、同一辆车连续进场两次(应该有重复校验)、离场时间早于进场时间(应该拒绝并提示)。这五组跑通,基本的功能正确性就有底了。
改造过程中有个验证技巧值得单独说:把menu()里的scanf全部抽走,让业务函数只接受参数、只返回结果,然后写一个不含任何输入输出的测试main,用固定数据把上面五组用例跑一遍,把结果printf出来对比预期。这样做的好处是调试时不用每次都手敲菜单,改一行代码跑一次,几秒钟就能看到全部用例的结果。
最后说个自己的教训。早年我改这类课设代码,习惯先动功能、最后补测试,结果每次加一个新特性就把旧逻辑撞坏一次,改到后面自己都不知道哪版是对的。从那以后我每次动这种“全局状态 + 菜单驱动”的老代码,都强制先把业务函数和输入输出拆开、把边界用例写成可重复执行的脚本,再开始改功能,哪怕多花半天也认。这套停车场系统尤其如此,top指针和便道队列的状态耦合很紧,没有测试兜底,一个top--放错位置就能让整个场子乱套。希望帮到你。
本文还有配套的精品资源,点击获取