拓冰建站拓冰建站
首页 / 资讯中心 / 正文

C++ Qt图书管理系统课设全攻略:从数据库设计到打包发布

简介面向期末大作业的 C 课程设计资料包基于 Qt 实现图书管理系统包含完整源码、项目配置与可运行程序适合正在完成课程设计或需要参考 Qt 实战项目的计算机专业学生。压缩包共 211 个文件以 cpp 源文件、h 头文件、ui 界面布局文件为主另有 qrc 资源文件、sql 数据库脚本、sln 解决方案及 exe 可执行文件结构清晰既可用于学习 Qt 界面与业务逻辑组织也可直接查看编译运行效果。包体约 11.34MB已有 6166 人浏览学习。资源中涵盖 BookComp、Borrow_Book、Classification 等模块对应图书信息管理、借阅登记、分类展示等功能场景有助于理解图书管理系统的模块划分与面向对象设计思路。文件类型中还包含 obj、tlog 等编译中间产物便于调试追踪而 ui 与 qrc 文件则直观呈现界面与图标资源配置读者可据此掌握 Qt 工程从设计到成型的完整流程也可作为二次开发或答辩演示的基础。1. C期末大作业用Qt做图书管理系统图的就是一次过每学期末群里最常出现的就是“求一个能跑的图书管理系统源码”。C课程设计选这个题老师实际在考三件事类的封装继承有没有真懂、界面与逻辑分不分得开、数据能不能真正落库。用Qt做是高校课设里的默认选项——SQLite驱动开箱即用Widgets组件能覆盖登录、表格、表单、弹窗的全部需求。标题里的“源码.zip”说明交付物是完整工程不是零散demo。答辩时老师先看三条线能否编译通过、数据是否写进数据库、增删改查与借还主流程有没有边界判断。下面的内容按“先定架构、再跑通代码、最后打包排错”的顺序拆解。适合两类人时间紧、想拿现成工程改造上交的已经会写点Qt想把借还事务、表格模型绑定、windeployqt打包这些细节弄透的。2. 先定架构Qt图书管理系统的模块划分与数据库选型2.1 为什么是 Qt Widgets SQLite而不是 QML 或 MySQL图书管理系统的核心场景是大量表单、表格和弹窗操作Qt Widgets 里的 QTableView、QDialog、QMessageBox 就是为这种桌面 CRUD 准备的。控件层级稳定Qt 5 到 Qt 6 的升级里这些 API 几乎没有变化网上能查到的 qt 教程也大多以 Widgets 为例子遇到问题最容易搜到答案。QML 做动效好看但意味着同时维护 QML 和 C 两套代码期末赶工节奏下只会增加风险不是课设应该碰的东西。数据库方面常见对比是 MySQL 和 SQLite。基于 Java 或 PHP 的图书管理系统毕业设计通常要搭 Tomcat、Apache 加服务端数据库交到老师机器上环境对不上就全盘崩溃SQLite 是单文件库Qt 自带 QSQLITE 驱动程序第一次运行时建表、写入、演示一个 library.db 文件全部带走。这也是“源码.zip”交付时最稳妥的组合——评分时不需要老师额外装任何服务双击就能跑。编译器选型同样要结合目标机器。老师习惯用 VS2022 打开 .sln 的话你要额外准备 CMake但多数高校课设的验收方式是打开 Qt Creator 导入 .pro 文件。所以我一般建议统一用 Qt 5.15.2 的 MSVC2019_64 套件5.15 是 LTS 版本资料量最大后面用 windeployqt 打包也最顺。MinGW 套件写起来没差别但打包产物在裸机上的运行库依赖更多演示翻车概率高。2.2 三张表设计books、readers、borrow_records答辩时第一个问题往往是“你数据库怎么设计的”。只建一张 book 表、把借阅状态当字段不断覆盖的写法会在统计历史记录时全部乱掉。最合理的拆分是三张表books 表结构如下字段类型说明idINTEGER PRIMARY KEY AUTOINCREMENT图书主键titleTEXT NOT NULL书名authorTEXT作者publisherTEXT出版社isbnTEXT UNIQUEISBN 编号totalINTEGER馆藏总量availableINTEGER当前可借数量readers 表保存读者 id、name、phoneborrow_records 表保存 id、book_id、reader_id、borrow_date、return_date、status。这里有两个设计点提前想清楚写代码时就不用返工。第一total 和 available 必须分开。total 是馆藏量available 是剩余可借量每次借书只减 available还书再加回来。判断某本书能不能借只需要一条 SELECT available FROM books WHERE id ?不需要 JOIN 计算。第二borrow_records 的 status 用 0 表示在借、1 表示已还而不是靠 return_date 是否为空判断。NULL 在 QSqlQuery 的取值和传参里都要多写分支用整型状态位在 WHERE 和 ORDER BY 里都更直接答辩时可以解释成“用一点冗余换查询简单”老师一般认可。2.3 工程布局与 .pro 配置交出去的工程目录要一眼能看清模块边界。常见做法是拆成数据库、登录、主窗口、图书编辑四个部分LibraryManager/ ├── LibraryManager.pro ├── main.cpp ├── dbmanager.h / dbmanager.cpp ├── login.h / login.cpp / login.ui ├── mainwindow.h / mainwindow.cpp / mainwindow.ui ├── bookdialog.h / bookdialog.cpp └── library.db // 首次运行自动生成LibraryManager.pro 里最关键的几行QT core gui widgets sql CONFIG c11sql 模块漏加是新手最高频的错误头文件都写对了编译时 QSqlDatabase 就是解析不过。widgets 在 Qt 5 之后必须显式声明Qt 6 里同样保留。C11 是底线配置Qt 5.15 官方预编译包按 C17 生成代码这里写 c11 只是为了兼容老师机器上可能的老编译器。.ui 文件由 Qt Designer 生成moc 元对象编译器在构建时自动处理不需要手动添加额外依赖。类之间的继承关系也在这一阶段定好LoginDialog 继承 QDialogMainWindow 继承 QMainWindowDBManager 用单例封装所有数据库访问都走它。子类里要覆盖 QDialog 的 accept() 时记得加 override 关键字——这是 C 覆盖与隐藏最容易混淆的地方覆盖基类虚函数用 override普通函数隐藏编译器不报错但行为完全不同答辩时被追问过这个问题的人不在少数。3. 跑通核心代码数据库初始化、登录与图书增删改查3.1 用 QSqlDatabase 初始化 SQLite连接名与建表语句数据库连接统一放在 dbmanager.cpp 里main() 启动时调用一次// dbmanager.cpp #include QSqlDatabase #include QSqlQuery #include QSqlError #include QDebug bool initDatabase() { // QSQLITE 是 Qt 内置驱动名不指定连接名时默认叫 qt_sql_default_connection QSqlDatabase db QSqlDatabase::addDatabase(QSQLITE); // 数据库文件放可执行文件同目录规避工作目录不一致问题 db.setDatabaseName(QCoreApplication::applicationDirPath() /library.db); if (!db.open()) { qDebug() open database failed: db.lastError().text(); return false; } QSqlQuery query; // IF NOT EXISTS 保证程序重复启动不会报表已存在 query.exec(CREATE TABLE IF NOT EXISTS books ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, author TEXT, publisher TEXT, isbn TEXT UNIQUE, total INTEGER DEFAULT 1, available INTEGER DEFAULT 1)); // readers 与 borrow_records 的建表语句同理不再重复列出 return true; }QSqlDatabase::addDatabase 里的字符串是 Qt 的驱动标识符写成 “QSQLITE3” 或 “SQLITE” 都会失败运行时直接报 driver not loaded。setDatabaseName 只接受文件路径且目录必须存在SQLite 不会替你创建目录。路径用 QCoreApplication::applicationDirPath() 而不是 QDir::currentPath()是因为 Qt Creator 运行时的工作目录是 build 目录打包后双击运行时是 exe 所在目录两处不一致会导致“在我机器上好好的换台机器找不到数据”的经典问题。建表语句带 IF NOT EXISTS 是刻意为之的幂等设计initDatabase() 每次启动都执行表存在就跳过。isbn 加 UNIQUE 约束防止同书不同 id 重复录入这是答辩时一个很自然的加分点。3.2 登录对话框槽函数没有返回值用 accept/reject 传结果登录按钮的点击监听用 Qt 5 的新式 connect 写法// login.cpp 构造函数里 connect(ui-btnLogin, QPushButton::clicked, this, LoginDialog::onLoginClicked); void LoginDialog::onLoginClicked() { QString username ui-editUser-text().trimmed(); QString password ui-editPass-text(); // 课设里先写死账号密码数据库校验方案见下方说明 if (username admin password 123456) { accept(); // 关闭对话框并返回 QDialog::Accepted } else { QMessageBox::warning(this, 提示, 用户名或密码错误); ui-editPass-clear(); } }很多人在这一步搜索“qt 槽函数 返回值”想在槽里把校验结果 return 给调用方——槽函数的返回值在信号槽机制里会被忽略等于没有。正确做法是先让对话框记住结果调用方靠 exec() 的返回值判断成败// main.cpp 里的调用方式 LoginDialog dlg; if (dlg.exec() QDialog::Accepted) { MainWindow w; w.show(); } else { return 0; }exec() 进入模态事件循环accept() 或 reject() 退出循环这是 Qt 对话框的标准协议比手动维护“登录成功”标志位干净得多。show() 之前不要提前 new MainWindow登录失败再销毁会有隐藏的内存和窗口闪烁问题。如果被追问“账号密码写死算不算缺陷”准备一个升级方案users 表存 username 和 password_hash密码用 QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Sha256) 算哈希后入库存哈希值登录时比对。这一问一答基本能把评分从“功能完整”抬到“考虑过安全问题”这一档。3.3 QSqlTableModel 绑定 QTableView图书列表与新增编辑图书列表直接用 QSqlTableModel不写自定义 model是课设性价比最高的选择// mainwindow.cpp model new QSqlTableModel(this); model-setTable(books); // OnManualSubmit: 改动缓存在内存调用 submitAll() 才真正入库存 model-setEditStrategy(QSqlTableModel::OnManualSubmit); model-select(); ui-tvBooks-setModel(model); // 中文表头 model-setHeaderData(1, Qt::Horizontal, 书名); model-setHeaderData(2, Qt::Horizontal, 作者); // 隐藏 id 列 ui-tvBooks-hideColumn(0);三种编辑策略的差别要在答辩前想清楚策略提交时机适用场景OnFieldChange单元格修改立即写库单字段快速编辑OnRowChange整行编辑完成时提交行内编辑OnManualSubmit调用 submitAll() 时提交表单对话框录入误改风险最小课设里建议用 OnManualSubmit因为图书新增与编辑走独立对话框列表本身只读唯一需要 submitAll() 的操作被收敛到“保存”按钮上误改单元格不会直接污染数据库。新增图书走 BookDialog 收集字段再写 SQLvoid MainWindow::onAddBook() { BookDialog dlg(this); if (dlg.exec() ! QDialog::Accepted) return; QSqlQuery query; query.prepare(INSERT INTO books (title, author, publisher, isbn, total, available) VALUES (?, ?, ?, ?, ?, ?)); query.addBindValue(dlg.title()); query.addBindValue(dlg.author()); query.addBindValue(dlg.publisher()); query.addBindValue(dlg.isbn()); query.addBindValue(dlg.total()); query.addBindValue(dlg.total()); // 新入库图书的可用量等于总量 if (!query.exec()) { QMessageBox::critical(this, 错误, query.lastError().text()); return; } model-select(); // 重新拉取数据刷新视图 }写这段时遵循预备语句 addBindValue 的固定用法理由有二一是避免 SQL 注入二是中文和特殊符号书名里的书名号、ISBN 里的短横线不会破坏语句结构。还有一点容易被 C 语言习惯带偏isbn 字段在 C 侧用 QString 而不用 char bookName[20] 或 long longISBN-13 是 13 位数字超出 32 位整型范围字符串是最稳的存储形态。新增完成后调 model-select() 而不是手动往 TableView 里插行让模型自己同步少写一堆遍历代码。3.4 借书还书事务保证两条更新要么都成功要么都失败借书流程要动两张表borrow_records 插入记录books 扣减 available。两步之间如果程序中途出错会出现“记录有了但库存没减”的脏数据。用事务包住bool borrowBook(int bookId, int readerId) { QSqlDatabase db QSqlDatabase::database(); db.transaction(); QSqlQuery query; // 先查可借数量等于 0 直接拒绝 query.prepare(SELECT available FROM books WHERE id ?); query.addBindValue(bookId); if (!query.exec() || !query.next() || query.value(0).toInt() 0) { db.rollback(); return false; } // 扣减可借数量 query.prepare(UPDATE books SET available available - 1 WHERE id ?); query.addBindValue(bookId); if (!query.exec()) { db.rollback(); return false; } // 写借阅记录status 0 表示在借 query.prepare(INSERT INTO borrow_records (book_id, reader_id, borrow_date, status) VALUES (?, ?, ?, 0)); query.addBindValue(bookId); query.addBindValue(readerId); query.addBindValue(QDate::currentDate().toString(Qt::ISODate)); if (!query.exec()) { db.rollback(); return false; } return db.commit(); }transaction() 之后任何一步出错都要 rollback()全部成功才是 commit()。这里最容易写错的地方是忘记把 SELECT 的失败也纳入回滚判断——查不到书和扣减失败同样会让后续插入基于错误状态继续。available 用 “available - 1” 的 UPDATE 写法而不是先 SELECT 出来减完再写回是为了避免两个窗口并发借同一本书时读到相同的旧值这是 SQL 层的原子操作比在 C 层加锁可靠得多答辩时提到这一点是加分项。还书逻辑完全对称把 borrow_records 的 status 置 1、return_date 写当天日期books 的 available 加 1同样用事务包住。注意还书要带 record 的 id 定位到具体某一次借阅而不是只按 book_id 把该书的记录全部置为已还。4. 编译打包与高频排错Qt 5.15.2 MSVC2019 的实战路径4.1 安装与套件选择Qt 的安装器会同时提供 MinGW 和 MSVC 两套二进制这里选 MSVC2019_64。原因很实际MSVC 编译产物在老师机器上是否运行取决于目标机有没有对应版本的 VC 运行库而学校机房几乎都装过 Visual C RedistributableMinGW 的 libgcc、libstdc 反而经常缺失。安装时只勾 Qt 5.15.2 下的 MSVC2019 64-bit 和 Qt Creator 即可组件装多了只会拖慢套件解析。如果你本机只装了 VS2022用 msvc2019_64 套件编译通常没有二进制兼容问题但若报找不到 cl.exe需要在 Qt Creator 的构建套件页里把 C 编译器手动指到 VS2022 的 VC 工具集路径。这里不用额外处理环境变量Qt Creator 会自动调用 vcvars 初始化编译环境。4.2 Release 构建与 windeployqt 打包打包前确认两件事切到 Release 模式、清理旧的构建产物。Debug 版带 Qt 调试 DLL体积大且不能对外分发。切换编译器套件比如从 MinGW 换到 MSVC后必须清理构建目录旧的目标文件混进新编译器会产生无法解释的链接错误这种问题查一晚上都不一定能定位。编译成功后在 Qt 自带的命令行环境里执行cd /d D:\workspace\LibraryManager-build\release D:\Qt\5.15.2\msvc2019_64\bin\windeployqt.exe LibraryManager.exewindeployqt 会扫描 exe 的依赖把 Qt5Core.dll、Qt5Widgets.dll、Qt5Sql.dll 以及 platforms、sqldrivers、styles 等插件目录拷到 exe 旁边。执行完人工检查两个目录platforms 里必须有 qwindows.dllsqldrivers 里必须有 qsqlite.dll。前者缺失会报平台插件错误后者缺失会报 QSQLITE driver not loaded——这是课程设计演示当场翻车的头号原因。提示windeployqt 只拷贝 Qt 依赖不处理 VC 运行库目标机缺 VCRUNTIME140.dll 时把 vc_redist.x64.exe 一起放进交付压缩包比让老师自己找下载入口省事。4.3 三个高发报错与对应处理报错现象根本原因处理方案QSqlDatabase: QSQLITE driver not loadedsqldrivers 插件缺失或与编译器不匹配检查 exe 同级的 sqldrivers\qsqlite.dllMSVC 编译的程序不能用 MinGW 编译的插件qt.qpa.plugin: Could not find the Qt platform plugin找不到 platforms\qwindows.dll临时设置 QT_QPA_PLATFORM_PLUGIN_PATH 定位问题交付方案是重新 windeployqt界面和数据库里的中文变成问号源文件编码错误或多余转码源文件统一保存为 UTF-8字符串用 QStringLiteral 包裹qt.qpa.plugin 报错里如果带出 D:\qt\5.15.2\msvc2019_64 这样的路径说明 exe 在开发机上回退到了 Qt 安装目录的插件换台机器秒挂。临时环境变量写法是set QT_QPA_PLATFORM_PLUGIN_PATHD:\Qt\5.15.2\msvc2019_64\plugins\platforms这条命令只能用来排查不是交付方案。正常交付时 windeployqt 生成的 platforms 目录和 exe 同级程序会自动在可执行文件所在目录查找插件不需要任何环境变量。中文乱码在 Qt 5 里已经很少见源码默认按 UTF-8 编译如果仍出现检查 .cpp 是否被 IDE 存成了 GBK 编码或者字符串是否被 fromLocal8Bit() 之类的老代码二次转换过。Qt 5 的正确姿势是所有字符串直接写中文或 QStringLiteral不做任何手动的编码转换。5. 给课设加分的四个细节搜索过滤、统计报表与数据备份5.1 搜索框用 QSortFilterProxyModel不动原模型图书管理系统没有搜索是明显的功能缺口但每次按键都拼 SQL 的 LIKE 去查库既慢又容易把视图状态搞乱。常见做法是在 model 和 view 之间插一层 QSortFilterProxyModelproxy new QSortFilterProxyModel(this); proxy-setSourceModel(model); proxy-setFilterKeyColumn(-1); // -1 表示在所有列里匹配 proxy-setFilterCaseSensitivity(Qt::CaseInsensitive); ui-tvBooks-setModel(proxy);搜索框的 textChanged 信号里只需一行connect(ui-editSearch, QLineEdit::textChanged, proxy, QSortFilterProxyModel::setFilterFixedString);过滤只发生在代理层books 表数据不动排序、选中状态不会因过滤丢失。注意切换成 proxy 之后新增图书的刷新仍是 model-select()代理会自动感知源模型的变化不需要额外操作。5.2 热门图书统计一条 JOIN 语句的事统计模块不需要额外建表答辩前准备这一个查询就够撑场面SELECT b.title, COUNT(br.id) AS borrow_count FROM borrow_records br JOIN books b ON br.book_id b.id GROUP BY b.title ORDER BY borrow_count DESC;执行结果放进 QSqlQueryModel再绑到一个独立的 QTableView。注意统计视图不要复用主表的 model 变量单独声明一个 queryModel 或者切换时明确指定映射列否则两个 view 共用同一模型时一个排序会连带影响另一个。5.3 数据备份复制文件比导 SQL 可靠SQLite 的备份在课设场景下直接复制文件即可QString src QCoreApplication::applicationDirPath() /library.db; QString dst QCoreApplication::applicationDirPath() /backup/library_ QDate::currentDate().toString(yyyyMMdd) .db; QDir().mkpath(QFileInfo(dst).absolutePath()); QFile::copy(src, dst);演示时先点一次备份再删几条数据再点恢复——把备份文件复制回去并刷新模型。这个流程展示的是对数据完整性的理解比界面多一个按钮更打动老师。5.4 交压缩包前的自查清单删除 build 目录后全量重新编译 Release确认没有警告之外的报错把整个 release 目录拷到另一台机器跑一次验证 platforms 和 sqldrivers 插件齐全测试边界库存为 0 的图书借书必须被拒绝还书后 available 恢复中文书名、中文读者名、ISBN 含短横线三种输入逐一验证确认 library.db 生成在 exe 同目录而不是 build 目录最后补一个最容易被忽略的路径约定全工程数据库路径统一写成 QCoreApplication::applicationDirPath() 拼接文件名替代 QDir::currentPath()。课设里十有八九的“在我电脑上好好的到演示就崩”根源都是这两个目录在 Qt Creator 和双击运行时不一致。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门