从零到一:C++飞机订票系统项目复盘,聊聊我踩过的那些坑(文件操作、全局变量设计)

张开发
2026/6/24 20:58:37 15 分钟阅读
从零到一:C++飞机订票系统项目复盘,聊聊我踩过的那些坑(文件操作、全局变量设计)
从零到一C飞机订票系统项目复盘聊聊我踩过的那些坑文件操作、全局变量设计最近完成了一个C飞机订票系统的项目整个过程可以说是痛并快乐着。作为一个C学习者这个项目让我深刻体会到了理论知识和实际开发之间的差距。今天就来分享几个让我熬夜调试的典型问题希望能帮到正在做类似项目的你。1. 文件操作的那些坑文件操作看似简单但实际开发中处处是陷阱。我在这个项目中至少踩了三个大坑。1.1 文件打开模式的迷思刚开始我天真地以为ios::out就能搞定所有写入需求结果数据被反复覆盖。后来才发现不同场景需要不同的打开模式组合// 错误示范 - 每次都会清空文件 fstream fs(data.txt, ios::out); // 正确做法 - 追加模式 fstream fs(data.txt, ios::app | ios::out); // 读取时 fstream fs(data.txt, ios::in);实际开发中发现最安全的做法是写入新数据ios::app | ios::out覆盖写入ios::trunc | ios::out读取数据ios::in1.2 重复读取导致的数据混乱我的航班管理类中有多个函数都需要读取文件数据最初的设计是这样的void ControlFlight::AddFlights() { ReadfromFile_Flights(); // 读取文件 // ...添加新航班 WritetoFile_Flights(); // 写入文件 } void ControlFlight::DeleteFlights() { ReadfromFile_Flights(); // 再次读取 // ...删除航班 WritetoFile_Flights(); }问题来了每次操作都重新读取文件如果连续调用多个函数容器中的数据会不断累积。解决方案是在读取函数开头清空容器void ControlFlight::ReadfromFile_Flights() { mv_Flights.clear(); // 关键一步 // ...读取数据 }1.3 文件流状态管理另一个坑是没检查文件流状态。有用户反馈程序崩溃最后发现是因为文件不存在。现在我会这样处理fstream fs(data.txt, ios::in); if (!fs) { cerr 文件打开失败创建新文件... endl; fs.open(data.txt, ios::out); fs.close(); return; }2. 全局变量的设计陷阱订单号管理是我遇到的另一个头疼问题。2.1 全局变量的重置问题我最初的设计是用全局变量C_OrderNumber来生成订单号int C_OrderNumber 0; // 全局变量 void ClientActions::BookTicket() { ReadfromFile_Orders(); C_OrderNumber; // 自增 // ...其他操作 }但很快发现每次调用ReadfromFile_Orders()都会重新计算订单号导致订单号重复。解决方案是在每个函数结束时重置全局变量void ClientActions::BookTicket() { ReadfromFile_Orders(); C_OrderNumber; // ...订票逻辑 WritetoFile_Orders(); C_OrderNumber 0; // 重置 }2.2 更优雅的解决方案后来我意识到全局变量终究不是最佳实践。改进方案是使用类的静态成员class ClientActions { private: static int s_OrderCounter; // ... public: static int GetNextOrderNumber() { return s_OrderCounter; } };初始化时从文件读取最大值这样就避免了全局变量的管理问题。3. vector操作的注意事项STL容器用起来方便但也有不少坑。3.1 erase的迭代器陷阱删除航班时我最初这样写for (size_t i 0; i mv_Flights.size(); i) { if (shouldDelete(mv_Flights[i])) { mv_Flights.erase(mv_Flights.begin() i); } }这会导致迭代器失效。正确做法是for (auto it mv_Flights.begin(); it ! mv_Flights.end(); ) { if (shouldDelete(*it)) { it mv_Flights.erase(it); } else { it; } }3.2 性能优化当数据量增大时频繁的vector操作会成为性能瓶颈。我做了以下优化减少不必要的拷贝使用移动语义预留空间mv_Flights.reserve(100)考虑改用list当频繁插入删除时4. 项目架构的演进随着功能增加最初的架构开始显得力不从心。4.1 从面向过程到面向对象第一版代码把所有逻辑都写在main.cpp里很快就变得难以维护。重构后的架构├── ControlFlight.h/cpp // 航班管理 ├── ManagerActions.h/cpp // 管理员操作 ├── ClientActions.h/cpp // 客户操作 └── Main.cpp // 入口4.2 数据持久化的改进最初使用纯文本存储后来发现几个问题没有数据校验没有备份机制并发访问会出问题改进方案添加数据校验逻辑定期生成备份文件考虑使用SQLite等轻型数据库4.3 异常处理的重构最初的错误处理全是cout输出用户体验很差。改进后try { // 业务逻辑 } catch (const FileException e) { // 专门处理文件错误 ShowErrorDialog(e.what()); } catch (const std::exception e) { // 通用错误处理 LogError(e.what()); }5. 测试与调试经验调试过程让我收获了不少实战经验。5.1 单元测试的重要性为关键功能编写测试用例void TestFlightManagement() { ControlFlight cf; // 测试添加 cf.AddTestFlight(TEST123); assert(cf.FindFlight(TEST123)); // 测试删除 cf.DeleteTestFlight(TEST123); assert(!cf.FindFlight(TEST123)); }5.2 日志系统的价值添加简单的日志功能帮助巨大class Logger { public: static void Log(const string msg) { ofstream log(system.log, ios::app); log GetCurrentTime() - msg endl; } };5.3 用户输入验证最初的版本几乎没有输入验证导致各种异常。后来添加了bool ValidateTimeFormat(const string time) { // 检查时间格式HH:MM regex pattern(R(\d{2}:\d{2})); return regex_match(time, pattern); }6. 性能优化实战当航班数据达到上千条时性能问题开始显现。6.1 查询优化最初的航班查询是线性搜索for (const auto flight : mv_Flights) { if (flight.m_Flight_Number target) { return flight; } }优化方案使用std::unordered_map建立索引对常用查询字段建立多级索引6.2 内存管理发现内存占用过高后我做了以下改进使用std::string_view替代字符串拷贝对大型数据使用智能指针实现延迟加载机制6.3 多线程尝试虽然最终没有采用但我实验性地添加了多线程支持std::futurevoid result std::async(std::launch::async, [](){ // 后台加载数据 LoadFlightData(); }); // 主线程继续其他工作7. 项目总结与反思回顾整个项目有几个关键收获设计先于编码前期设计不充分导致多次重构测试驱动开发越早开始写测试后期越轻松代码可维护性良好的命名和注释节省了大量调试时间性能考量数据量小时忽略的问题在规模增长后都会暴露如果重做这个项目我会采用更现代的C特性如C17的std::filesystem实现真正的GUI界面而非控制台加入网络功能实现多终端访问这个项目虽然基础但涵盖了C开发的多个核心概念。对于想深入学习C的朋友我的建议是先完成一个这样的综合项目再回头系统学习STL、内存管理等高级主题效果会比单纯看书好得多。

更多文章