资讯动态

C语言停车场管理系统完整代码:从数据结构到计费持久化

发布时间:2026/10/13 16:38:22 来源:尧图企业网站定制
简介这份资源是面向C语言初学者与课程设计学习者的停车场管理系统完整代码包围绕车辆进出登记、车位分配、计时计费与数据记录等核心业务帮助读者理解如何用C语言实现一套可运行的管理程序。压缩包共22个文件约1.9MB以cpp源码为核心同时包含exe可执行文件、pdb调试符号、dsp与dsw工程文件、opt与plg编译配置、pch预编译头、obj与ilk中间产物等覆盖从源码到编译运行的完整工程结构可直接在VC环境中打开学习。目前已有11799人学习下载热度较高。代码中涉及结构体定义车辆与车位信息、链表或数组管理车位状态、文件操作实现数据持久化以及计费函数处理不同收费标准等知识点适合作为C语言综合练习、数据结构应用或课程设计的参考实现便于读者对照理解系统模块划分与关键流程。1. 停车场管理系统从课程设计到能跑起来的 C 语言工程很多人第一次接触「C语言停车场管理系统完整代码」是在课程设计或者期末大作业里。标题看着简单真动手才发现坑不少车牌怎么存、车位状态怎么实时更新、计费按小时还是按分钟、退出时数据要不要落盘。我带过几届学生的实训也帮某公司做过一套内部车辆进出登记的小工具血泪经验是——能跑通的最小版本永远比功能列表写满的版本更值钱。这篇笔记面向两类人一类是要交课程设计、需要一份能编译能演示的完整代码另一类是想把这套逻辑真正用到小型停车场、园区门岗的开发者。我会从数据结构选型讲到计费函数怎么写再到数据持久化和几个必踩的坑。全程用标准 CC99 以上不依赖任何第三方库gcc 直接编译就能跑。你照着敲一遍能拿到一个带增删改查、进出场计费、文件存盘的完整系统。2. 先定数据结构车位、车辆、时间三张表怎么设计写管理系统第一件事不是敲main而是想清楚数据长什么样。停车场这个场景核心就三个实体车位位置、状态、车辆车牌、进出时间、计费规则费率、免费时长。很多人一上来就写一堆全局变量写到一半发现「一个车位只能停一辆车」这个约束没法表达回头重构时间全浪费了。2.1 用结构体数组还是链表常见做法是用结构体数组因为停车场车位数量是固定的数组下标天然对应车位编号查找 O(1)。链表适合数量动态变化的场景停车场不适用。我一般会定义一个MAX_SLOTS宏方便改规模。#include stdio.h #include stdlib.h #include string.h #include time.h #define MAX_SLOTS 100 // 最大车位数按实际场地改 #define PLATE_LEN 16 // 车牌最大长度含结束符 #define RATE_PER_HOUR 5 // 每小时费率单位元 // 车位状态 typedef enum { FREE 0, OCCUPIED 1 } SlotState; // 单个车位 typedef struct { int id; // 车位编号从 1 开始 SlotState state; // 空闲/占用 char plate[PLATE_LEN]; // 占用时的车牌空闲为空串 time_t in_time; // 入场时间戳 } Slot; // 停车场整体 typedef struct { Slot slots[MAX_SLOTS]; int total; // 实际启用车位数 double revenue; // 累计收入 } ParkingLot;这段代码的关键点time_t用time.h里的时间戳比存「2024-05-01 10:30」这种字符串好算得多计费时直接做减法。state用枚举而不是 int可读性高也防止有人写state 2这种非法值。plate用定长数组而不是char*避免动态内存管理课程设计级别完全够用。2.2 车牌为什么不能用 int 存新手最容易翻车的地方想用整数存车牌。蓝牌是「京A12345」这种字母数字混合新能源牌还多一位整数根本存不下。必须用字符串。比较车牌用strcmp拷贝用strncpy并手动补\0因为strncpy在源串超长时不会自动加结束符这是个经典坑。// 安全拷贝车牌防止溢出 void set_plate(char *dst, const char *src) { strncpy(dst, src, PLATE_LEN - 1); dst[PLATE_LEN - 1] \0; // 强制截断并保证结束符 }参数说明dst是目标缓冲区src是用户输入的车牌。PLATE_LEN - 1留一个字节给结束符。这行dst[PLATE_LEN - 1] \0是后悔药少了它在极端输入下会读到脏内存。2.3 初始化函数与车位编号约定车位编号从 1 开始对用户友好但数组下标从 0 开始。我一般约定对外显示id index 1内部操作统一用下标。初始化时把所有车位置为空闲。void init_lot(ParkingLot *lot, int n) { if (n MAX_SLOTS) n MAX_SLOTS; lot-total n; lot-revenue 0.0; for (int i 0; i n; i) { lot-slots[i].id i 1; lot-slots[i].state FREE; lot-slots[i].plate[0] \0; lot-slots[i].in_time 0; } }逻辑说明循环里把每个车位置成空闲车牌置空串首字符为\0即空串入场时间清零。n超过MAX_SLOTS时截断防止越界。这个函数在程序启动和「重置系统」时都会调用。3. 核心功能落地入场、出场、计费三个函数怎么写数据结构定好接下来是业务逻辑。停车场系统 90% 的价值就在三个动作车进来、车出去、算钱。这三个函数写扎实剩下的查询、统计都是锦上添花。3.1 入场函数先查重再找空位入场有两个约束同一车牌不能重复入场防止有人蹭车位必须有空位。顺序很重要先查重再找位否则会出现「找到空位但车牌已存在」的尴尬。// 返回值成功返回车位号失败返回 -1 int park_in(ParkingLot *lot, const char *plate) { // 1. 查重 for (int i 0; i lot-total; i) { if (lot-slots[i].state OCCUPIED strcmp(lot-slots[i].plate, plate) 0) { printf(车牌 %s 已在 %d 号车位不能重复入场\n, plate, lot-slots[i].id); return -1; } } // 2. 找第一个空位 for (int i 0; i lot-total; i) { if (lot-slots[i].state FREE) { lot-slots[i].state OCCUPIED; set_plate(lot-slots[i].plate, plate); lot-slots[i].in_time time(NULL); // 记录当前时间戳 printf(入场成功车位号%d\n, lot-slots[i].id); return lot-slots[i].id; } } printf(停车场已满无法入场\n); return -1; }参数说明lot是停车场指针plate是车牌字符串。time(NULL)返回当前 Unix 时间戳单位秒。返回 -1 表示失败调用方据此判断。这里用「第一个空位」策略简单可靠如果想做「就近分配」可以把车位按区域分组但课程设计没必要。3.2 出场与计费时间差怎么算才不扯皮计费是最容易出纠纷的地方。核心公式费用 ceil(停车秒数 / 3600) * 费率。用ceil向上取整停 1 小时 1 分钟按 2 小时算这是行业惯例。要引入math.h编译时加-lm。#include math.h // 出场并计费返回费用车牌不存在返回 -1 double park_out(ParkingLot *lot, const char *plate) { for (int i 0; i lot-total; i) { if (lot-slots[i].state OCCUPIED strcmp(lot-slots[i].plate, plate) 0) { time_t now time(NULL); double seconds difftime(now, lot-slots[i].in_time); double hours ceil(seconds / 3600.0); if (hours 1) hours 1; // 不足一小时按一小时 double fee hours * RATE_PER_HOUR; printf(车牌 %s 出场车位 %d\n, plate, lot-slots[i].id); printf(停车时长%.0f 秒计费 %.0f 小时费用 %.2f 元\n, seconds, hours, fee); // 释放车位 lot-slots[i].state FREE; lot-slots[i].plate[0] \0; lot-slots[i].in_time 0; lot-revenue fee; return fee; } } printf(未找到车牌 %s 的入场记录\n, plate); return -1; }逻辑说明difftime返回两个时间戳的差值秒比直接相减可移植性好。ceil(seconds / 3600.0)向上取整注意除数写3600.0而不是3600否则整数除法会先截断。hours 1的兜底是防止停几秒就免费。费用累加到lot-revenue方便后面统计。3.3 费率与免费时长的参数化上面费率写死成宏实际项目里应该做成可配置。我一般用一个Config结构体从文件读或让用户改。参数含义建议值说明RATE_PER_HOUR每小时费率5 元按当地物价调整FREE_MINUTES免费时长15 分钟0 表示不免费MAX_SLOTS车位数100按场地规模PLATE_LEN车牌缓冲16新能源牌够用免费时长的处理在park_out里判断seconds FREE_MINUTES * 60则费用为 0。注意免费时长和「不足一小时按一小时」的优先级一般先判免费再计费。4. 数据持久化退出不丢数据重启能恢复课程设计演示时最怕断电或误关窗口数据全没。加一层文件存盘成本很低但体验提升巨大。常见做法是程序启动读文件每次增删改后写回或者退出时统一写。4.1 用文本文件存车位状态文本格式可读性好方便调试。每行一个车位id state plate in_time。void save_lot(const ParkingLot *lot, const char *path) { FILE *fp fopen(path, w); if (!fp) { perror(保存失败); return; } fprintf(fp, %d %.2f\n, lot-total, lot-revenue); for (int i 0; i lot-total; i) { fprintf(fp, %d %d %s %ld\n, lot-slots[i].id, lot-slots[i].state, lot-slots[i].state OCCUPIED ? lot-slots[i].plate : -, (long)lot-slots[i].in_time); } fclose(fp); }参数说明第一行存总车位数和累计收入。空闲车位的车牌用-占位避免空字符串导致fscanf读取错位。in_time用%ld存long因为time_t在不同平台可能是long或long long强制转换保证一致。4.2 读取时的容错处理读文件比写文件容易翻车因为文件可能被手改坏。每读一行都要检查返回值。int load_lot(ParkingLot *lot, const char *path) { FILE *fp fopen(path, r); if (!fp) return 0; // 文件不存在用默认初始化 if (fscanf(fp, %d %lf, lot-total, lot-revenue) ! 2) { fclose(fp); return 0; } if (lot-total MAX_SLOTS) lot-total MAX_SLOTS; for (int i 0; i lot-total; i) { char plate[PLATE_LEN]; long t; int id, st; if (fscanf(fp, %d %d %s %ld, id, st, plate, t) ! 4) break; lot-slots[i].id id; lot-slots[i].state (SlotState)st; set_plate(lot-slots[i].plate, strcmp(plate, -) 0 ? : plate); lot-slots[i].in_time (time_t)t; } fclose(fp); return 1; }逻辑说明fscanf返回值是成功匹配的项数不等于预期就说明文件损坏直接中断读取保留已读部分。strcmp(plate, -) 0把占位符还原成空串。返回 1 表示加载成功0 表示用默认值。4.3 主循环与菜单设计主函数就是一个while(1)加switch把上面函数串起来。菜单用数字选项输入用fgets而不是scanf避免缓冲区残留。int main(void) { ParkingLot lot; init_lot(lot, MAX_SLOTS); load_lot(lot, parking.dat); char buf[64]; while (1) { printf(\n1.入场 2.出场 3.查看 4.统计 0.退出\n请选择); if (!fgets(buf, sizeof(buf), stdin)) break; int choice atoi(buf); if (choice 0) break; if (choice 1) { printf(输入车牌); fgets(buf, sizeof(buf), stdin); buf[strcspn(buf, \n)] \0; // 去掉换行 park_in(lot, buf); } else if (choice 2) { printf(输入车牌); fgets(buf, sizeof(buf), stdin); buf[strcspn(buf, \n)] \0; park_out(lot, buf); } else if (choice 3) { for (int i 0; i lot.total; i) { printf(车位%d: %s %s\n, lot.slots[i].id, lot.slots[i].state FREE ? 空闲 : 占用, lot.slots[i].state OCCUPIED ? lot.slots[i].plate : ); } } else if (choice 4) { printf(累计收入%.2f 元\n, lot.revenue); } } save_lot(lot, parking.dat); printf(数据已保存再见\n); return 0; }buf[strcspn(buf, \n)] \0是去掉fgets读进来的换行符这是处理字符串输入的标配。atoi把选项字符串转整数比scanf(%d)安全因为不会在输入非数字时死循环。5. 避坑与排查这五个问题几乎人人都会遇到代码能编译不代表能正确运行。下面五个坑是我在实训和实际部署里反复见到的按「现象 → 原因 → 解决」列出来你对照排查能省几小时。5.1 输入车牌后程序直接跳过或乱码现象输入车牌回车程序没反应或者下一次菜单直接跳过。原因混用了scanf和fgetsscanf留下的换行符被fgets读走。解决统一用fgets读所有输入或者scanf后加getchar()清缓冲。我一般全用fgetsatoi一劳永逸。5.2 计费结果总是少一小时现象停 2 小时 5 分钟只收 2 小时的钱。原因seconds / 3600用了整数除法先截断再ceilceil已经没意义。解决除数写成3600.0保证浮点除法。这个坑隐蔽因为停整点时结果正确只有零头才暴露。5.3 文件读取后车位状态错乱现象重启后车牌和车位对不上或者最后几个车位是脏数据。原因写文件时空闲车位车牌写了空串fscanf的%s遇到空白就停导致后续字段错位。解决空闲车位用-占位读取时再还原成空串如 4.1 和 4.2 所示。5.4 车牌比较失败明明一样却查不到现象入场成功出场时提示「未找到记录」。原因车牌字符串带了尾部空格或换行strcmp逐字符比较不相等。解决输入后统一trim去掉首尾空白和换行。strcspn去换行是基础如果用户手滑打了空格还得额外处理。5.5 车位越界导致段错误现象程序运行中突然崩溃或者改大MAX_SLOTS后正常。原因init_lot传入的n超过MAX_SLOTS循环写越界。解决init_lot里加if (n MAX_SLOTS) n MAX_SLOTS;所有涉及数组下标的操作都做边界检查。C 语言不会帮你拦只能自己防。提示调试时把MAX_SLOTS改小到 3手动模拟满场、重复入场、跨天停车比随机点菜单更容易发现问题。6. 进阶技巧把计费逻辑抽成可测试的纯函数到这一步系统已经能用了但如果你想让代码更经得起推敲或者课程设计想拿高分我建议做一件事把计费逻辑从park_out里抽出来做成不依赖全局状态的纯函数。这样你可以单独写测试用例验证也方便以后换费率规则。// 纯函数根据停车秒数和费率算费用不碰任何全局变量 double calc_fee(double seconds, double rate, int free_minutes) { if (seconds free_minutes * 60.0) return 0.0; double hours ceil(seconds / 3600.0); if (hours 1) hours 1; return hours * rate; }这个函数的好处是输入输出明确没有副作用。你可以写一个简单的测试表停车秒数费率免费分钟预期费用说明600515010 分钟免费36005155整 1 小时3660515101 小时 1 分按 2 小时720050102 小时无免费写完对照跑一遍全对再集成回park_out。这个习惯我从做第一个商业项目就保持到现在凡是涉及钱的逻辑必须能脱离界面单独验证。界面会骗你测试表不会。另一个技巧是给车位加「区域」字段比如 A 区、B 区查找时按区域优先方便引导。实现上就是在Slot里加char zone;park_in里先找同区域空位。改动不大但演示时显得考虑周全。最后说个我自己的教训早期写这类系统总想把功能堆满结果每个功能都半吊子。后来某导师跟我说课程设计看的是「一条主线走通」不是功能数量。把入场、出场、计费、存盘这四件事做扎实比加十个花哨菜单有用得多。你现在手里的这份代码编译命令是gcc parking.c -o parking -lm跑起来先测重复入场和跨小时计费这两个过了基本就稳了。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑