用C++和STL手搓一个飞机订票系统(附完整源码和文件读写避坑指南)

张开发
2026/4/9 14:18:06 15 分钟阅读

分享文章

用C++和STL手搓一个飞机订票系统(附完整源码和文件读写避坑指南)
从零构建C飞机订票系统STL实战与文件操作避坑指南当C初学者掌握了基础语法后最迫切的需求就是找到一个能串联知识点的综合项目。飞机订票系统恰好是这样一个完美的练手项目——它涵盖了类设计、STL容器操作、文件I/O等核心知识点又不会过于复杂让人望而生畏。本文将带你从零开始用C和STL构建一个功能完整的飞机订票系统特别聚焦那些教科书上不会讲的坑点。1. 为什么选择飞机订票系统作为练手项目对初学者而言选择一个合适的练手项目需要考虑三个维度知识覆盖度、复杂度控制和学习收获。飞机订票系统在这三方面都表现出色知识覆盖全面涉及类与对象、继承与多态、STL容器、文件操作等核心概念复杂度适中业务逻辑清晰但不过于简单有足够的挑战性实用价值高完成后能得到一个真正可运行的系统成就感满满我曾指导过数十名C初学者完成这个项目发现它特别适合作为学完基础语法后的第一个综合练习。与常见的学生成绩管理系统相比飞机订票系统的业务场景更贴近现实能激发学习兴趣而与复杂的游戏开发相比它又不会让初学者陷入图形渲染等专业领域。// 示例基础类结构设计 class Flight { public: std::string flightNumber; std::string departure; std::string destination; // 其他成员变量... }; class Manager { public: void addFlight(); void modifyFlight(); // 其他管理功能... }; class Customer { public: void bookTicket(); void cancelTicket(); // 其他客户功能... };2. 系统架构设计与STL容器选型一个健壮的订票系统需要合理的数据结构来管理航班和订单信息。STL提供的容器各有所长我们需要根据业务特点选择最合适的容器类型适用场景在本项目中的应用性能特点vector动态数组存储航班列表O(1)随机访问map键值对快速查找航班O(log n)查找list频繁插入删除订单管理O(1)插入删除在实际开发中我推荐使用vectorFlight来管理航班数据而不是原始数组。vector会自动处理内存管理提供size()、push_back()等便利方法大大减少出错概率。对于需要快速查找的场景可以额外维护一个mapstring, Flight建立航班号到航班对象的映射。常见陷阱直接使用数组会导致需要手动管理内存难以动态扩容缺乏边界检查// 正确做法使用vector管理航班列表 std::vectorFlight flights; // 添加新航班 void addFlight(const Flight newFlight) { flights.push_back(newFlight); // 自动扩容 } // 遍历航班 for (const auto flight : flights) { std::cout flight.flightNumber std::endl; }3. 核心类设计详解3.1 航班类(Flight)设计航班类是系统的核心数据载体需要精心设计成员变量。根据航空业实际需求一个航班通常包含以下信息class Flight { private: std::string flightNumber; // 航班号 CA1234 std::string planeModel; // 机型 B737-800 std::string departureAirport; std::string arrivalAirport; std::tm departureTime; // 使用tm结构体存储时间 std::tm arrivalTime; double basePrice; // 基准票价 double currentDiscount; // 当前折扣 int totalSeats; // 总座位数 int availableSeats; // 可用座位数 public: // 构造函数 Flight(const std::string num, const std::string model, /*其他参数*/) : flightNumber(num), planeModel(model) /*初始化其他成员*/ {} // 成员函数 bool isAvailable() const { return availableSeats 0; } double getActualPrice() const { return basePrice * currentDiscount; } void bookSeat() { if(availableSeats 0) availableSeats--; } void cancelSeat() { availableSeats; } };设计要点使用std::tm存储时间便于后续的时间计算和比较将座位数设计为两个变量(totalSeats和availableSeats)而不是每次计算提供业务相关的方法如isAvailable()而不是暴露内部状态3.2 管理员类(Admin)设计管理员类需要处理航班的CRUD操作这里展示了如何使用vector容器管理航班数据class AdminSystem { private: std::vectorFlight flights; std::string adminId; std::string password; public: bool login(const std::string id, const std::string pwd) { return id adminId pwd password; } void addFlight(const Flight newFlight) { // 检查航班号是否已存在 if(std::any_of(flights.begin(), flights.end(), [](const Flight f) { return f.getFlightNumber() newFlight.getFlightNumber(); })) { throw std::runtime_error(航班号已存在); } flights.push_back(newFlight); } void removeFlight(const std::string flightNumber) { auto it std::remove_if(flights.begin(), flights.end(), [](const Flight f) { return f.getFlightNumber() flightNumber; }); if(it flights.end()) { throw std::runtime_error(未找到指定航班); } flights.erase(it, flights.end()); } // 其他管理功能... };3.3 客户类(Customer)设计客户类需要处理订票、退票等业务这里展示了订单管理的实现class CustomerSystem { private: struct Order { int orderId; std::string customerName; std::string flightNumber; std::string status; // CONFIRMED, CANCELLED }; std::vectorOrder orders; int nextOrderId 1; public: int bookTicket(const std::string name, const std::string flightNumber) { Order newOrder; newOrder.orderId nextOrderId; newOrder.customerName name; newOrder.flightNumber flightNumber; newOrder.status CONFIRMED; orders.push_back(newOrder); return newOrder.orderId; } bool cancelTicket(int orderId) { auto it std::find_if(orders.begin(), orders.end(), [](const Order o) { return o.orderId orderId; }); if(it ! orders.end() it-status CONFIRMED) { it-status CANCELLED; return true; } return false; } // 其他客户功能... };4. 文件读写中的那些坑4.1 数据重复读取问题在原始代码中我看到一个常见问题每次操作都重新读取整个文件。这种做法不仅低效而且在并发环境下会导致数据不一致。解决方案是使用内存缓存配合定期持久化class FlightDatabase { private: std::vectorFlight flightsCache; std::string dataFile flights.dat; void loadFromFile() { flightsCache.clear(); std::ifstream in(dataFile, std::ios::binary); if(!in) throw std::runtime_error(无法打开数据文件); Flight temp; while(in.read(reinterpret_castchar*(temp), sizeof(Flight))) { flightsCache.push_back(temp); } } void saveToFile() { std::ofstream out(dataFile, std::ios::binary | std::ios::trunc); if(!out) throw std::runtime_error(无法写入数据文件); for(const auto flight : flightsCache) { out.write(reinterpret_castconst char*(flight), sizeof(Flight)); } } public: FlightDatabase() { try { loadFromFile(); } catch(...) { // 初始化空数据库 } } ~FlightDatabase() { saveToFile(); // 析构时自动保存 } // 其他操作方法... };优化点使用二进制格式提高IO效率只在构造时加载一次数据析构时自动保存可添加定期保存机制4.2 全局变量的正确用法原始代码中使用全局变量C_OrderNumber来追踪订单号这在小型项目中可行但存在隐患。更健壮的做法是class OrderManager { private: static std::atomicint nextOrderId; // 使用原子变量保证线程安全 public: static int generateOrderId() { return nextOrderId; } static void initialize(int startId) { nextOrderId startId; } }; // 在程序启动时从文件加载最大的订单ID void loadOrderState() { int maxId 0; // 从文件读取现有订单找出最大ID... OrderManager::initialize(maxId 1); }改进点使用静态类成员替代全局变量考虑线程安全问题提供初始化接口支持持久化和恢复5. 完整系统实现与测试5.1 主程序框架int main() { FlightDatabase flightDB; OrderManager orderManager; // 初始化订单ID try { loadOrderState(); } catch(...) { orderManager.initialize(1); } // 主循环 while(true) { displayMainMenu(); int choice getInput(); switch(choice) { case 1: adminOperations(flightDB); break; case 2: customerOperations(flightDB, orderManager); break; case 0: return 0; default: showError(无效输入); } } }5.2 单元测试要点在开发过程中应当为关键功能编写测试用例void testFlightBooking() { FlightDatabase db; Flight testFlight(TEST123, A320, PEK, SHA, /*其他参数*/); db.addFlight(testFlight); CustomerSystem customer; int orderId customer.bookTicket(张三, TEST123); assert(orderId 0); assert(customer.getOrderStatus(orderId) CONFIRMED); assert(db.getFlight(TEST123).getAvailableSeats() testFlight.getTotalSeats() - 1); customer.cancelTicket(orderId); assert(customer.getOrderStatus(orderId) CANCELLED); assert(db.getFlight(TEST123).getAvailableSeats() testFlight.getTotalSeats()); }测试覆盖点航班添加是否正确订票是否成功生成订单座位数是否正确减少退票后状态和座位数是否正确恢复6. 进阶优化方向当基础功能完成后可以考虑以下优化引入数据库替换文件存储使用SQLite等轻量级数据库多线程支持使用mutex保护共享数据网络功能基于asio实现客户端/服务器架构GUI界面使用Qt或wxWidgets构建图形界面性能监控添加日志和性能统计// 简单的线程安全包装示例 templatetypename T class ThreadSafeContainer { private: std::mutex mtx; T data; public: templatetypename Func auto access(Func f) - decltype(f(data)) { std::lock_guardstd::mutex lock(mtx); return f(data); } }; // 使用示例 ThreadSafeContainerstd::vectorFlight safeFlights; // 线程安全地添加航班 safeFlights.access([](auto flights) { flights.push_back(newFlight); });这个飞机订票系统项目虽然基础但涵盖了C核心知识点。我在实际教学中发现学生完成这个项目后对面向对象编程和STL的理解会有质的飞跃。建议在实现基础功能后尝试自己添加新功能比如航班搜索、票价计算规则等这对提升编程能力大有裨益。

更多文章