ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C语言停车场管理系统课程设计:栈与队列实现与避坑指南

C语言停车场管理系统课程设计:栈与队列实现与避坑指南 简介这是一套面向高校数据结构课程设计的停车场管理系统完整项目基于C语言与链栈结构实现适合正在完成课程设计或期末大作业的本科生参考。系统包含停放入场、出场计费、车辆信息增删查改等核心逻辑能根据停放时长自动计算费用并配有简洁美观的终端交互界面。压缩包共40个文件约1.67MB涵盖cpp源代码、可直接运行的exe程序、课程设计报告md文档以及Visual Studio工程配置文件sln、vcxproj、filters等还包含调试生成的pdb、obj、tlog等辅助文件便于重新编译与排错。资源打包为一份zip目录结构完整从源码到报告一应俱全适合直接对照学习数据结构的实际应用。目前已有433人学习下载可作为课程设计选题思路、代码实现与报告撰写的综合参考。1. 停车场管理系统课程设计它在解决什么又卡住过谁数据结构课程设计里停车场管理系统是出现频率最高的题目之一。用 C 语言实现一个带等候区的停车场技术上就两点停车场内部按栈来管理等候区按队列来管理。车辆进场分配车位出场按时间计费车位满了就在外面排队。真写起来才发现难的不是那几个 printf 菜单而是结构体、指针、时间戳和文件读写怎么在同一份代码里不打架。这篇内容把一个可编译、可运行、能交报告的做法讲一遍包含数据结构定义、核心函数、编译命令和答辩前最容易踩的坑。适合正在准备课程设计、或者想把旧项目改进一下的读者。2. 停车场管理系统的数据结构选型为什么是栈加队列不是链表乱写2.1 停车场内部是天然的栈等候区是天然的队列停车场管理系统课程设计最常见的模板是把停车场内部建模成一个栈。原因是绝大多数停车场通道是窄的后进来的车停在靠里的位置要出去时只能让堵在外面的车先挪开这就是教科书式的“后进先出”。不用栈的话你就要自己在数组里反复搬元素代码丑不说报告里还讲不清楚。等候区则正好相反。车辆在停车场外排队有空位就补进去先到先补位这是“先进先出”对应队列。常见的做法是用链表实现队列等候区长度不用写死还能顺便把链表操作练到。把单链表当停车区硬写也能跑但课程设计答辩时老师问一句“你的数据结构体现在哪”你就得绕圈子。栈和队列的组合一张图就能讲明白。2.2 车辆节点和停车场、等候区的结构体定义先看最核心的数据结构定义。下面的代码我拆成了两块CarNode 描述一辆车ParkingLot 用数组模拟栈WaitQueue 是等候区的链表队列。#include stdio.h #include stdlib.h #include string.h #include time.h #define MAX_PARK 5 // 停车场容量改成 10 就是 10 个车位 #define RATE 3.0 // 每小时停车费用单位元 typedef struct { char plate[16]; // 车牌号留足字节中文车牌也放得下 time_t enter_time; // 进场时间用时间戳不要用字符串 } CarNode; typedef struct { CarNode spots[MAX_PARK]; // 用数组模拟栈 int top; // 栈顶下标初始化为 -1 } ParkingLot; typedef struct WaitNode { char plate[16]; struct WaitNode *next; } WaitNode; typedef struct { WaitNode *head; // 队列头出队从这里取 WaitNode *tail; // 队列尾入队从这里挂 } WaitQueue;这里有几个参数和类型的选择值得说。plate 用char plate[16]而不用char *plate是为了省掉动态分配和释放课程设计代码里最常见的崩溃就是 malloc 之后忘了 free。time_t 是 C 标准库里的时间类型配合time(NULL)和difftime()使用算停车时长不需要手动解析年月日。MAX_PARK 和 RATE 写成了宏答辩老师如果问“系统参数在哪改”你直接指这两行就行。宏写法的缺点是不能从配置文件读取但课程设计阶段可读性比灵活性重要。ParkingLot 里的 top 初始化为 -1表示空栈。压栈时先top再把车辆信息写进spots[top]。这里有个映射关系数组下标 0 对应 1 号车位下标的物理意义和车位编号会差 1后面打印时记得把栈顶下标加一再输出不然老师按报告核对车位号时会发现对不上。2.3 模块怎么拆单文件还是多文件很多同学习惯把所有代码塞进一个 parking.c程序短的时候没问题但只要一加文件读写和日志这个文件就会膨胀到六七百行滚动页面找函数都很痛苦。我一般会拆成 main.c、parking.c、queue.c 和 parking.h 四个文件。头文件放结构体定义和函数声明源文件各管一块。// parking.h #ifndef PARKING_H #define PARKING_H #define MAX_PARK 5 #define RATE 3.0 typedef struct { char plate[16]; time_t enter_time; } CarNode; typedef struct { CarNode spots[MAX_PARK]; int top; } ParkingLot; void enter_parking(ParkingLot *lot, WaitQueue *queue, char *plate); double leave_parking(ParkingLot *lot, WaitQueue *queue, char *plate); #endif头文件里不定义全局变量这是我要强调的一点。有人会在 main.c 外面声明一个ParkingLot lot;让所有函数直接访问省去传参但课程设计报告里讲模块边界时很难自圆其说。把 lot 和 queue 定义在 main 里面通过指针传给函数函数只操作传入的地址结构清晰答辩时也好回答“全局变量对模块化的影响”这类追问。多文件结构本身也是报告里“模块化设计”一节的现成素材。3. 核心业务逻辑进场、出场、计费、补位四个函数串起整个系统3.1 进场函数先查车位再决定停进去还是进队列进场逻辑的核心是判断栈还有没有空位。有空位就压栈并把当前时间写进节点没有就把车牌挂到等候队列。下面是 enter_parking 函数。void enter_parking(ParkingLot *lot, WaitQueue *queue, char *plate) { if (lot-top 1 MAX_PARK) { lot-top; strcpy(lot-spots[lot-top].plate, plate); lot-spots[lot-top].enter_time time(NULL); printf(车辆 %s 已停入 %d 号车位\n, plate, lot-top 1); } else { enqueue(queue, plate); printf(车位已满车辆 %s 进入等候区\n, plate); } }这里用lot-top 1 MAX_PARK判断而不是lot-top MAX_PARK - 1是因为当 top 为 -1 时后一种写法容易让人困惑。数组容量是 MAX_PARK栈空时 top -1栈满时 top MAX_PARK - 1所以条件写成top 1 MAX_PARK最直观。时间戳用time(NULL)获取调用一次就行不要这里取一次后面出场再取一次两者之间哪怕差一秒计费结果都可能让老师起疑。进场函数有个容易被忽略的边界当等候区不为空时其实不应该有新车辆直接进场而应该先处理等候区。有些版本的系统允许“插队”这在功能上是缺陷但课程设计一般只要求基本流程。如果要做得严谨可以在 main 里先判断排队人数只剩一条空车道有排队就直接让队头进。代码实现不复杂但报告里写清楚你的策略是什么更重要。3.2 出场函数临时挪车、算钱、让等候车辆补位出场流程是全场最绕的部分。车辆不一定停在栈顶如果停在中间需要把压在上面的车全部挪到临时栈等目标车离开后再挪回来。这个挪动过程正好用到了“栈”的特性也是答辩时最容易展开讲的地方。double leave_parking(ParkingLot *lot, WaitQueue *queue, char *plate) { int i, pos -1; CarNode temp[MAX_PARK]; // 临时栈容量和主栈一致 int temp_top -1; time_t now time(NULL); double fee -1.0; for (i 0; i lot-top; i) { if (strcmp(lot-spots[i].plate, plate) 0) { pos i; break; } } if (pos -1) { printf(未找到车辆 %s\n, plate); return -1; } // 把目标车上面的车全部挪到临时栈 while (lot-top pos) { temp[temp_top] lot-spots[lot-top--]; } // 计费并输出 double hours difftime(now, lot-spots[pos].enter_time) / 3600.0; fee hours * RATE; printf(车辆 %s 停车 %.2f 小时费用 %.2f 元\n, plate, hours, fee); // 车走栈顶减一再把临时栈的车挪回来 lot-top--; while (temp_top 0) { lot-spots[lot-top] temp[temp_top--]; } // 等候区队头补位 char new_plate[16]; if (dequeue(queue, new_plate)) { printf(等候区车辆 %s 准备进场\n, new_plate); enter_parking(lot, queue, new_plate); } return fee; }逻辑上分四步定位、挪车、计费、复位。定位时从栈底向栈顶遍历strcmp 逐字符比较车牌。临时栈用定长数组 CarNode temp[MAX_PARK]容量必须和主栈一致这是新手最容易踩的地方后面避坑章节会专门说。计费用difftime(now, enter_time)返回值是秒除以3600.0变成小时再乘 RATE。补位逻辑放在出场函数的最后是整套系统的关键联动。如果出场后有空位从等候队列的 head 取出一个车牌调用 enter_parking 让车进场。注意这里要防止一种情况取出来的车进入 enter_parking 后如果停车场又满了它会再次被 enqueue 进去形成“无限进出”。但因为主循环里每次出场只补一辆车实际不会死循环只是逻辑上会有两次入队的重复输出。要规避的话可以给 enter_parking 加一个返回值进场失败返回 0出场补位时根据返回值决定要不要清掉队头。3.3 链表队列的入队和出队别漏了头尾指针等候区的队列如果用链表实现enqueue 和 dequeue 是标配函数。队列头和尾都要维护好否则补位时找不到队头。void enqueue(WaitQueue *queue, char *plate) { WaitNode *node (WaitNode*)malloc(sizeof(WaitNode)); if (node NULL) { printf(内存申请失败\n); return; } strcpy(node-plate, plate); node-next NULL; if (queue-tail NULL) { queue-head node; queue-tail node; } else { queue-tail-next node; queue-tail node; } } int dequeue(WaitQueue *queue, char *plate) { if (queue-head NULL) { return 0; // 队列空 } WaitNode *tmp queue-head; strcpy(plate, tmp-plate); queue-head tmp-next; if (queue-head NULL) { queue-tail NULL; } free(tmp); return 1; }enqueue 在队尾插入新节点tail 指针负责找队尾。dequeue 从 head 取出第一个节点并释放如果删完后队列为空要把 tail 也置空。很多人漏了最后这一步导致下一次 enqueue 时 tail 还是指向已释放的内存这是经典悬空指针程序不一定立刻崩但运行几次后数据就乱了。malloc 后一定要检查返回值课程设计的演示环境内存通常够用但答辩老师会看你的代码习惯。检查一下不费多少事报告里还能多写一句“本项目对内存分配失败做了处理”印象分会不一样。3.4 计费规则和命令行菜单怎么把交互做稳计费规则独立成一个函数最合理。把费率宏和时长计算封起来后面如果改成“首小时免费之后每小时递增”只改这个函数就好。double calc_fee(time_t in, time_t out) { return difftime(out, in) / 3600.0 * RATE; }命令行菜单是整套系统的门面交互不稳定会让答辩现场直接翻车。下面这段是 main 的骨架重点在于输入处理。int main() { setbuf(stdout, NULL); // 让 printf 立即输出避免 Windows 下缓存看不到提示 ParkingLot lot; lot.top -1; WaitQueue queue { NULL, NULL }; int choice; char plate[16]; while (1) { printf(\n1. 进场 2. 出场 3. 查看车位 4. 退出\n); printf(请选择: ); scanf(%d, choice); while (getchar() ! \n); // 清空输入缓冲区残留的换行符 if (choice 1) { printf(输入车牌: ); scanf(%15s, plate); while (getchar() ! \n); enter_parking(lot, queue, plate); } else if (choice 2) { printf(输入车牌: ); scanf(%15s, plate); while (getchar() ! \n); leave_parking(lot, queue, plate); } else if (choice 3) { show_parking(lot); } else { break; } } return 0; }scanf 的坑在菜单场景里特别明显。scanf(%d, choice)只读取整数用户敲回车后换行符还留在缓冲区下一次scanf(%s, plate)会把这个残留换行读成空串导致程序跳过车牌输入直接报错。解决方式是在每次 scanf 后加一条while (getchar() ! \n);把这一行的剩余字符全部清掉。scanf(%15s, plate)里的 15 是宽度限制防止用户输入超长字符串覆盖 plate 数组后面的内存这个问题不处理压栈时 strcpy 就会越界。setbuf(stdout, NULL) 是我自己的习惯。Windows 的命令行窗口下printf 的输出可能被缓冲程序卡住时看不到提示文字容易误判成死循环。关闭标准输出的缓冲后提示会即时显示答辩现场演示会顺很多。4. 编译生成可执行文件从源码到能交差的命令行程序4.1 单文件用 gcc 编译参数别只敲 gcc parking.c -o parking.exe课程设计交的是源码加可执行文件编译这一步最怕的是“在我电脑上能跑”这句话。如果用的是命令行 gcc推荐这样编译gcc -Wall -Wextra -stdc11 -pedantic parking.c -o parking.exe-Wall 打开常见警告比如变量声明了没用、if 条件里赋值等。这些警告不影响生成 exe但里面往往藏着 bug 线索。比如time(NULL)如果忘了写头文件编译器给出隐式声明的警告你早发现早解决。比如 -Wextra 会提示无符号和有符号比较这在处理 top 和 MAX_PARK 比较时很有用。比如 -stdc11 固定了语言标准避免老的 C89 行为和新的 C11 行为混在一起报告里也能明确写“本课程设计采用 C11 标准”。如果没有 gcc常见做法是用 Dev-C 或 Code::Blocks菜单里点“编译并运行”也能生成 exe。但课程设计报告里要写清楚自己用的编译器命令行方式给出的信息更规范。生成的文件名是 parking.exe在 Windows 下双击或命令行输入 parking.exe 就能跑。如果是在 Linux 下测试把输出名改成 parking 不带扩展名。4.2 多文件项目用 Makefile改一个文件不用重编全部如果按前面说的把代码拆成四个文件命令行编译要写成这样gcc -Wall -Wextra -stdc11 -pedantic main.c parking.c queue.c -o parking.exe每次都敲这么长一串很累还容易漏文件。写一个 Makefile 管理编译流程是课程设计报告里“工程化”的加分项。CC gcc CFLAGS -Wall -Wextra -stdc11 -pedantic OBJ main.o parking.o queue.o TARGET parking.exe $(TARGET): $(OBJ) $(CC) $(OBJ) -o $(TARGET) main.o: main.c parking.h parking.o: parking.c parking.h queue.o: queue.c queue.h clean: del *.o $(TARGET) 2nulMakefile 里最重要的一条是目标下面的命令行开头必须是一个 Tab 字符不能用空格代替否则 make 直接报错。写 clean 目标是为了重复编译前清理中间文件Windows 下用 delLinux 下用 rm -f。如果老师要求交源码包记得把 .o 和可执行文件清理干净再打包包里只有源码和报告会显得整洁。4.3 运行方式和日志文件可执行文件要能导出停车记录可执行文件生成后运行方式是./parking.exe或直接parking.exe。交互式程序的问题是关掉窗口后数据全丢课程设计一般会要求把停车记录保存下来。我习惯在出场函数里追加写一个日志文件。void save_log(char *plate, time_t in, time_t out, double fee) { FILE *fp fopen(parking.log, a); if (fp NULL) { perror(打开日志文件失败); return; } fprintf(fp, %s,%ld,%ld,%.2f\n, plate, (long)in, (long)out, fee); fclose(fp); }fopen 的第二个参数是字符串 a表示追加写每次都在文件末尾写入新记录。fprintf 里的 %ld 对应对 long 类型的时间戳feof 和 ferror 这套检查在简单场景里可以省但 fclose 一定不能省。fclose 会把缓冲区里的数据真正写到磁盘如果程序在 fprintf 后直接 return日志文件可能还是空的这就是很多人“明明写了文件却看不到数据”的原因。parking.log 在报告里很有用。运行两次程序生成几条进出场记录把文件内容截图放进课程设计报告“测试结果”一节比空口描述有说服力得多。5. 停车场管理系统的 5 个高频坑点现象、原因、解决办法这些坑大部分人在调试当晚都会撞见我按“现象 → 原因 → 解决”把最常见的 5 条列出来。5.1 等待队列的车永远进不了停车场现象停车场有车位空出来但等候区的车就停在原地没有自动补位。程序输出里看不到“等候区车辆准备进场”。原因出场函数里只做了计费和挪车没有调用 dequeue。入场函数只在满位时入队但没有哪个函数负责“出场后把队头补进去”。解决在 leave_parking 的最后取出队头并调用 enter_parking。这段代码就是 3.2 节里的补位逻辑。注意在 main 函数之外任何一个新加的函数只要看到“有空位”就调用 enter_parking都可能造成重复补位。补位动作只保留在出场流程里这样逻辑最集中。5.2 停车两小时收费只有 3 元整数除法的问题现象RATE 是 3.0停了两小时应收费 6 元实际收 3 元。原因有人在计算时长时写difftime(out, in) / 3600 * RATE虽然 difftime 返回 double但如果你把结果先存到 int hours 里小数部分被截断两小时变成一小时。还有人直接(out - in) / 3600两个整数相除结果向下取整到整数小时。解决统一用double hours difftime(out, in) / 3600.0;除数带上点零强制整个表达式以浮点数计算。调试时打印 hours 的值先确认时长对不对再检查费用。5.3 菜单输入第二遍就开始乱跳现象程序第一次运行正常输入“1”和车牌后第二次再输入数字程序不等你打字就直接跳过了车牌输入甚至进入死循环。原因scanf 的缓冲区残留问题。第一次调scanf(%d, choice)时用户输入“1”并回车缓冲区里除了字符“1”还有一个换行符。第二次调用scanf(%s, plate)时换行符被当成了空串读走plate 没有拿到有效数据。解决菜单的每次 scanf 后面加while (getchar() ! \n);把当前行剩余字符全部消费掉。如果你想更稳可以改成fgets读一行再用 sscanf 解析但对课程设计来说上面这行清理代码已经够用。5.4 把 MAX_PARK 改成 10 程序就崩临时数组写死现象开始只有 5 个车位运行正常。老师让你“演示一下扩大容量”把 MAX_PARK 改成 10重新编译后出场挪车时程序乱码或崩溃。原因出场函数里定义了CarNode temp[10]临时栈只有 10 个容量。当 MAX_PARK 大于 10 时主栈的车全部挪到临时栈就会越界写入。解决临时栈容量不要写死改成CarNode temp[MAX_PARK];让这两个值永远保持一致。这个坑的行为很隐蔽程序不一定会立刻崩但一旦越界就可能破坏栈上的其他数据出现“上一秒正常下一秒乱码”的诡异现象。5.5 报告流程图和代码行为对不上现象课程设计报告里画的等候区流程图是先进先出的队列但老师结合你演示的代码一看发现入场时没有调用 dequeue实际行为和后进先出差不多。原因写报告的时间在代码完成之前或者画图时没有对着代码一步一步核对。解决拿一个不超过 8 辆车的测试序列把每一步的输出记录下来再对照这个输出画流程图。图里的每一个分支都要能在代码里找到对应的 if 或 while。比如报告写“队头补位”你就要能找到dequeue函数的调用位置。写报告不是写小说答辩老师更相信代码实际跑出来的行为。6. 课程设计报告的验收演示如何把代码讲成完整项目6.1 报告骨架直接套这个结构一份够用的课程设计报告包含六段需求分析、概要设计、详细设计、测试与运行结果、总结、参考文献。重点放在“数据结构设计”和“核心模块实现”两节把栈、队列的结构体定义和进场、出场函数的流程图放进去。流程图画得简单没关系但函数名要和代码保持一致。6.2 准备一份可回放的演示脚本答辩现场乱输容易翻车最好提前编排一个剧本。下面这个序列可以覆盖停车满、排队、补位、计费四个关键分支。步骤操作输入预期输出T01进场车辆A001停入 1 号车位T02连续进场 4 辆B001, C001, D001, E001分别停入 2 到 5 号车位T03再进场 1 辆F001车位已满F001 进入等候区T04A001 出场A001按实际停车时长计费并生成日志F001 补位照这个顺序操作每一步的输出都可预期。如果老师临时想打断看中间状态调用“查看车位”功能展示当前栈里有哪些车。6.3 答辩必问的三个问题提前想好答案第一为什么停车场用栈因为内部单车道后进车必须先挪出这是后进先出场景栈的结构特性正好匹配。第二时间怎么算time_t 存的是时间戳difftime 返回秒数除以 3600.0 得到小时。第三如果停车场通道两头通这个模型还成立吗不成立那时应该考虑双向队列。这个问题是在考建模前提答得出这一句老师的印象分就到手了。最后说一个我自己的血泪经验。有一回我把报告里的流程图提前画好代码后来又改过两次流程图却没跟着改。答辩时老师顺着图问“你这等候区是先进先出还是后进先出”我看了一眼图说是栈再看了一眼代码——算法是队列当场卡了三秒。后来养成的习惯是画完图对着代码的函数名逐个核一遍图里一个箭头对应代码一个调用图里一个分支对应代码一个 if。流程图画得丑不怕怕的是和代码对不上。希望帮到你。本文还有配套的精品资源点击获取
返回列表