- 录入学生信息:包括学号、姓名、多门课程的成绩。
- 显示所有学生信息:以表格形式展示所有学生的数据。
- 按学号查询学生:根据学号查找并显示特定学生的信息。
- 按学号修改学生信息:找到学生后,可以修改其姓名或成绩。
- 按学号删除学生:从系统中移除指定学号的学生。
- 计算并显示单科平均分:计算并输出某一门课程的平均成绩。
- 计算并显示总分排名:根据学生的总分进行排序并显示排名。
- 将数据保存到文件:在程序退出时,将所有学生信息保存到
students.dat文件中。 - 从文件加载数据:在程序启动时,从
students.dat文件中加载之前保存的学生信息。
程序的整体结构
为了使代码清晰、易于维护,我们将采用模块化设计思想,将不同功能的代码分离到不同的函数中。

(图片来源网络,侵删)
- 主函数 (
main):程序的入口,负责显示菜单、调用其他函数,并控制程序的主循环。 - 数据结构:使用
struct来定义学生信息。 - 功能函数:每个菜单项对应一个功能函数(如
addStudent,displayAll等)。 - 辅助函数:一些通用的小函数,如
clearInputBuffer(清空输入缓冲区)。 - 文件操作:专门用于数据持久化的函数(
saveToFile,loadFromFile)。
完整C语言代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h> // 用于 getch(),实现“按任意键继续”
// --- 1. 定义学生数据结构 ---
#define MAX_NAME_LEN 50
#define MAX_COURSE_NUM 5 // 假设最多有5门课程
typedef struct {
char id[20]; // 学号
char name[MAX_NAME_LEN]; // 姓名
float scores[MAX_COURSE_NUM]; // 各科成绩
float total; // 总分
float average; // 平均分
} Student;
// 全局变量:学生数组和学生数量
Student students[1000]; // 假设最多存储1000名学生
int studentCount = 0;
// --- 2. 函数声明 ---
void showMenu();
void addStudent();
void displayAllStudents();
void searchStudent();
void modifyStudent();
void deleteStudent();
void calculateCourseAverage();
void sortByTotalAndDisplay();
void saveToFile();
void loadFromFile();
void clearInputBuffer();
// --- 3. 主函数 ---
int main() {
loadFromFile(); // 程序启动时,先从文件加载数据
int choice;
do {
showMenu();
printf("请输入您的选择 (0-8): ");
scanf("%d", &choice);
clearInputBuffer(); // 清除输入缓冲区中的换行符
switch (choice) {
case 1:
addStudent();
break;
case 2:
displayAllStudents();
break;
case 3:
searchStudent();
break;
case 4:
modifyStudent();
break;
case 5:
deleteStudent();
break;
case 6:
calculateCourseAverage();
break;
case 7:
sortByTotalAndDisplay();
break;
case 8:
saveToFile();
printf("数据已保存,程序即将退出,\n");
break;
case 0:
saveToFile(); // 退出前再次保存
printf("感谢使用,再见!\n");
break;
default:
printf("无效的输入,请重新输入!\n");
break;
}
if (choice != 0) {
printf("\n按任意键返回主菜单...");
getch(); // 等待用户按键
}
} while (choice != 0);
return 0;
}
// --- 4. 功能函数实现 ---
// 显示主菜单
void showMenu() {
system("cls || clear"); // 清屏 (Windows: cls, Linux/macOS: clear)
printf("========================================\n");
printf(" 欢迎使用学生成绩管理系统\n");
printf("========================================\n");
printf(" 1. 录入学生信息\n");
printf(" 2. 显示所有学生信息\n");
printf(" 3. 按学号查询学生\n");
printf(" 4. 按学号修改学生信息\n");
printf(" 5. 按学号删除学生\n");
printf(" 6. 计算并显示单科平均分\n");
printf(" 7. 计算并显示总分排名\n");
printf(" 8. 保存数据到文件\n");
printf(" 0. 退出系统\n");
printf("========================================\n");
}
// 录入学生信息
void addStudent() {
if (studentCount >= 1000) {
printf("学生数量已达上限,无法添加!\n");
return;
}
Student s;
printf("请输入学号: ");
scanf("%s", s.id);
clearInputBuffer();
// 检查学号是否已存在
for (int i = 0; i < studentCount; i++) {
if (strcmp(students[i].id, s.id) == 0) {
printf("错误:该学号已存在!\n");
return;
}
}
printf("请输入姓名: ");
fgets(s.name, MAX_NAME_LEN, stdin);
s.name[strcspn(s.name, "\n")] = '\0'; // 去掉fgets读取的换行符
s.total = 0;
printf("请输入 %d 门课程的成绩:\n", MAX_COURSE_NUM);
for (int i = 0; i < MAX_COURSE_NUM; i++) {
printf(" 课程 %d 成绩: ", i + 1);
scanf("%f", &s.scores[i]);
s.total += s.scores[i];
}
clearInputBuffer();
s.average = s.total / MAX_COURSE_NUM;
students[studentCount++] = s;
printf("学生信息添加成功!\n");
}
// 显示所有学生信息
void displayAllStudents() {
if (studentCount == 0) {
printf("当前没有学生信息!\n");
return;
}
printf("\n%-15s %-20s", "学号", "姓名");
for (int i = 0; i < MAX_COURSE_NUM; i++) {
printf(" 课程%d ", i + 1);
}
printf(" 总分 平均分\n");
printf("-----------------------------------------------------------------\n");
for (int i = 0; i < studentCount; i++) {
printf("%-15s %-20s", students[i].id, students[i].name);
for (int j = 0; j < MAX_COURSE_NUM; j++) {
printf("%-8.2f", students[i].scores[j]);
}
printf("%-8.2f %-8.2f\n", students[i].total, students[i].average);
}
}
// 按学号查询学生
void searchStudent() {
if (studentCount == 0) {
printf("当前没有学生信息!\n");
return;
}
char id[20];
printf("请输入要查询的学号: ");
scanf("%s", id);
clearInputBuffer();
for (int i = 0; i < studentCount; i++) {
if (strcmp(students[i].id, id) == 0) {
printf("\n查询到学生信息:\n");
printf("学号: %s\n", students[i].id);
printf("姓名: %s\n", students[i].name);
printf("成绩: ");
for (int j = 0; j < MAX_COURSE_NUM; j++) {
printf("课程%d: %.2f ", j + 1, students[i].scores[j]);
}
printf("\n总分: %.2f\n", students[i].total);
printf("平均分: %.2f\n", students[i].average);
return; // 找到后直接返回
}
}
printf("未找到学号为 %s 的学生!\n", id);
}
// 按学号修改学生信息
void modifyStudent() {
if (studentCount == 0) {
printf("当前没有学生信息!\n");
return;
}
char id[20];
printf("请输入要修改的学号: ");
scanf("%s", id);
clearInputBuffer();
for (int i = 0; i < studentCount; i++) {
if (strcmp(students[i].id, id) == 0) {
printf("找到了学生: %s, %s\n", students[i].id, students[i].name);
printf("请输入新的姓名 (直接回车保持不变): ");
char newName[MAX_NAME_LEN];
fgets(newName, MAX_NAME_LEN, stdin);
if (strlen(newName) > 1) { // 如果用户输入了内容
newName[strcspn(newName, "\n")] = '\0';
strcpy(students[i].name, newName);
}
students[i].total = 0; // 重新计算总分和平均分
printf("请输入新的 %d 门课程成绩:\n", MAX_COURSE_NUM);
for (int j = 0; j < MAX_COURSE_NUM; j++) {
printf(" 课程 %d 成绩: ", j + 1);
scanf("%f", &students[i].scores[j]);
students[i].total += students[i].scores[j];
}
clearInputBuffer();
students[i].average = students[i].total / MAX_COURSE_NUM;
printf("学生信息修改成功!\n");
return;
}
}
printf("未找到学号为 %s 的学生!\n", id);
}
// 按学号删除学生
void deleteStudent() {
if (studentCount == 0) {
printf("当前没有学生信息!\n");
return;
}
char id[20];
printf("请输入要删除的学号: ");
scanf("%s", id);
clearInputBuffer();
for (int i = 0; i < studentCount; i++) {
if (strcmp(students[i].id, id) == 0) {
// 将最后一个学生元素移到被删除的位置
students[i] = students[studentCount - 1];
studentCount--;
printf("学号为 %s 的学生信息已删除!\n", id);
return;
}
}
printf("未找到学号为 %s 的学生!\n", id);
}
// 计算并显示单科平均分
void calculateCourseAverage() {
if (studentCount == 0) {
printf("当前没有学生信息!\n");
return;
}
int courseIndex;
printf("请输入要计算平均分的课程编号 (1-%d): ", MAX_COURSE_NUM);
scanf("%d", &courseIndex);
clearInputBuffer();
if (courseIndex < 1 || courseIndex > MAX_COURSE_NUM) {
printf("无效的课程编号!\n");
return;
}
float sum = 0;
for (int i = 0; i < studentCount; i++) {
sum += students[i].scores[courseIndex - 1];
}
float average = sum / studentCount;
printf("课程 %d 的平均分为: %.2f\n", courseIndex, average);
}
// 计算并显示总分排名
void sortByTotalAndDisplay() {
if (studentCount == 0) {
printf("当前没有学生信息!\n");
return;
}
// 创建一个临时数组用于排序,避免修改原始数据顺序
Student tempStudents[1000];
memcpy(tempStudents, students, sizeof(Student) * studentCount);
// 使用冒泡排序(简单易懂,数据量小足够)
for (int i = 0; i < studentCount - 1; i++) {
for (int j = 0; j < studentCount - 1 - i; j++) {
if (tempStudents[j].total < tempStudents[j + 1].total) {
Student temp = tempStudents[j];
tempStudents[j] = tempStudents[j + 1];
tempStudents[j + 1] = temp;
}
}
}
printf("\n%-5s %-15s %-20s", "排名", "学号", "姓名");
for (int i = 0; i < MAX_COURSE_NUM; i++) {
printf(" 课程%d ", i + 1);
}
printf(" 总分\n");
printf("-----------------------------------------------------------------\n");
for (int i = 0; i < studentCount; i++) {
printf("%-5d %-15s %-20s", i + 1, tempStudents[i].id, tempStudents[i].name);
for (int j = 0; j < MAX_COURSE_NUM; j++) {
printf("%-8.2f", tempStudents[i].scores[j]);
}
printf("%-8.2f\n", tempStudents[i].total);
}
}
// 将数据保存到文件
void saveToFile() {
FILE *fp = fopen("students.dat", "wb"); // "wb" 表示二进制写入
if (fp == NULL) {
printf("无法打开文件进行保存!\n");
return;
}
// 先写入学生数量
fwrite(&studentCount, sizeof(int), 1, fp);
// 再写入所有学生数据
fwrite(students, sizeof(Student), studentCount, fp);
fclose(fp);
printf("数据已成功保存到 students.dat 文件,\n");
}
// 从文件加载数据
void loadFromFile() {
FILE *fp = fopen("students.dat", "rb"); // "rb" 表示二进制读取
if (fp == NULL) {
// 文件不存在是正常情况(第一次运行)
return;
}
// 先读取学生数量
fread(&studentCount, sizeof(int), 1, fp);
// 再读取所有学生数据
fread(students, sizeof(Student), studentCount, fp);
fclose(fp);
printf("数据已成功从 students.dat 文件加载,\n");
}
// 清空输入缓冲区
void clearInputBuffer() {
int c;
while ((c = getchar()) != '\n' && c != EOF); // 读取并丢弃所有字符,直到遇到换行符或文件结束符
}
如何编译和运行
- 保存代码:将上面的代码复制并粘贴到一个文本编辑器中,保存为
student_management.c。 - 编译:
- 如果你使用的是 GCC (在 Linux, macOS 或 Windows 的 MinGW/Cygwin 中),打开终端或命令提示符,进入文件所在目录,然后运行:
gcc student_management.c -o student_management
- 如果你使用的是 Visual Studio,创建一个新的C++项目(兼容C),将代码粘贴到主
.c文件中,然后生成解决方案。 - 如果你使用的是 Dev-C++,新建一个C源文件,粘贴代码,然后编译运行。
- 如果你使用的是 GCC (在 Linux, macOS 或 Windows 的 MinGW/Cygwin 中),打开终端或命令提示符,进入文件所在目录,然后运行:
- 运行:
- 在GCC环境中,运行生成的可执行文件:
./student_management
- 在其他IDE中,直接点击“运行”按钮即可。
- 在GCC环境中,运行生成的可执行文件:
注意:system("cls || clear"); 这行代码在Windows上使用 cls 命令清屏,在Linux/macOS上使用 clear 命令,如果你的系统不支持其中一个,程序可能会在切换菜单时多输出几行,这是一个小瑕疵,不影响核心功能。
代码讲解
-
数据结构 (
Student结构体):- 我们定义了一个
Student结构体,包含了学号(字符串)、姓名(字符串)、成绩数组(浮点数数组)、总分和平均分,这使得我们可以将一个学生的所有相关信息打包在一起。 - 使用
#define定义了常量MAX_COURSE_NUM,方便以后修改课程数量,而无需在代码中多处修改。
- 我们定义了一个
-
全局变量:
students数组和studentCount变量是全局的,这使得在所有函数中都可以访问和修改它们,简化了函数间的数据传递,对于大型项目,全局变量可能带来风险,但对于这种小型管理系统,这是一种简单直接的方法。
-
文件操作 (
saveToFile,loadFromFile):
(图片来源网络,侵删)- 我们使用
fopen,fwrite,fread,fclose等标准C库函数进行文件操作。 - 模式:
"wb"(二进制写入) 和"rb"(二进制读取) 是关键,二进制模式比文本模式更高效,因为它直接将内存中的数据块写入文件,无需进行格式转换。 - 写入顺序:我们先写入
studentCount,这样在读取时,程序首先就知道要读取多少个Student结构体,这是一个非常重要的技巧。 - 数据持久化:
loadFromFile()在main()函数的开头被调用,确保每次启动程序时都能加载上次保存的数据。saveToFile()在用户选择“保存”或“退出”时被调用,确保数据不会丢失。
- 我们使用
-
输入处理 (
clearInputBuffer):scanf在读取数字或字符串时,会将换行符(\n)留在输入缓冲区中,如果下一个输入是fgets(如读取姓名),它会直接读取这个换行符,导致用户无法输入姓名。clearInputBuffer函数专门解决这个问题,它会在每次scanf之后被调用,清空缓冲区,确保后续的输入操作正常进行。
-
核心算法 (
sortByTotalAndDisplay):- 排序是一个经典算法,这里我们使用了冒泡排序,因为它实现简单,对于几百个数据量的学生来说,性能完全足够。
- 为了不改变原始
students数组的顺序,我们创建了一个临时副本tempStudents在其上进行排序,这是一个很好的编程习惯。
这个项目涵盖了C语言中许多核心知识点,包括结构体、数组、函数、文件I/O、指针(间接通过数组索引使用)和基本的算法思想,它是一个非常好的C语言课程设计范例。

(图片来源网络,侵删)
