ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

MFC学生成绩管理系统实战:C++桌面GUI开发与SQLite数据持久化

MFC学生成绩管理系统实战:C++桌面GUI开发与SQLite数据持久化 简介本资源是一套完整的C与MFC课程设计实践项目——学生成绩管理系统面向高校计算机专业本科生及C初学者聚焦Windows桌面应用开发与数据库集成实战。系统基于MFC框架构建图形界面支持成绩录入、修改、多条件查询、删除及基础统计分析配套SQLite数据库文件.db实现数据持久化管理。压缩包共66个文件含7个头文件.h、6个源码文件.cpp、2个可执行程序.exe、1个Visual Studio解决方案.sln及1份PDF说明文档涵盖UI逻辑、业务模块与资源定义结构完整便于逐模块学习调试包体大小34.58MB。已有47人下载学习读者可直接运行体验完整功能深入理解MFC消息映射机制、对话框交互设计、数据库连接封装及C面向对象在实际项目中的落地应用。1. 这不是又一个“Hello World”——MFC学生成绩管理系统的真实落地场景你打开 Visual Studio新建一个 MFC 应用程序界面拖几个编辑框、按钮、列表控件再连上 Access 或 SQLite 数据库文件编译运行后能增删改查学生成绩——这看起来像课程设计的终点但实际是工程能力的起点。这个标题里的“C、MFC课程设计学生成绩管理系统源码db文件”本质是一套面向 Windows 桌面端、具备完整数据持久化能力的 C GUI 实践闭环它不依赖 .NET 或 Qt纯 Win32 MFC 封装它要求理解 CDocument/CView 架构、消息映射机制、资源脚本.rc与类向导协同它必须把CStudent类的字段映射到数据库表结构还要处理CListCtrl多列排序、CEdit输入校验、CComboBox下拉联动等真实交互细节。适合大三刚学完《Windows 编程基础》的学生复现也适合想快速验证 MFC 数据绑定可行性的开发者补全技术栈断点。它不是玩具项目而是检验你能否把 C 对象生命周期、Windows 消息泵、ODBC/DAO/SQLite API 三者拧成一股绳的关键标尺。2. 为什么选 MFC 而不是 Qt 或 WinUI从架构层看学生成绩管理的约束条件2.1 MFC 在课程设计场景下的不可替代性轻量、可控、教学友好MFC 的核心价值不在“现代”而在“可解剖”。一个CStudentRecord类继承自CObject其序列化函数Serialize()直接控制二进制存盘逻辑CDocument派生类封装全部业务数据CView派生类只负责呈现这种严格分离让初学者一眼看清“数据在哪、视图在哪、谁触发更新”。对比 Qt 的信号槽自动连接或 WinUI 的 XAML 绑定MFC 的ON_COMMAND(IDC_BTN_ADD, CMainFrame::OnBtnAdd)是显式、可断点、可单步跟踪的消息路由——这对理解 Windows 消息循环WM_COMMAND、WM_NOTIFY至关重要。更关键的是部署约束课程设计交付物要求“双击 exe 即可运行”MFC 程序打包只需附带msvcp140.dll等 VC Redistributable 文件而非 Qt 的几十 MB 动态库而 SQLite 的单文件数据库.db天然契合“源码db文件”交付模式无需安装服务端。提示Visual C Redistributable AIO 包虽能一键安装所有运行时但课程设计中应明确标注所用 VS 版本如 VS2019 v142 工具集并在 README 中写明对应 redistributable 名称vc_redist.x64.exe避免学生在同学电脑上因 DLL 版本错配导致0xc000007b错误。2.2 数据层选型SQLite 单文件数据库的工程化取舍学生成绩管理系统对事务一致性要求不高非金融级但需支持多表关联学生表、课程表、成绩表和简单查询按班级查平均分、按姓名模糊搜索。SQLite 完全满足单.db文件即数据库无服务进程#include sqlite3.h后静态链接即可支持标准 SQL 语法CREATE TABLE IF NOT EXISTS student (id INTEGER PRIMARY KEY, name TEXT NOT NULL, class TEXT);可直接在OnInitDialog()中执行建表通过sqlite3_exec()执行 DML用sqlite3_prepare_v2()sqlite3_bind_*()防止 SQL 注入如插入姓名含单引号的学生记录。对比 Access 数据库.mdbSQLite 无需 ODBC 配置跨平台兼容性更好同一.db文件可在 Linux 下用sqlite3命令行验证对比嵌入式内核源码或 muduo 这类网络库SQLite 的 API 层级更贴近课程设计需求——它不暴露线程模型或 epoll 细节专注解决“怎么把 C 对象存进磁盘”。2.2.1 SQLite 初始化与错误处理模板// 在 CStudentDoc::OnNewDocument() 或 InitInstance() 中调用 bool CStudentDoc::InitDatabase() { int rc sqlite3_open(student.db, m_db); if (rc ! SQLITE_OK) { AfxMessageBox(CString(_T(数据库打开失败)) CString(sqlite3_errmsg(m_db))); return false; } // 创建学生表若不存在 const char* create_sql CREATE TABLE IF NOT EXISTS student ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, class TEXT, score REAL DEFAULT 0.0);; char* errMsg nullptr; rc sqlite3_exec(m_db, create_sql, nullptr, nullptr, errMsg); if (rc ! SQLITE_OK) { AfxMessageBox(CString(_T(建表失败)) CString(errMsg)); sqlite3_free(errMsg); return false; } return true; }这段代码的关键在于sqlite3_open()返回值必须检查非 NULL 判断sqlite3_exec()的错误信息通过errMsg输出且必须sqlite3_free()释放AUTOINCREMENT确保主键递增避免手动维护 ID 冲突。很多学生源码在此处忽略errMsg检查导致建表失败却无提示后续插入数据时才报错。3. 从零搭建可运行的 MFC 学生成绩管理系统四步完成最小可行原型3.1 创建 MFC 应用框架并配置 SQLite 支持使用 Visual Studio 2019或 VS2015新建项目项目类型MFC 应用程序→ 应用程序类型选单文档SDI勾选使用 Unicode 库高级功能取消ActiveX 控件和打印和打印预览课程设计无需复杂打印生成的类CStudentApp应用类、CStudentDoc文档类、CStudentView视图类、CMainFrame主框架。注意若项目提示“此项目需要 MFC 库”请确认安装时已勾选 “使用 C 的桌面开发” 工作负载并在项目属性 → 配置属性 → 常规 → 使用 MFC 设置为在共享 DLL 中使用 MFC调试时选在静态库中使用 MFC可避免运行时依赖。添加 SQLite 支持下载 sqlite-amalgamation 最新版解压得sqlite3.c和sqlite3.h将两文件拖入解决方案资源管理器 → 源文件/头文件文件夹右键sqlite3.c→ 属性 → C/C → 预编译头 → 设置为不使用预编译头避免与 stdafx.h 冲突在StudentDoc.h顶部添加#include sqlite3.h并在类中声明成员变量sqlite3* m_db;。3.2 设计学生数据模型与文档-视图同步机制CStudentDoc不仅是数据容器更是业务逻辑中枢。定义CStudent结构体并实现序列化// StudentDoc.h struct CStudent { int id; CString name; CString className; double score; void Serialize(CArchive ar) { if (ar.IsStoring()) { ar id name className score; } else { ar id name className score; } } }; class CStudentDoc : public CDocument { // ... 其他成员 private: CStudent m_currentStudent; // 当前编辑的学生 CArrayCStudent m_studentList; // 全部学生数据缓存 public: void LoadFromDB(); // 从 SQLite 加载全部记录 void SaveToDB(const CStudent stu); // 插入/更新单条 void DeleteFromDB(int id); // 删除指定 ID };关键点在于LoadFromDB()如何将数据库结果映射到CArray使用sqlite3_exec()的回调函数每行数据调用一次回调回调中解析argv[0]id、argv[1]name等构造CStudent对象并Add()到m_studentList调用UpdateAllViews(nullptr)通知视图刷新这是 MFC 文档/视图架构的核心契约。3.2.1 CListCtrl 列表控件的数据绑定实现在CStudentView中放置CListCtrlID:IDC_LIST_STUDENT初始化列头// StudentView.cpp void CStudentView::OnInitialUpdate() { CListView::OnInitialUpdate(); CListCtrl list GetListCtrl(); // 添加列宽度单位为对话框单位非像素 list.InsertColumn(0, _T(学号), LVCFMT_LEFT, 80); list.InsertColumn(1, _T(姓名), LVCFMT_LEFT, 120); list.InsertColumn(2, _T(班级), LVCFMT_LEFT, 100); list.InsertColumn(3, _T(成绩), LVCFMT_RIGHT, 80); // 从文档加载数据 CStudentDoc* pDoc GetDocument(); pDoc-LoadFromDB(); // 此时 m_studentList 已填充 RefreshListControl(); // 自定义函数遍历 m_studentList 插入行 }RefreshListControl()必须处理CListCtrl的项索引与CArray下标的映射且每次清空后重新插入——不能简单DeleteAllItems()后循环InsertItem()因为InsertItem()返回新项索引需用SetItemText()填充各列。3.3 实现增删改查核心操作与输入校验所有按钮事件如IDC_BTN_ADD均在CMainFrame或CStudentView中响应。以“添加学生”为例// StudentView.cpp void CStudentView::OnBnClickedBtnAdd() { // 获取编辑框内容 CString strName, strClass; double score 0.0; GetDlgItemText(IDC_EDIT_NAME, strName); GetDlgItemText(IDC_EDIT_CLASS, strClass); GetDlgItemText(IDC_EDIT_SCORE, strScore); // 强制校验姓名非空、成绩为数字且在 0-100 if (strName.Trim().IsEmpty()) { AfxMessageBox(_T(姓名不能为空)); GetDlgItem(IDC_EDIT_NAME)-SetFocus(); return; } if (!strScore.IsEmpty() !strScore.IsNumeric()) { AfxMessageBox(_T(成绩必须为数字)); GetDlgItem(IDC_EDIT_SCORE)-SetFocus(); return; } score _tstof(strScore); if (score 0 || score 100) { AfxMessageBox(_T(成绩应在 0-100 之间)); GetDlgItem(IDC_EDIT_SCORE)-SetFocus(); return; } // 构造 CStudent 对象并保存 CStudent stu; stu.name strName; stu.className strClass; stu.score score; CStudentDoc* pDoc GetDocument(); pDoc-SaveToDB(stu); // 执行 INSERT INTO student... pDoc-UpdateAllViews(nullptr); // 触发 OnUpdate 刷新列表 }此处校验逻辑必须前置IsNumeric()检查字符串是否全为数字字符支持负号和小数点_tstof()转换后二次范围判断避免数据库层报错回传。很多源码直接INSERT不校验导致插入scoreabc时 SQLite 静默转为 0数据失真。4. 解决 MFC 学生成绩管理系统三大高频故障从崩溃到稳定运行4.1 “程序启动即崩溃”MFC 资源 ID 与控件变量未同步的连锁反应典型现象双击 exe 后黑屏闪退调试器显示Access violation reading location 0x00000000。根源往往是DoDataExchange()中控件变量如CEdit m_editName与资源 IDIDC_EDIT_NAME不匹配在资源编辑器中修改了编辑框 ID但未右键 → “添加变量”导致DDX_Text(pDX, IDC_EDIT_NAME, m_editName)中 ID 无效或CStudentView构造函数中未调用m_editName.SubclassDlgItem(IDC_EDIT_NAME, this)使CEdit对象未绑定到窗口句柄。验证方法在CStudentView::OnInitialUpdate()开头加断点检查GetDlgItem(IDC_EDIT_NAME)是否返回非 NULLCWnd*若为 NULL说明资源 ID 错误或控件未创建。修复步骤打开StudentView.h删除旧的CEdit m_editName;声明在资源视图中右键编辑框 → “添加变量”选择类别Control、变量类型CEdit、变量名m_editName确认生成的DoDataExchange()中DDX_Control(pDX, IDC_EDIT_NAME, m_editName)存在且 ID 正确。4.2 “列表显示空白”CListCtrl 列宽与 DPI 缩放适配问题在高分辨率屏幕如 2560×1440 150% 缩放下InsertColumn(0, _T(学号), LVCFMT_LEFT, 80)设置的 80 对话框单位可能被缩放为 120 像素导致列宽总和超过控件宽度内容被截断。解决方案是启用 DPI 感知项目属性 → 配置属性 → 清单工具 → 输入和输出 → 启用用户帐户控制UAC→ 设置为true在manifest文件中添加application xmlnsurn:schemas-microsoft-com:asm.v3 windowsSettings dpiAware xmlnshttp://schemas.microsoft.com/SMI/2005/WindowsSettingstrue/pm/dpiAware /windowsSettings /application或在InitInstance()中调用SetProcessDPIAware()Windows 8.1。更稳健的做法是动态计算列宽GetSystemMetrics(SM_CXSCREEN)获取屏幕宽度按比例分配列宽如学号占 15%姓名占 35%避免硬编码像素值。4.3 “数据库写入失败但无提示”SQLite 事务与线程安全陷阱当连续点击“添加”按钮多次部分记录丢失日志显示SQLITE_BUSY错误。这是因为 SQLite 默认以SERIALIZABLE隔离级别工作多线程并发写入时需显式事务控制// StudentDoc.cpp void CStudentDoc::SaveToDB(const CStudent stu) { char* errMsg nullptr; // 开启事务 sqlite3_exec(m_db, BEGIN IMMEDIATE;, nullptr, nullptr, errMsg); if (errMsg) { /* 处理 BEGIN 错误 */ } CString sql; sql.Format(_T(INSERT INTO student (name, class, score) VALUES (%s, %s, %.1f);), stu.name, stu.className, stu.score); int rc sqlite3_exec(m_db, CT2CA(sql), nullptr, nullptr, errMsg); if (rc ! SQLITE_OK) { // 回滚事务 sqlite3_exec(m_db, ROLLBACK;, nullptr, nullptr, nullptr); AfxMessageBox(CString(_T(插入失败)) CString(errMsg)); sqlite3_free(errMsg); return; } // 提交事务 sqlite3_exec(m_db, COMMIT;, nullptr, nullptr, nullptr); }BEGIN IMMEDIATE防止写冲突COMMIT确保原子性。若省略事务高并发下 SQLite 可能返回SQLITE_BUSY并静默失败学生无法感知数据未保存。5. 让学生成绩管理系统真正可用三个提升专业度的实战技巧5.1 用 CTreeCtrl 实现班级-学生两级导航替代扁平列表CListCtrl适合单表展示但成绩管理天然有层级班级 → 学生 → 成绩明细。CTreeCtrl可直观呈现// 在 CStudentView 中添加 CTreeCtrl 成员 m_treeClass void CStudentView::BuildClassTree() { m_treeClass.DeleteAllItems(); HTREEITEM hRoot m_treeClass.InsertItem(_T(全部班级), TVI_ROOT); // 查询 DISTINCT class FROM student sqlite3_stmt* stmt; const char* sql SELECT DISTINCT class FROM student ORDER BY class;; sqlite3_prepare_v2(m_db, sql, -1, stmt, nullptr); while (sqlite3_step(stmt) SQLITE_ROW) { CString className CA2CT((char*)sqlite3_column_text(stmt, 0)); HTREEITEM hClass m_treeClass.InsertItem(className, hRoot); m_treeClass.SetItemData(hClass, (DWORD_PTR)CLASS_NODE); // 标记为班级节点 // 查询该班级所有学生 CString query; query.Format(_T(SELECT id, name, score FROM student WHERE class%s ORDER BY name;), className); sqlite3_stmt* stmt2; sqlite3_prepare_v2(m_db, CT2CA(query), -1, stmt2, nullptr); while (sqlite3_step(stmt2) SQLITE_ROW) { int id sqlite3_column_int(stmt2, 0); CString name CA2CT((char*)sqlite3_column_text(stmt2, 1)); double score sqlite3_column_double(stmt2, 2); CString itemText; itemText.Format(_T(%s (%.1f)), name, score); HTREEITEM hStudent m_treeClass.InsertItem(itemText, hClass); m_treeClass.SetItemData(hStudent, id); // 存储学生 ID供双击编辑 } sqlite3_finalize(stmt2); } sqlite3_finalize(stmt); }双击树节点时OnTvnItemActivate()获取GetItemData()得到 ID跳转到编辑界面——这比在百行列表中滚动查找更符合教师日常操作习惯。5.2 为 CEdit 控件添加实时输入校验阻止非法字符输入CEdit的EN_CHANGE事件在内容变化后触发但用户已输入非法字符如在成绩框输入字母。更优方案是拦截WM_CHAR消息在字符进入编辑框前过滤// StudentView.h class CStudentView : public CListView { // ... 其他声明 protected: afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags); DECLARE_MESSAGE_MAP() }; // StudentView.cpp BEGIN_MESSAGE_MAP(CStudentView, CListView) ON_WM_CHAR() END_MESSAGE_MAP() void CStudentView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { // 若焦点在成绩编辑框且输入非数字、小数点、负号、退格、删除键 CWnd* pFocus GetFocus(); if (pFocus m_editScore) { if (!(nChar 0 nChar 9) nChar ! . nChar ! - nChar ! VK_BACK nChar ! VK_DELETE) { return; // 拦截不传递给编辑框 } // 防止多个小数点检查当前文本是否已含 . CString str; m_editScore.GetWindowText(str); if (nChar . str.Find(.) ! -1) { return; } } CListView::OnChar(nChar, nRepCnt, nFlags); }此技巧让输入体验接近 Web 表单无需等待用户点击“确定”才提示错误。5.3 导出 Excel 报表用 libxlsxwriter 生成免 Office 依赖的 .xlsx 文件课程设计常需“导出成绩单”。与其调用 Excel COM 接口需安装 Office不如用轻量库下载 libxlsxwriter 编译为静态库在项目中链接libxlsxwriter.lib包含头文件导出逻辑在CMainFrame::OnExportExcel()中void CMainFrame::OnExportExcel() { // 创建工作簿 lxw_workbook* workbook workbook_new(score_report.xlsx); lxw_worksheet* worksheet workbook_add_worksheet(workbook, 成绩单); // 写入表头 worksheet_write_string(worksheet, 0, 0, 学号, nullptr); worksheet_write_string(worksheet, 0, 1, 姓名, nullptr); worksheet_write_string(worksheet, 0, 2, 班级, nullptr); worksheet_write_string(worksheet, 0, 3, 成绩, nullptr); // 从数据库查询全部数据 sqlite3_stmt* stmt; sqlite3_prepare_v2(AfxGetApp()-m_db, SELECT id, name, class, score FROM student;, -1, stmt, nullptr); int row 1; while (sqlite3_step(stmt) SQLITE_ROW) { int id sqlite3_column_int(stmt, 0); const char* name (const char*)sqlite3_column_text(stmt, 1); const char* cls (const char*)sqlite3_column_text(stmt, 2); double score sqlite3_column_double(stmt, 3); worksheet_write_number(worksheet, row, 0, id, nullptr); worksheet_write_string(worksheet, row, 1, name, nullptr); worksheet_write_string(worksheet, row, 2, cls, nullptr); worksheet_write_number(worksheet, row, 3, score, nullptr); row; } sqlite3_finalize(stmt); workbook_close(workbook); AfxMessageBox(_T(导出完成score_report.xlsx)); }生成的.xlsx文件可直接用 WPS 或 Excel 打开且不依赖任何外部组件完美契合“源码db文件”的交付要求。本文还有配套的精品资源点击获取
返回列表