拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Qt+MySQL股票管理系统课设:驱动与连接避坑全攻略

Qt+MySQL股票管理系统课设:驱动与连接避坑全攻略

简介:这是一份面向C++初学者的课程设计完整项目,采用Qt搭建图形界面、MySQL存储数据,实现股票信息管理、用户管理等核心功能。资源共56个文件,包含14个cpp源文件、13个h头文件、12个ui界面设计文件,以及数据库建表SQL脚本、课程设计报告PDF、系统流程图、数据库截图和可直接运行的打包程序,压缩包约36.51MB。项目覆盖C++面向对象编程、Qt信号与槽机制、MySQL连接与SQL操作、数据库模型设计等关键知识点,还涉及多线程更新行情、文件读写、异常处理等进阶内容。已有247人学习下载,适合正在准备课设或希望综合训练C++/Qt/MySQL开发能力的学生参考。通过阅读源代码、界面文件和报告,可以快速梳理从需求分析、界面布局到数据库交互的完整开发流程,并在此基础上按自己的思路扩展功能。报告中对数据库表结构、类设计与界面交互做了说明,能帮助理解项目全貌。

1. 课设包里的股票管理系统:为什么看着能编译,一换机器就翻车

每年答辩季总能看到“股票管理系统 orz”这种压缩包:Qt 界面做得挺像样,MySQL 脚本也放在里头,可一旦换台电脑打开,不是弹数据库连接失败,就是列表里只剩一条样例数据。这个标题看似只是大一 C++ 课设作业,实际暴露了 Qt+MySQL 项目的常见病:代码能过编译,界面也能弹出来,但运行环境的驱动版本、数据库连接时序、字符集三者只要有一个不对,整个股票管理系统就变成黑匣子。这篇文章不负责替谁猜源码,而是从“拿到一份 Qt+MySQL 课设包”的真实处境出发,把工程结构、建表选型、连接时序和答辩前要改的地方逐条拆开。适合正在补课设、接盘二手代码或想把项目做成能现场演示版本的人。

2. 拆开课设包:先分清哪些文件在负责“股票界面”,哪些在负责“数据仓库”

2.1 为什么大一课设都爱用 Qt+MySQL:观感好,但门槛藏在驱动和编码里

“Qt+MySQL”这个组合之所以在课设里反复出现,核心原因是观感好且证据强。Qt 提供 QTableView、QLineEdit、QPushButton 这些现成控件,把股票信息表放进去只需要几行 setModel;MySQL 又能让答辩老师看到“项目里有数据库”,比纯文件读写听起来高级不少。加上网上这类 demo 素材多,很多人会从旧工程里拼出一个界面,最后形成了一套“看起来能跑”的模板。

但它的门槛不在 C++ 语法,而在连接。Qt 要接到 MySQL,需要同时满足四个前提:Qt 的 sql 模块里有 QMYSQL 驱动;系统里装了和 Qt 编译器架构匹配的 MySQL 客户端库;MySQL 服务运行在预期端口;连接时明确字符集。这四条里任何一条不满足,编译往往能通过,程序也会启动,但一执行查询就返回空列表,或者直接崩掉。很多学生把“数据库连不上”当成玄学,其实按顺序排查完版本匹配,八成问题就没了。

还有一层隐藏门槛是 QSqlQuery 和 QSqlDatabase 的生命周期。很多人只在按钮槽函数里临时 new 一个 QSqlQuery,没指定连接名,窗口一关连接状态就混乱,最后变成“第一次查询正常,第二次再点就卡死”。这属于课设包里最常见的隐蔽问题,第 3 章会给出具体的连接时序。

2.2 压缩包里的标准文件:哪些是源码,哪些是你必须删掉的残骸

拿到一个课设 zip,先把目光从 main.cpp 移开,去看工程根目录有哪些文件。常见结构如下:

文件名/目录作用要不要改
stock_system.proQt 工程入口,决定模块和源文件列表必须改
main.cpp创建 QApplication 并显示主窗口一般不用大改
mainwindow.h/cpp主界面逻辑,按钮槽函数都在这核心修改点
dbhelper.h/cpp数据库连接封装必须检查
stock.h/cpp股票实体类,不一定每个版本都有看情况
mainwindow.uiQt Designer 生成的界面布局建议重排
stock.sql建库建表脚本交付时必须带
README.md / 答辩 PPT说明和素材建议重写
build-* / .user / .pro.user编译残留和本机配置打包前删掉

我一般拿到压缩包后的第一件事,是先删掉 build 目录和 *.user 文件。它们是你本地 Qt Creator 的套件信息,不是源码;留着只会让接收者一打开就陷入“找不到 Qt 版本”的界面。真正的源码通常就是那几个 .cpp、.h、.ui 和 .pro 文件,数量不多,但每一条都值得看明白。

2.3 打开 .pro 文件先做三件事:确认 Qt 模块、指定编译器、检查 sql 驱动

.pro 是 Qt 工程的心脏。下面是一个典型的最小 .pro 内容:

QT += core gui sql greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = stock_system TEMPLATE = app SOURCES += \ main.cpp \ mainwindow.cpp \ dbhelper.cpp \ stock.cpp HEADERS += \ mainwindow.h \ dbhelper.h \ stock.h FORMS += \ mainwindow.ui CONFIG += c++11

第一行里缺了sql,就是很多“明明写了 #include 却报找不到头文件”的来源。Qt 的模块必须在工程文件里显式声明,不是 include 就能自动带上的。greaterThan(QT_MAJOR_VERSION, 4): QT += widgets是 Qt5 兼容写法,如果你直接装了 Qt 6,这行不是必须的,但建议把CONFIG += c++11改成CONFIG += c++17,至少能解决一批老代码用 lambda 编译不过的问题。

打开 .pro 后先做三件事。第一,把 TARGET 改成你自己的英文名,比如stock_manager_mine,避免答辩时暴露连文件名都没改的尴尬。第二,在 Qt Creator 左侧点“项目”页签,看当前使用哪个套件。Windows 上通常是 Qt 5.15.2 MinGW 64-bit 或 Qt 5.15.2 MSVC2019 64-bit,这两者对应不同的运行库,后面连 MySQL 也要匹配架构。第三,确认 Qt 安装目录下有 MySQL 驱动插件,在plugins/sqldrivers里找qsqlmysql.dll。如果没有,编译照样能过,但运行时QSqlDatabase::drivers()里不会出现QMYSQL,程序会提示 driver not loaded。

很多人到这里就开始慌,其实可以先在代码里输出一下驱动列表,确认问题再动手。Qt 官方开源版经常不预编译 MySQL 插件,Oracle 的许可限制决定了很多东西只能自己来。但如果你用的是 Qt 在线安装包,有些版本并不会把 qsqlmysql.dll 直接放出来,这时有两条路:换一个带 MySQL 插件的 Qt 发行版,或者把 MySQL 客户端库放到 Qt 的搜索路径里。对课设来说,最省时间的是先确认“是不是驱动缺失”,而不是反复改连接参数。

3. 把数据库和界面先跑通:建表、连库、完成最小可用的股票增删改查

3.1 股票表设计:用 DECIMAL 存价格,用 INT UNSIGNED 存持仓

建表脚本是整套系统的地基。常见的课设版本里会把所有东西塞进一张表,字段命名随意,类型全是 VARCHAR,这会导致后面的增删改查到处出问题。我更建议一上来就按下面的脚本把基础表建好:

CREATE DATABASE IF NOT EXISTS stock_db DEFAULT CHARACTER SET utf8mb4; USE stock_db; DROP TABLE IF EXISTS stock; CREATE TABLE stock ( code VARCHAR(10) NOT NULL COMMENT '股票代码', name VARCHAR(50) NOT NULL COMMENT '股票名称', price DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '当前价', change_pct DECIMAL(5,2) NOT NULL DEFAULT 0.00 COMMENT '涨跌幅', hold_qty INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '持仓数量', update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间', PRIMARY KEY (code) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; INSERT INTO stock (code, name, price, change_pct, hold_qty) VALUES ('600001', '示例股份', 8.35, 1.20, 1000), ('000001', '平安银行', 11.24, -0.35, 200);

这里有四个参数值得较真。第一,code用 VARCHAR 而不是 INT,因为股票代码可能有 600、000、300 等前缀,纯数字在 INT 里会丢掉前导零,打印出来就不对。第二,价格用DECIMAL(10,2)不用 FLOAT,MySQL 里的 FLOAT 是近似值,算总市值时容易差几分钱,答辩时被问到“为什么这里用 DECIMAL”,正好是一个能接住的问题。第三,hold_qty INT UNSIGNED表示持仓量不允许为负,卖出校验放在业务层,数据库从结构上先挡住一批非法数据。第四,DEFAULT 0.00这个写法对应很多人搜过的“mysql设置默认值为0”,但要注意不是所有列都该默认 0,只有价格和涨跌幅这种“先占位,等实时行情来了再更新”的字段才适合。

update_time用TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,意思是插入时自动写当前时间,更新时也自动刷新,不需要在 C++ 代码里手动维护时间。ENGINE=InnoDB也很关键,后面写买卖事务时必须依赖 InnoDB 的事务能力,如果是 MyISAM,transaction()会静默失效。

3.2 DbHelper 连接:先确认驱动再 open,host 写 127.0.0.1

数据库操作最好封装成一个 DbHelper 或一个独立的命名空间。这样主窗口代码只调用DbHelper::initConnection(),不用到处写 QSqlDatabase。最小实现如下:

#include "dbhelper.h" #include <QSqlDatabase> #include <QSqlError> #include <QDebug> bool DbHelper::initConnection() { if (!QSqlDatabase::isDriverAvailable("QMYSQL")) { qWarning() << "no QMYSQL driver"; return false; } QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", "stock_conn"); db.setHostName("127.0.0.1"); // 不要写 localhost,避免走 unix socket db.setPort(3306); db.setDatabaseName("stock_db"); db.setUserName("root"); db.setPassword("123456"); if (!db.open()) { qWarning() << "open failed:" << db.lastError().text(); return false; } db.exec("SET NAMES utf8mb4"); return true; }

isDriverAvailable("QMYSQL")这一步很有必要,它把驱动缺失和连接失败分开,省得你对着一个含糊的 lastError 猜半天。连接名"stock_conn"是显式命名连接,这样一来,后续所有 QSqlQuery 都能通过QSqlDatabase::database("stock_conn")拿到同一个连接,避免因为误用默认连接导致状态混乱。host 写 127.0.0.1 而不是 localhost,是因为 localhost 在 MySQL 客户端协议里经常被解析成 Unix socket,这一改能绕开后续一大堆 socket 路径问题,具体表现就是第 4 章要讲的那个 error 2002。

连接失败的弹窗最好放在 main() 里,而不是 MainWindow 构造函数中:

int main(int argc, char *argv[]) { QApplication app(argc, argv); if (!DbHelper::initConnection()) { QMessageBox::critical(nullptr, "错误", "MySQL 连接失败,请确认服务已启动"); return 1; } MainWindow w; w.show(); return app.exec(); }

这么做的理由很简单:数据库都连不上时,窗口做出来也没有意义。在构造函数里弹模态框,界面还没初始化完就卡住,用户看到的可能是个白屏,这会给答辩评委留下非常差的印象。

3.3 列表展示和增删改查:QSqlQueryModel 适合展示,写操作走 QSqlQuery

展示股票列表最省力的做法是用 QTableView 加 QSqlQueryModel。在 MainWindow 里准备一个成员变量,然后每次刷新都重新 setQuery:

void MainWindow::refreshStockTable() { model->setQuery( "SELECT code AS 代码, name AS 名称, price AS 现价, " "change_pct AS 涨跌幅, hold_qty AS 持仓量 " "FROM stock ORDER BY code", QSqlDatabase::database("stock_conn")); if (model->lastError().isValid()) { QMessageBox::warning(this, "查询失败", model->lastError().text()); return; } model->setHeaderData(1, Qt::Horizontal, "名称"); model->setHeaderData(2, Qt::Horizontal, "现价"); ui->tableView->setModel(model); ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); }

这里 setQuery 的第二个参数传了连接名,这是关键。很多人不传,Qt 会走默认连接,如果你在 main() 里用了自定义连接名,列表就会一直查不到数据。用 QSqlQueryModel 而不直接用 QSqlTableModel,是因为查询的 SQL 完全由你控制,答辩时被问“数据从哪来”可以直接讲出整条 SQL,不会被 QSqlTableModel 封装好的拼写卡住。

新增和修改建议用 QSqlQuery 配合 prepare 绑定参数,下面是一个新增股票的函数:

bool DbHelper::addStock(const QString &code, const QString &name, double price, double changePct, int qty, QString *errMsg) { QSqlQuery q(QSqlDatabase::database("stock_conn")); q.prepare("INSERT INTO stock(code, name, price, change_pct, hold_qty) " "VALUES(:code, :name, :price, :change_pct, :hold_qty)"); q.bindValue(":code", code); q.bindValue(":name", name); q.bindValue(":price", price); q.bindValue(":change_pct", changePct); q.bindValue(":hold_qty", qty); if (!q.exec()) { if (errMsg) *errMsg = q.lastError().text(); return false; } return true; }

用 bindValue 而不是字符串拼 SQL,一方面能处理价格里的小数点,另一方面也避免了单引号和反斜杠转义的问题。答辩时如果老师说“你怎么防 SQL 注入”,你至少可以说“我用了参数化查询”,这比直接拼字符串要稳妥。

3.4 买和卖的加分操作:用事务保证持仓和交易记录一致

只做增删改查的课设太常见,加分项通常落在“买入卖出”。核心要求是:持仓变动和交易记录必须同时成功,不能出现股票数量减了但日志没写的情况。先补一张交易日志表:

CREATE TABLE trade_log ( id INT AUTO_INCREMENT PRIMARY KEY, code VARCHAR(10) NOT NULL, qty INT NOT NULL, price DECIMAL(10,2) NOT NULL, trade_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

然后封装一个 tradeStock 函数:

bool DbHelper::tradeStock(const QString &code, int deltaQty, double tradePrice) { QSqlDatabase db = QSqlDatabase::database("stock_conn"); if (!db.transaction()) { qWarning() << "begin transaction failed"; return false; } QSqlQuery q(db); q.prepare("UPDATE stock SET hold_qty = hold_qty + :delta " "WHERE code = :code AND hold_qty + :delta >= 0"); q.bindValue(":delta", deltaQty); q.bindValue(":code", code); if (!q.exec() || q.numRowsAffected() != 1) { db.rollback(); return false; } QSqlQuery log(db); log.prepare("INSERT INTO trade_log(code, qty, price, trade_time) " "VALUES(:code, :qty, :price, NOW())"); log.bindValue(":code", code); log.bindValue(":qty", deltaQty); log.bindValue(":price", tradePrice); if (!log.exec()) { db.rollback(); return false; } db.commit(); return true; }

这里有几个细节。deltaQty正数代表买入,负数代表卖出。UPDATE 语句里带了AND hold_qty + :delta >= 0,这一行直接在 SQL 层挡住卖出数量超过持仓的情况,省得在 C++ 里先 SELECT 再判断,减少一次查询。numRowsAffected() != 1说明代码不存在或超卖,立刻回滚。两个 exec 只要有一个失败就 rollback,只有两个都成功才 commit。这个模式在业务系统里叫“事务边界”,课设里能用出来,含金量会明显不一样。

4. Qt+MySQL 股票管理系统避坑:5 个高频报错的现象、原因与解决

4.1 编译报错“fatal: cannot mix incompatible Qt library (version ex50601) with this library”

现象:构建时报cannot mix incompatible Qt library,后面跟着版本号,比如 ex50601,有时还会在程序启动时直接崩溃。

原因:源码里引用的 Qt 头文件和链接的 Qt 库不是同一套版本。最常见的场景是电脑里装了多个 Qt,某一台机器上用 Qt 5.6 写的课设,换到另一台机器用 Qt 5.15.2 打开,或者环境变量 PATH 指到了旧版本。

解决:先在 Qt Creator 的“项目”页签里确认套件,然后清空整个 build 目录重新构建。如果是命令行编译,用当前项目实际使用的 qmake 绝对路径:

/opt/Qt/5.15.2/gcc_64/bin/qmake -v make clean make

这一步的要点是“全局统一”,不要只改代码。只要编译器和 qmake 不是同一个 Qt 前缀,报错就会反复出现。这类问题没有什么好办法,只能从套件配置一路查到 PATH,把每一层都对齐。

4.2 链接期“cannot find -lpublic”

现象:编译到链接阶段直接失败,提示cannot find -lpublic,看起来像是少了一个叫 public 的库文件。

原因:九成情况是在网上下载的 .pro 文件里有一行多余的LIBS += -lpublic。这种写法来自某些计费或加密模块的示例代码,被复制到课设工程后没有删掉。还有一种是原本想写PUBLIC_HEADERS += xxx.h,但手滑写成了LIBS +=,链接器就会去 libpublic 方向找。

解决:先在 .pro 文件里搜索“LIBS”,把不属于 Qt 的库项全部注释掉。比如:

# LIBS += -lpublic

然后右键项目选择“执行 qmake”,再重新构建,这个报错就会消失。排查的原则很简单:如果工程里没有对应的 libpublic.a,就别让 LIBS 指向它。

4.3 运行期“qt.qpa.plugin: could not find the qt platform plugin "linuxfb"”

现象:程序在某些 Linux 环境或嵌入式板上启动失败,报could not find the qt platform plugin "linuxfb",随后退出。

原因:系统环境变量QT_QPA_PLATFORM被设置成了 linuxfb,但你的 Qt 安装里没有对应的平台插件。另一种可能是在打包产物里只带了可执行文件,没有把 Qt 的platforms目录复制过去。

解决:先在终端里查一下当前变量:

echo $QT_QPA_PLATFORM

如果输出不是 xcb,可以临时改成桌面环境:

export QT_QPA_PLATFORM=xcb

如果是打包部署问题,就要确保可执行文件旁边有platforms/libqlinuxfb.so或libqxcb.so,并且目录结构保持plugins/platforms。这个坑在 Linux 上很常见,Windows 上少见,但理解机制比死记报错更有用:Qt 平台插件是一组动态库,程序启动时会按固定路径找它们。

4.4 MySQL 连接报“error 2002 (HY000): Can't connect to local MySQL server through socket”

现象:程序启动后弹窗提示Can't connect to local MySQL server through socket '/tmp/mysql.sock',但你在命令行里用 mysql 连同样的库又正常。

原因:连接参数里 host 写成了 localhost。MySQL 客户端在 Linux 上看到 localhost 会优先走 unix socket,而不是 TCP 端口。Qt 代码里如果用db.setHostName("localhost"),最终也会被驱动翻译成 socket 路径。如果 MySQL 服务端 socket 路径不同,或者服务没启动,就会报这个 2002 错误。

解决:先把 Qt 代码里的 host 改成 127.0.0.1,然后验证服务本身:

mysql -u root -p -h 127.0.0.1 -P 3306 -e "SELECT 1"

如果这条命令也报 2002,说明 MySQL 服务没启动,先启动服务再查 Qt 程序。如果命令行正常但 Qt 弹窗,多半就是驱动或端口的问题。把 host、port、databaseName、userName、password 五个参数打出来逐项比对,很快能定位。

4.5 中文乱码:界面上的“示例股份”变成“??”或“锟斤拷”

现象:代码里写了“示例股份”,MySQL 命令行查询也是中文,但 Qt 界面显示乱码,或者 MySQL 表里存进来的是问号。

原因:两层问题。第一层是数据库本身字符集不是 utf8mb4,导致存进去的字节已经错了;第二层是 Qt 连接 MySQL 时没有执行SET NAMES,客户端的字符集和表的字符集不一致。Windows 上还有第三层:MSVC 编译器默认把源码里的窄字符串按本地代码页处理,中文保存成 GBK,Qt 读的时候按 UTF-8 解码,于是乱码。

解决:建库时用DEFAULT CHARACTER SET utf8mb4,连接建立后执行db.exec("SET NAMES utf8mb4"),代码里尽量把中文放到.ui文件或tr()里,不要直接给QString("示例股份")这种硬编码。如果用的是 MSVC 套件,可以在 .pro 里加:

msvc { QMAKE_CXXFLAGS += /utf-8 }

这样编译器会把源码当作 UTF-8 处理,能解决大部分 Windows 下的中文乱码。需要记住的是,一旦数据以乱码形式写入数据库,改显示代码也救不回来,必须把表里错误的记录清掉重新插入。

5. 答辩前把课设改成“自己的版本”:类名、排序统计、校验和刷新四个实招

5.1 全局替换类名和文件名,去掉“orz”痕迹

答辩老师见过太多下载下来的课设包,第一眼就会看类名和文件名。如果 .pro 里还写着 StockSystem,窗口标题也是 StockSystem,那就等于告诉老师“这是网上原封不动下载的”。我建议拿到任何课设包后,先全局替换类名和工程名,而不是只改窗口标题。

手动一个个改容易漏,比较稳的方法是用一个短脚本批量替换。Windows 上如果没有 sed,Python 脚本是跨平台方案:

import os for root, dirs, files in os.walk('src'): for name in files: if name.endswith(('.cpp', '.h', '.pro', '.ui')): path = os.path.join(root, name) text = open(path, encoding='utf-8', errors='ignore').read() text = text.replace('StockSystem', 'MyStockManager') text = text.replace('stock_system', 'my_stock_manager') open(path, 'w', encoding='utf-8').write(text)

注意,替换时至少区分大小写写两组。StockSystem是类名,stock_system可能是工程名或文件名。.ui文件本质是 XML,类名引用同样会被替换,但前提是保持大小写一致,否则 TARGET、窗口类、头文件包含三方对不上,就会编不过。跑完脚本后重新执行 qmake,再构建一次,确保没有遗漏。

5.2 用 std::sort 做涨跌幅排行,比冒泡排序更像现代 C++

如果项目里要“涨幅榜”或“按市值排序”,很多课设会写一个冒泡排序。冒泡本身没错,但既然用了 Qt 5.15.2 和 C++11 以后的标准,直接调用 std::sort 会更像工程实践。写一个函数把数据查出来后排序:

#include <algorithm> #include <QVector> struct StockBrief { QString name; double marketValue; double changePct; }; QVector<StockBrief> getSortedStocks() { QVector<StockBrief> result; QSqlQuery q(QSqlDatabase::database("stock_conn")); q.exec("SELECT name, price * hold_qty, change_pct FROM stock"); while (q.next()) { StockBrief item; item.name = q.value(0).toString(); item.marketValue = q.value(1).toDouble(); item.changePct = q.value(2).toDouble(); result.append(item); } std::sort(result.begin(), result.end(), [](const StockBrief &a, const StockBrief &b) { return a.changePct > b.changePct; }); return result; }

这个函数的核心是后面那个 lambda 表达式,它定义了两个元素谁排在谁前面。a.changePct > b.changePct表示降序,想要按市值排序就把比较对象换成 marketValue。由于 QVector 在内存里连续存放,result.begin()和result.end()可以直接喂给 std::sort。

答辩时如果被问“为什么要用 std::sort”,可以说一句:排序复杂度同样是 O(n log n),但标准库排序经过大量优化,比自己写的两层循环更可靠。这句话就能把“课程水平”和“工程意识”拉开。

5.3 给输入框加上校验:QDoubleValidator 和正则拦截非法数据

很多课设的界面允许你随便往价格框里输入 abc,点新增后程序弹一个数据库错误,体验很糟。一个 5 分钟的改进是给输入框挂校验器:

#include <QDoubleValidator> #include <QRegularExpressionValidator> QDoubleValidator priceValidator(0.0, 999999.99, 2, this); priceValidator.setNotation(QDoubleValidator::StandardNotation); ui->priceEdit->setValidator(&priceValidator); QRegularExpression codeRegex("^[A-Za-z0-9]{1,10}$"); ui->codeEdit->setValidator(new QRegularExpressionValidator(codeRegex, this));

QDoubleValidator 负责价格范围和小数位,正则负责股票代码只能由字母数字组成,长度 1 到 10。注意,Qt 的 validator 不会阻止用户在已经非法的字符串里继续输入,它更多是让人“输不进去非法字符”。真正提交时还是要再做一次空值判断。

if (ui->codeEdit->text().isEmpty() || ui->priceEdit->text().isEmpty()) { QMessageBox::warning(this, "提示", "股票代码和价格不能为空"); return; }

这个校验逻辑加在按钮槽函数开头,顺序是先判空,再让数据库执行。最终效果是:非法数据到不了 SQL 语句。

5.4 状态栏显示总市值,再用 QTimer 定时刷新

给主窗口底部状态栏加一个总市值,会让演示时的信息量立刻不一样。刷新函数如下:

void MainWindow::refreshTotalValue() { QSqlQuery q(QSqlDatabase::database("stock_conn")); q.exec("SELECT COUNT(*), COALESCE(SUM(price * hold_qty), 0.0) FROM stock"); if (q.next()) { int count = q.value(0).toInt(); double total = q.value(1).toDouble(); ui->statusBar->showMessage( QString("股票数量: %1 总市值: %2 元") .arg(count) .arg(total, 0, 'f', 2)); } }

COALESCE的作用是当表为空时返回 0,而不是 NULL,否则 toDouble 只能得到一个无效的 0 或直接告警。.arg(total, 0, 'f', 2)表示保留两位小数。

下一步是让这个数字自己动起来:

QTimer *timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &MainWindow::refreshTotalValue); timer->start(30000);

30000 毫秒是 30 秒。这里不把 refreshTotalValue 放在 refreshStockTable 里,是因为数据库更新后需要手动刷新的只是列表,而状态栏更适合保持一个固定频率自动更新。两种刷新职责分开,逻辑更清楚。

6. 冷启动验证:训练到 3 分钟恢复数据库,答辩就不会卡在“连接失败”上

6.1 用 mysqldump 把数据库变成一份可重置的快照

答辩当天最怕的不是代码 bug,而是 MySQL 服务没启动或者数据被改得乱七八糟。我自己的习惯是:在答辩前一天导出一份演示数据快照,现场万一出问题,三分钟内就能恢复。

导出命令要指定字符集:

mysqldump -u root -p --default-character-set=utf8mb4 stock_db > demo_reset_utf8.sql

--default-character-set=utf8mb4这参数不能省,否则导出的文件可能存成单字节字符集,恢复进去又是一堆乱码。模拟“现场翻车”时,先把库删了再重建:

mysql -u root -p -e "DROP DATABASE stock_db; CREATE DATABASE stock_db DEFAULT CHARACTER SET utf8mb4;" mysql -u root -p --default-character-set=utf8mb4 stock_db < demo_reset_utf8.sql

两步执行完,样例数据和表结构都回到最初状态。这个做法相当于给课设买了一颗后悔药,数据库状态被改乱了也能一键重置。

6.2 一个“检查四项”的启动顺序,练到形成肌肉记忆

演示前按固定顺序验证:

  1. 在系统服务管理器里确认 MySQL 服务已启动。
  2. 在终端执行mysql -u root -p -h 127.0.0.1 -P 3306 -e "SELECT 1",确认 TCP 能连通。
  3. 打开 Qt Creator 编译运行,看到控制台没有 driver not loaded 提示。
  4. 在主界面点刷新,确认列表有数据,状态栏总市值不是 0。

这套操作应该练到不用看笔记就能完成。只要第 2 步通了,Qt 程序里常见的 2002 和 socket 问题基本都不会发生;只要第 3 步没有驱动报错,买的 Qt 版本和 MySQL 客户端库就是匹配的。

我当年做课设时,最大的教训就是只在“环境正常”的状态下演示过一遍,没练过数据库重启之后的冷启动流程。结果答辩现场评委让我重开一次程序,我卡在连接失败上了十分钟。后来我才养成导快照和写操作清单的习惯。这套方法很简单,但真的能救场。希望你这次能用上,希望帮到你。

本文还有配套的精品资源,点击获取

返回列表