实战:学生成绩管理系统(数组+结构体+文件)
说实话,这个题目特别经典。我当年刚入行时,第一个独立完成的项目就是类似的学生管理系统。那时候用的是C语言,结构体套数组,再配上文件读写,折腾了好几天才跑通。现在回头看,这个例子其实把C++里最核心的几个知识点都串起来了——数组、结构体、文件操作,再加上一点点业务逻辑。
嗯,咱们今天就来手把手把它实现一遍。我会把代码拆开讲,每个模块都说明白为什么这么写。
系统功能设计
先想清楚我们要做什么。一个学生成绩管理系统,至少要有这些功能:
- 录入学生信息(学号、姓名、各科成绩)
- 显示所有学生信息
- 按学号或姓名查找
- 统计平均分、最高分、最低分
- 数据保存到文件,下次启动能加载
说白了,就是增删改查加统计。别小看这些,很多企业级系统底层也是这套逻辑。
数据结构设计
我习惯先定义结构体。一个学生包含哪些字段?学号、姓名、语文、数学、英语成绩,再加一个平均分。这样设计比较直观:
struct Student {
int id; // 学号
char name[20]; // 姓名
float chinese; // 语文
float math; // 数学
float english; // 英语
float average; // 平均分
};
你可能会问,平均分为什么不实时计算?我在项目中遇到过类似情况——如果每次显示都重新算一遍,数据量大时性能会下降。所以存一份冗余字段,更新成绩时同步计算,反而更高效。
接下来用数组来管理多个学生。我一般会定义一个全局数组和计数器:
#define MAX_STUDENTS 100
Student students[MAX_STUDENTS];
int studentCount = 0;
这里有个坑。数组大小固定为100,如果学生超过100怎么办?嗯,这个后面可以用动态数组或vector来优化。但作为教学例子,先这样够用。
核心功能实现
1. 录入学生信息
录入时要注意输入校验。我曾经见过一个系统,用户输入负数成绩也没报错,结果平均分算出来是负的,闹了笑话。所以这里加个简单的范围检查:
void addStudent() {
if (studentCount >= MAX_STUDENTS) {
cout << "学生数量已满!" << endl;
return;
}
Student &s = students[studentCount];
cout << "请输入学号: ";
cin >> s.id;
cout << "请输入姓名: ";
cin >> s.name;
cout << "请输入语文成绩: ";
cin >> s.chinese;
cout << "请输入数学成绩: ";
cin >> s.math;
cout << "请输入英语成绩: ";
cin >> s.english;
// 计算平均分
s.average = (s.chinese + s.math + s.english) / 3.0;
studentCount++;
cout << "添加成功!" << endl;
}
注意这里用了引用 Student &s,避免每次写 students[studentCount] 那么长。小技巧,但能提升代码可读性。
2. 显示所有学生
显示部分我习惯用表格形式输出,对齐好看:
void displayAll() {
cout << "学号\t姓名\t语文\t数学\t英语\t平均分" << endl;
for (int i = 0; i < studentCount; i++) {
Student &s = students[i];
cout << s.id << "\t" << s.name << "\t"
<< s.chinese << "\t" << s.math << "\t"
<< s.english << "\t" << s.average << endl;
}
}
3. 查找功能
查找分两种:按学号和按姓名。我一般写成一个函数,加个参数区分:
void searchById(int id) {
for (int i = 0; i < studentCount; i++) {
if (students[i].id == id) {
// 显示该学生信息
return;
}
}
cout << "未找到该学号!" << endl;
}
按姓名查找类似,只是比较字符串用 strcmp 或 ==(如果用string的话)。
4. 统计功能
统计平均分、最高分、最低分,其实就是遍历数组做计算。我举个例子:
void statistics() {
if (studentCount == 0) {
cout << "没有学生数据!" << endl;
return;
}
float totalAvg = 0;
float maxScore = 0;
float minScore = 100;
for (int i = 0; i < studentCount; i++) {
totalAvg += students[i].average;
if (students[i].average > maxScore) maxScore = students[i].average;
if (students[i].average < minScore) minScore = students[i].average;
}
totalAvg /= studentCount;
cout << "全班平均分: " << totalAvg << endl;
cout << "最高平均分: " << maxScore << endl;
cout << "最低平均分: " << minScore << endl;
}
文件读写
这是重点。没有文件保存,程序一关数据就丢了,那还叫系统吗?
我习惯用二进制文件读写,速度快,而且结构体可以直接读写:
void saveToFile() {
ofstream outFile("students.dat", ios::binary);
if (!outFile) {
cout << "文件打开失败!" << endl;
return;
}
outFile.write((char*)&studentCount, sizeof(studentCount));
outFile.write((char*)students, sizeof(Student) * studentCount);
outFile.close();
cout << "保存成功!" << endl;
}
void loadFromFile() {
ifstream inFile("students.dat", ios::binary);
if (!inFile) {
cout << "没有找到数据文件,将使用空数据。" << endl;
return;
}
inFile.read((char*)&studentCount, sizeof(studentCount));
inFile.read((char*)students, sizeof(Student) * studentCount);
inFile.close();
cout << "加载成功!共 " << studentCount << " 条记录。" << endl;
}
string 类型),否则读出来的是指针地址,不是实际数据。我当年就踩过这个坑,数据存进去好好的,读出来全乱码。所以这里用 char[] 而不是 string。
知识体系结构图
下面这张图把整个系统的模块关系画清楚了,你可以对照着看:
完整代码整合
上面这些函数拼起来,再加上一个主菜单循环,就是一个完整的系统了。主菜单我习惯用 switch 实现:
int main() {
loadFromFile(); // 启动时加载数据
int choice;
do {
cout << "\n===== 学生成绩管理系统 =====\n";
cout << "1. 添加学生\n";
cout << "2. 显示所有学生\n";
cout << "3. 按学号查找\n";
cout << "4. 统计成绩\n";
cout << "5. 保存到文件\n";
cout << "0. 退出\n";
cout << "请选择: ";
cin >> choice;
switch (choice) {
case 1: addStudent(); break;
case 2: displayAll(); break;
case 3: /* 查找 */ break;
case 4: statistics(); break;
case 5: saveToFile(); break;
case 0: saveToFile(); cout << "再见!\n"; break;
default: cout << "无效选择!\n";
}
} while (choice != 0);
return 0;
}
case 0 里调用 saveToFile(),这样用户就算忘了手动保存,数据也不会丢。
避坑指南
最后分享几个我踩过的坑:
- 数组越界:录入前一定要检查
studentCount < MAX_STUDENTS,否则写爆了内存,程序直接崩。 - 文件路径:二进制文件默认生成在程序运行目录。如果你用IDE调试,可能跑到临时目录去了,找半天找不到文件。
- 输入缓冲区:用
cin读数字后,如果后面要读字符串,记得cin.ignore()清掉换行符。不然getline会直接读到空行。
嗯,这个例子虽然简单,但把C++的基础语法、数据结构、文件操作都练了一遍。你把它跑通之后,可以试着加功能——比如按成绩排序、修改删除学生、用vector代替数组等等。每加一个功能,你对C++的理解就深一层。