☰
C++实现停车场管理系统
2026/11/2 2:00:50 网站建设 项目流程

前言


停车场管理系统的核心逻辑可以概括为:车辆入场时分配一个空车位并记录入场时间,出场时按停放时长计费并释放车位,随时可以查询空位和在场车辆。功能不复杂,但要把「车位状态」和「车辆索引」保持一致,还要处理满位、重复入场、找错车牌、计费边界这些情况。


常见的实现问题有三个。第一,只用一个vector存车位,出场时线性查找车牌,车辆多时效率低;第二,车位数组和车牌索引各自更新,某条路径漏改一处,出现「车位显示占用但查不到车」;第三,计费直接用浮点小时数相乘,边界和精度都不可控。


本文用std::vector存车位、std::unordered_map存车牌到车位下标的索引,两者在入场/出场时同步更新;计费用std::chrono计算分钟数,再按「不足一小时按一小时」的规则换算。示例以 C++17 为标准,时间点由调用方传入,便于测试和替换时钟。


一、数据结构与不变量




结构内容作用



Slot车位号、是否占用、车牌、入场时间表示单个车位的状态

slots_std::vector<Slot>按车位号顺序存储

index_std::unordered_map<std::string, std::size_t>车牌到slots_下标的映射



需要维持的不变量:



  1. slot.occupied == true当且仅当index_中存在该车牌,且映射到的下标正是这个车位。

  2. 同一车牌最多出现在一个车位上。

  3. 空车位的plate为空,entry的值不被使用。

  4. index_中不存在指向已释放车位的条目。


入场时先检查车牌非空、未在场,再线性扫描第一个空位;找到后同时修改slot和index_。出场时先通过index_找到下标,计算费用,再同时清空两者。两处更新放在同一个函数里,避免调用方只改一半。


std::unordered_map的平均查找是 O(1),最坏 O(n);车位扫描是 O(车位总数)。如果车位很多且入场频繁,可以额外维护一个空闲车位栈或队列,把分配降到 O(1),代价是出场时要归还车位。本文为保持状态简单,使用线性扫描。


二、计费规则


时间用std::chrono::system_clock::time_point表示。出场时间减入场时间得到duration,转换成分钟:


// 适用于 C++17
const auto minutes = std::chrono::duration_cast<std::chrono::minutes>(
exitTime - entryTime).count();

count()返回的是整数类型(通常是long long),单位是分钟。若出场时间早于入场时间(时钟回拨或调用方传错),差值为负,应先钳到 0,再计费。


计费规则设为「不足一小时按一小时,每小时 rate 元」:


hours = (minutes + 59) / 60
if hours == 0: hours = 1
fee = hours * rate

整数除法向下取整,加 59 实现向上取整。最低一小时是业务规则,不是数学必然;如果允许免费停放若干分钟,应在计算小时数之前先判断。


金额用double只是演示。真实计费应以「分」为单位的整数或定点数计算,避免二进制浮点无法精确表示 0.1 这类小数;最后再格式化为两位小数输出。


三、完整实现


// 适用于 C++17
#include <chrono>
#include <cstddef>
#include <iomanip>
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>

class ParkingLot {
public:
using Clock = std::chrono::system_clock;

explicit ParkingLot(std::size_t capacity) : slots_(capacity) {
for (std::size_t i = 0; i < slots_.size(); ++i) {
slots_[i].id = static_cast<int>(i + 1);
}
}

bool enter(const std::string& plate, Clock::time_point at) {
if (plate.empty() || index_.find(plate) != index_.end()) {
return false; // 空车牌或已在场
}
for (std::size_t i = 0; i < slots_.size(); ++i) {
if (!slots_[i].occupied) {
slots_[i].occupied = true;
slots_[i].plate = plate;
slots_[i].entry = at;
index_[plate] = i;
return true;
}
}
return false; // 车位已满
}

bool leave(const std::string& plate, Clock::time_point at,
double hourlyRate, double& fee) {
const auto found = index_.find(plate);
if (found == index_.end()) {
return false;
}
Slot& slot = slots_[found->second];

auto minutes = std::chrono::duration_cast<std::chrono::minutes>(
at - slot.entry).count();
if (minutes < 0) {
minutes = 0; // 防御时钟回拨或错误参数
}
long long hours = (minutes + 59) / 60;
if (hours == 0) {
hours = 1; // 最低计费一小时
}
fee = static_cast<double>(hours) * hourlyRate;

slot.occupied = false;
slot.plate.clear();
index_.erase(found);
return true;
}

bool isParked(const std::string& plate) const {
return index_.find(plate) != index_.end();
}

std::size_t available() const {
std::size_t count = 0;
for (const Slot& slot : slots_) {
if (!slot.occupied) {
++count;
}
}
return count;
}

void printStatus() const {
for (const Slot& slot : slots_) {
std::cout << "车位 " << std::setw(3) << slot.id << " ";
if (slot.occupied) {
std::cout << "占用 " << slot.plate << '\n';
} else {
std::cout << "空闲\n";
}
}
}

private:
struct Slot {
int id = 0;
bool occupied = false;
std::string plate;
Clock::time_point entry{};
};

std::vector<Slot> slots_;
std::unordered_map<std::string, std::size_t> index_;
};

使用示例:


// 适用于 C++17
#include <chrono>
#include <iostream>

int main() {
ParkingLot lot(3);
const auto base = ParkingLot::Clock::now();

lot.enter("A12345", base);
lot.enter("B67890", base);
lot.printStatus();

double fee = 0.0;
if (lot.leave("A12345", base + std::chrono::hours(2), 5.0, fee)) {
std::cout << "费用: " << fee << '\n';
}

std::cout << "空位: " << lot.available() << '\n';
lot.printStatus();
return 0;
}

按标准语义,三车位停两辆后剩一个空位;A12345停放两小时后按每小时 5 元计费 10 元;出场后空位变为两个。这里描述的是业务规则,不是某次运行的输出。


几个实现细节:



  • Slot::entry{}使用值初始化,time_point的默认值是纪元时间,不会被读取,除非occupied为真。

  • leave中先算费用、再改状态;如果计费过程抛异常(本例不会),状态保持不变,避免「车已出场但费用未算」。

  • index_.erase(found)使用的是迭代器,found在slot引用被使用后仍然有效,因为unordered_map的 erase 只影响被删元素;slot引用指向slots_,与 map 无关。

  • 车牌区分大小写。若业务要求忽略大小写,应在入口统一转换(例如转大写),而不是在比较时临时处理,否则index_的键会不一致。


四、扩展方向


真实停车场还需要:预约车位、月卡/免费时长、分时段费率、出口多通道、逃费处理、数据持久化。每增加一条规则,都要重新检查状态不变量:例如「预约」会引入第三种车位状态,occupied布尔量就不够用了,应改成枚举;「分时段费率」要把计费逻辑从leave中抽成独立策略,避免函数膨胀。


如果需要在多线程中使用,enter/leave会同时修改slots_和index_,必须用互斥量保护整个操作,而不是只保护其中一个容器;否则两个线程可能同时选中同一个空位。锁的粒度、是否会阻塞、是否需要读写锁,都要按实际并发量设计。


常见坑点



  1. ❌ 同一车牌重复入场。


// 适用于 C++17
lot.enter("A12345", t1);
lot.enter("A12345", t2); // 占用两个车位,索引只指向一个

✅ 入场前检查是否已在场。


// 适用于 C++17
if (index_.find(plate) != index_.end()) { return false; }


  1. ❌ 车位满了仍继续分配。


// 适用于 C++17
for (auto& slot : slots_) { /* 找不到空位也修改状态 */ }

✅ 扫描失败时返回false,不改变任何状态。


// 适用于 C++17
return false; // 没有空位


  1. ❌ 出场时只清车位,不清索引。


// 适用于 C++17
slot.occupied = false;
// index_ 仍保留车牌,之后查询错误

✅ 两处同时更新。


// 适用于 C++17
slot.occupied = false;
index_.erase(found);


  1. ❌ 用浮点小时数直接相乘。


// 适用于 C++17
double hours = minutes / 60.0;
fee = hours * rate; // 边界和精度不可控

✅ 整数分钟换算整小时,再乘费率。


// 适用于 C++17
long long hours = (minutes + 59) / 60;


  1. ❌ 不处理负的停放时长。


// 适用于 C++17
auto minutes = duration_cast<minutes>(at - entry).count(); // 可能为负

✅ 钳到 0。


// 适用于 C++17
if (minutes < 0) { minutes = 0; }


  1. ❌ 车牌大小写不一致导致索引分裂。


// 适用于 C++17
lot.enter("abc", t);
lot.isParked("ABC"); // false,但业务上可能是同一辆车

✅ 入口统一规范化。


// 适用于 C++17
std::string key = toUpper(plate);


  1. ❌ 用std::map的迭代器长期保存车位下标。


// 适用于 C++17
auto it = index_.begin();
// 之后发生 rehash,it 失效

✅ 只在操作内使用迭代器,或保存值而不是迭代器。


// 适用于 C++17
const std::size_t slotIndex = found->second;


  1. ❌ 多线程下只锁其中一个容器。


// 适用于 C++17
std::lock_guard<std::mutex> lock(slotsMutex);
// index_ 未受保护,另一个线程同时修改

✅ 用同一把锁保护整个入场/出场操作。


// 适用于 C++17
std::lock_guard<std::mutex> lock(lotMutex);

总结




模块实现要点



车位vector<Slot>,记录占用、车牌、入场时间

索引unordered_map车牌到下标,平均 O(1)

入场查重、找空位、同步更新两处

出场查索引、算时长、更新两处

计费分钟向上取整为小时,最低一小时

一致性车位与索引同增同减

并发整段操作同一把锁



停车场系统的难点不在业务规则,而在状态一致性:车位和索引是两个视图,必须一起更新。把不变量写清楚、把入场和出场各自封成一个原子操作,满位、重复入场、计费边界这些情况就都有明确的处理位置。




需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询