news 2026/9/14 14:30:00

基于Qt6与SQLite的学生信息管理系统:从登录到CRUD的完整工程化实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Qt6与SQLite的学生信息管理系统:从登录到CRUD的完整工程化实践

简介:这是一份基于Qt6.2.4开发的学生信息管理系统完整源码与配套设计报告,面向C++/Qt学习者、高校学生及需要完成课程设计或毕业设计的开发者。系统实现了注册登录、学生信息增删查改、管理员信息管理等核心功能,界面采用QPushButton、QLineEdit、QTableView等常用控件构建,风格简约大方。数据层使用SQLite数据库,并通过C++结构体封装表字段,便于统一读写;配套设计报告涵盖课题要求、整体流程与重要代码说明,能有效帮助读者理解Qt与数据库结合开发的完整思路。资源包共82个文件,主要包含24个cpp源文件、20个h头文件、16个ui界面文件,以及pro工程文件、数据库文件、资源文件和docx设计报告,压缩包整体约617KB。包内还保留了多个版本目录,可对照各版本功能演进,适合边读报告边调试;目前已有312人学习下载,从界面布局到数据库操作均有对应模块,结构清晰,是练习桌面应用开发、完善课程设计的实用参考。

1. 为什么这份基于 Qt 的学生信息管理系统值得拆开看

多数课程设计里的学生信息管理系统,代码还停留在黑窗口里 printf 菜单,能跑通业务却看不出工程素养。这份项目不一样:它是用 Qt6.2.4 做的完整 GUI 应用,压缩包里有登录页、主窗口、增删查改对话框、SQLite 数据库文件,以及四个递进版本(mgr 1.0 到 4.0),能清楚看到界面从裸控件到带图标、带头像的演进过程。更难得的是附了一份详细设计报告,把需求分析、数据库设计、关键代码讲得很完整。适合正在做 C++ 课程设计的学生,也适合想快速上手 Qt Widgets + SQLite 开发范式、但又不想啃官方文档的入门开发者。接下来围绕数据库层、登录鉴权、CRUD 链路和版本演进四个维度展开。

2. 数据库设计与 stusql 结构体映射

2.1 为什么是 SQLite 而不是 MySQL

项目里 data.db 是一个 SQLite 文件,开发工具虽然列出了 Navicat Premium 16,但它只负责可视化编辑,运行时真正干活的是 Qt 自带的 QSQLITE 驱动。选 SQLite 的理由很直接:零配置、单文件、随项目拷走即用,不需要安装数据库服务端。对课程设计答辩演示来说,这三点比 MySQL 的「专业感」更有实际价值——评委大概率当场让你换一台机器跑,SQLite 不用配环境,双击就能起。

Qt6.2.4 里使用 SQLite 只需在 .pro 文件加一行QT += sql,然后在代码里通过QSqlDatabase::addDatabase("QSQLITE")建立连接。整个数据库层被封装在stusql.h/stusql.cpp里,专门负责连接管理、建表、增删查改。这种把数据库操作与界面层隔离的做法,后续换数据库或改表结构时,只需要动这一层。

2.2 两张核心表的结构定义

系统数据分成两类:学生业务数据和用户登录数据。学生信息表存储学号、姓名、年龄等教育属性,用户表存储账号密码与权限。两个表没有外键关系,登录认证和业务查询是两条独立链路。

字段名类型约束说明
idINTEGERPRIMARY KEY AUTOINCREMENT自增主键,仅内部使用
stu_idTEXTUNIQUE NOT NULL学号,业务唯一标识
nameTEXTNOT NULL姓名
ageINTEGER年龄
gradeTEXT年级
stu_classTEXT班级
phoneTEXT电话
addressTEXT地址

用户表字段相对精简,核心是 role 权限标记:

字段名类型约束说明
usernameTEXTPRIMARY KEY登录用户名
passwordTEXTNOT NULL密码,课程设计为明文存储
roleINTEGERDEFAULT 00 普通用户,1 管理员

建表语句使用CREATE TABLE IF NOT EXISTS,保证程序重复启动不会报错。学号加了 UNIQUE 约束,防止同一学号被重复录入两次,这是「增删查改」里最容易踩到的边界问题。

CREATE TABLE IF NOT EXISTS student_info ( id INTEGER PRIMARY KEY AUTOINCREMENT, stu_id TEXT UNIQUE NOT NULL, name TEXT NOT NULL, age INTEGER, grade TEXT, stu_class TEXT, phone TEXT, address TEXT ); CREATE TABLE IF NOT EXISTS user_info ( username TEXT PRIMARY KEY, password TEXT NOT NULL, role INTEGER DEFAULT 0 );

这里把班级字段命名为stu_class而不是class,是因为在部分 SQL 方言和 ORM 映射里class容易与关键字混淆。设计数据库结构时,字段名尽量带业务前缀,既避免关键字冲突,也让跨表引用时能一眼看出归属。

2.3 表结构如何映射成 C++ 结构体

stusql.h里定义了两个结构体,这是整个项目数据流的核心约定。界面层不直接操作 QSqlQuery,而是通过结构体传值,好处是函数签名清晰、类型安全,界面代码里不会到处散落字符串拼接的 SQL。

// stusql.h —— 把数据库表映射成 C++ 结构体,供界面层传参使用 struct StudentInfo { int id; // 自增主键,新增时不需要手动赋值 QString stuId; // 学号 QString name; // 姓名 int age; // 年龄 QString grade; // 年级 QString stuClass; // 班级,避开 class 关键字 QString phone; // 电话 QString address; // 地址 }; struct UserInfo { QString username; // 登录用户名 QString password; // 密码 int role; // 权限:0 普通用户,1 管理员 };

结构体字段与数据库列一一对应,顺序保持一致,这样写 INSERT 语句时绑定参数不容易错位。id字段在新增时忽略,交给数据库自增;stuId才是业务主键,删除、修改、查询都优先用学号定位记录。

2.4 连接数据库与初始化建表

stusql.cpp里的初始化函数完成两件事:建立数据库连接、确保表存在。很多入门项目把建表 SQL 写在 main 函数里,表一多就乱;集中到initDb里统一管理,后续加表只需要在这个函数里追加一条exec

// stusql.cpp —— 初始化数据库连接,返回是否成功 bool StuSql::initDb(const QString &dbPath) { // QSQLITE 是 Qt 内置驱动,不需要额外配置 m_db = QSqlDatabase::addDatabase("QSQLITE"); m_db.setDatabaseName(dbPath); // 指定 data.db 所在路径 if (!m_db.open()) { qDebug() << "open database failed:" << m_db.lastError().text(); return false; } QSqlQuery query; // IF NOT EXISTS 保证重复初始化不会因为表已存在而报错 query.exec("CREATE TABLE IF NOT EXISTS student_info (" "id INTEGER PRIMARY KEY AUTOINCREMENT," "stu_id TEXT UNIQUE NOT NULL," "name TEXT NOT NULL," "age INTEGER," "grade TEXT," "stu_class TEXT," "phone TEXT," "address TEXT)"); query.exec("CREATE TABLE IF NOT EXISTS user_info (" "username TEXT PRIMARY KEY," "password TEXT NOT NULL," "role INTEGER DEFAULT 0)"); return true; }

调用时传入 data.db 的完整路径。这里有一个容易被忽略的细节:如果只写setDatabaseName("data.db"),程序的工作目录决定了它找到哪个文件。从 Qt Creator 直接运行和打包后双击 exe 运行,工作目录往往不一样,后续章节会专门讲怎么处理这个坑。

值得一提的是,这套「结构体 + 独立数据库类」分层模型,即使以后把 SQLite 换成 MySQL,界面代码也几乎不用改。数据库层的返回值、入参保持结构体不变,底层 SQL 方言差异被隔离在stusql.cpp内部,这就是它比直接堆 QSqlQuery 更工程化的原因。

3. 登录模块与权限控制

3.1 登录界面的控件组成

page_login.ui是系统的入口界面,结构不复杂:两个 QLineEdit 分别接收用户名和密码,一个登录按钮触发校验,一个注册按钮拉起注册对话框,密码框设置了EchoMode = Password让输入以圆点显示。整个页面用垂直布局排列,控件居中,是 Qt Widgets 里最标准的登录窗体写法。

界面代码和业务逻辑分离这一点在 Qt 里是天然优势。.ui文件由 Qt Designer 维护,生成的ui_page_login.h负责控件初始化,开发者只需要在page_login.cpp里写槽函数。即使完全不懂布局代码,在设计器里拖控件也能完成 90% 的界面工作。

登录按钮的点击信号在构造函数里连接到自定义槽函数:

// page_login.cpp —— 构造函数中连接信号与槽 PageLogin::PageLogin(QWidget *parent) : QWidget(parent) { ui->setupUi(this); // 点击登录按钮时调用 onLoginClicked 槽函数 connect(ui->btnLogin, &QPushButton::clicked, this, &PageLogin::onLoginClicked); // 点击注册按钮时打开注册对话框 connect(ui->btnRegister, &QPushButton::clicked, this, &PageLogin::onRegisterClicked); }

Qt6 推荐使用函数指针形式的 connect 语法,在编译期就能检查信号和槽是否存在,比旧版的SIGNAL/SLOT宏更安全。按钮对象名btnLoginbtnRegister也是后续代码里引用的唯一标识,所以 Designer 里给控件起名要有语义,不要保留默认的pushButton_2这种名字。

3.2 登录校验的槽函数实现

登录的核心是查user_info表,比对用户名和密码是否匹配。这里有两个容易出错的点:输入框要trimmed()去掉首尾空格;空值要提前拦截,避免无意义的数据库查询。

// page_login.cpp —— 登录按钮点击后的完整处理 void PageLogin::onLoginClicked() { QString username = ui->editUser->text().trimmed(); QString password = ui->editPwd->text(); // 空值直接拦截,不进入数据库查询流程 if (username.isEmpty() || password.isEmpty()) { QMessageBox::warning(this, "提示", "用户名和密码不能为空"); return; } UserInfo user; // 调用 stusql 层的登录校验,成功时把用户信息带回来 if (m_stuSql->login(username, password, user)) { // 登录成功后进入主窗口,并携带当前用户信息 MainWindow *w = new MainWindow(user); w->show(); this->close(); } else { QMessageBox::critical(this, "错误", "用户名或密码错误"); } }

留意m_stuSql->login的参数设计:用户名和密码是入参,UserInfo &user是出参,用于把数据库里的 role 权限值带回主窗口。权限判断不放在登录页这里,而是交给 MainWindow 构造函数去决定显示哪些功能入口,职责更清晰。

数据库层的login函数用的是参数绑定查询,不是字符串拼接:

// stusql.cpp —— 数据库层登录校验 bool StuSql::login(const QString &username, const QString &password, UserInfo &info) { QSqlQuery query; query.prepare("SELECT username, password, role " "FROM user_info WHERE username = ? AND password = ?"); query.addBindValue(username); query.addBindValue(password); if (query.exec() && query.next()) { info.username = query.value(0).toString(); info.password = query.value(1).toString(); info.role = query.value(2).toInt(); return true; } return false; }

prepareaddBindValue是 Qt 里替代字符串拼接的标准做法。传入的用户名密码无论包含什么特殊字符,都会被当作普通值处理,不会破坏 SQL 语句结构。这个习惯比「把用户输入拼进 SQL 再 exec」安全得多,哪怕课程设计不面向上线环境,也应该按正确方式写。

3.3 注册流程与权限字段

注册对话框dlg_adduser.ui一般包含用户名、密码、确认密码三个输入框和一个保存按钮。保存前做两次校验:确认密码与密码是否一致;用户名是否已在user_info表中存在。后者的 SQL 是SELECT COUNT(*) FROM user_info WHERE username = ?,计数大于 0 就提示更换用户名。

新注册用户的 role 固定写 0,即普通用户。管理员账号要么在设计报告里的初始化数据中预置,要么通过后续的「用户管理」功能由已有管理员创建。这种设计避免了注册页出现「请选择权限」这种安全漏洞——如果注册时能自选管理员,整个权限体系就形同虚设了。

注册的核心插入语句如下:

// stusql.cpp —— 注册新用户 bool StuSql::registerUser(const QString &username, const QString &password) { QSqlQuery query; // role 固定为 0,管理员只能由管理员在用户管理中创建 query.prepare("INSERT INTO user_info (username, password, role) " "VALUES (?, ?, 0)"); query.addBindValue(username); query.addBindValue(password); return query.exec(); }

3.4 登录安全上的两个取舍

这个项目密码以明文存在 SQLite 里,这是课程设计场景下的普遍做法,但至少应该知道它的局限。正式系统通常用QCryptographicHash::hash(password, QCryptographicHash::Sha256)加盐后存摘要,校验时对输入做同样哈希再比较。迁移成本也不高:给 user_info 表加一个 salt 字段,login 时改成查回 salt 再哈希比对即可。

另一个取舍是登录成功后的状态保持。这个项目直接构造 MainWindow 并把 UserInfo 传进去,没有做会话管理和超时退出。如果后续想扩展,可以在 UserInfo 里加一个loginTime字段,主窗口用 QTimer 定期检查登录时长,超过阈值就强制返回登录页面。这两点写进设计报告的「改进方向」里,答辩时反而是加分项。

4. 学生信息的增删查改完整链路

4.1 主窗口模块划分与信号槽连接

mainwindow.ui是系统的核心界面,布局分左右两区:左侧是编辑表单区,放置学号、姓名、年龄、年级、班级、电话、地址等一组 QLineEdit;右侧是 QTableView 表格,下方是查询、添加、修改、删除、重置五个按钮。这种「表单 + 表格」的布局是管理类系统的通用范式,增删查改都在同一个页面完成,不需要频繁跳转。

表格通过QSqlQueryModel与数据库连接。这个模型类直接执行 SQL 并将结果集转换为表格数据,不需要手动填充 QTableWidgetItem,代码量大幅减少。mainwindow.cpp 里的构造函数做了两件事:初始化模型绑定 tableView,以及根据登录用户的 role 决定是否显示用户管理入口。

// mainwindow.cpp —— 构造函数中初始化表格模型与权限 MainWindow::MainWindow(const UserInfo &user, QWidget *parent) : QMainWindow(parent) { ui->setupUi(this); m_model = new QSqlQueryModel(this); // 给 tableView 设置只读模型,禁止在表格里直接编辑 ui->tableView->setModel(m_model); ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers); // 权限控制:普通用户隐藏用户管理按钮 if (user.role != 1) { ui->btnManageUser->setVisible(false); } refreshTable(); // 初始加载全部学生数据 }

refreshTable()是所有增删改操作完成后的统一刷新入口。它重新执行一次查询 SQL,并把结果重新赋给 model,表格随之更新。把刷新逻辑抽成独立函数,避免每个槽函数里重复写这段代码。

4.2 查询:按条件过滤并刷新表格

查询按钮的核心逻辑是动态拼接 WHERE 子句。学号和姓名两个条件可以同时输入,也可以只填一个,所以不能写死 SQL,要根据输入框状态组装查询语句。

// mainwindow.cpp —— 查询按钮处理 void MainWindow::onBtnQueryClicked() { QString name = ui->editName->text().trimmed(); QString stuId = ui->editStuId->text().trimmed(); // 基础语句,WHERE 1 = 1 是为了方便后续追加条件 QString sql = "SELECT stu_id, name, age, grade, stu_class, " "phone, address FROM student_info WHERE 1 = 1"; if (!name.isEmpty()) { // LIKE 模糊匹配,满足「记不全名字只记得姓」的场景 sql += QString(" AND name LIKE '%%1%'").arg(name); } if (!stuId.isEmpty()) { sql += QString(" AND stu_id LIKE '%%1%'").arg(stuId); } sql += " ORDER BY stu_id"; m_model->setQuery(sql); // 设置表格列标题,默认显示数据库字段名 m_model->setHeaderData(0, Qt::Horizontal, "学号"); m_model->setHeaderData(1, Qt::Horizontal, "姓名"); m_model->setHeaderData(2, Qt::Horizontal, "年龄"); // 其余列头设置省略,写法一致 }

WHERE 1 = 1是一个常用写法,它本身没有任何过滤效果,纯粹是为了让后面的AND可以直接拼接。这样写虽然简洁,但要注意QString::arg不会转义单引号,如果输入框里带了单引号,查询 SQL 会出错。生产级代码应该把整个查询改成参数绑定,这里用拼接是课程设计里常见的简化处理。

查询结果直接通过setQuery反映到表格,不需要额外刷新操作。列头用setHeaderDatastu_id改成中文「学号」,否则表格顶部显示英文字段名,用户体验会差一截。

4.3 新增与修改:复用同一个对话框

新增和修改功能对应的对话框是dlg_addstu.ui。新增时所有输入框都是空白,修改时双击表格某一行,该行数据会回填到对话框。两种模式用同一个对话框类DlgAddStu,通过一个构造函数参数区分是添加还是编辑,这样表单校验逻辑只需要维护一份。

修改时回填数据的关键代码:

// dlg_addstu.cpp —— 编辑模式下回填数据 void DlgAddStu::setEditData(const StudentInfo &info) { ui->editStuId->setText(info.stuId); ui->editName->setText(info.name); ui->spinAge->setValue(info.age); ui->editGrade->setText(info.grade); ui->editClass->setText(info.stuClass); ui->editPhone->setText(info.phone); ui->editAddress->setText(info.address); // 编辑模式下学号不允许修改,锁定输入框 ui->editStuId->setReadOnly(true); }

学号在编辑模式下被锁定只读,原因是stu_id是 UNIQUE 约束的主键字段,修改它可能导致主键冲突,也会破坏与其他表潜在的业务引用关系。这个设计在数据一致性上是对的:要改学号,应该删除这条记录再新增,而不是直接编辑主键。

新增保存时,从对话框控件收集数据,组装成StudentInfo结构体传给数据库层。业务校验放在对话框的accept()之前,比如学号不能为空、年龄必须在合理范围内,不满足就直接QMessageBox::warning并拒绝关闭对话框:

// dlg_addstu.cpp —— 保存按钮校验 void DlgAddStu::onBtnSaveClicked() { QString stuId = ui->editStuId->text().trimmed(); if (stuId.isEmpty()) { QMessageBox::warning(this, "提示", "学号不能为空"); return; } // 校验通过,把数据存入成员变量并关闭对话框 m_info.stuId = stuId; m_info.name = ui->editName->text().trimmed(); m_info.age = ui->spinAge->value(); // ... accept(); // 关闭对话框并返回 QDialog::Accepted }

主窗口里通过dlg.exec()的返回值判断用户是否点了保存:

// mainwindow.cpp —— 添加按钮处理 void MainWindow::onBtnAddClicked() { DlgAddStu dlg(this); if (dlg.exec() == QDialog::Accepted) { StudentInfo info = dlg.getInfo(); // 参数绑定插入,避免手动拼接 SQL QSqlQuery query; query.prepare("INSERT INTO student_info " "(stu_id, name, age, grade, stu_class, phone, address) " "VALUES (?, ?, ?, ?, ?, ?, ?)"); query.addBindValue(info.stuId); query.addBindValue(info.name); query.addBindValue(info.age); query.addBindValue(info.grade); query.addBindValue(info.stuClass); query.addBindValue(info.phone); query.addBindValue(info.address); if (query.exec()) { QMessageBox::information(this, "成功", "学生信息已添加"); refreshTable(); } else { QMessageBox::critical(this, "错误", query.lastError().text()); } } }

INSERT 的参数顺序与prepare里的占位符一一对应,写代码时保持与结构体字段顺序一致能有效降低错位概率。lastError().text()在很多情况下能直接给出 SQLite 的原始错误原因,比如 UNIQUE 约束冲突,调试时极有用。

4.4 删除:先选中行再按学号删除

删除操作依赖用户先在表格里选中一行。实现时通过currentIndex()获取选中位置,再从模型里取出该行的stu_id,然后执行 DELETE。删除前弹确认框是为了防止误点,这属于最基本的交互保护。

// mainwindow.cpp —— 删除按钮处理 void MainWindow::onBtnDeleteClicked() { QModelIndex idx = ui->tableView->currentIndex(); if (!idx.isValid()) { QMessageBox::warning(this, "提示", "请先选中要删除的行"); return; } // 从当前行的第 0 列取学号 QString stuId = m_model->record(idx.row()).value("stu_id").toString(); int ret = QMessageBox::question(this, "确认删除", "确定要删除学号为 " + stuId + " 的学生吗?"); if (ret == QMessageBox::Yes) { QSqlQuery query; query.prepare("DELETE FROM student_info WHERE stu_id = ?"); query.addBindValue(stuId); if (query.exec()) { refreshTable(); } } }

这里以stu_id作为 WHERE 条件而不是自增 id,是因为学号在业务层面是稳定的唯一标识。如果后续做了「学生成绩表」等关联表,外键引用的也是学号而非内部 id。删除兜底逻辑是可选的:如果不希望物理删除数据,可以在 student_info 表加一个status字段,把删除操作改成UPDATE student_info SET status = 0 WHERE stu_id = ?,查询时默认过滤status = 1。这种软删除方案在生产系统里更常见,写进设计报告的「系统可扩展性」一节会很加分。

4.5 管理员信息管理复用同一套 CRUD

「管理员信息管理」即用户管理功能,它复用同一套 Model-View 架构。主窗口里点击「用户管理」按钮后,弹出一个新的管理对话框,内部也是一个 QTableView 加一组增删改按钮。区别只是操作的表换成了user_info,以及修改密码、重置权限这些字段维度上的变化。

// 用户管理对话框 —— 加载用户列表 void DlgManageUser::loadUsers() { QSqlQueryModel *model = new QSqlQueryModel(this); model->setQuery("SELECT username, password, role FROM user_info " "ORDER BY username"); // role 字段转成可读文本显示 ui->tableView->setModel(model); }

用户管理里有一点和普通 CRUD 不同:不允许删除当前正在登录的管理员账号,否则系统里可能没有任何管理员。实现上可以把当前登录用户名传进对话框,删除时做个比对,相等就弹窗拒绝。这个细节设计报告里如果写了,会让整体方案显得更完整。

5. 从 mgr 1.0 到 4.0:版本演进里的工程化细节

5.1 四个版本到底改了些什么

压缩包内保留了四个递进版本,对照文件列表能还原一条真实的开发路径。mgr 1.0 是功能骨架:登录页、主窗口、增删查改、SQLite 库都齐了,但没有任何资源文件,界面是纯系统默认样式。mgr 2.0 开始引入res.qrcpic1.icouser.jpg,意味着程序有了自定义图标和用户头像,可执行文件在资源文件编译后变成单一 exe,拷给别人不用附带图片。mgr 3.0 在此基础上继续打磨界面,mgr 4.0 文件结构最完整,是最终提交版。

这个演进顺序对课程设计答辩很有参考价值:先跑通业务流程,再做视觉和工程化完善。很多学生一上来就折腾 QSS 样式,结果业务逻辑没写完,后面被功能问题拖垮。正确顺序应该是「功能完整 → 界面美化 → 资源整合 → 撰写报告」。

资源文件相关的 .pro 配置如下:

# stumgr.pro —— Qt 工程文件关键配置 QT += core gui sql greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = StudentManager TEMPLATE = app SOURCES += main.cpp \ mainwindow.cpp \ page_login.cpp \ stusql.cpp \ dlg_addstu.cpp \ dlg_adduser.cpp HEADERS += mainwindow.h \ page_login.h \ stusql.h \ dlg_addstu.h \ dlg_adduser.h FORMS += mainwindow.ui \ page_login.ui \ dlg_addstu.ui \ dlg_adduser.ui # 资源文件,图标和头像图片都挂在 qrc 下 RESOURCES += res.qrc

QT += sql必须显式声明,否则链接阶段会报一堆 QSqlDatabase 相关的未定义错误。RESOURCES指向的 res.qrc 里用 XML 格式列出了 pic1.ico 和 user.jpg,Qt 会把这些资源编译进二进制文件,运行时通过:/pic1.ico这样的路径访问,不依赖外部文件。

5.2 设计报告的核心结构建议

项目自带的高级语言程序设计大作业实验报告.docx,结构上可以按实验报告模板拆成五个部分。需求分析写清楚系统要解决什么问题,画用例图比纯文字描述更直观。概要设计给模块划分图,说明界面层、逻辑层、数据层三类模块如何交互。详细设计挑两到三个核心类重点写:StuSql 的数据库操作、MainWindow 的信号槽处理、PageLogin 的登录校验,配合类图和时序图。

测试部分值得多花笔墨。把登录失败、重复学号录入、空列表删除这些典型场景列成测试用例表,每条写明输入、操作、预期结果。这部分最能体现文档完成度,也是评委喜欢翻的地方。最后加一节「系统不足与改进方向」,提出压缩包内代码已知的优化空间,与上述代码中提到的软删除、密码加盐、参数绑定查询防护方案相结合,显示系统思维深度。

5.3 data.db 路径问题的根治方案

程序里用相对路径访问 data.db 会带来一个恼人的问题:在 Qt Creator 里运行正常,把 exe 拷到另一个目录双击,数据库连不上了。原因是工作目录变动,相对路径指向的位置变了。根治方案是程序启动时用应用所在目录拼绝对路径:

// main.cpp —— 使用应用程序路径定位数据库 #include <QApplication> #include <QDir> #include "stusql.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); // 始终指向 exe 所在目录,避免工作目录不同导致的路径问题 QString dbPath = QDir(QCoreApplication::applicationDirPath()) .filePath("data.db"); // 若想每次发布都重新初始化空库,可在开发阶段强制删除旧文件 StuSql sql; if (!sql.initDb(dbPath)) { return -1; } // 启动登录页 PageLogin login; login.show(); return a.exec(); }

applicationDirPath()返回 exe 所在目录,不随系统当前目录变化。QDir::filePath负责拼接路径并自动处理斜杠方向,在 Windows 和 Linux 下行为一致。配合QTextCodec处理中文编码,Qt6 默认 UTF-8 环境下中文基本不出问题,但如果是老工程迁移,需要在 main 函数里显式指定编码策略,否则从数据库读出的中文姓名字段可能出现乱码。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 14:29:41

元初混沌体系 第四卷 太赫兹高频通信与超宽带频谱体系:第四十九篇 星际高温航天器天线太赫兹热畸变修正模型

第四十九篇 星际高温航天器天线太赫兹热畸变修正模型本篇单元定位本篇隶属第四卷太赫兹高频通信与超宽带频谱体系 第三单元星际真空太赫兹超远距传输架构&#xff08;37–54&#xff09;&#xff0c;为星际太赫兹传输体系极端温度工况适配、硬件形变误差清零、波束物理偏移修正…

作者头像 李华
网站建设 2026/9/14 14:28:12

2026汽车轮胎锁选购全攻略:类型、价格、品牌与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 14:27:15

手机购物商城HTML源码解析:从静态页面到可联调的前端原型

简介&#xff1a;手机购物商城网站HTML源码是一套面向移动端电商场景的前端页面源码包&#xff0c;适合正在学习HTML/CSS/JavaScript的开发者&#xff0c;以及需要快速搭建手机购物商城原型的个人站长或产品经理。资源包为RAR压缩格式大小9.86MB&#xff0c;内部以HTML页面为主…

作者头像 李华
网站建设 2026/9/14 14:25:48

二手交易小程序全栈拆解:Spring Boot + 微信小程序毕设实战

简介&#xff1a;微信小程序二手物品交易系统源码与数据库&#xff0c;面向高校毕业设计及Java课程设计场景&#xff0c;为需要完成类似选题的学生提供一套可直接运行学习的完整项目。源码经过本地编译验证&#xff0c;下载后配置JDK、MySQL及微信开发者工具环境即可运行&#…

作者头像 李华