ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

LinuxC+QT+MySQL机票售票模拟系统:从数据库设计到并发事务与界面开发

LinuxC+QT+MySQL机票售票模拟系统:从数据库设计到并发事务与界面开发 简介在管理系统类课程设计与毕业设计中如何让项目兼具技术深度与工程实用性是许多开发者思考的起点。数据库设计决定了数据一致性的根基而C语言则适合编写贴近底层、逻辑清晰的业务核心。通过合理划分界面层、逻辑层与存储层利用QT的信号槽机制实现交互响应同时将MySQL事务与条件更新用于余票扣减和订单创建可以避免超卖、保证数据原子性。这种组合不仅覆盖了查航班、锁余票、下订单等真实业务流程也为并发场景下的库存类应用提供了可复用的解决方案。从建表索引、连接池管理到工作线程与界面线程的协作再到中文乱码、Qt平台插件等常见问题的排查完整呈现了LinuxCQTMySQL三者协同开发机票售票模拟系统的落地路径。1. 毕业设计选题里最“能打”的组合LinuxCQTMySQL做机票售票模拟系统每年到了做课程设计和毕业设计的季节总有人抱着“XXX管理系统”的题目找我清一色增删改查答辩老师问两句就问穿了。相比之下基于LinuxCQTMySQL开发的机票网络售票模拟系统是个难得的“全栈”题目界面归QT管核心票务逻辑归LinuxC管数据落库归MySQL管三块缺一不可正好把大学四年最常考的三板斧串起来。它解决的是“怎么把一个真实业务流程——查航班、锁余票、下订单——用C语言和数据库协作模拟出来”的问题适合C语言、数据结构和数据库原理都有一定底子的人如果是一张白纸的新手跟着这篇把表结构和业务函数先抄明白也比从零憋强得多。这篇笔记我会按自己的落地顺序来写先搭数据库再写C业务逻辑然后接QT界面最后把编译运行中真正折腾过人的几个坑一次性讲透。2. 先立框架数据库表设计和三层结构怎么搭才不翻车2.1 把机票业务拆成三层C逻辑层、QT界面层、MySQL存储层模拟系统最容易犯的第一个错是打开Qt Creator就直接拖控件写着写着一个按钮的槽函数里塞了三百行SQL。我习惯先拿一张纸把边界画清楚界面层QT Widgets负责显示航班列表、接收用户输入、弹提示框不直接拼SQL语句。业务逻辑层LinuxC也就是在Linux环境下用C写的库或源文件负责处理查航班、扣余票、创建订单这些规则的“判断”。比如余票够不够扣、下单后返回什么错误码都是这一层说了算。存储层MySQL负责数据持久化和并发控制。航班表、订单表、用户表都在这边。这么分层最直接的好处是替答辩备好了话术老师问“你代码怎么组织的”你回答“界面和业务分离业务层拿着数据库连接去操作界面只认结果回调”这一句话就能把“管理系统”和“模拟系统”拉开档次。另一个好处是调试友好C语言里可以用fprintf打日志、printf输出错误码QT界面崩了不用跟UI死磕先怀疑业务层接口没返回正确值。在LinuxC这个环节我指的不是纯C而是用C语言gcc直接编译来写数据库访问和业务逻辑模块再由QT的C层通过extern C或者直接include头文件调用。很多课程设计项目都是这种混编路线——核心逻辑全用结构化C界面交给QT代码量可控也足够讲清楚“系统编程”的味道。2.2 建库建表航班、订单、用户三张核心表的字段与索引设计数据库设计是这个题目的地基。航班表管“有哪些票可卖”订单表管“谁买了哪张票”用户表管“谁在登录”。我的建议是至少建这三张别图省事只建一张表塞所有字段后面写SQL联查时会非常痛苦。先看航班表的核心建表语句CREATE TABLE IF NOT EXISTS flight ( flight_id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 航班内部ID, flight_no VARCHAR(10) NOT NULL COMMENT 航班号如CZ3101, dep_city VARCHAR(20) NOT NULL COMMENT 出发城市, arr_city VARCHAR(20) NOT NULL COMMENT 到达城市, dep_time DATETIME NOT NULL COMMENT 计划起飞时间, arr_time DATETIME NOT NULL COMMENT 计划到达时间, price DECIMAL(10,2) NOT NULL COMMENT 经济舱票价, remain_cnt INT UNSIGNED NOT NULL DEFAULT 100 COMMENT 剩余票数, PRIMARY KEY (flight_id), KEY idx_route (dep_city, arr_city, dep_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT航班表;这里有三个参数选择要解释清楚。第一dep_time用DATETIME而不是用DATE加一个单独的dep_time_str字符串列因为日期范围查询走DATETIME才能命中索引字符串比较虽然也能出结果但性能差、而且格式一乱就排序错。第二remain_cnt用INT UNSIGNED并设置默认100是防止SQL里出现负余票——如果某次扣减写错了字段本身的无符号约束马上报错比业务代码里if判断更早暴露问题。第三ENGINEInnoDB必须写因为后面要依赖事务来保证扣余票和创建订单的一致性MyISAM不支持行级事务千万别默认用旧习惯建表。订单表我保留了相对精简的字段CREATE TABLE IF NOT EXISTS orders ( order_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, flight_id INT UNSIGNED NOT NULL COMMENT 关联航班ID, user_id INT UNSIGNED NOT NULL COMMENT 下单用户ID, order_sn VARCHAR(32) NOT NULL COMMENT 订单号程序生成, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待支付 1已出票 2已取消, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (order_id), UNIQUE KEY uk_sn (order_sn), KEY idx_user (user_id), KEY idx_flight (flight_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表;order_sn加上唯一索引用处很大因为模拟系统里可能同时有两个模拟客户端下单时间戳加随机数生成的订单号万一重复唯一索引能兜底否则后面逻辑层还得再查一次有没有重复。create_time用DEFAULT CURRENT_TIMESTAMPC代码里不用自己拼时间省一顿格式化的麻烦。建完表之后可以用mysql workbench可视化确认表结构也可以直接命令行source导入二选一都行但表名、字段名建议全小写加下划线避免Linux环境下大小写敏感导致的“表不存在”。2.3 连接池思路LinuxC下托管MySQL连接避免每次查询都握手模拟系统的数据量不大但既然标题里写了LinuxCMySQL连接池这块值得做出来它是答辩时一个很加分的“非增删改查”亮点。每次调用mysql_real_connect去连一次MySQL服务握手加认证的开销虽然单看不大可在循环下单演示里体感明显而且频繁短连接会让MySQL端TIME_WAIT涨得很难看。常见做法是自己维护一个固定大小的连接数组初始化时建好使用时取一个空闲连接用归还机制放回去。我一般用互斥锁保护池子/* src/db_pool.c —— 最小可用连接池 */ #define MAX_POOL 8 static MYSQL *pool[MAX_POOL]; static int pool_used[MAX_POOL]; static pthread_mutex_t pool_mutex PTHREAD_MUTEX_INITIALIZER; int db_pool_init(const char *host, const char *user, const char *passwd, const char *dbname) { int i; pthread_mutex_lock(pool_mutex); for (i 0; i MAX_POOL; i) { pool[i] mysql_init(NULL); if (mysql_real_connect(pool[i], host, user, passwd, dbname, 0, NULL, 0) NULL) { fprintf(stderr, pool init fail: %s\n, mysql_error(pool[i])); pthread_mutex_unlock(pool_mutex); return -1; } pool_used[i] 0; } pthread_mutex_unlock(pool_mutex); return 0; } MYSQL *db_pool_get(void) { MYSQL *conn NULL; int i; pthread_mutex_lock(pool_mutex); for (i 0; i MAX_POOL; i) { if (!pool_used[i]) { pool_used[i] 1; conn pool[i]; break; } } pthread_mutex_unlock(pool_mutex); return conn; /* 返回NULL时上层自行决定等待或直接报错 */ }这段代码的逻辑是初始化阶段一次性建立8条TCP连接并认证好之后业务每次想操作数据库只需要在池子里取一个“没被占用”的连接用完归还避免重复建连和销毁。两个参数值得注意MAX_POOL设成8是照顾并发不高但可能要开线程轮询的模拟场景太大浪费内存太小会阻塞mysql_init(NULL)在MySQL 8.0里也可以用它为连接对象分配内存之后再交给mysql_real_connect填充连接信息。当然真正的生产连接池还要处理连接失效后的重建、空闲超时回收这里模拟系统做到“取连接、用连接、还连接”就够了。关键是让看代码的人明白你懂“连接复用”这回事。3. 用 LinuxC 把核心票务逻辑跑通余票扣减与订单事务3.1 条件UPDATE代替“先查再扣”让并发不超卖“查余票、判断大于0、再UPDATE减一”是很多新手的第一直觉但这是错的坑就坑在“查”和“改”之间隔着时间窗口两个客户端同时查到余票为1然后各自去扣最后超卖到负数。正确做法是把判断写进UPDATE的条件里让数据库一行行锁帮你做原子操作/* src/ticket_order.c —— 扣减余票的核心函数 */ int deduct_remain(MYSQL *conn, int flight_id) { char sql[256] {0}; int ret; snprintf(sql, sizeof(sql), UPDATE flight SET remain_cnt remain_cnt - 1 WHERE flight_id %d AND remain_cnt 0, flight_id); if (mysql_real_query(conn, sql, strlen(sql)) ! 0) { fprintf(stderr, update remain fail: %s\n, mysql_error(conn)); return -1; } ret (int)mysql_affected_rows(conn); if (ret 1) { return 0; /* 扣减成功影响了一行 */ } return -2; /* 影响0行说明余票已经没了 */ }这段代码的关键在于WHERE里带上了remain_cnt 0。MySQL执行UPDATE时会对命中的行加锁判断条件不满足就直接跳过所以“判断余票大于0”和“减一”在数据库层面是一次完成的。调用mysql_affected_rows()看结果返回1说明确实扣掉了一张票返回0说明航班余票已经为0此时上层可以直接提示“余票不足”不用再查一遍。这里有个经验别在UPDATE之后再SELECT一次“剩余多少”再决定成不成功多余的网络往返和数据不一致窗口完全没必要。如果确实要把最新余票展示给界面在UPDATE成功后紧接着做一次SELECT读取即可——此时读到的就是扣减后的值。3.2 事务边界出票失败时订单和余票怎样一起回滚余票扣了但订单插入时外键失败、或者模拟支付环节返回失败这单票不能变成“扣了票却没订单”也不能变成“有订单但余票没扣”。所以下单过程必须包在一个事务里先关掉自动提交全部成功再COMMIT/* src/ticket_order.c —— 创建订单 扣余票同一个事务完成 */ int create_order(MYSQL *conn, int flight_id, int user_id, const char *order_sn) { char sql[512] {0}; mysql_autocommit(conn, 0); /* 第一步扣余票 */ if (deduct_remain(conn, flight_id) ! 0) { mysql_rollback(conn); mysql_autocommit(conn, 1); return -1; } /* 第二步插入订单 */ snprintf(sql, sizeof(sql), INSERT INTO orders (flight_id, user_id, order_sn, status) VALUES (%d, %d, %s, 0), flight_id, user_id, order_sn); if (mysql_real_query(conn, sql, strlen(sql)) ! 0) { mysql_rollback(conn); mysql_autocommit(conn, 1); fprintf(stderr, insert order fail: %s\n, mysql_error(conn)); return -2; } mysql_commit(conn); mysql_autocommit(conn, 1); return 0; }逻辑不复杂但边界要交代清楚mysql_autocommit(conn, 0)之后同一条连接里所有SQL都进入同一个事务只有mysql_commit(conn)才会真正落盘。任何一个步骤出错就mysql_rollback(conn)把这一步的余票扣减或订单插入全部撤销最后记得把autocommit恢复成1否则这条连接后续的查询全都在隐式事务里锅很难查。事务隔离级别用默认的REPEATABLE READ就够不需要动。真正要小心的是事务里的SELECT、UPDATE一定要走主键或者索引否则会锁更多的行模拟系统里明明只想锁一行结果把整张flight表都锁了另一个窗口查航班直接卡住——这个问题我第一次跑并发被测出来时非常懵后来习惯性地给WHERE条件字段都建了索引再没复发。3.3 查询航班列表多表联查与日期边界航班查询在界面上通常表现为“输入出发城市、到达城市、出发日期点查询”。SQL层面最直接是查flight表按航线过滤/* src/flight_query.c —— 查询可用航班列表 */ int query_flights(MYSQL *conn, const char *dep_city, const char *arr_city, const char *dep_date) { char sql[512] {0}; char dep_begin[32] {0}, dep_end[32] {0}; /* 把用户选的日期转成当天00:00:00 到 次日00:00:00 */ snprintf(dep_begin, sizeof(dep_begin), %s 00:00:00, dep_date); snprintf(dep_end, sizeof(dep_end), %s 23:59:59, dep_date); snprintf(sql, sizeof(sql), SELECT flight_id, flight_no, dep_city, arr_city, dep_time, arr_time, price, remain_cnt FROM flight WHERE dep_city %s AND arr_city %s AND dep_time BETWEEN %s AND %s ORDER BY dep_time ASC, dep_city, arr_city, dep_begin, dep_end); ... }这里为什么不用DATE(dep_time) 2025-06-01因为一旦在WHERE里对dep_time列使用了DATE函数MySQL的索引就失效了全表扫描虽然模拟数据量小感觉不出来但属于习惯性问题。我的做法是把用户传进来的日期拼成“当天零点”和“当天23:59:59”两个边界。更严谨的写法是左闭右开BETWEEN dep_begin AND 次日00:00:00但模拟系统里用23:59:59可读性更好前提是数据量小、没有刚好压在边界上的航班。另外值得提一句有同学喜欢把扣余票的SQL写进MySQL存储过程让应用层只CALL一下。这种做法能让事务完全待在数据库里确实少很多C代码但调试时存储过程里printf不出来错误信息要靠SELECT封装课程设计还是建议把逻辑写在C里界面层可以直接拿到错误码和错误描述。4. 用 QT 把模拟系统画出界面Qt Designer、信号槽与线程4.1 在 Qt Creator 里搭主窗口查询区、结果表、下单按钮界面部分依赖QT我用的是QT 5.15.2这个常规版本下载安装后直接用Qt Creator打开工程。创建工程时选“Qt Widgets Application”基类选QMainWindow会生成一个mainwindow.cpp和mainwindow.h后面在mainwindow.cpp里通过ui指针访问控件。打开工程后用Qt Designer编辑mainwindow.ui我习惯把控件命名成有业务含义的前缀方便后面信号槽代码阅读否则默认的pushButton_3写多了自己都分不清谁是谁。常用控件和命名如下控件类型对象名用途QComboBoxcb_from下拉选择出发城市QComboBoxcb_to下拉选择到达城市QDateEditde_depart选择出发日期默认今天QPushButtonbtn_query触发航班查询QTableWidgettbl_flights展示航班列表QPushButtonbtn_order选中航班后下单QStatusBarstatusBar显示操作结果信息QDateEdit要设置setCalendarPopup(true)这样弹日历选日期体验更接近真实售票系统同时设置最小日期为今天防止用户选过去日期。两个QComboBox的条目可以直接在Designer里手工填几个城市也可以在mainwindow构造函数中用ui-cb_from-addItem(北京)批量加。我推荐后者因为城市列表应该和数据库里的航线数据保持一致手工填很容易出现“界面有上海数据库却没有上海出发的航班”这种低级BUG。4.2 信号槽写法界面按钮怎么把请求交给C业务线程QT里最核心的机制是信号槽。按钮点击、表格双击都会发出信号槽函数负责响应。模拟系统里我建议让槽函数只干“取界面参数、调C业务函数、把结果填回去”三件事不要在槽函数里写复杂SQL。// mainwindow.cpp —— 查询按钮槽函数 void MainWindow::on_btn_query_clicked() { QString fromCity ui-cb_from-currentText().trimmed(); QString toCity ui-cb_to-currentText().trimmed(); QDate departDate ui-de_depart-date(); if (fromCity.isEmpty() || toCity.isEmpty()) { QMessageBox::warning(this, 提示, 请选择出发和到达城市); return; } if (fromCity toCity) { QMessageBox::warning(this, 提示, 出发城市不能等于到达城市); return; } // 调用C函数查询航班列表fmtDate转成数据库需要的 yyyy-MM-dd QString dateStr departDate.toString(yyyy-MM-dd); ... }槽函数开头的两个if是模拟系统很需要的“防呆设计”真实系统里空城市、同城出发都是非法请求提前拦下比等SQL返回错误再弹窗友好得多。currentText()从下拉框拿字符串date().toString(yyyy-MM-dd)是QT里标准的日期格式化方式注意这句不会受系统locale影响格式永远是四位年份加两位月日正好喂给MySQL的DATETIME字段。连接方式可以直接在Designer里点“转到槽”自动生成on_btn_query_clicked()也可以手动connect。我习惯手动connect因为代码里能顺手把业务线程的调用方式写清楚// mainwindow.cpp 构造函数中 connect(ui-btn_query, QPushButton::clicked, this, MainWindow::on_btn_query_clicked);4.3 用 QTableWidget 展示航班结果列表双击触发下单查询结果返回后用一个表格把所有航班列出来。QTableWidget适合这种“固定列数、行数动态”的小数据量场景不用自己写Model代码量最少// 用查询到的航班数据填充QTableWidget void MainWindow::fillFlightTable(const QListFlightInfo flights) { ui-tbl_flights-clearContents(); ui-tbl_flights-setRowCount(flights.size()); ui-tbl_flights-setColumnCount(7); QStringList headers {航班号, 出发, 到达, 起飞时间, 到达时间, 票价, 余票}; ui-tbl_flights-setHorizontalHeaderLabels(headers); for (int row 0; row flights.size(); row) { const FlightInfo f flights.at(row); ui-tbl_flights-setItem(row, 0, new QTableWidgetItem(f.flightNo)); ui-tbl_flights-setItem(row, 1, new QTableWidgetItem(f.depCity)); ui-tbl_flights-setItem(row, 2, new QTableWidgetItem(f.arrCity)); ... // 把航班ID藏进单元格的自定义数据里下单时取出来用 QTableWidgetItem* idItem new QTableWidgetItem(f.flightNo); idItem-setData(Qt::UserRole, f.flightId); } }Qt::UserRole是这里的关键它是QT给开发者预留的“自定义数据槽”把数据库里的主键flight_id以数字形式挂到界面项上用户点这行时就不用通过解析字符串拿ID直接从item里取void MainWindow::on_tbl_flights_cellDoubleClicked(int row, int column) { QTableWidgetItem* item ui-tbl_flights-item(row, 0); if (!item) return; int flightId item-data(Qt::UserRole).toInt(); // 继续调下单接口 ... }QTableWidget默认双击进入编辑状态要顺手setEditTriggers(QAbstractItemView::NoEditTriggers)关掉否则用户双击本来想下单结果表格先变编辑框体验很怪。4.4 把数据库操作挪进QT工作线程别卡死界面查询和下单都涉及MySQL网络交互如果直接在槽函数里同步调用MySQL响应慢时整个界面会“假死”。模拟系统里数据量不大但演示时如果数据库服务刚重启连接池还没热起来第一次点击查询就可能卡一两秒。更好的做法是开一个QThread子类跑数据库操作。// flightworker.h —— 工作线程 class FlightWorker : public QThread { Q_OBJECT public: explicit FlightWorker(MYSQL* conn, QObject* parent nullptr); void run() override; signals: void queryFinished(const QListFlightInfo flights); void queryError(const QString err); private: MYSQL* m_conn; QString m_depCity; QString m_arrCity; QString m_date; };run()函数里执行真正阻塞的C查询函数查询完通过emit queryFinished(flights)把结果传回主线程。这里强调一下QT里跨线程传自定义结构体需要在类里用Q_DECLARE_METATYPE(FlightInfo)声明一下否则排队信号时Qt不知道如何帮你拷贝这个参数。我用的是QThread::run这种直接写法简单直观进阶的moveToThread方案更标准但对模拟系统学习成本偏高属于锦上添花的优化项。5. 踩坑记录模拟系统编译和运行时的 5 个高频翻车点5.1 cannot mix incompatible Qt libraryqmake路径串了现象编译时报fatal: cannot mix incompatible Qt library (version ex50601) with this library或者运行时直接崩溃提示Qt版本不匹配。我当时用的是QT 5.15.2系统里还残留着一个老版本QTqmake指向了老版本但链接器找到了新版本库。原因环境变量里的PATH和QTDIR没对应上Qt Creator用的是自带套件时没问题但在终端里手动qmake、make时系统PATH里先找到了旧版qmake生成Makefile后按旧版路径去头文件和库文件于是新旧头文件、库文件混在一起。解决先把PATH里QT相关路径检查清楚which qmake确认用的是哪个版本然后统一用Qt Creator自带的qmake路径同时把之前生成的Makefile、.pro.user缓存删掉重新qmake。血泪经验不要在同一个环境里装两套大版本QT老项目指定版本前先备份环境。5.2 could not find the qt platform plugin linuxfb无显示环境跑QT现象在纯命令行Linux服务器上运行编译好的QT程序报qt.qpa.plugin: Could not find the qt platform plugin linuxfb然后程序退出。原因QT默认走xcb插件需要X Window显示环境服务器或远程终端没有X服务QT找不到合适的“平台插件”。解决分两种场景。如果是本地有桌面的Linux装libxcb相关库或者用export QT_QPA_PLATFORMoffscreen让QT在“虚拟显示”下运行适合跑自动化测试如果是在嵌入式开发板上跑需要保留linuxfb插件。最简单快速验证程序能起来的办法是QT_QPA_PLATFORMoffscreen ./main界面不显示但逻辑照样跑日志能出来。5.3 ERROR 2002MySQL服务没起或socket路径不对现象程序连数据库时报ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock。原因看到“localhost”时MySQL客户端默认走Unix socket文件而不走TCP如果mysqld没启动、或者socket文件不在默认路径就会报这个错。解决先systemctl status mysql确认服务在跑再查/etc/mysql/my.cnf里的socket路径是否一致。最简单稳妥的验证方式是把程序里的host从localhost改成127.0.0.1强制走TCP连接绕开socket路径问题代价是多一点网络开销对模拟系统完全无所谓。这是我认为“模拟系统最值得背下来的一句经验”——先排查服务再排查socket再排查防火墙。5.4 超卖问题同一时段两个客户端抢到最后一张票现象并发测试时两个客户端都显示“下单成功”但航班余票变成负数或者显示成功后查订单发现两张订单指向同一张票。原因没有使用条件UPDATE而是“先SELECT余票数、再UPDATE扣减”两步走两个请求在SELECT阶段都看到余票为1然后各自执行UPDATE自然超卖。解决回到3.1节的条件UPDATE写法把remain_cnt 0放进WHERE并检查mysql_affected_rows是否为1。这个坑可以说覆盖了所有“模拟库存/票务”类系统的核心答辩时主动说出来老师会知道你理解并发下的原子性问题。5.5 中文乱码和日期格式化utf8mb4与连接字符集现象QT界面上显示的城市名变成了???或者MySQL里存进去的中文读出来乱码。原因建表时没用utf8mb4、连接建立后没设置客户端字符集。解决分两步建表统一带DEFAULT CHARSETutf8mb4C代码在连接建立后立刻执行一次SET NAMES utf8mb4。QT方面从MySQL取出的字符串是UTF-8直接用QString::fromUtf8包一层再放进界面控件。遇到过最隐蔽的情况是命令行下mysql查表中文正常但程序读出来乱码——这就是连接字符集没设各管各的程序客户端和数据库服务器用了不同字符集。6. 把模拟系统做成“能答辩的毕业设计”日志、状态机与最后的验证前面五章讲的是“跑得通”第六章我想说说“怎么让它更像一个真正能交差的成品”。第一个建议是给订单加状态机。不要只有一张订单表没有状态流转至少要有“待支付、已出票、已取消”三个状态界面上下单后弹一个“模拟支付”确认框确认后把status从0改成1如果C业务函数返回扣票失败则把状态置为2。状态机让系统有了“业务过程”而不是一个静态列表。第二个建议是加上日志模块。模拟系统的黑匣子是所有人都头疼的事我在业务层每个关键函数入口用fprintf写一行带时间戳的记录到logs/ticket.log比如2025-06-01 10:23:11 flight_id5 order_snT202506011023 cancel。答辩现场无论演示什么功能出了问题都可以打开日志说明“这里发生了什么”这比你在台上口述“我刚才点了下单”有说服力得多。交付前我会做三件事第一用valgrind跑一遍程序valgrind --leak-checkfull ./main把“definitely lost”清零这是给老师看程序质量的狠招第二写一个demo.sql或reset.sql把三张表清空并插入几组固定航班数据每次答辩演示前重置一遍防止演示现场数据被自己改乱第三在QT里把编译模式从Debug切到ReleaseRelease版体积小、运行快、不像Debug版那样依赖一堆调试库在别人电脑上演示时少翻一个“缺DLL”的坑。我自己的习惯是每完成一个模块就完整跑一遍“查询→下单→看余票”的闭环哪怕只改了一个变量名。这比最后统一测试时一次性面对十几个报错要舒服得多也是我这么多年踩坑踩出来的止血办法。希望这些内容能帮到你把你自己的机票售票模拟系统从“能编译”做成“能讲清楚、能扛住追问、能拿出来演示”的完整毕业设计。本文还有配套的精品资源点击获取
返回列表