Qt实战—从零构建学生成绩管理系统(文件存储与UI交互)
1. 项目需求分析与设计思路
第一次用Qt做学生成绩管理系统时,我对着空白的Qt Creator界面发了半小时呆。后来发现,与其直接写代码,不如先拿张纸把需求理清楚。这个系统最核心的功能其实就五个:增删改查排。
数据结构设计是项目的骨架。我建议用一个StudentInfo类来封装学生信息,包含以下字段:
- 姓名(QString类型)
- 学号(int类型)
- 数学成绩(int类型)
- 英语成绩(int类型)
- C语言成绩(int类型)
- 平均分(double类型,自动计算)
class StudentInfo { public: QString name; int id; int math; int english; int languageC; double avg; StudentInfo(QString n, int i, int m, int e, int c) { name = n; id = i; math = m; english = e; languageC = c; avg = (m + e + c) / 3.0; } };UI界面规划我走了不少弯路。最初把所有功能堆在一个窗口,结果代码乱得像毛线团。后来改用多窗口设计:
- 主菜单窗口(导航中心)
- 添加学生窗口(带5个输入框)
- 修改/删除窗口(学号检索+数据修改)
- 查询窗口(学号搜索+结果展示)
- 排序窗口(单选按钮+表格展示)
经验之谈:修改和删除功能可以合并,因为它们都需要先通过学号定位学生记录。这样能减少窗口数量,降低代码复杂度。
2. 项目搭建与基础配置
在Qt Creator中新建项目时,新手常会踩两个坑:一是选错项目类型,二是漏掉关键库。这里给出详细步骤:
- 创建空项目:文件 → 新建文件或项目 → 其他项目 → 空的Qt项目
- 命名规范:建议用
StudentManagementSystem这样的驼峰命名 - 添加Qt Widgets库:在.pro文件中追加:
QT += widgets多窗口创建技巧:
- 右键项目 → Add New → Qt → Qt设计师界面类
- 依次创建:
- MenuWidget(主菜单)
- AddStudentWidget(添加)
- ModifyWidget(修改/删除)
- SelectWidget(查询)
- SortWidget(排序)
我遇到过窗口切换卡顿的问题,后来发现用QStackedLayout比单纯show/hide更流畅。在MainWidget中初始化布局:
// mainwidget.h QStackedLayout *stackLayout; MenuWidget *menu; AddStudentWidget *addWin; //...其他窗口指针 // mainwidget.cpp stackLayout = new QStackedLayout; stackLayout->addWidget(menu); stackLayout->addWidget(addWin); //...添加其他窗口 setLayout(stackLayout);3. 核心功能实现详解
3.1 数据持久化存储
用QFile和QTextStream读写文本文件时,要注意三个关键点:
文件打开模式:
QIODevice::WriteOnly:只写(覆盖)QIODevice::Append:追加QIODevice::Text:处理换行符
数据格式:建议用空格分隔字段,例如:
张三 1001 85 90 78 李四 1002 92 88 95- 完整示例:
// 写入数据 QFile file("students.txt"); if(file.open(QIODevice::Append | QIODevice::Text)){ QTextStream out(&file); out << name << " " << id << " " << math << " " << english << " " << languageC << "\n"; file.close(); } // 读取数据 if(file.open(QIODevice::ReadOnly | QIODevice::Text)){ QTextStream in(&file); while(!in.atEnd()){ QString line = in.readLine(); QStringList data = line.split(" "); // 解析到StudentInfo对象 } }踩坑提醒:一定要检查文件是否成功打开!我早期版本没做判断,导致数据丢失还不报错。
3.2 窗口交互与信号槽
主菜单到子窗口的切换,推荐使用信号槽机制:
// menuwidget.h signals: void changeWindow(int index); // 0=主菜单,1=添加窗口... // menuwidget.cpp void MenuWidget::on_addButton_clicked(){ emit changeWindow(1); } // mainwidget.cpp connect(menu, &MenuWidget::changeWindow, [=](int index){ stackLayout->setCurrentIndex(index); });返回按钮的实现更简单:
void AddStudentWidget::on_returnButton_clicked(){ emit changeWindow(0); // 返回主菜单 }4. 功能模块优化技巧
4.1 输入验证
在添加/修改学生信息时,必须验证:
- 学号是否重复(遍历现有数据)
- 成绩是否在0-100范围内
- 必填字段是否为空
// 示例:学号查重 bool isDuplicate(int id){ QFile file("students.txt"); // ...打开文件 while(!in.atEnd()){ QStringList data = in.readLine().split(" "); if(data[1].toInt() == id) return true; } return false; }4.2 排序算法实现
四种排序方式(数学/英语/C语言/平均分)可以共用一套逻辑:
// sortwidget.cpp void SortWidget::sortData(int subject){ QVector<StudentInfo> students; // ...读取数据到students std::sort(students.begin(), students.end(), [=](const StudentInfo &a, const StudentInfo &b){ switch(subject){ case 0: return a.math > b.math; case 1: return a.english > b.english; case 2: return a.languageC > b.languageC; default: return a.avg > b.avg; } }); // 更新表格显示... }4.3 表格展示优化
QTableWidget的实用设置:
// 设置表头 QStringList headers; headers << "姓名" << "学号" << "数学" << "英语" << "C语言" << "平均分"; ui->tableWidget->setHorizontalHeaderLabels(headers); // 自动调整列宽 ui->tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); // 填充数据 for(int i=0; i<students.size(); i++){ ui->tableWidget->setItem(i, 0, new QTableWidgetItem(students[i].name)); // ...其他列 }5. 常见问题解决方案
中文乱码问题: 在main.cpp中添加:
QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));数据文件路径问题: 建议使用绝对路径,或者将数据文件放在可执行文件同级目录:
QString path = QCoreApplication::applicationDirPath() + "/students.txt";内存泄漏检查: 在pro文件中添加:
CONFIG += debug DEFINES += QT_DEBUG这个项目最让我头疼的是文件读写时的异常处理。后来总结出三板斧:
- 每次操作前检查文件是否存在
- 读写操作放在try-catch块中
- 重要操作前备份数据文件
