《学生成绩管理系统》课程设计报告
| 课程名称 | C语言程序设计 | 学生姓名 | [你的姓名] |
|---|---|---|---|
| 学 号 | [你的学号] | 指导教师 | [教师姓名] |
| 专 业 | [你的专业] | 完成日期 | [年/月/日] |
摘要
本报告旨在详细阐述一个基于C语言开发的学生成绩管理系统的设计与实现过程,该系统旨在解决传统学生成绩管理方式效率低下、易出错、信息不集中等问题,系统采用模块化设计思想,主要实现了学生信息的录入、修改、删除、查询、排序以及数据文件存储等功能,通过结构体(struct)来组织学生数据,利用文件(FILE)操作实现数据的持久化存储,并运用指针、数组等C语言核心技术进行数据处理,经过测试,本系统运行稳定,界面友好,基本功能完善,能够满足对学生成绩信息进行日常管理的需求,达到了预期的设计目标。

C语言;学生成绩管理;文件操作;结构体;模块化设计
1 项目背景与意义
在高校或中学的教学管理中,学生成绩是衡量学生学习效果、评估教学质量的重要依据,随着学生数量的增加,传统的纸质或简单电子表格管理方式已难以满足高效、准确、便捷的管理需求,手动管理不仅耗时耗力,而且容易出现数据录入错误、信息查找困难、数据备份与恢复麻烦等问题。
开发一个功能完善、操作简单、稳定可靠的学生成绩管理系统具有重要的现实意义,本系统利用C语言强大的底层操作能力和灵活性,构建一个命令行界面的管理工具,旨在将学生成绩管理工作自动化、规范化,减轻管理人员的工作负担,提高管理效率和数据准确性。
2 开发环境
- 操作系统: Windows 10 / Linux
- 编程语言: C语言 (C99标准)
- 开发工具: Visual Studio Code / Dev-C++ / GCC
- 编译器: GCC (GNU Compiler Collection)
需求分析
1 功能需求分析
通过对学生成绩管理流程的分析,本系统应具备以下核心功能:

- 录入功能: 能够从键盘输入单个或多个学生的信息,包括学号、姓名、各科成绩(如C语言、高等数学、英语等),并将其保存到系统中。
- 显示功能: 能够以表格形式在屏幕上清晰、美观地显示所有学生的信息,包括学号、姓名和各科成绩。
- 查询功能: 能够根据学号或姓名快速查找并显示指定学生的详细信息。
- 修改功能: 能够根据学号查找到学生后,修改其姓名或某科成绩。
- 删除功能: 能够根据学号从系统中删除指定学生的所有信息。
- 排序功能: 能够按照总分或任意单科成绩对学生信息进行升序或降序排序,并显示排序结果。
- 保存与加载功能: 能够将系统中的所有学生信息保存到磁盘文件(如
students.dat)中,并在程序启动时自动从文件中加载数据,实现数据的持久化。 - 退出功能: 安全退出系统,并提示用户是否保存数据。
2 性能需求分析
- 响应速度: 系统的各项操作(如查询、排序)应在用户可接受的短时间内完成,对于少量数据(如几百条记录)应做到即时响应。
- 稳定性: 系统应能稳定运行,不易崩溃,对用户的非法输入(如查询不存在的学号)应有相应的提示和处理,避免程序异常。
- 易用性: 提供清晰、直观的菜单驱动界面,用户无需复杂培训即可上手操作。
系统设计
1 功能模块设计
根据需求分析,将系统划分为以下几个功能模块,采用模块化设计思想,便于开发、测试和维护。
各模块功能说明:
- 主模块: 显示系统主菜单,接收用户输入,并根据调用相应的功能模块。
- 信息录入模块: 处理学生信息的输入,并进行基本的合法性校验。
- 信息显示模块: 遍历学生数组,格式化输出所有学生信息。
- 信息查询模块: 提供按学号和按姓名两种查询方式,并显示结果。
- 信息修改模块: 先定位学生,再修改其指定字段。
- 信息删除模块: 先定位学生,然后从数组中移除该学生信息(通常通过覆盖实现)。
- 信息排序模块: 实现多种排序算法(如冒泡排序、选择排序),按不同字段和顺序进行排序。
- 文件操作模块: 提供数据保存到文件和从文件加载数据的函数。
2 数据结构设计
为了存储和管理学生信息,采用结构体(struct)来定义学生数据类型。
#define MAX_NAME_LEN 50
#define MAX_ID_LEN 20
#define MAX_STUDENTS 1000 // 最大学生数量
// 定义科目
#define SUBJECT_NUM 3
const char* SUBJECT_NAMES[] = {"C语言", "高等数学", "英语"};
// 学生结构体
typedef struct {
char id[MAX_ID_LEN]; // 学号
char name[MAX_NAME_LEN]; // 姓名
float scores[SUBJECT_NUM]; // 各科成绩
float total; // 总分
float average; // 平均分
} Student;
// 学生管理系统结构体
typedef struct {
Student students[MAX_STUDENTS]; // 学生数组
int count; // 当前学生数量
} StudentManager;
设计说明:

- 使用
typedef定义Student和StudentManager类型,使代码更清晰。 StudentManager结构体封装了学生数组和当前学生数量,便于管理。SUBJECT_NAMES数组用于在显示时动态生成表头,方便增减科目。total和average字段可以在录入或修改成绩时自动计算,提高查询效率。
3 文件结构设计
系统数据以二进制文件形式存储,命名为 students.dat。
- 文件名:
students.dat - 存储格式: 二进制格式。
- 首先存储一个整数
count,表示学生总数,紧接着,连续存储count个Student结构体的二进制数据。 - 优点: 二进制文件读写速度快,占用空间小,且不易被用户直接篡改。
详细设计与实现
1 核心函数设计
以下是系统核心功能函数的伪代码或C语言实现片段。
1.1 主菜单函数
void showMenu() {
system("cls || 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("* 0. 退出系统 *\n");
printf("************************************\n");
printf("请输入您的选择 (0-6): ");
}
1.2 信息录入函数
void inputStudent(StudentManager* sm) {
if (sm->count >= MAX_STUDENTS) {
printf("学生数量已达上限,无法继续录入!\n");
return;
}
Student s;
printf("请输入学号: ");
scanf("%s", s.id);
printf("请输入姓名: ");
scanf("%s", s.name);
s.total = 0;
for (int i = 0; i < SUBJECT_NUM; i++) {
printf("请输入%s成绩: ", SUBJECT_NAMES[i]);
scanf("%f", &s.scores[i]);
s.total += s.scores[i];
}
s.average = s.total / SUBJECT_NUM;
sm->students[sm->count++] = s;
printf("学生信息录入成功!\n");
}
1.3 文件保存与加载函数
// 保存数据到文件
void saveToFile(const StudentManager* sm) {
FILE* fp = fopen("students.dat", "wb");
if (fp == NULL) {
perror("无法打开文件进行写入");
return;
}
fwrite(&sm->count, sizeof(int), 1, fp);
fwrite(sm->students, sizeof(Student), sm->count, fp);
fclose(fp);
printf("数据已成功保存到 students.dat 文件,\n");
}
// 从文件加载数据
void loadFromFile(StudentManager* sm) {
FILE* fp = fopen("students.dat", "rb");
if (fp == NULL) {
printf("未找到数据文件,将创建一个新文件,\n");
return;
}
fread(&sm->count, sizeof(int), 1, fp);
fread(sm->students, sizeof(Student), sm->count, fp);
fclose(fp);
printf("已成功从 students.dat 文件加载 %d 条学生记录,\n", sm->count);
}
1.4 排序函数(以按总分降序为例,使用冒泡排序)
// 比较函数,用于qsort
int compareByTotalDesc(const void* a, const void* b) {
Student* sa = (Student*)a;
Student* sb = (Student*)b;
if (sa->total > sb->total) return -1;
if (sa->total < sb->total) return 1;
return 0;
}
// 排序功能封装
void sortStudents(StudentManager* sm) {
if (sm->count == 0) {
printf("没有学生数据可以排序,\n");
return;
}
// 使用标准库的快速排序函数
qsort(sm->students, sm->count, sizeof(Student), compareByTotalDesc);
printf("已按总分从高到低排序完成,\n");
}
2 主程序流程
int main() {
StudentManager sm = {0}; // 初始化管理器
loadFromFile(&sm); // 启动时加载数据
int choice;
do {
showMenu();
scanf("%d", &choice);
switch (choice) {
case 1: inputStudent(&sm); break;
case 2: displayAllStudents(&sm); break;
case 3: searchStudent(&sm); break;
case 4: modifyStudent(&sm); break;
case 5: deleteStudent(&sm); break;
case 6: sortStudents(&sm); break;
case 0:
saveToFile(&sm); // 退出时保存数据
printf("感谢使用,再见!\n");
break;
default:
printf("无效的输入,请重新选择 (0-6),\n");
}
printf("\n按任意键返回主菜单...");
getchar(); getchar(); // 暂停
} while (choice != 0);
return 0;
}
系统测试
为了验证系统的正确性和稳定性,设计了以下测试用例。
| 测试模块 | 测试用例 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|
| 录入功能 | 录入一个新学生(学号: 2025001, 姓名: 张三, 成绩: 90, 85, 88) | 系统提示成功,并在显示功能中能看到该学生。 | 与预期结果一致。 | 通过 |
| 显示功能 | 显示所有学生信息 | 以表格形式清晰展示所有已录入的学生信息。 | 表格格式正确,信息完整。 | 通过 |
| 查询功能 | 按学号 "2025001" 查询 | 显示学号为 2025001 的学生信息。 | 成功查询并显示。 | 通过 |
| 查询一个不存在的学号 "9999999" | 提示“未找到该学生”。 | 提示信息正确。 | 通过 | |
| 修改功能 | 将学号 "2025001" 的学生姓名改为 "李四" | 显示功能中,该学生姓名已更新为 "李四"。 | 修改成功。 | 通过 |
| 删除功能 | 删除学号 "2025001" 的学生 | 该学生信息从系统中消失,显示学生数量减少。 | 删除成功。 | 通过 |
| 排序功能 | 对已有学生按总分降序排序 | 学生列表按总分从高到低重新排列。 | 排序正确。 | 通过 |
| 文件操作 | 录入数据后退出程序,然后重新启动 | 重新启动后,之前录入的数据仍然存在。 | 数据成功加载。 | 通过 |
测试结论: 经过上述测试,系统的各项基本功能均能正常运行,能够正确处理用户输入,并在各种边界条件下给出合理的反馈,系统达到了预期的设计要求。
总结与展望
1 项目总结
本次课程设计成功实现了一个功能相对完整的学生成绩管理系统,通过本项目,我深入实践了C语言的核心知识点,包括:
- 结构体的灵活运用,用于复杂数据的建模。
- 文件操作(
fopen,fwrite,fread等)实现数据的持久化。 - 指针的熟练使用,特别是在函数间传递结构体和数组时。
- 模块化编程思想,将复杂问题分解为若干小模块,提高了代码的可读性和可维护性。
- 标准库函数(如
qsort)的高效使用。
该系统具备基本的增、删、改、查、排序功能,并利用文件实现了数据保存,是一个可用的命令行管理工具。
2 不足之处与展望
尽管系统基本功能已完成,但仍存在一些可以改进和扩展的地方:
- 用户界面: 当前为纯命令行界面,较为单调,未来可以使用图形库(如EasyX, GTK)开发图形用户界面,提升用户体验。
- 数据结构: 使用静态数组存储学生,限制了最大学生数量,可以改进为使用动态链表或动态数组,实现内存的动态分配,从而支持海量数据。
- 功能扩展:
- 数据统计: 增加计算班级平均分、最高分、最低分、及格率、分数段分布等统计功能。
- 数据导入/导出: 增加从Excel/CSV文件导入数据和导出到Excel/CSV文件的功能,方便与其他办公软件交互。
- 密码保护: 增加登录功能,对系统进行权限管理,防止非法访问。
- 模糊查询: 实现按姓名模糊查询,提高查询的灵活性。
- 代码健壮性: 可以增加更完善的输入验证,例如对成绩范围(0-100)的校验,防止非法数据进入系统。
附录:核心源代码
(以下为 main.c 文件的完整代码,其他功能函数可按模块拆分为 .h 和 .c 文件)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
// --- 常量定义 ---
#define MAX_NAME_LEN 50
#define MAX_ID_LEN 20
#define MAX_STUDENTS 1000
#define SUBJECT_NUM 3
const char* SUBJECT_NAMES[] = {"C语言", "高等数学", "英语"};
// --- 数据结构定义 ---
typedef struct {
char id[MAX_ID_LEN];
char name[MAX_NAME_LEN];
float scores[SUBJECT_NUM];
float total;
float average;
} Student;
typedef struct {
Student students[MAX_STUDENTS];
int count;
} StudentManager;
// --- 函数声明 ---
void showMenu();
void initManager(StudentManager* sm);
void inputStudent(StudentManager* sm);
void displayAllStudents(const StudentManager* sm);
void searchStudent(const StudentManager* sm);
void modifyStudent(StudentManager* sm);
void deleteStudent(StudentManager* sm);
void sortStudents(StudentManager* sm);
void saveToFile(const StudentManager* sm);
void loadFromFile(StudentManager* sm);
int compareByTotalDesc(const void* a, const void* b);
void pauseScreen();
// --- 主函数 ---
int main() {
StudentManager sm;
initManager(&sm);
loadFromFile(&sm);
int choice;
do {
showMenu();
scanf("%d", &choice);
switch (choice) {
case 1: inputStudent(&sm); break;
case 2: displayAllStudents(&sm); break;
case 3: searchStudent(&sm); break;
case 4: modifyStudent(&sm); break;
case 5: deleteStudent(&sm); break;
case 6: sortStudents(&sm); break;
case 0:
saveToFile(&sm);
printf("感谢使用,再见!\n");
break;
default:
printf("无效的输入,请重新选择 (0-6),\n");
}
pauseScreen();
} while (choice != 0);
return 0;
}
// --- 函数实现 ---
void initManager(StudentManager* sm) {
sm->count = 0;
}
void showMenu() {
system("cls || 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("* 0. 退出系统 *\n");
printf("************************************\n");
printf("请输入您的选择 (0-6): ");
}
void pauseScreen() {
printf("\n按任意键返回主菜单...");
getchar(); // 清除输入缓冲区中的回车
getchar();
}
void inputStudent(StudentManager* sm) {
if (sm->count >= MAX_STUDENTS) {
printf("学生数量已达上限,无法继续录入!\n");
return;
}
Student s;
printf("请输入学号: ");
scanf("%s", s.id);
printf("请输入姓名: ");
scanf("%s", s.name);
s.total = 0;
for (int i = 0; i < SUBJECT_NUM; i++) {
printf("请输入%s成绩: ", SUBJECT_NAMES[i]);
scanf("%f", &s.scores[i]);
s.total += s.scores[i];
}
s.average = s.total / SUBJECT_NUM;
sm->students[sm->count++] = s;
printf("学生信息录入成功!\n");
}
void displayAllStudents(const StudentManager* sm) {
if (sm->count == 0) {
printf("当前没有学生信息,\n");
return;
}
printf("\n%-15s %-20s", "学号", "姓名");
for (int i = 0; i < SUBJECT_NUM; i++) {
printf("%-10s", SUBJECT_NAMES[i]);
}
printf("%-10s %-10s\n", "总分", "平均分");
printf("------------------------------------------------------------\n");
for (int i = 0; i < sm->count; i++) {
printf("%-15s %-20s", sm->students[i].id, sm->students[i].name);
for (int j = 0; j < SUBJECT_NUM; j++) {
printf("%-10.1f", sm->students[i].scores[j]);
}
printf("%-10.1f %-10.1f\n", sm->students[i].total, sm->students[i].average);
}
}
void searchStudent(const StudentManager* sm) {
if (sm->count == 0) {
printf("当前没有学生信息,\n");
return;
}
int method;
printf("1. 按学号查询\n2. 按姓名查询\n请选择查询方式: ");
scanf("%d", &method);
if (method == 1) {
char id[MAX_ID_LEN];
printf("请输入要查询的学号: ");
scanf("%s", id);
for (int i = 0; i < sm->count; i++) {
if (strcmp(sm->students[i].id, id) == 0) {
printf("找到学生:\n");
printf("学号: %s, 姓名: %s\n", sm->students[i].id, sm->students[i].name);
for (int j = 0; j < SUBJECT_NUM; j++) {
printf("%s: %.1f\n", SUBJECT_NAMES[j], sm->students[i].scores[j]);
}
printf("总分: %.1f, 平均分: %.1f\n", sm->students[i].total, sm->students[i].average);
return;
}
}
printf("未找到学号为 %s 的学生,\n", id);
} else if (method == 2) {
char name[MAX_NAME_LEN];
printf("请输入要查询的姓名: ");
scanf("%s", name);
int found = 0;
for (int i = 0; i < sm->count; i++) {
if (strcmp(sm->students[i].name, name) == 0) {
printf("找到学生:\n");
printf("学号: %s, 姓名: %s\n", sm->students[i].id, sm->students[i].name);
for (int j = 0; j < SUBJECT_NUM; j++) {
printf("%s: %.1f\n", SUBJECT_NAMES[j], sm->students[i].scores[j]);
}
printf("总分: %.1f, 平均分: %.1f\n", sm->students[i].total, sm->students[i].average);
found = 1;
}
}
if (!found) {
printf("未找到姓名为 %s 的学生,\n", name);
}
} else {
printf("无效的选择,\n");
}
}
void modifyStudent(StudentManager* sm) {
if (sm->count == 0) {
printf("当前没有学生信息,\n");
return;
}
char id[MAX_ID_LEN];
printf("请输入要修改的学生学号: ");
scanf("%s", id);
for (int i = 0; i < sm->count; i++) {
if (strcmp(sm->students[i].id, id) == 0) {
printf("找到学生 %s,请输入新信息,\n", sm->students[i].name);
printf("请输入新姓名 (原: %s): ", sm->students[i].name);
scanf("%s", sm->students[i].name);
sm->students[i].total = 0;
for (int j = 0; j < SUBJECT_NUM; j++) {
printf("请输入新%s成绩 (原: %.1f): ", SUBJECT_NAMES[j], sm->students[i].scores[j]);
scanf("%f", &sm->students[i].scores[j]);
sm->students[i].total += sm->students[i].scores[j];
}
sm->students[i].average = sm->students[i].total / SUBJECT_NUM;
printf("学生信息修改成功!\n");
return;
}
}
printf("未找到学号为 %s 的学生,\n", id);
}
void deleteStudent(StudentManager* sm) {
if (sm->count == 0) {
printf("当前没有学生信息,\n");
return;
}
char id[MAX_ID_LEN];
printf("请输入要删除的学生学号: ");
scanf("%s", id);
for (int i = 0; i < sm->count; i++) {
if (strcmp(sm->students[i].id, id) == 0) {
printf("确认删除学生 %s (学号: %s) 的信息吗? (y/n): ", sm->students[i].name, sm->students[i].id);
char confirm;
scanf(" %c", &confirm);
if (tolower(confirm) == 'y') {
// 将最后一个学生移到当前位置,并减少计数
sm->students[i] = sm->students[sm->count - 1];
sm->count--;
printf("学生信息删除成功!\n");
} else {
printf("取消删除,\n");
}
return;
}
}
printf("未找到学号为 %s 的学生,\n", id);
}
int compareByTotalDesc(const void* a, const void* b) {
Student* sa = (Student*)a;
Student* sb = (Student*)b;
if (sa->total > sb->total) return -1;
if (sa->total < sb->total) return 1;
return 0;
}
void sortStudents(StudentManager* sm) {
if (sm->count == 0) {
printf("没有学生数据可以排序,\n");
return;
}
qsort(sm->students, sm->count, sizeof(Student), compareByTotalDesc);
printf("已按总分从高到低排序完成,\n");
}
void saveToFile(const StudentManager* sm) {
FILE* fp = fopen("students.dat", "wb");
if (fp == NULL) {
perror("无法打开文件进行写入");
return;
}
fwrite(&sm->count, sizeof(int), 1, fp);
fwrite(sm->students, sizeof(Student), sm->count, fp);
fclose(fp);
printf("数据已成功保存到 students.dat 文件,\n");
}
void loadFromFile(StudentManager* sm) {
FILE* fp = fopen("students.dat", "rb");
if (fp == NULL) {
printf("未找到数据文件,将创建一个新文件,\n");
return;
}
fread(&sm->count, sizeof(int), 1, fp);
if (sm->count > MAX_STUDENTS) {
printf("警告: 文件中的学生数量超过系统上限,只加载前 %d 条,\n", MAX_STUDENTS);
sm->count = MAX_STUDENTS;
}
fread(sm->students, sizeof(Student), sm->count, fp);
fclose(fp);
printf("已成功从 students.dat 文件加载 %d 条学生记录,\n", sm->count);
}
