
前言酒店管理系统的核心是房间状态与客人信息的对应关系哪些房间空着、哪些被预订、哪些已入住、每位客人住几晚、退房时该收多少钱。把这几件事建模清楚控制台版本用标准库就能实现建模不清楚堆再多界面也只是把混乱藏起来。常见误解有三个。第一把「预订」和「入住」当成同一件事只用一个布尔变量表示房间是否可用结果无法区分「已预订但未到店」与「已入住」也无法处理取消预订。第二用double保存金额多次计算后出现分位误差。第三用房间号直接当下标访问数组遇到不连续编号如 101、205、308就浪费大量空间甚至越界。本文实现一个控制台版酒店系统房间有单人间、双人间、套房三种类型状态在「空闲、已预订、已入住」之间转换支持预订、取消、入住、退房与查询。金额以「分」为单位用整数保存避免浮点误差。示例以 C17 为基准。一、房间状态机把房间的状态显式建模成枚举比用多个布尔标志可靠得多。三个状态之间的合法转换是当前状态操作目标状态说明空闲预订已预订记录客人姓名已预订取消空闲清空客人信息已预订入住已入住核对企业姓名记录住几晚空闲直接入住已入住散客现场登记已入住退房空闲结算房费清空信息已入住预订/取消不变非法操作拒绝状态机的价值在于把「哪些操作在什么状态下合法」集中到一处。如果每个操作都散落着if (occupied)、if (reserved)的判断新增一个「维修中」状态时就要改遍所有函数有了枚举和统一的转换函数只需扩展枚举与转换表。房间实体保存编号、类型、每晚价格以分为单位、当前状态、客人姓名、预订或入住的晚数。价格用long long保存「分」展示时再除以 100这样加减乘都是整数运算不会出现0.1 0.2之类的问题。二、核心操作与容器选择房间用std::mapint, Room保存键是房间号。选择map而不是vector的原因有三点房间号通常不连续用下标会浪费空间需要按编号快速查找遍历时按房间号有序输出符合前台习惯。数据量大且不需要有序时std::unordered_map也可以查找平均更快但输出顺序不稳定。查找一律用find不要用operator[]。map::operator[]在键不存在时会插入一个值初始化的元素对于「查询房间是否存在」这种场景会悄悄创建一间编号正确但类型、价格全为默认值的“幽灵房间”后续遍历就会多出一间不存在的房。// 适用于 C17#include algorithm#include iomanip#include iostream#include map#include optional#include stringenum class RoomType { Single, Double, Suite };enum class RoomState { Free, Reserved, Occupied };struct Room {int number;RoomType type;long long price_cents; // 每晚价格单位分RoomState state RoomState::Free;std::string guest;int nights 0;};class Hotel {public:bool addRoom(int number, RoomType type, long long price_cents) {if (number 0 || price_cents 0) return false;auto result rooms_.emplace(number, Room{number, type, price_cents});return result.second; // 已存在同号房间时返回 false}bool reserve(int number, const std::string guest) {auto it rooms_.find(number);if (it rooms_.end()) return false;if (it-second.state ! RoomState::Free) return false;if (guest.empty()) return false;it-second.state RoomState::Reserved;it-second.guest guest;return true;}bool cancel(int number) {auto it rooms_.find(number);if (it rooms_.end()) return false;if (it-second.state ! RoomState::Reserved) return false;it-second.state RoomState::Free;it-second.guest.clear();it-second.nights 0;return true;}bool checkIn(int number, const std::string guest, int nights) {auto it rooms_.find(number);if (it rooms_.end() || nights 0) return false;Room room it-second;if (room.state RoomState::Occupied) return false;if (room.state RoomState::Reserved room.guest ! guest) {return false; // 预订人与入住人不一致}if (guest.empty()) return false;room.state RoomState::Occupied;room.guest guest;room.nights nights;return true;}std::optionallong long checkOut(int number, int actual_nights) {auto it rooms_.find(number);if (it rooms_.end()) return std::nullopt;Room room it-second;if (room.state ! RoomState::Occupied) return std::nullopt;const int billed std::max(actual_nights, 1);const long long amount room.price_cents * billed;room.state RoomState::Free;room.guest.clear();room.nights 0;return amount;}void printAvailable() const {for (const auto entry : rooms_) {const Room room entry.second;if (room.state RoomState::Free) {std::cout room.number typeName(room.type) room.price_cents / 100 . std::setw(2) std::setfill(0) room.price_cents % 100 std::setfill( ) CNY\n;}}}private:static const char* typeName(RoomType type) {switch (type) {case RoomType::Single: return Single;case RoomType::Double: return Double;case RoomType::Suite: return Suite;}return Unknown;}std::mapint, Room rooms_;};几个细节值得说明emplace返回std::pairiterator, boolsecond表示是否插入成功用它判重比先find再插入少一次查找。checkOut的计费按「实际入住晚数」计算最少收一晚。提前退房按实际晚数收费是一种常见策略也可以改成按预订晚数收费取决于酒店政策无论选哪种都要在接口语义里写清楚。actual_nights为 0 或负数时按 1 晚处理避免出现零元或负数的账单。这是防御性校验不是业务规则的全部。退房后清空guest与nights否则下一位客人查询时会看到上一位的信息。三、日期、预订与扩展方向上面的模型只记录「住几晚」不记录具体日期。真实系统必须记录入住日期与离店日期否则无法判断预订是否过期、无法计算跨月账单、也无法处理「今天满房但明天有空」的查询。日期的表示有两种常见选择方案优点缺点YYYY-MM-DD字符串可直接显示、可直接比较需要严格定长格式自纪元起的天数整数日期加减方便、可做区间运算需要转换函数与展示逻辑std::chrono::year_month_dayC20类型安全、支持日历运算需要 C20 及以上的标准库支持如果只做「住几晚」的计算用整数天数最方便离店日期减入住日期就是晚数不需要处理月份天数。如果要做预订日历则要按日期区间判断房间是否可用此时可以把每个房间的预订记录存成「起始日 晚数」的列表查询时做区间重叠判断。预订还应记录「保留到什么时候」。没有日期的预订永远占着房间客人不到店也不会自动释放。简单做法是给预订加一个hold_until字段后台定期清理过期预订复杂一点则引入「超时未到自动取消」的定时任务。控制台版本可以在启动时检查一次或者提供手动清理命令。账单方面除了房费还可能有押金、加床、餐饮、损坏赔偿。不要在Room里不断加字段而应当把「消费项」抽成独立的记录退房时汇总。这样房价调整、折扣、税率都能独立变化。金额始终用最小货币单位的整数保存展示时再格式化涉及多币种时还要保存币种代码不能只存一个数字。持久化可以用文本或 CSV每行一间房字段依次是编号、类型、价格、状态、客人、晚数。读取时逐行解析遇到格式错误的行跳过并记录日志不要让一行坏数据导致整个文件加载失败。写文件时先写临时文件再重命名避免程序中途崩溃留下半个文件。常见坑点坑点一用double保存金额。❌double price 199.9; double total price * 3;结果可能是599.699999...对账时出现分位误差。✅ 用long long保存「分」所有计算用整数展示时再除以 100。坑点二用map::operator[]查询房间。❌if (rooms_[number].state RoomState::Free)键不存在时插入一间默认房间。✅ 用find并判断it ! rooms_.end()。坑点三不校验状态就办理入住。❌ 对已入住房间再次checkIn覆盖原客人的姓名与晚数。✅ 检查state Occupied时拒绝预订状态还要核对客人姓名。坑点四退房后不清空客人信息。❌ 只把state改成Freeguest仍是上一位客人。✅ 退房时清空guest、nights恢复为初始状态。坑点五房间号直接用数组下标。❌std::vectorRoom rooms(1000);用 101、205、308 做下标大量空位且容易越界。✅ 用std::mapint, Room或std::unordered_map以编号为键。坑点六预订没有日期永远不会过期。❌ 客人预订后不到店房间一直显示「已预订」。✅ 记录入住日期与保留时限提供取消或超时释放逻辑。坑点七用std::cin guest读姓名。❌ 姓名含空格时只读到第一个词剩余内容污染后续输入。✅ 用std::getline并注意清理前一次输入留下的换行符。坑点八账单只算房费忽略附加费用。❌ 退房金额固定为price * nights加床、餐饮全部漏算。✅ 把消费项独立建模退房时汇总金额仍用整数分。总结模块设计要点房间状态枚举三态转换集中校验容器map以编号为键查找用find预订记录客人、日期、保留时限入住校验状态与预订人退房结算、清空信息、恢复空闲金额整数分避免浮点误差持久化逐行解析坏行跳过临时文件替换酒店系统的难度不在代码量而在状态与边界预订和入住不是一回事、退房必须清理干净、金额不能用浮点、房间号不能当下标。把状态机画出来把每个操作的合法前提写进函数入口剩下的查询、统计与持久化都只是在这套骨架上添砖加瓦。