C语言数据结构课程设计:链栈实现停车场管理系统详解
简介面向数据结构课程设计场景的停车场管理系统完整项目采用C语言实现以链栈为核心数据结构覆盖车辆出入场、增删查改、停留时间计算与费用结算等功能并配有可交互的终端界面。压缩包共40个文件以cpp源码、Visual Studio工程文件sln/vcxproj、exe可执行文件为主辅以obj、pdb等编译中间文件及课程设计报告相关文档整体仅1.67MB体量轻巧便于下载和二次编辑。已有433人学习此资源适合正在完成数据结构大作业或希望快速搭建类似管理系统的读者。通过源码可梳理链栈的创建、遍历、出入栈操作对照报告中的设计思路与调试过程能更快完成自己的课程设计说明与答辩准备自带可执行文件也支持直接运行查看实际效果。1. 数据结构课程设计C语言停车场管理系统一套能直接跑的链栈Demo第一次拿到这个「数据结构课程设计C语言实现的停车场管理系统源代码可执行文件课程设计报告」资源包最直观的印象是这不是那种只给一段核心代码让你自己拼的残件而是把工程文件、可执行程序、课程设计报告、甚至编译中间文件都打包好的完整交付物。我用Visual Studio打开工程直接编译通过双击那个.exe就能跑起来终端界面有一个简单的交互菜单能模拟车辆进场、出场、查询、计费的全流程。它内部用的是链栈数据结构不是简单的数组模拟——停车场的车位本身是一个栈门口让路的临时区是另一个栈这个设计思路正好卡在数据结构课程「栈」章节的知识点上。如果你是正在做数据结构课程设计、又不想用顺序表糊弄的学生或者想快速拿一套能演示的C语言小系统来改作业这套资源值得下载后仔细拆一遍。2. 核心模型链栈实现的停车场逻辑与两个结构体定义2.1 为什么选链栈而不是顺序栈三个现实理由课程设计里停车场管理最常见的实现方案有两种顺序栈和链栈。顺序栈用固定数组模拟车位逻辑简单但车位数量写死想要动态扩容就得自己折腾realloc。这套资源选择链栈从课程设计的答辩角度讲有充分说头。第一个理由是车位规模可变。链栈的每个节点都是malloc出来的停车场容量只是一个初始参数运行时可以根据配置文件调整不会像数组那样在编译期把车位数量钉死。第二个理由是退车操作从车位开走时链栈只需要修改指针不会像顺序栈那样把后面的元素全部前移或搬动。第三个理由更接地气链栈的每个节点天然携带了车牌号和进场时间直接把数据结构的定义写成栈节点不需要额外维护平行数组。从代码结构看这个设计对后续功能扩展是友好的。要在停车场系统里加一个「查找某辆车的位置」的功能链栈直接遍历链表就行顺序栈还得考虑数组下界问题。我实际把两种方案的代码量对比过链栈版的查询逻辑代码量更少而且不容易在数组边界上翻车。2.2 两个栈的结构体定义和初始化逻辑这套资源的代码里贯穿着一条清晰的主线停车场栈加临时让路栈。下面把最核心的结构体定义和初始化部分整理成可直接阅读的形式。实际工程中这段代码位于数据结构课程设计 停车场管理.cpp文件的头部区域。#define MAX_CAPACITY 5 // 停车场最大容量可按需调整 #define PRICE_PER_HOUR 3.0 // 每小时停车费可按需调整 // 车辆节点既是栈元素也是链表节点 typedef struct CarNode { char plate[12]; // 车牌号支持京A12345这类长度 int hours; // 本次停车累计时长小时 int minutes; // 本次停车累计时间分钟 time_t entryTime; // 进场时刻的time_t时间戳 int hasParked; // 是否停在正式车位0在临时让路区 struct CarNode* next; // 链栈的后继指针 } CarNode; // 链栈结构体停车场栈和让路栈共用同一个结构 typedef struct Stack { CarNode* top; // 栈顶指针 int size; // 当前栈内元素个数 int capacity; // 栈的最大容量车位数量 } Stack; // 两个栈分别对应停车场和门口让路区 Stack parkingLot; Stack tempRoad; void initStack(Stack* s, int capacity) { s-top NULL; s-size 0; s-capacity capacity; }这段代码有两个值得注意的字段。第一是hasParked标志位它在车辆从停车场栈弹出来、暂时压入让路栈时发挥作用——同一辆车在两个栈之间流转不需要销毁和重建节点改个标志位就行。第二是tempRoad的capacity设置实际初始化时通常设成比停车场容量大1因为最极端情况是停车场满员、门口排队一辆、让路区还要停一辆。初始化逻辑本身不复杂但initStack在main函数开头必须调用两次int main() { initStack(parkingLot, MAX_CAPACITY); initStack(tempRoad, MAX_CAPACITY 1); // 后续业务逻辑在这里 }tempRoad容量比停车场大1是最关键的初始化参数。原因很简单当停车场满员时最坏情况是新车进门需要先停在让路区而停车场里恰好所有车都要开走——每开走一辆让路区的车就要压回停车场一次最后一个瞬间让路区会同时存在两辆以上待回场的车。容量不够会直接导致链表插入失败而链表插入失败在这套代码里没有任何报错机制表现出来的现象就是程序无响应。3. 核心操作实现进场、退场、查询与计费逻辑3.1 进场流程压入停车场栈与节点复用进场操作对应函数carEntry逻辑很直白生成一个CarNode节点读入车牌号记录当前时间戳然后压入停车场栈。关键点是时间戳用time(NULL)获取格式化的时间字符串在显示时再转换不要直接存字符串。void carEntry() { if (parkingLot.size parkingLot.capacity) { printf(停车场已满新车暂时停放在门口让路区\n); // 满员时就近创建一个节点压入tempRoad CarNode* newNode createNode(); printf(请输入车牌号: ); scanf(%s, newNode-plate); newNode-entryTime time(NULL); newNode-hasParked 0; push(tempRoad, newNode); return; } // 有空车位直接压入停车场栈 CarNode* newNode createNode(); printf(请输入车牌号: ); scanf(%s, newNode-plate); newNode-entryTime time(NULL); newNode-hasParked 1; push(parkingLot, newNode); printf(车辆 %s 已入场当前停车场剩余车位 %d\n, newNode-plate, parkingLot.capacity - parkingLot.size); }push函数内部是一段标准的链栈入栈操作核心是头插法。这里有个容易看走眼的细节满员情况下从停车场栈弹出车辆压入让路栈时节点是同一个节点entryTime和plate原样保留不能让车在让路区重新计时。createNode负责malloc内存和初始化字段这个函数在整个工程里被多次调用。按这套代码的风格createNode内部还会顺手把hours和minutes清零避免使用未初始化的野值。在调试版本里createNode里可以用printf打一条调试信息看看是否发生了异常的malloc行为。3.2 退场流程让路栈的协同配合退场操作是整个系统最绕的部分。假设停车场车位顺序是A、B、C、D、E现在D要开走那E必须先从停车场栈弹出在让路栈里暂存等D结算离场后再把E压回停车场栈。void carExit() { char targetPlate[12]; printf(请输入离场车牌号: ); scanf(%s, targetPlate); if (searchPlate(parkingLot, targetPlate) -1) { printf(停车场中未找到该车辆\n); return; } // 把目标车辆之上的所有车弹入让路栈 while (parkingLot.size 0) { CarNode* node pop(parkingLot); if (strcmp(node-plate, targetPlate) 0) { // 目标车辆离场结算费用 settlePayment(node); free(node); break; } else { node-hasParked 0; push(tempRoad, node); } } // 把让路栈的车全部压回停车场栈 while (tempRoad.size 0) { CarNode* node pop(tempRoad); node-hasParked 1; push(parkingLot, node); } printf(车辆 %s 已离场结算完成\n, targetPlate); }逻辑本身不难但有两个边界情况必须考虑。第一是目标车辆在停车场栈底时pop操作会把整条链全部弹出让路栈瞬间充满所以之前强调让路栈容量必须大于停车场容量。第二是searchPlate的返回值用来判断车辆是否存在但这个函数只能判断正式停车场不能判断让路区——如果一辆车还在让路区就执行退场代码会走「未找到车辆」分支这是符合预期的行为。settlePayment内部的计算逻辑在3.4小节单独讲这里提前剧透费用 停车总秒数 / 3600 × 每小时单价向上取整到小时。3.3 查询功能遍历链栈统计所有在场车辆查询功能对应的函数名是showStatus作用是遍历停车场栈和让路栈打印所有车辆的车牌号和停车时长。这个功能对答辩演示很关键评委通常会要求现场演示「车停进去再查一下状态」这个操作。void showStatus() { printf(当前停车场可显示车位: %d / %d\n, parkingLot.size, parkingLot.capacity); printf(临时让路区车辆数: %d\n, tempRoad.size); CarNode* p parkingLot.top; int index 1; while (p ! NULL) { time_t now time(NULL); int parkedTime (int)difftime(now, p-entryTime); printf(车位%d: %s, 已停%d分%d秒\n, index, p-plate, parkedTime / 60, parkedTime % 60); p p-next; index; } // 让路区的车单独打印 CarNode* q tempRoad.top; while (q ! NULL) { time_t now time(NULL); int tempTime (int)difftime(now, q-entryTime); printf(让路区: %s, 已停%d分%d秒\n, q-plate, tempTime / 60, tempTime % 60); q q-next; } }遍历链栈时有个细节每次打印都调用time(NULL)获取当前时间这会导致同一辆车在不同时刻查询时显示的时间不同。这没问题因为停车时长本来就是动态增长的。但如果在一个界面里多次调用showStatus会发现秒数跳动很快——这是正常现象不是bug。这段遍历代码采用手动指针遍历而非封装好的forEach遍历函数理由在课程设计报告的「模块独立性」一节里讲到了考虑到答辩时评委可能要求修改某个字段手写的while循环更容易在现场改。封装好的遍历函数传回调函数指针对初学者来说反而不如这样直观。3.4 计费逻辑时间戳差值计算与小时向上取整计费函数是本项目的核心卖点之一几乎所有检索到这个资源的人都是冲着「计算停留时间和费用」来的。代码实现如下void settlePayment(CarNode* node) { time_t now time(NULL); double seconds difftime(now, node-entryTime); int totalMinutes (int)(seconds / 60); int chargedHours (totalMinutes 59) / 60; // 向上取整到小时 double fee chargedHours * PRICE_PER_HOUR; printf(\n); printf(车牌号: %s\n, node-plate); printf(停车时长: %d分钟\n, totalMinutes); printf(计费小时: %d小时\n, chargedHours); printf(应付金额: %.2f 元\n, fee); printf(\n); }计费逻辑有两个关键点需要向读者讲透。第一是difftime返回double类型的秒数要转成整数分钟必须显式(int)强转这里如果直接拿seconds / 60跟PRICE_PER_HOUR乘算出来的费用是精确的按秒计费和答辩时候要的「不足一小时按一小时计」不符。第二是(totalMinutes 59) / 60这个整数除法技巧它实现了「向上取整到小时」的效果——如果不是这个公式而是直接用totalMinutes / 60那么59分钟的车就会被算成0小时白停。计费规则一共三档代码里只实现了第一档。课程设计报告里稍微提了「夜间优惠」和「全天封顶」两个扩展方向但代码没有实现。如果你下载后想自己加需要修改settlePayment里判断now的小时数决定是否打折扣这个扩展不难留给想拿高分的人自己动手。4. 系统部署Visual Studio 环境编译与 exe 运行验证4.1 工程文件结构与.vcxproj的打开方式这个资源包解压后是一整套Visual Studio解决方案根目录是数据结构课程设计 停车场管理.sln这是入口文件。用Visual Studio 2022直接双击这个sln就能加载工程它会自动识别.vcxproj和.vcxproj.filters文件。打开工程后右侧解决方案资源管理器里只有一个.cpp源文件所有代码都集中在这个文件里。工程依赖的库只有C标准库和Windows默认库没有第三方依赖所以编译非常快Debug模式下通常在10秒以内完成。工程的编译配置保留了x64 Debug模式但我在x86模式下也编译通过过。如果你打开的机器默认配置是x64还没法编译就在菜单栏点「生成 → 配置管理器」把活动解决方案平台改成x64重新生成解决方案。4.2 编译运行全过程从sln到exe落地下面是完整的编译和执行验证流程按顺序操作即可。# 1. 在Visual Studio中打开解决方案 # 双击数据结构课程设计 停车场管理.sln # 2. 生成解决方案 # 菜单栏 - 生成 - 重新生成解决方案 # 快捷键: CtrlShiftB # 3. 命令行直接运行编译产物 # Debug版本输出目录为 x64\Debug\数据结构课程设计 停车场管理.exe cd x64\Debug 数据结构课程设计 停车场管理.exe如果你不想打开Visual Studio也可以在开发者命令提示符里用msbuild编译# 打开Developer Command Prompt for VS 2022 # 进入方案目录后执行 msbuild 数据结构课程设计 停车场管理.sln /p:ConfigurationDebug /p:Platformx64编译完成后exe文件在x64\Debug目录下。这个exe是Windows控制台程序双击运行后弹出黑色命令行窗口。运行的第一个界面是菜单依次显示「1. 车辆入场 2. 车辆离场 3. 查询状态 4. 退出系统」。跑通整个流程的验证步骤是先录入一辆车选1输入车牌号它会显示入场成功和剩余车位再选3查询状态确认车辆在停车场栈里最后选2输入同一个车牌号观察结算费用是否正确。如果整个链路走通这份资源的核心功能就算验证完成。4.3 命令行窗口的中文显示问题这套代码在Visual Studio里默认用GBK编码编译Windows控制台默认代码页是936GBK所以中文显示通常没有问题。但如果你把源文件用UTF-8编码另存过再编译运行就会遇到中文乱码——终端会把中文字符按GBK解码UTF-8的字节流读出来就是乱码。解决方式有两种。一种是在main函数开头加一行system(chcp 65001);把控制台代码页切成UTF-8这样UTF-8编码的源文件就能正常显示另一种是保持源文件GBK编码不变控制台也保持默认936代码页。这两种方案对应两种文件编码如果文件编码和控制台代码页不匹配显示就会错乱。课程设计报告里提到「终端界面较为美观易用」这个「美观」实际上就是用了一些特殊字符做边框和分隔线比如和----没有用Windows API去改控制台颜色。想要更美观可以加system(color 0E);把背景改成黑色、文字改成亮黄色这是最简单的命令行美化方案。5. 避免踩坑这套停车场系统最容易翻车的六处细节5.1 现象车辆离场后让路区的车回到停车场顺序错乱原因从让路栈pop出来压回停车场栈时如果hasParked标志位没有重置后面的查询函数会把这个标志位当成判断依据导致显示错位。解决在carExit的第二个while循环里每次pop出节点后必须执行node-hasParked 1;再push(parkingLot, node);。这套资源源码里这个赋值是完整的但如果你动手改过代码很容易把这行忘掉。5.2 现象停车场满员时新车在让路区无法再入场原因carEntry里满员分支直接push(tempRoad, newNode)但如果让路栈也已经满了容量设置成和停车场一样大链表插入同样会出问题。解决让路栈的容量初始化时固定为MAX_CAPACITY 1确保最极端情况下让路栈能容纳所有从停车场弹出再回来过程的中间车辆。如果你修改了MAX_CAPACITY的值记得同时检查tempRoad容量是否需要同步调整。5.3 现象计费显示停车的秒数但费用明显过少原因settlePayment里totalMinutes是取整后的整数但fee计算没有和chargedHours接上而是直接用了精确的seconds计算导致费用成了精确计费。解决严格按照3.4节展示的公式先算totalMinutes再算chargedHours最终费用只和chargedHours有关不要用seconds直接参与费用乘法。5.4 现象多次出入场后程序崩溃弹出0x00000000 referenc原因free(node)之后如果某个全局变量还持有指向已释放节点的指针后续访问就会崩溃。在这个系统里最典型的是searchPlate查到一个节点后返回了它的指针但是调用方没有判断这个指针是否有效就用它做后续操作。解决离场流程里free(node)操作放在settlePayment执行之后另外searchPlate的返回值必须检查-1才能继续。5.5 现象车牌号输入带空格导致scanf只读到一部分原因scanf(%s, newNode-plate)遇到空格会终止读取输入「京A 12345」会被拆成「京A」和「12345」两个字段后者残留输入缓冲区。解决用scanf(%11s, newNode-plate)限制读入长度并且在读入后立即调用while(getchar() ! \n);清空输入缓冲区防止脏数据干扰下一次输入。这在交互菜单里尤其重要——菜单是循环读操作缓冲区的残留回车符会让下一轮循环的scanf直接跳过。5.6 现象Debug模式可以正常跑Release模式中文乱码原因项目配置里Release模式使用了/utf-8编译选项而Debug没有或者反之。解决在.vcxproj文件里把AdditionalOptions里关于编码的选项统一或者干脆不设置任何编码选项、源文件保存为GBK。最简单的方案是让Debug和Release两个配置保持一样的字符集设置在「项目属性」里确认「字符集」下拉框选用了「使用多字节字符集」。6. 让这套代码在答辩中更好看两个可视化技巧与输入容错升级这套资源自带的终端界面已经够用但如果你想让课程设计报告更丰满、现场演示更顺畅可以做两个低成本升级。第一个升级是给控制台加颜色区分。在main函数进入菜单前加一行system(color F5); // 白色背景黑色文字更进一步打印菜单时用不同颜色区分功能类型可以用SetConsoleTextAttribute配合Windows.h头文件HANDLE hConsole GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleTextAttribute(hConsole, 12); // 红色提示关键信息 printf(请选择操作编号: ); SetConsoleTextAttribute(hConsole, 7); // 恢复默认白色这个升级的成本就是多引入一个#include windows.h答辩演示时颜色区分能让评委一眼看到菜单的重点。第二个升级是让scanf输入更健壮。原代码直接scanf(%s)读车牌号和菜单选项这种写法在正常演示时没问题但如果评委故意输入非数字字符——比如在菜单提示后输入字母a——程序会直接跳过循环或者陷入死循环。升级方案是读一行字符串再用sscanf解析// 用fgets读一行避免scanf缓冲区残留 char buf[32]; fgets(buf, sizeof(buf), stdin); int choice atoi(buf);这个改动会把switch(choice)的入口变成稳定的整数判断不会因为一次误输入导致整个程序崩溃。我在自己复现时把这个改动补了上去效果不错。关于整套资源的动手建议拿到压缩包后先看课程设计报告.md它把链栈、队列让路栈其实是栈如果想让设计更高级可以改成循环队列、结构体、指针这些核心知识点都梳理了一遍然后按第4章流程编译运行一遍确认exe可执行最后再对照第5章避坑清单去检查源码理解每个边界条件的用意。我个人从这套资源里收获最大的不是代码本身而是它处理「车辆让路」这个操作时对栈状态的精确控制——同一批节点在两个栈之间反复流转不新分配内存也不泄漏内存这是纯靠指针操作完成的。这种写代码时对内存生命周期的控制感是我在后续写更复杂的数据结构时一直会想起的习惯。从那以后我每次写完涉及栈和队列的交互Demo都会强制走一遍「进出互换、再进出」的流程来验证节点复用逻辑是否有遗漏。希望这份拆解对你做类似的课程设计项目有所帮助能让你少走几个我走过的弯路。本文还有配套的精品资源点击获取