简介:面向计算机科学与技术专业学生毕业设计/课程设计的C++ Qt学生信息管理系统项目包,以Qt构建图形界面、MySQL作为数据存储,完整覆盖数据库设计、界面布局、SQL增删改查、事件绑定与异常处理等环节,适合希望系统提升C++与数据库应用能力的开发者参考。压缩包共79个文件,包括26个cpp源码、25个h头文件、23个ui界面文件,另含pro工程文件、sql建表脚本、md说明与docx使用报告,整体大小212KB,结构清晰便于工程导入和学习。已有710人学习下载。项目中内置学生、课程、宿舍、缴费、奖惩等管理模块,并附有完整的毕业设计报告与数据库脚本,既能帮助快速搭建可运行的学生信息管理Demo,也能为毕业设计文档编写、答辩演示和功能扩展提供直接参考。
1. 毕设项目是C++ Qt与MySQL的组合其实并不复杂,把 Mysql 集成进界面系统是主要工作量
学生信息管理系统可分解成三个问题:数据库建模与访问、Qt界面交互、发布部署。第一点决定数据存取的稳定性,第二点决定演示效果,第三点决定能否交得出差。框架选择上,Qt 5.15 LTS稳定,MySQL 8.0普遍;驱动用QSqlDatabase,ORM可选QSqlQuery直接执行SQL。要避免直接把SQL字符串拼进槽函数,后续维护很麻烦。真正需要想清楚的是,怎么把查询出来的数据放到表格里,删除一条学生记录时如何处理关联成绩表,以及在没有安装Qt的环境中如何跑起来。
2. Mysql数据库与Qt项目的环境适配思路里,常见问题是驱动版本不匹配和编码不一致
系统要先建库建表,再通过Qt连接,最后处理中文乱码。很多人的毕设一上来就写界面,结果连不上数据库,后面全部返工。正确顺序是先让命令行能与 MySQL 正常通信,再写 Qt 代码。
2.1 数据库基础,student表给出结构,包含 id、name、gender、major、phone、birth_date、enroll_year 等字段;成绩表 score_info 关联 student_id 与 course_id
学生信息管理系统的核心数据由两部分组成:学生基本信息和课程成绩。把这两类数据放进同一张表会带来大量冗余,所以需要拆成两个表。student 表存储学生的自然属性,score 表负责记录多门课程的成绩,通过学号把两张表关联起来。
CREATE DATABASE IF NOT EXISTS student_db DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; USE student_db; CREATE TABLE IF NOT EXISTS student ( student_id VARCHAR(20) PRIMARY KEY, name VARCHAR(50) NOT NULL, gender ENUM('男','女') DEFAULT '男', major VARCHAR(100), class_name VARCHAR(50), phone VARCHAR(11), birth_date DATE, enroll_year YEAR ) ENGINE=InnoDB; CREATE TABLE IF NOT EXISTS score ( id INT AUTO_INCREMENT PRIMARY KEY, student_id VARCHAR(20) NOT NULL, course_name VARCHAR(100) NOT NULL, score DECIMAL(5,1), FOREIGN KEY (student_id) REFERENCES student(student_id) ON DELETE CASCADE ) ENGINE=InnoDB;上面的脚本里,“CREATE DATABASE IF NOT EXISTS”只会创建一次,多次执行不会报错,适合在 Qt 启动时预检数据库中自动调用。student_id 用 VARCHAR(20) 而不是 INT,因为很多学校的学号带字母或前导零,整数类型会把这些信息丢掉。ENUM 字段在 Qt 表格里显示中文,绑定数据时注意传给 QSqlTableModel 的值要与枚举定义一致。score 表使用 AUTO_INCREMENT 作为内部主键,是为了避免一个学生选多门课时 student_id 重复导致主键冲突。外键带 ON DELETE CASCADE,删除学生时数据库会自动清理该生的所有成绩记录,省得在 Qt 代码里手动先删成绩再删学生。
2.2 在 Windows 下安装 MySQL 8.0 并配置 Qt 5.15 的连接驱动,注意 MSVC 与 MinGW 的驱动文件路径不同,需要将 libmysql.dll 放到正确位置
MySQL 安装配置教程中经常强调字符集,安装时选择 Server only 后,配置类型选 Development Machine 即可,重点是字符集必须选 utf8mb4。如果安装时选错,后面可以统一改表,但连接层的字符集还会出问题。Qt 官方在 Windows 上提供 MSVC 和 MinGW 两套编译版本,MySQL 官网下载的 C 连接器是 MSVC 编译的,两者不能随意互用。
驱动不匹配是毕设最常见问题,表现为 QSqlDatabase 查询时报 “Driver not loaded”。要确认当前 Qt 是否能加载 MySQL 驱动,可以先用一段代码列出所有可用驱动:
QStringList drivers = QSqlDatabase::drivers(); qDebug() << drivers;如果列表里包含 QMYSQL,说明驱动文件存在;如果只有 QSQLITE,说明 qsqlmysql.dll 缺失或路径不对。缺驱动时有两个处理方式:一是把 Qt 安装目录中 plugins/sqldrivers/qsqlmysql.dll 和 MySQL 的 libmysql.dll 复制到程序可执行文件旁;二是重新编译驱动,但毕设场景下复制文件更快。注意 Qt 套件必须与编译环境一致,用 MSVC 编译的 Qt 项目不能搭配 MinGW 的插件文件,否则会出现运行时崩溃。
2.3 连接字串与编码配置,在代码中设置连接参数并使用 QSqlQuery 执行 DDL 和 DML,随后检查错误细节
连接 MySQL 时用到 QSqlDatabase 单例模式。每次 addDatabase 使用不同的连接名,避免多个窗口同时打开默认连接带来的冲突。
QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", "main_conn"); db.setHostName("127.0.0.1"); db.setPort(3306); db.setDatabaseName("student_db"); db.setUserName("root"); db.setPassword("your_password"); if (!db.open()) { qDebug() << "open error:" << db.lastError().text(); return; } QSqlQuery query(db); query.exec("SELECT COUNT(*) FROM student"); if (query.next()) { qDebug() << "total:" << query.value(0).toInt(); }这里关键点是 open 失败后 lastError().text() 的输出。如果是 “Access denied”,先检查密码或用户名;如果是 “Unknown database”,说明 student_db 还没有创建,可以先不指定数据库名,连接成功后用 SQL 建库。端口参数传递 3306 即可,注意不要写成字符串,Qt 会自动转换。驱动连接成功后执行 SET NAMES utf8mb4 也可以,但与数据库默认字符集重复设置没有明显副作用。错误排查时,先用 MySQL Workbench 或命令行确认同样的连接参数能够工作,这样能快速把问题定位到 Qt 侧还是 MySQL 侧。
3. 用 Qt 构建学生信息管理系统的数据层与界面层,数据层封装再界面调用,核心是 QSqlTableModel 与信号槽联动
界面层负责录入与显示,数据层负责查询与提交。直接用 QSqlQuery 写每次增删改并不是不行,但 QSqlTableModel 可以大幅减少代码量。表格控件与数据库表形成映射关系,单元格编辑完成后可以直接提交。
3.1 QSqlTableModel 与 QTableView 的组合用法,直接修改表格内容即可同步数据库,避免手写大量 UPDATE 语句
QSqlTableModel 是 Qt 提供的表模型,它与数据库表一一对应,并提供 select、insert、delete、submit 等接口。用它操作学生表非常自然:
model = new QSqlTableModel(this, db); model->setTable("student"); model->setEditStrategy(QSqlTableModel::OnManualSubmit); model->select(); model->setHeaderData(0, Qt::Horizontal, "学号"); model->setHeaderData(1, Qt::Horizontal, "姓名"); ui->tableView->setModel(model);setEditStrategy 选择 OnManualSubmit,这样用户在表格里改数据后,只有点击“保存”按钮才真正写入数据库,避免点到其他单元格时立刻更新落库。调用 submitAll 时,如果有一条记录提交失败,整个事务回滚,不会出现一半数据写进去一半没有写。添加一条新记录的代码是:
int row = model->rowCount(); model->insertRow(row); model->setData(model->index(row, 0), ui->newIdEdit->text()); model->setData(model->index(row, 1), ui->newNameEdit->text());这里有一个看不见的坑:student_id 是主键,insertRow 之后如果学号输入为空或重复,submitAll 会在数据库层报错,Qt 这边只能拿到通用错误信息,看不到具体字段。因此在提交之前要做一次重复检查,可以通过 QSqlQuery 先查到现有学号集合,再与将要插入的数据比对。删除学生时,直接用 removeRow 然后 submitAll,外键的级联删除会自动清理 score 表中的成绩记录,这一点在设计表时就避免了遗留孤儿数据。
3.1.1 在按钮槽中实现添加、删除、修改的逻辑
添加、删除、修改三个操作集中在三个槽函数里,界面按钮通过 clicked 信号连接。修改不是单独按钮,而是依赖 QTableView 的编辑功能,用户双击单元格即可修改,需要保存时点击“保存”按钮。
void MainWindow::onSave() { if (!model->submitAll()) { qDebug() << "submit error:" << model->lastError().text(); model->revertAll(); } else { model->submitAll(); ui->statusBar->showMessage("保存成功", 3000); } } void MainWindow::onDelete() { QModelIndex cur = ui->tableView->currentIndex(); if (!cur.isValid()) { QMessageBox::warning(this, "提示", "请先选择要删除的行"); return; } model->removeRow(cur.row()); }代码中的 model->lastError() 是 QSqlError 类型,直接转 text 输出即可。删除时 currentIndex 返回无效索引,意味着点击的是表格空白区域,这时应该提示用户,而不是直接执行删除。revertAll 只回滚还没有提交到数据库的修改,已经 submitAll 的数据无法撤销,这是设计上的注意点。对毕设来说,提交前弹窗让用户确认会很加分,但确认框不要放在槽函数内部,可以单独抽成一个判断函数。
3.2 用 QSqlQuery 实现按学号与姓名模糊查询,并支持排序,前端通过行编辑框输入条件
QSqlTableModel 虽然方便,但自定义查询条件时太受限。比如按学号模糊查询,可以选择 setFilter 方式,也可以换用 QSqlQueryModel。QSqlQueryModel 是只读模型,适合显示查询结果,不直接编辑。
void MainWindow::onSearch() { QString keyword = ui->searchEdit->text().trimmed(); QSqlQuery query(db); query.prepare("SELECT student_id, name, gender, major, class_name " "FROM student WHERE student_id LIKE :kw OR name LIKE :kw " "ORDER BY enroll_year DESC"); query.bindValue(":kw", "%" + keyword + "%"); if (!query.exec()) { qDebug() << query.lastError().text(); return; } QSqlQueryModel *resultModel = new QSqlQueryModel(this); resultModel->setQuery(std::move(query)); ui->tableView->setModel(resultModel); }代码中 LIKE 语句加上两个百分号,可以匹配学号或姓名的任意位置,例如输入“张”能把“张三”“老张头”都查出来。查询模型替换了原来的表格模型后,之前编辑功能会失效,这是正常的,因为查询结果本身是只读视图。ORDER BY enroll_year DESC 让新入学的学生排在最前面,方便老师查看最近一届数据。如果想回到全部列表,重新执行 model->select() 并把模型换回 QSqlTableModel 即可。
3.2.1 模糊查询代码与参数说明
bindValue 绑定参数的做法是为防止 SQL 注入,毕设中直接拼字符串也能运行,但不建议。用占位符后,即使关键字包含单引号,也不会破坏 SQL 结构。查询结果为空时,QTableView 会显示空行,不会报错,需要另一段判断逻辑提示“没有找到匹配记录”,可以使用 query.next() 判断。
关于列头显示:QSqlQueryModel 默认列名来自数据库字段名,直接显示英文不好看。给列设置别名更有效,查询中用 AS 重命名,例如 SELECT student_id AS 学号, name AS 姓名。但 QTableView 绑定模型后,列名会直接变成中文,若通过 setHeaderData 会失效,应当优先选择 SQL 别名方案。系统常态下默认表格显示 student 表全部内容,搜索时替换成查询模型,搜索结束后重新加载。
3.3 信号槽与数据刷新一致性,多个窗口共用同一个数据库连接,使用全局单例管理连接
系统如果包含“学生信息管理”和“成绩录入”两个窗口,每个窗口各自调用 addDatabase 会创建多个物理连接,浪费资源且容易踩并发修改的问题。合理设计是让整个程序共享一个 QSqlDatabase。
class DbManager { public: static QSqlDatabase getConnection() { if (QSqlDatabase::contains("main_conn")) { return QSqlDatabase::database("main_conn"); } QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", "main_conn"); db.setHostName("127.0.0.1"); db.setPort(3306); db.setDatabaseName("student_db"); db.setUserName("root"); db.setPassword("your_password"); if (!db.open()) { qDebug() << "open failed:" << db.lastError().text(); } return db; } };利用 Qt 的连接池特性,每次通过 QSqlDatabase::database("main_conn") 获取同一个连接句柄,不需要重复打开。在子窗口对数据库做出改变后,主窗口需要刷新时直接 model->select(),但要注意该操作必须是在同一个连接对象上执行。事务操作时,两个窗口同时提交,后提交的一方可能会覆盖前面的记录,毕设中减少这种风险的办法是提交前都重新 select 一次。
4. 发布与常用排错场景:Qt 程序在另一台电脑上运行时的常见问题,以及 MySQL 驱动缺失的解决步骤
能把系统在开发机上跑通只是第一步,交毕设或者给老师演示时,经常换到一台没有 Qt 环境的电脑上,问题会在发布环节集中爆发。打包过程有固定的动作,不能只复制 exe。
4.1 用 windeployqt 工具生成 exe 的依赖文件,注意 Qt 版本与编译器配套,并把 MySQL 的 dll 一起放进部署目录
发布流程从 Release 构建开始。打开 Qt 提供的命令行工具,例如 “Qt 5.15.2 (MSVC 2019 64-bit)”,进入 Release 输出目录。运行:
windeployqt StudentSystem.exewindeployqt 会递归复制 Qt 运行时、平台插件、样式插件。但有两个东西它不会自动处理:一是 MySQL 驱动,二是 libmysql.dll。手动复制时,从 Qt 安装路径下找到 plugins/sqldrivers/qsqlmysql.dll,复制到发布目录的 sqldrivers 子目录下。然后从 MySQL 安装目录的 lib 下复制 libmysql.dll,放到 exe 同级目录。两个文件缺一不可,否则最终运行时会出现 “Driver not loaded”。
常见错误清单如下:
| 错误现象 | 原因 | 处理方法 |
|---|---|---|
| 程序启动提示缺少 Qt5Core.dll | windeployqt 未成功或平台插件缺失 | 重新执行 windeployqt,确认生成 platforms 目录 |
| QSqlDatabase: QMYSQL driver not loaded | 缺少 qsqlmysql.dll 或 libmysql.dll | 检查 sqldrivers 目录及 libmysql.dll 位置 |
| Access denied for user ‘root’ | 密码错误或 root 权限限制 | 核对 MySQL 账号密码,确认 host 为 127.0.0.1 |
| Unknown database | 未建库或库名不匹配 | 在 MySQL 中执行建库脚本 |
| Can’t connect to MySQL server | MySQL 服务未启动或端口被防火墙拦截 | 打开服务管理器,启动 MySQL80;放行 3306 |
windeployqt 生成的目录里还包括 translations 文件夹,保留它用于 qt 国际化支持。如果程序界面显示中文,但按钮文字变成乱码,多半是源文件编码问题,需要将 .cpp 文件另存为 UTF-8 with BOM。
4.2 中文乱码的两处根源:外部数据源编码与 Qt 内部显示编码,建议统一为 UTF8
Qt 5 内部使用 Unicode,中文处理本身没大问题。乱码通常出在三个环节:源文件编码、数据库表字符集、控制台输出。源文件用 GB2312 或 ANSI 保存时,编译器 VS 会按本地代码页解释,导致中文字符串显示为“鎴戠殑”之类的内容。解决方案是统一用 UTF-8 with BOM,并添加预处理指令查看编译器警告。
数据库表字符集配置为 utf8mb4 后,Qt 连接时不需要额外转换。但需要注意,MySQL 连接层默认字符集可能与表格不一致。可以在连接后执行:
SET NAMES 'utf8mb4';这条语句可以放在连接成功后执行,也可以作为一条 QSqlQuery 调用。如果还是乱码,检查 Windows 区域设置是否使用了 Unicode UTF-8,在“控制面板-区域”中勾选“Beta:使用 Unicode UTF-8 提供全球语言支持”后重启,很多诡异的乱码问题会消失。毕设中不建议通过 QString::fromLocal8Bit 来处理,那只是掩盖了编码不一致的根源。
5. 学生信息管理系统的进阶用法主要有三条:打包、加密连接配置与报表输出。这三项会让系统从能用变为好用
当基础增删改查完成,可以加入一些能够提升完成度的功能,同时保持代码量可控。下面三个方向任选其一都能让系统观感有明显提升。
5.1 把密码加密存储到配置文件中,不再写在代码里,每次运行时读取配置文件
代码中写死数据库密码在开发时方便,但系统一旦给老师或同学演示,源码会被看到,数据库密码就泄露了。常见做法是把连接参数移到一个 ini 文件,程序启动时读取。另一个层面,本地配置文件本身就是明文,所以还需要对密码做简单混淆。使用 QCryptographicHash 对密码进行 SHA-256 加盐处理,然后保存哈希值;连接数据库时,把用户输入的密码做同样处理,比对哈希是否一致,这样可以保护密码不被直接读出来,而真正用于连接数据库的密码密钥可以用 macOS 钥匙串或 Windows 凭据管理器存储,但实现成本较高,不建议在毕设中引入。
可行的方式是提供一个配置文件,里面存放 MySQL 用户名和密码。使用 QSettings 读取:
QSettings settings("config.ini", QSettings::IniFormat); settings.beginGroup("mysql"); QString host = settings.value("host", "127.0.0.1").toString(); int port = settings.value("port", 3306).toInt(); QString user = settings.value("user", "root").toString(); QString password = settings.value("password").toString(); settings.endGroup();参数后面的默认值省去了手工填写的步骤,host 默认 127.0.0.1,port 默认 3306。密码字段在部署时由人工写入。配置文件与 exe 放在同一目录,程序启动时如果找不到,可以直接用写死的默认值,再给出“配置文件缺失,使用默认连接参数”的提示,这样能兼容不同使用环境。
5.2 导出一份包含学生信息和成绩统计的 Excel 或 PDF 报表,能够提升系统完成度
在 Qt 中输出 Excel 有几种路径:若目标机器安装了 Office,可以用 QAxObject 操作 Excel COM 组件,编程灵活但依赖 Office;如果不依赖 Office,可以使用 CSV 文件,Excel 也能打开,通用性好且代码量少。
生成 CSV 的核心是读取数据库内容并组合写文件:
QFile file("students_report.csv"); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { return; } QTextStream out(&file); out.setEncoding(QStringConverter::Utf8); out << "学号,姓名,专业,班级\n"; QSqlQuery query(db); query.exec("SELECT student_id, name, major, class_name FROM student"); while (query.next()) { out << query.value(0).toString() << "," << query.value(1).toString() << "," << query.value(2).toString() << "," << query.value(3).toString() << "\n"; } file.close();上述代码使用 QTextStream 并设置 UTF-8 编码,生成的 CSV 文件用 Excel 打开时中文能正常显示。如果把内容变成绩统计,SQL 中可以用 GROUP BY 和 AVG 计算各课程平均分,然后把结果写入 CSV 另一列。如果需要真正的图表展示,推荐 Qt Charts 模块,在 .pro 文件里添加 QT += charts,然后使用 QChart 显示柱状统计图,能直观展示各课程平均分差异。
5.3 给系统加上一份自检脚本,启动时验证 MySQL 驱动、连接与数据表完整性,减少现场排错时间
系统接手另一台电脑运行前,任何人都无法预判目标环境的 MySQL 服务是否启动、驱动是否完整。因此,程序启动后立即执行一次自检,错误信息直接弹窗或写入日志,能省去很多现场排查时间。
自检顺序先驱动、再连接、后检查表:
void MainWindow::selfCheck() { if (!QSqlDatabase::isDriverAvailable("QMYSQL")) { QMessageBox::critical(this, "驱动错误", "QMYSQL 驱动不可用,请检查 Qt 插件目录"); return; } QSqlDatabase db = DbManager::getConnection(); if (!db.isOpen()) { QMessageBox::critical(this, "连接失败", db.lastError().text()); return; } QSqlQuery query(db); if (!query.exec("SELECT COUNT(*) FROM student")) { QMessageBox::critical(this, "表缺失", "student 表不存在,请执行建表脚本"); return; } }isDriverAvailable 检查的是当前 Qt 编译是否包含了 MySQL 驱动,连接失败再检查 MySQL 服务状态,表缺失则提示执行建表脚本。自检函数在构造函数中使用 QTimer::singleShot(0, this, &MainWindow::selfCheck) 调用,让界面先显示出来,然后再进行耗时操作。状态栏可显示最终通过自检的学生数量,如“共加载 128 条学生记录”,用户一眼就知道数据是否完整。这套流程之后,即使在另一台电脑上部署,最多只需要调整 config.ini 里的密码,剩下的错误会直接指出具体瓶颈,减少无头绪的日志翻找。
本文还有配套的精品资源,点击获取