
1. 项目概述与核心价值又到了期末课程设计扎堆的时候后台私信里问得最多的就是“C课程设计做什么好”。说实话一个能拿高分、又能真正学到东西的课程设计关键在于选题的“度”——不能太简单显得敷衍也不能太复杂导致无从下手。今天我就拿一个经典中的经典也是我当年带学生时最常推荐的选题——“飞机订票系统”来给大家彻底拆解一遍。这不仅仅是一个交差的项目更是你串联起C面向对象、数据结构、文件I/O乃至基础软件工程思想的绝佳练手场。这个“飞机订票系统”的核心目标很明确模拟一个简化的民航售票后台。用户乘客可以查询航班、预订机票、退票管理员可以管理航班信息、查看销售情况。听起来简单对吧但魔鬼藏在细节里。如何设计类结构来清晰表达“航班”、“乘客”、“订单”这些实体如何用文件持久化数据保证程序关闭后信息不丢失如何处理多个乘客同时预订同一航班最后一个座位的“并发”问题即使在单机环境下这些才是这个项目真正的价值所在也是你报告里能写出彩、让老师眼前一亮的地方。网上源码很多但大多只实现了功能缺乏设计思想和健壮性考量。接下来我将带你从零开始构建一个结构清晰、可扩展、且代码风格良好的C飞机订票系统并附上关键源码和避坑指南。2. 系统整体设计与核心思路拆解在动手写第一行代码之前我们必须把系统的蓝图规划好。一个混乱的设计会导致后期代码难以维护添加新功能如同拆东墙补西墙。2.1 需求分析与功能模块划分首先我们需要将模糊的“飞机订票系统”转化为具体、可执行的功能点。这通常分为前台用户功能和后台管理功能两大模块。前台用户功能航班查询支持按航班号、起飞地、目的地、日期等条件查询并显示余票。机票预订用户选择航班和座位类型如经济舱、商务舱输入乘客信息完成订票生成唯一订单号并减少对应航班余票。订单查询与退票用户凭订单号或身份证号查询自己的订单并支持退票操作退票后对应航班余票应增加。用户注册/登录可选但推荐为系统增加基础的用户体系使订单与用户绑定体验更完整。后台管理功能航班信息管理对航班进行增、删、改、查。航班信息至少应包括航班号、起飞城市、抵达城市、起飞时间、抵达时间、机型、总座位数、剩余座位数、票价等。订单管理与统计查看所有订单可按航班、日期等筛选。统计某航班的售票情况、总收入等。数据持久化所有航班、订单、用户数据必须在程序退出时保存到文件启动时从文件加载。注意课程设计的范围要合理。初次实现可以暂不考虑“在线支付”、“选座”、“中转联程”等复杂商业逻辑。把核心的“增删改查”和“业务规则”如余票控制做扎实就已经能拿到一个不错的分数了。2.2 核心类设计与面向对象思想应用这是整个项目的灵魂。好的类设计能让代码逻辑清晰职责分明。我们主要设计以下几个类Flight航班类系统的核心实体。属性航班号string、起飞/抵达城市string、起飞/抵达时间可以用std::tm或自定义时间类、总座位数int、剩余座位数int、票价double等。方法构造函数、获取信息的方法、一个关键的bool bookTicket(int num)方法用于尝试订票成功则减少余票并返回true。设计思考为什么要把“订票”这个动作封装成航班类的方法因为这符合“谁的数据谁负责操作”的面向对象原则。余票是航班的属性修改它的逻辑理应放在航班类内部便于维护和保证数据一致性。Passenger乘客类/User用户类基础版Passenger类包含姓名string、身份证号string、联系电话string。它作为订单的一部分存在。进阶版引入User类包含用户名、密码、以及一个Passenger对象作为其身份信息。这样可以将登录账户与乘机人关联。Order订单类连接用户和航班的纽带。属性订单号string可自动生成如ORD20240521001、关联的航班对象或航班号、乘客信息、订票数量、订单状态已预订、已取消、创建时间、总金额。方法生成订单详情的方法、取消订单的方法会调用关联航班的退票逻辑。设计思考订单类应该持有航班的引用指针还是仅仅存储航班号我推荐存储航班号。因为当从文件加载所有订单和航班后我们需要根据订单里的航班号去查找内存中对应的航班对象。如果存储指针序列化到文件会非常麻烦。这是一种“用ID关联”的常用设计。AirTicketSystem系统管理类这是整个系统的“大脑”或“服务层”。属性用一个vectorFlight或mapstring, Flight管理所有航班用一个vectorOrder管理所有订单用一个vectorUser管理所有用户如果实现当前登录用户User*。方法提供所有业务功能的接口例如queryFlights(...),bookTicket(...),cancelOrder(...),addFlight(...)等。它还负责协调各个类之间的交互并调用DataManager进行数据保存和加载。设计思考这个类遵循了“单一职责原则”它不负责具体的存储细节那是DataManager的事也不负责底层的数据结构那是Flight、Order的事它只负责业务流程的调度。DataManager数据管理类负责与文件打交道实现数据的持久化。方法loadFlightsFromFile(),saveFlightsToFile(),loadOrdersFromFile(),saveOrdersToFile()等。设计思考为什么要把文件操作单独抽成一个类为了“高内聚、低耦合”。所有脏活累活文件打开关闭、格式解析都封装在这里。如果未来想把数据存到数据库只需要修改这个类其他业务代码几乎不用动。这是非常重要的工程思想。2.3 数据结构选型与内存管理C课程设计必须展现你对数据结构的理解。航班集合使用std::mapstring, Flight键是航班号。查找特定航班的时间复杂度是O(log n)效率远高于用vector线性查找。订单集合使用std::vectorOrder或std::listOrder。因为订单需要频繁遍历如管理员查看所有订单vector的连续内存访问效率更高。如果订单量极大且需要频繁在中间插入删除可考虑list但课程设计规模下vector足矣。用户集合类似航班用std::mapstring, User键是用户名。关于指针与智能指针在系统类AirTicketSystem中管理这些容器时是直接存储对象还是存储指针对于课程设计我建议在容器中直接存储对象如mapstring, Flight。这样内存管理简单无需担心深拷贝和内存泄漏。只有在对象很大或需要多态时才考虑使用指针。如果使用指针强烈推荐使用std::shared_ptr或std::unique_ptr来避免手动new/delete这是现代C的好习惯。3. 核心模块实现与关键技术点理论说得再多不如一行代码。我们挑几个最核心、最容易出错的模块来详细实现。3.1 Flight类的实现与余票控制逻辑Flight类是业务的基石。头文件flight.h可能长这样// flight.h #ifndef FLIGHT_H #define FLIGHT_H #include string #include ctime class Flight { private: std::string flightNumber; std::string departureCity; std::string arrivalCity; std::tm departureTime; // 使用struct tm表示时间 std::tm arrivalTime; int totalSeats; int availableSeats; double price; public: // 构造函数 Flight(const std::string num, const std::string dep, const std::string arr, const std::tm depTime, const std::tm arrTime, int seats, double p); // 获取信息的方法 std::string getFlightNumber() const { return flightNumber; } int getAvailableSeats() const { return availableSeats; } void displayInfo() const; // 打印航班信息 // 核心业务方法 bool bookSeats(int num); // 预订座位返回是否成功 bool cancelSeats(int num); // 退订座位 // 用于文件读写的方法后续DataManager会用到 friend std::ostream operator(std::ostream os, const Flight f); friend std::istream operator(std::istream is, Flight f); }; #endif对应的flight.cpp中bookSeats和cancelSeats的实现是关键// flight.cpp #include flight.h #include iostream #include iomanip bool Flight::bookSeats(int num) { if (num 0) { std::cerr 订票数量必须为正数 std::endl; return false; } if (availableSeats num) { availableSeats - num; std::cout 成功预订 num 个座位。航班 flightNumber 剩余座位: availableSeats std::endl; return true; } else { std::cout 预订失败航班 flightNumber 余票不足。需求: num , 可用: availableSeats std::endl; return false; } } bool Flight::cancelSeats(int num) { if (num 0) { std::cerr 退票数量必须为正数 std::endl; return false; } if (availableSeats num totalSeats) { // 防止退票超过原始总数 availableSeats num; std::cout 成功退订 num 个座位。航班 flightNumber 剩余座位: availableSeats std::endl; return true; } else { std::cerr 退票数量异常 std::endl; return false; } }实操心得在bookSeats和cancelSeats中一定要先进行参数校验数量是否为正。这是防御性编程的基本功。另外cancelSeats中的检查availableSeats num totalSeats也很重要它防止了数据错误例如文件被手动篡改导致的可售座位数超过物理座位数的荒谬情况。3.2 订单生成与唯一ID设计订单号需要全局唯一。一个简单可靠的方案是“前缀时间戳序列号”。// order.h 部分代码 #include string #include chrono #include sstream #include iomanip class Order { private: std::string orderId; std::string flightNumber; Passenger passenger; int ticketCount; double totalAmount; std::time_t createTime; bool isCancelled; public: Order(const std::string fNum, const Passenger p, int count, double price) : flightNumber(fNum), passenger(p), ticketCount(count), totalAmount(price * count), isCancelled(false) { createTime std::chrono::system_clock::to_time_t( std::chrono::system_clock::now() ); // 生成订单ID: ORD 年月日时分秒 随机数简单示例 auto now std::chrono::system_clock::now(); auto in_time_t std::chrono::system_clock::to_time_t(now); std::stringstream ss; ss ORD std::put_time(std::localtime(in_time_t), %Y%m%d%H%M%S); // 在实际项目中这里应加上一个防止并发的序列号例如从静态变量递增 static int sequence 0; ss std::setfill(0) std::setw(3) sequence; orderId ss.str(); } // ... 其他方法 };注意事项上面的序列号sequence是静态变量在程序运行期间能保证唯一。但如果程序重启时间戳部分相同序列号又会从0开始可能导致重复。对于课程设计这足够了。更严谨的做法是将上次使用过的最大序列号也保存到文件中程序启动时加载。3.3 数据持久化文件读写实战这是很多C新手最容易崩溃的环节。我们选择文本文件如flights.txt,orders.txt来存储格式清晰易调试。使用C的fstream库。1. 定义文件格式对于航班我们可以每行存储一个航班字段用逗号或制表符分隔。CA1234,北京,上海,2024-05-21 08:00,2024-05-21 10:30,200,150,1200.5 MU5678,上海,广州,2024-05-21 14:00,2024-05-21 16:15,180,180,900.0对于订单格式可以更复杂一些可能需要多行或使用JSON/XML。但为了简单我们也可以用特定分隔符的一行来表示乘客信息作为子字段。ORD20240521080001,CA1234,张三|110101199001011234|13800138000,2,2401.0,2024-05-21 08:00:00,false2. 实现DataManager类// datamanager.h #ifndef DATAMANAGER_H #define DATAMANAGER_H #include string #include vector #include map #include flight.h #include order.h class DataManager { public: // 加载数据 static bool loadFlights(const std::string filename, std::mapstd::string, Flight flights); static bool loadOrders(const std::string filename, std::vectorOrder orders, const std::mapstd::string, Flight flights); // 需要航班信息来关联 // 保存数据 static bool saveFlights(const std::string filename, const std::mapstd::string, Flight flights); static bool saveOrders(const std::string filename, const std::vectorOrder orders); }; #endif3. 关键实现运算符重载与字符串解析在Flight和Order类中重载和运算符会让DataManager的代码非常简洁。// 在flight.cpp中实现 std::ostream operator(std::ostream os, const Flight f) { // 将tm结构体转换为可存储的字符串 char depBuffer[20], arrBuffer[20]; std::strftime(depBuffer, sizeof(depBuffer), %Y-%m-%d %H:%M, f.departureTime); std::strftime(arrBuffer, sizeof(arrBuffer), %Y-%m-%d %H:%M, f.arrivalTime); os f.flightNumber , f.departureCity , f.arrivalCity , depBuffer , arrBuffer , f.totalSeats , f.availableSeats , f.price; return os; } std::istream operator(std::istream is, Flight f) { std::string line; if (std::getline(is, line)) { std::stringstream ss(line); std::string token; std::vectorstd::string tokens; while (std::getline(ss, token, ,)) { tokens.push_back(token); } if (tokens.size() 8) { // 确保有8个字段 f.flightNumber tokens[0]; f.departureCity tokens[1]; f.arrivalCity tokens[2]; // 解析时间字符串到tm结构体 std::istringstream timeStream(tokens[3]); timeStream std::get_time(f.departureTime, %Y-%m-%d %H:%M); timeStream.clear(); timeStream.str(tokens[4]); timeStream std::get_time(f.arrivalTime, %Y-%m-%d %H:%M); f.totalSeats std::stoi(tokens[5]); f.availableSeats std::stoi(tokens[6]); f.price std::stod(tokens[7]); } } return is; }DataManager::loadFlights的实现就变得非常简单bool DataManager::loadFlights(const std::string filename, std::mapstd::string, Flight flights) { std::ifstream inFile(filename); if (!inFile.is_open()) { std::cerr 无法打开航班数据文件: filename 将创建新文件。 std::endl; return false; // 文件不存在不是错误可能是第一次运行 } Flight f; while (inFile f) { // 这里巧妙地使用了重载的 运算符 flights[f.getFlightNumber()] f; } inFile.close(); return true; }踩坑实录文件读写最大的坑是状态处理和格式一致性。文件打开失败一定要检查is_open()并给出友好提示。首次运行时文件不存在是正常的不应视为错误而终止程序。格式解析错误如果文件内容被手动修改格式错误如逗号数量不对时间格式不对会导致整个读取失败。在实际项目中需要更健壮的解析和错误恢复机制。课程设计中至少保证自己程序写入和读取的格式一致。中文乱码如果文件路径或内容包含中文在Windows下可能会乱码。可以尝试使用std::locale设置全局locale或者使用宽字符std::wifstream/std::wofstream。一个简单的规避方法是全程使用英文。4. 用户界面与主程序循环设计虽然课程设计对UI要求不高但一个清晰、友好的控制台菜单能极大提升体验。我们使用一个简单的while循环和switch语句来实现。4.1 分层菜单系统主程序结构如下// main.cpp #include AirTicketSystem.h #include iostream #include limits void clearInputBuffer() { std::cin.clear(); std::cin.ignore(std::numeric_limitsstd::streamsize::max(), \n); } int main() { AirTicketSystem system; // 启动时加载数据 if (!system.loadAllData()) { std::cout 数据加载失败或为首次运行将使用空系统。 std::endl; } int roleChoice; bool running true; while (running) { std::cout \n 飞机订票系统 \n; std::cout 1. 用户登录\n; std::cout 2. 管理员登录\n; std::cout 3. 用户注册\n; std::cout 0. 退出系统\n; std::cout 请选择身份: ; if (!(std::cin roleChoice)) { clearInputBuffer(); std::cout 输入无效请输入数字 std::endl; continue; } clearInputBuffer(); switch (roleChoice) { case 1: userLoginMenu(system); break; case 2: adminLoginMenu(system); break; case 3: userRegisterMenu(system); break; case 0: running false; // 退出前保存数据 system.saveAllData(); std::cout 数据已保存感谢使用 std::endl; break; default: std::cout 无效选择请重新输入。 std::endl; } } return 0; }用户登录后的菜单userLoginMenu和adminLoginMenu函数内部也是类似的循环结构分别调用AirTicketSystem类提供的各种业务方法。4.2 输入处理与鲁棒性控制台程序最烦人的就是错误的输入导致程序崩溃或进入死循环。必须做好输入验证。int getValidatedInt(const std::string prompt, int min, int max) { int value; while (true) { std::cout prompt; if (std::cin value) { if (value min value max) { clearInputBuffer(); return value; } else { std::cout 输入超出范围 [ min - max ]请重新输入。 std::endl; } } else { std::cout 输入无效请输入一个整数。 std::endl; clearInputBuffer(); } } } std::string getValidatedString(const std::string prompt, bool allowEmpty false) { std::string input; while (true) { std::cout prompt; std::getline(std::cin, input); if (!allowEmpty input.empty()) { std::cout 输入不能为空请重新输入。 std::endl; } else { return input; } } }在需要用户输入数字选择菜单或订票数量时使用getValidatedInt在输入姓名、城市时使用getValidatedString。这能有效避免因用户误输入导致的程序异常。5. 进阶优化与功能扩展思路完成基础功能后如果你想拿更高的分数或者单纯想挑战自己可以考虑以下扩展方向。每一个都能让你的项目脱颖而出。5.1 引入简单的“并发”控制即使在单机单线程程序里也可能出现“超售”问题。想象一下这个时序用户A查询航班CA1234看到余票为1。几乎同时在极短的时间差内用户B也查询同一航班看到余票为1。用户A下单预订1张票系统检查余票(11)通过余票减为0。用户B也下单预订1张票系统检查余票(此时内存中已是0但B的查询结果是1)检查不通过不因为B的预订请求是基于他查询时的“旧”数据但系统在处理时会再次从内存读取余票此时为0所以bookSeats会失败。看起来没问题问题在于如果bookSeats函数不是“原子”的比如它先检查再扣减中间如果被其他操作打断虽然在单线程控制台程序里很难但理解这个概念很重要就会出问题。更常见的问题是在没有事务的文件保存中A订票成功余票在内存中变为0但还没保存到文件程序崩溃了。重启后从文件加载的余票还是1导致超售。解决方案课程设计级别原子操作确保bookSeats中的检查和扣减是连续、不可分割的。在单线程中这自然成立。事务性保存在AirTicketSystem的bookTicket方法中顺序应该是调用flight.bookSeats(num)内存修改成功。生成新订单加入订单列表。立即调用DataManager::saveFlights(...)和DataManager::saveOrders(...)保存到文件。如果任何一步失败应该尝试回滚比如把扣减的座位加回去删除刚生成的订单。这模拟了数据库事务的思想。使用锁文件简易版在写入关键数据文件前先创建一个锁文件如data.lock写入完成后再删除。其他进程如果存在或本程序的其他保存操作在检测到锁文件存在时应等待或重试。这可以防止程序异常退出导致文件处于半写入状态。5.2 使用数据库替代文件存储这是从“玩具项目”迈向“正经项目”的关键一步。你可以选择轻量级的SQLite它无需安装数据库服务器一个sqlite3.dll或libsqlite3.so库文件加上C接口就行。优势数据一致性SQLite支持ACID事务完美解决上述超售和文件损坏问题。复杂查询可以用SQL语句轻松实现“查询从北京到上海今天下午之后的航班”而不用自己写循环过滤。数据关系天然支持航班、订单、用户表之间的关联。你需要做引入SQLite的C/C接口如sqlite3.h。设计数据库表结构flights,orders,users。重写DataManager类将所有文件操作替换为SQL语句sqlite3_exec,sqlite3_prepare_v2等。在bookTicket中使用事务BEGIN TRANSACTION;- 更新航班余票 - 插入订单 -COMMIT;。这会让你的课程设计报告有十足的份量。5.3 设计模式的应用单例与工厂模式单例模式SingletonAirTicketSystem类在整个程序中只应有一个实例。你可以将其设计为单例确保全局状态统一。class AirTicketSystem { private: AirTicketSystem() {} // 私有构造函数 static AirTicketSystem* instance; public: static AirTicketSystem* getInstance() { if (instance nullptr) { instance new AirTicketSystem(); } return instance; } // ... 其他成员 // 删除拷贝构造和赋值 AirTicketSystem(const AirTicketSystem) delete; AirTicketSystem operator(const AirTicketSystem) delete; }; // 在.cpp文件中初始化 AirTicketSystem* AirTicketSystem::instance nullptr;在main函数中通过AirTicketSystem* sys AirTicketSystem::getInstance();获取实例。工厂模式Factory如果需要支持多种类型的用户如普通用户、VIP用户、管理员可以定义一个User基类然后通过一个UserFactory类根据类型字符串创建不同的用户对象。这体现了“开闭原则”新增用户类型时无需修改大量代码。引入设计模式并能在报告中阐述清楚其用意是加分项。6. 常见问题排查与调试技巧开发过程中你肯定会遇到各种“妖魔鬼怪”。这里记录一些典型问题和解决方法。6.1 编译与链接问题问题现象可能原因解决方案undefined reference to ...1. 函数只有声明没有定义实现。2. 定义了函数但实现文件.cpp没有添加到编译命令或IDE的项目中。1. 检查对应的.cpp文件是否实现了该函数。2. 确保你的编译命令包含了所有.cpp文件例如g main.cpp flight.cpp order.cpp AirTicketSystem.cpp DataManager.cpp -o ticket_system。在VS或Code::Blocks中检查文件是否在项目内。multiple definition of ...同一个函数或全局变量在多个.cpp文件中被定义了。通常因为把函数实现写在了头文件里且该头文件被多个源文件包含。将函数定义实现移到.cpp文件中。头文件只放声明。对于全局变量在头文件中用extern声明在一个.cpp文件中定义。‘class’ does not name a type头文件相互包含或包含顺序问题导致编译器在解析时还没看到某个类的定义。使用前向声明class Flight;并在可能的情况下在.cpp文件中包含所需的头文件而不是在头文件中包含。确保头文件有#ifndef或#pragma once防止重复包含。6.2 运行时逻辑错误问题现象排查思路程序运行后数据没有保存。1. 检查saveAllData函数是否在程序退出前被调用如main函数return 0之前或处理退出信号的函数中。2. 检查文件路径是否正确程序是否有写入权限。3. 在saveFlights和saveOrders函数中在打开文件后和关闭文件前添加调试输出确认是否执行到了写入步骤。退票后余票数超过了总座位数。1. 检查Flight::cancelSeats方法中的逻辑判断if (availableSeats num totalSeats)。2. 检查在从文件加载数据时totalSeats和availableSeats的值是否正确是否文件内容有误。3. 检查是否有其他代码直接修改了availableSeats成员变量绕过了cancelSeats方法。查询航班时条件过滤不起作用。1. 在查询函数中打印出用于过滤的变量值确认它们是否正确获取了用户输入。2. 单步调试进入循环判断条件的那一行观察比较的结果。订单号重复。检查订单号生成逻辑。如果使用了“时间戳序列号”确保序列号如静态变量在程序运行期间能正确递增并且时间戳的精度足够比如到秒在1秒内可能有多个订单。可以考虑使用更精确的时间如毫秒或结合随机数。6.3 调试技巧“穷人的调试法”——打印日志在关键函数入口、出口和决策点使用std::cout输出变量的值。这是最直接有效的方法。bool Flight::bookSeats(int num) { std::cout [DEBUG] bookSeats called for flight flightNumber with num num , available availableSeats std::endl; // ... 原有逻辑 std::cout [DEBUG] bookSeats result: (success ? success : failed) std::endl; return success; }使用调试器如果你用Visual Studio、CLion、VSCode配置了GDB/LLDB一定要学会设断点、单步执行、查看变量值。这比打印日志高效得多。单元测试思维为Flight::bookSeats这样的核心函数编写简单的测试代码在main函数开头调用验证其行为是否符合预期。void testFlight() { std::tm dep {}, arr {}; // 初始化时间... Flight f(TEST001, A, B, dep, arr, 100, 500.0); assert(f.getAvailableSeats() 100); assert(f.bookSeats(30) true); assert(f.getAvailableSeats() 70); assert(f.bookSeats(80) false); // 应该失败 assert(f.getAvailableSeats() 70); // 余票不应变 assert(f.cancelSeats(20) true); assert(f.getAvailableSeats() 90); std::cout 所有Flight测试通过 std::endl; }7. 项目总结与源码结构建议走到这里一个完整的、具有工业级代码雏形的飞机订票系统就搭建起来了。我们来回顾一下整个项目的核心收获面向对象设计你学会了如何将现实世界的“航班”、“订单”抽象成类并思考它们之间的关系聚合、组合。数据持久化掌握了使用文件进行数据存储和加载理解了序列化/反序列化的概念并体会到了数据一致性的重要性。程序结构理解了如何用“系统管理类”来组织业务逻辑用“数据管理类”来分离关注点这是软件工程中“分层架构”的初体验。鲁棒性编程学会了输入验证、错误处理、资源管理文件打开关闭让程序更健壮。调试与排错积累了宝贵的实战调试经验。给你的最终源码目录结构建议AirTicketSystem/ ├── include/ # 头文件 │ ├── Flight.h │ ├── Passenger.h │ ├── Order.h │ ├── User.h │ ├── AirTicketSystem.h │ └── DataManager.h ├── src/ # 源文件 │ ├── Flight.cpp │ ├── Passenger.cpp │ ├── Order.cpp │ ├── User.cpp │ ├── AirTicketSystem.cpp │ ├── DataManager.cpp │ └── main.cpp ├── data/ # 数据文件目录 │ ├── flights.txt │ ├── orders.txt │ └── users.txt ├── README.md # 项目说明文档 └── Makefile (或 CMakeLists.txt) # 构建脚本在README.md中写明项目名称、功能、如何编译运行例如g -I./include src/*.cpp -o ticket_system以及你的设计思路。这会让你的项目更加专业。最后别忘了在课程设计报告里用UML类图展示你的类设计用流程图说明订票、退票的业务流程用截图展示程序运行效果。把这些扎实的思考和实现过程展现出来这份课程设计一定能成为你的得意之作。编程就像搭积木理解了每一块的作用和连接方式你就能创造出更复杂、更稳定的系统。这个飞机订票系统就是你C学习路上一个非常结实的地基。