4、C++项目实战:学生成绩管理系统(数组与结构体)
说实话,数组和结构体这两个东西,单独拿出来讲谁都懂。但把它们放到一个真实的项目里,很多新手就开始犯迷糊了。我当年带团队的时候,面试过不少候选人,问起结构体数组怎么用,背得滚瓜烂熟。一让写个学生管理系统,立马卡壳。
今天这个项目,就是要把这两个知识点焊在一起。咱们做一个真正能跑起来的成绩管理系统。
项目需求拆解
先看看我们要做什么。一个学生成绩管理系统,核心功能其实就这几条:
- 录入学生信息(学号、姓名、三门课成绩)
- 计算每个学生的总分和平均分
- 按总分排名
- 查询某个学生的成绩
- 显示所有学生的成绩单
听起来不复杂对吧?但这里有个坑——数据怎么组织。你想想看,每个学生有多个属性,而且我们要管理一堆学生。用数组存?用结构体存?还是两者结合?
核心思路:用结构体描述单个学生,用结构体数组管理所有学生。这是C/C++里最经典的数据组织方式。
定义学生结构体
我个人习惯,写项目第一步不是写main函数,而是先定义数据结构。数据模型定好了,代码怎么写都顺。
struct Student {
int id; // 学号
char name[20]; // 姓名
float chinese; // 语文成绩
float math; // 数学成绩
float english; // 英语成绩
float total; // 总分
float average; // 平均分
};
这里有个细节我提一下。为什么total和average也放在结构体里?而不是临时计算?
嗯,这是性能上的考虑。如果学生数量很大(比如上千人),每次排序都重新计算总分,效率就低了。我在项目中遇到过类似场景——一个报表系统每次查询都实时计算,结果用户等得直拍桌子。后来改成预计算字段,速度直接翻倍。
用数组管理多个学生
结构体定义好了,接下来就是声明一个数组来装学生:
#define MAX_STUDENTS 100
Student students[MAX_STUDENTS]; // 最多管理100个学生
int studentCount = 0; // 当前学生数量
说白了,这就是一个「结构体数组」。每个元素是一个Student结构体,里面装着这个学生的所有信息。
小技巧:用#define定义最大容量,而不是写死数字。这样后期改容量只需要改一处,不会漏掉。
核心功能实现
1. 录入学生信息
录入的时候要注意什么?我踩过最大的坑就是——忘记检查数组是否已满。曾经有个项目上线后,用户录到第101个学生时程序直接崩溃,那叫一个尴尬。
void addStudent(Student students[], int &count) {
if (count >= MAX_STUDENTS) {
cout << "学生数量已满,无法继续录入!" << endl;
return;
}
Student s;
cout << "请输入学号: ";
cin >> s.id;
cout << "请输入姓名: ";
cin >> s.name;
cout << "请输入语文成绩: ";
cin >> s.chinese;
cout << "请输入数学成绩: ";
cin >> s.math;
cout << "请输入英语成绩: ";
cin >> s.english;
// 计算总分和平均分
s.total = s.chinese + s.math + s.english;
s.average = s.total / 3.0;
students[count] = s;
count++;
cout << "录入成功!" << endl;
}
注意这里count用的是引用传递。为什么?因为我们要修改它的值。如果传值进去,函数里改了count,外面根本不知道——这个坑我见过太多次了。
2. 按总分排名(冒泡排序)
排名嘛,说白了就是排序。这里我用冒泡排序,因为逻辑最直观,适合教学。实际项目中我会用sort函数,但咱们现在练的是基本功。
void sortByTotal(Student students[], int count) {
for (int i = 0; i < count - 1; i++) {
for (int j = 0; j < count - 1 - i; j++) {
if (students[j].total < students[j + 1].total) {
// 交换整个结构体
Student temp = students[j];
students[j] = students[j + 1];
students[j + 1] = temp;
}
}
}
}
看到没?交换的是整个结构体,而不是只交换总分。这一点很多新手会搞错——只换了分数,结果学号和姓名对不上,数据全乱了。
注意:结构体赋值是深拷贝。如果结构体里有指针成员,直接赋值会出问题。但咱们这里只有基本类型和字符数组,没问题。
3. 查询学生信息
查询功能,我习惯按学号查。因为学号是唯一的,不会重名。
void searchById(Student students[], int count, int targetId) {
for (int i = 0; i < count; i++) {
if (students[i].id == targetId) {
cout << "学号: " << students[i].id << endl;
cout << "姓名: " << students[i].name << endl;
cout << "语文: " << students[i].chinese << endl;
cout << "数学: " << students[i].math << endl;
cout << "英语: " << students[i].english << endl;
cout << "总分: " << students[i].total << endl;
cout << "平均分: " << students[i].average << endl;
return;
}
}
cout << "未找到该学号的学生!" << endl;
}
这里有个小细节——找到后直接return,不要再继续循环了。效率问题,虽然学生少的时候无所谓,但养成好习惯总没错。
知识体系总览
下面这张图,是我梳理的本章知识结构。你看一眼就能明白,数组和结构体是怎么配合的。
完整代码框架
最后,我把整个程序的骨架贴出来。你照着这个结构去填充,就能跑起来。
#include <iostream>
#include <cstring>
using namespace std;
struct Student {
int id;
char name[20];
float chinese, math, english;
float total, average;
};
#define MAX_STUDENTS 100
// 函数声明
void addStudent(Student[], int &);
void sortByTotal(Student[], int);
void searchById(Student[], int, int);
void displayAll(Student[], int);
int main() {
Student students[MAX_STUDENTS];
int count = 0;
int choice;
do {
cout << "\n===== 学生成绩管理系统 =====\n";
cout << "1. 录入学生信息\n";
cout << "2. 按总分排名\n";
cout << "3. 查询学生\n";
cout << "4. 显示所有学生\n";
cout << "0. 退出\n";
cout << "请选择: ";
cin >> choice;
switch(choice) {
case 1: addStudent(students, count); break;
case 2: sortByTotal(students, count); break;
case 3: {
int id;
cout << "请输入学号: ";
cin >> id;
searchById(students, count, id);
break;
}
case 4: displayAll(students, count); break;
}
} while(choice != 0);
return 0;
}
我的建议:先别急着抄代码。自己动手把每个函数实现一遍,遇到卡住的地方再回来看。这样印象最深。
这个项目虽然小,但五脏俱全。它教会你的不只是语法,更是一种思维方式——用结构体封装数据,用数组管理批量数据。这种模式,在后面的链表、文件操作、数据库编程里都会反复出现。
好了,动手试试吧。有问题随时翻回来看。
公众号:蓝海资料掘金营,微信deep3321