项目概述
学生成绩管理系统是一个基于C语言开发的综合性管理软件,实现了学生信息管理、成绩管理、数据查询、统计分析以及数据持久化等核心功能。该系统采用模块化设计,使用动态数组存储数据,支持CSV格式的数据导入导出,提供了直观的文本菜单界面。
界面展示
1. 主页面

2. 学生信息管理页面

3.统计分析页面

4.成绩管理页面

功能架构
1. 核心数据结构
typedef struct {
char id[ID_LEN]; // 学号
char name[NAME_LEN]; // 姓名
char gender[GENDER_LEN]; // 性别
int age; // 年龄
char className[CLASS_LEN];// 班级
float chinese; // 语文
float math; // 数学
float english; // 英语
float total; // 总分
float average; // 平均分
int rank; // 排名
} Student;
2. 系统模块划分
2.1 学生信息管理模块
- 添加学生信息:完整录入学生基本信息和成绩
- 删除学生信息:按学号删除指定学生记录
- 修改学生信息:支持选择性修改各字段
- 显示所有学生:以表格形式展示全部数据
2.2 成绩管理模块
- 批量录入成绩:按学号循环录入多学生成绩
- 单科录入:针对特定学生修改单科成绩
- 成绩统计:各科目平均分、最高分、最低分
- 班级统计:按班级统计成绩分布
- 分数段统计:按分数区间统计学生人数
- 排名计算:支持按总分或单科成绩排名
2.3 数据查询模块
- 按学号查询:精确查询指定学生
- 按姓名模糊查询:支持姓名关键字搜索
- 按班级查询:筛选指定班级学生
- 多条件组合查询:班级+姓名的复合查询
2.4 统计分析模块
- 排序显示:支持按各字段升序/降序排列
- 数据导出:将当前数据导出为CSV文件
2.5 系统设置模块
- 数据保存:手动保存到默认文件
- 数据备份:时间戳命名的备份文件
- 数据加载:从指定文件加载数据
技术特色
1. 动态内存管理
void ensure_capacity() {
if (student_capacity==0) {
student_capacity = 16;
students = malloc(student_capacity * sizeof(Student));
} else if (student_count >= student_capacity) {
student_capacity *= 2;
students = realloc(students, student_capacity * sizeof(Student));
}
}
- 初始容量16,满时自动扩容为2倍
- 避免频繁内存分配,提高性能
2. 数据持久化
采用CSV格式存储,兼容Excel等工具:
id,name,gender,age,class,chinese,math,english,total,average,rank
2023001,张三,男,18,计算机1班,85.00,92.00,78.00,255.00,85.00,1
3. 输入验证机制
float read_float_range(const char *prompt, float min, float max) {
// 循环验证输入范围
while (1) {
printf("%s", prompt);
if (!read_line(buf, sizeof(buf))) return 0.0f;
char *end; float v = strtof(buf, &end);
if (end!=buf && *end=='\0') {
if (v < min || v > max) {
printf("值应在 %.2f - %.2f 之间\n", min, max);
continue;
}
return v;
}
printf("输入无效,请输入数字。\n");
}
}
4. 用户界面优化
- 彩色输出:Windows平台支持彩色控制台输出
- 加载动画:系统启动时的视觉效果
- 暂停功能:关键操作后的暂停确认
核心算法详解
1. 排名计算算法
void compute_ranks(int field) {
// 创建临时数组进行排序
Student *tmp = malloc(student_count * sizeof(Student));
for (size_t i=0;i<student_count;i++) tmp[i]=students[i];
// 根据选择字段排序
if (field==0) qsort(tmp, student_count, sizeof(Student), cmp_total_desc);
else if (field==1) qsort(tmp, student_count, sizeof(Student), cmp_chinese_desc);
// 处理并列排名
int rank = 1;
for (size_t i=0;i<student_count;i++) {
if (i>0) {
int same = check_same_score(tmp, i, field); // 检查分数是否相同
if (!same) rank = (int)i+1;
}
// 写回排名到原数组
update_rank(tmp[i].id, rank);
}
free(tmp);
}
2. 数据加载策略
int load_data(const char *filename) {
// 读取CSV文件
while (fgets(line, sizeof(line), f)) {
// 解析CSV行数据
// 检查学号是否已存在
int idx = find_by_id(s.id);
if (idx != -1) {
students[idx] = s; // 覆盖更新
} else {
ensure_capacity(); // 确保容量
students[student_count++] = s; // 追加新记录
}
}
}
3. 快速查询优化
int find_by_id(const char *id) {
for (size_t i=0;i<student_count;i++) {
if (strcmp(students[i].id, id)==0) return (int)i;
}
return -1;
}
注:当前使用线性搜索,数据量大时可优化为哈希表
编译与运行
环境要求
- Windows系统 + MinGW编译器
- 或其他支持C99标准的C编译器
编译命令
gcc student_manager.c -o student_manager.exe
文件结构
项目根目录/
├── student_manager.c # 主程序源文件
├── students.csv # 数据文件(自动生成)
└── backup_*.csv # 备份文件(按时间戳生成)
使用指南
1. 系统启动
- 显示加载动画,自动加载
students.csv数据文件 - 进入彩色主菜单界面
2. 数据管理流程
- 信息录入:通过学生信息管理添加基础数据
- 成绩录入:使用批量录入或单科录入功能
- 排名计算:在成绩管理中计算各类排名
- 数据保存:定期保存防止数据丢失
3. 查询技巧
- 多条件查询时,留空条件表示忽略该条件
- 姓名查询支持模糊匹配(包含关系)
- 导出功能可用于数据迁移或外部分析
4. 备份策略
- 系统设置中提供手动备份功能
- 备份文件包含时间戳,便于版本管理
- 支持从任意CSV文件加载数据
扩展性与优化建议
1. 性能优化
- 大数据量时可将线性搜索改为二分查找
- 添加数据索引机制提高查询效率
- 实现分页显示避免控制台溢出
2. 功能扩展
- 增加用户登录和权限管理
- 支持更多科目和自定义字段
- 添加图表统计可视化输出
- 实现网络版多用户访问
3. 代码优化
- 添加更完善的错误处理机制
- 实现配置文件和命令行参数
- 增加单元测试和调试模式
源代码
/*
* 学生成绩管理系统 v1.0
* 功能摘要:学生信息管理 / 成绩管理 / 数据查询 / 统计分析 / 数据持久化 (CSV 文本)
* 编译 (Windows, MinGW): gcc student_manager.c -o student_manager.exe
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#ifdef _WIN32
#include <windows.h>
#endif
#define ID_LEN 12
#define NAME_LEN 20
#define GENDER_LEN 4
#define CLASS_LEN 20
/*
* Student 数据结构说明:
* - 字段长度采用固定数组以简化输入验证与序列化。
* - `total` 与 `average` 在内存中由程序计算并覆盖 CSV 中可能存在的值,
* 因此 CSV 中的这两列仅作参考。
* - `rank` 字段由 `compute_ranks()` 计算并写会到内存与导出文件中。
* 注意:当前实现并未对字符串输入做完整的越界/编码检查,生产环境请加强验证。
*/
typedef struct {
char id[ID_LEN]; // 学号
char name[NAME_LEN]; // 姓名
char gender[GENDER_LEN]; // 性别
int age; // 年龄
char className[CLASS_LEN];// 班级
float chinese; // 语文
float math; // 数学
float english; // 英语
float total; // 总分
float average; // 平均分
int rank; // 排名
} Student;
// 动态学生数组
Student *students = NULL;
size_t student_count = 0;
size_t student_capacity = 0;
// 默认数据文件(CSV 文本)
const char *DATA_FILE = "students.csv";
// ------------ 辅助函数 ---------------
void pause_anykey() {
printf("按回车继续...\n");
getchar();
}
#ifdef _WIN32
void set_color(WORD attr) { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), attr); }
#define COLOR_RESET (FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE)
#define COLOR_RED (FOREGROUND_RED | FOREGROUND_INTENSITY)
#define COLOR_GREEN (FOREGROUND_GREEN | FOREGROUND_INTENSITY)
#define COLOR_YELLOW (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY)
#define COLOR_CYAN (FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY)
#else
void set_color(int attr) { (void)attr; }
#define COLOR_RESET 0
#define COLOR_RED 0
#define COLOR_GREEN 0
#define COLOR_YELLOW 0
#define COLOR_CYAN 0
#endif
int load_data(const char *filename);
/* 加载动画(视觉效果) */
void proc() {
int i = 0;
printf("\n\n\n\n\n");
for (i = 1; i <= 20; i++) {
printf("\t\t\t\t\t系统加载中:");
printf("[%.1f%%]\r", (i + 1) * 5.0);
fflush(stdout);
#ifdef _WIN32
Sleep(100);
#else
struct timespec ts = {0, 100 * 1000000}; nanosleep(&ts, NULL);
#endif
}
printf("\n");
system("cls");
}
void trim_newline(char *s) { if (!s) return; size_t n = strlen(s); if (n>0 && s[n-1]=='\n') s[n-1]='\0'; }
char *read_line(char *buf, size_t sz) { if (!fgets(buf, (int)sz, stdin)) return NULL; trim_newline(buf); return buf; }
int read_int(const char *prompt) {
char buf[64];
while (1) {
printf("%s", prompt);
if (!read_line(buf, sizeof(buf))) return 0;
if (strlen(buf)==0) return 0;
char *end; long v = strtol(buf, &end, 10);
if (end!=buf && *end=='\0') return (int)v;
printf("输入无效,请输入整数。\n");
}
}
float read_float_range(const char *prompt, float min, float max) {
char buf[64];
while (1) {
printf("%s", prompt);
if (!read_line(buf, sizeof(buf))) return 0.0f;
if (strlen(buf)==0) return 0.0f;
char *end; float v = strtof(buf, &end);
if (end!=buf && *end=='\0') {
if (v < min || v > max) { printf("值应在 %.2f - %.2f 之间\n", min, max); continue; }
return v;
}
printf("输入无效,请输入数字。\n");
}
}
/*
* ensure_capacity
* -----------------
* 确保 `students` 动态数组具有足够容量以存放新条目。
* 实现策略:
* - 当 `student_capacity == 0` 时,分配初始容量(当前实现为 16)。
* - 当 `student_count >= student_capacity` 时,将容量乘以 2 并调用 `realloc` 扩容。
* 注意事项:
* - 本函数为简洁实现,未对 `malloc`/`realloc` 失败做完整的恢复处理;在内存分配失败时
* 程序可能会在后续写入时崩溃。可根据需要修改为返回错误码并在调用方做优雅处理。
*/
void ensure_capacity() {
if (student_capacity==0) {
student_capacity = 16;
students = malloc(student_capacity * sizeof(Student));
// 若 malloc 失败 students 为 NULL,调用端若尝试写入会出错(可以改进为返回错误码)。
} else if (student_count >= student_capacity) {
student_capacity *= 2;
students = realloc(students, student_capacity * sizeof(Student));
// realloc 若失败将返回 NULL 并保持原指针;这里为简洁实现未做细致错误处理。
}
}
/* 线性查找:按学号查找,返回索引或 -1 */
int find_by_id(const char *id) {
for (size_t i=0;i<student_count;i++) {
if (strcmp(students[i].id, id)==0) return (int)i;
}
return -1;
}
/* 计算总分和平均分(覆盖文件中可能存在的 total/average 字段) */
void calc_score(Student *s) {
s->total = s->chinese + s->math + s->english;
s->average = s->total / 3.0f;
}
/*
* save_data
* 将当前内存中的学生数据保存为 CSV 文本文件。
* - 文件以文本模式打开并写入表头:id,name,gender,age,class,chinese,math,english,total,average,rank
* - 按照固定字段顺序输出每条记录(浮点数保留两位小数)。
* - 若打开/写入失败,函数返回 0 并在 stderr 打印错误信息。
* 返回值:保存成功返回 1,失败返回 0。
*/
int save_data(const char *filename) {
FILE *f = fopen(filename, "w");
if (!f) { perror("打开文件写入失败"); return 0; }
fprintf(f, "id,name,gender,age,class,chinese,math,english,total,average,rank\n");
for (size_t i=0;i<student_count;i++) {
Student *s = &students[i];
fprintf(f, "%s,%s,%s,%d,%s,%.2f,%.2f,%.2f,%.2f,%.2f,%d\n",
s->id, s->name, s->gender, s->age, s->className,
s->chinese, s->math, s->english, s->total, s->average, s->rank);
}
fclose(f);
return 1;
}
/*
* load_data
* ---------
* 从 CSV 文本文件读取学生数据并加载到内存:
* - 跳过第一行表头(若检测到 header 包含 "id,name")。
* - 使用简单的逗号分割(`strtok`)解析字段,注意该方法不支持带引号的复杂 CSV。
* - 解析出学号、姓名、性别、年龄、班级、三科成绩等字段后,重新计算 `total` 与 `average`。
* - 若内存中已存在相同学号,将覆盖原记录;否则追加新记录(调用 `ensure_capacity()` 扩容)。
* 返回值:加载成功返回 1,文件不存在或打开失败返回 0。
*/
int load_data(const char *filename) {
FILE *f = fopen(filename, "r");
if (!f) return 0; // 文件不存在视为没有数据
char line[1024];
int line_no = 0;
while (fgets(line, sizeof(line), f)) {
line_no++;
trim_newline(line);
if (line[0] == '\0') continue; // 跳过空行
if (line_no == 1 && (strncmp(line, "id,", 3) == 0 || strstr(line, "id,name") != NULL)) continue; // 跳过表头
// 使用 strtok 简单分割(会修改 buf)
char buf[1024]; strncpy(buf, line, sizeof(buf)-1); buf[sizeof(buf)-1]='\0';
char *tokens[12] = {0}; int t = 0;
char *p = strtok(buf, ",");
while (p && t < 12) { tokens[t++] = p; p = strtok(NULL, ","); }
if (t < 8) continue; // 至少需要 8 个字段
Student s; memset(&s, 0, sizeof(s));
strncpy(s.id, tokens[0], ID_LEN-1);
strncpy(s.name, tokens[1], NAME_LEN-1);
if (t>2 && tokens[2]) strncpy(s.gender, tokens[2], GENDER_LEN-1);
if (t>3 && tokens[3]) s.age = atoi(tokens[3]);
if (t>4 && tokens[4]) strncpy(s.className, tokens[4], CLASS_LEN-1);
if (t>5 && tokens[5]) s.chinese = (float)atof(tokens[5]);
if (t>6 && tokens[6]) s.math = (float)atof(tokens[6]);
if (t>7 && tokens[7]) s.english = (float)atof(tokens[7]);
// 重新计算以保证内部数据一致
calc_score(&s);
int idx = find_by_id(s.id);
if (idx != -1) {
// 覆盖已有记录
students[idx] = s;
} else {
// 追加新记录:先扩容再写入并自增计数
ensure_capacity();
students[student_count++] = s;
}
}
fclose(f);
return 1;
}
int backup_data(const char *src, const char *dest) {
FILE *f1 = fopen(src, "rb"); if (!f1) { perror("备份源打开失败"); return 0; }
FILE *f2 = fopen(dest, "wb"); if (!f2) { perror("备份目标打开失败"); fclose(f1); return 0; }
char buf[4096]; size_t n;
while ((n = fread(buf, 1, sizeof(buf), f1))>0) fwrite(buf, 1, n, f2);
fclose(f1); fclose(f2); return 1;
}
int export_csv(const char *filename) { return save_data(filename); }
/* ---------- 学生信息管理 ---------- */
void add_student() {
char buf[128]; Student s; memset(&s,0,sizeof(s));
printf("添加学生信息\n");
while (1) {
printf("学号: "); if (!read_line(buf, sizeof(buf))) return; if (strlen(buf)==0) { printf("学号不能为空\n"); continue; }
if (find_by_id(buf) != -1) { printf("学号已存在,请重新输入\n"); continue; }
strncpy(s.id, buf, ID_LEN-1); break;
}
printf("姓名: "); read_line(buf, sizeof(buf)); strncpy(s.name, buf, NAME_LEN-1);
printf("性别: "); read_line(buf, sizeof(buf)); strncpy(s.gender, buf, GENDER_LEN-1);
s.age = read_int("年龄: ");
printf("班级: "); read_line(buf, sizeof(buf)); strncpy(s.className, buf, CLASS_LEN-1);
s.chinese = read_float_range("语文成绩 (0-100): ", 0.0f, 100.0f);
s.math = read_float_range("数学成绩 (0-100): ", 0.0f, 100.0f);
s.english = read_float_range("英语成绩 (0-100): ", 0.0f, 100.0f);
calc_score(&s);
ensure_capacity();
// 追加写入:先写入数组末尾,再增加计数(保持插入顺序)
students[student_count++] = s;
printf("添加成功\n");
}
void delete_student() {
char id[ID_LEN]; printf("输入要删除的学号: "); read_line(id, sizeof(id));
int idx = find_by_id(id); if (idx==-1) { printf("未找到学号 %s\n", id); return; }
printf("确认删除学生 %s %s ? (Y/N): ", students[idx].id, students[idx].name);
char ans[8]; read_line(ans, sizeof(ans)); if (ans[0]!='Y' && ans[0]!='y') { printf("已取消\n"); return; }
// 将后续元素前移覆盖被删除元素
for (size_t i=idx;i+1<student_count;i++) students[i]=students[i+1];
student_count--;
printf("删除成功\n");
}
void modify_student() {
char id[ID_LEN]; char buf[128];
printf("输入要修改的学号: "); read_line(id, sizeof(id));
int idx = find_by_id(id); if (idx==-1) { set_color(COLOR_RED); printf("未找到学号 %s\n", id); set_color(COLOR_RESET); return; }
Student *s = &students[idx]; set_color(COLOR_CYAN); printf("找到: %s %s\n", s->id, s->name); set_color(COLOR_RESET);
while (1) {
printf("选择要修改的字段:\n1. 姓名\n2. 性别\n3. 年龄\n4. 班级\n5. 语文\n6. 数学\n7. 英语\n0. 完成并返回\n");
int choice = read_int("选择(0-7): "); if (choice==0) break;
switch (choice) {
case 1: printf("当前 姓名: %s\n新 姓名: ", s->name); read_line(buf,sizeof(buf)); if (strlen(buf)) strncpy(s->name, buf, NAME_LEN-1); else printf("未修改\n"); break;
case 2: printf("当前 性别: %s\n新 性别: ", s->gender); read_line(buf,sizeof(buf)); if (strlen(buf)) strncpy(s->gender, buf, GENDER_LEN-1); else printf("未修改\n"); break;
case 3: printf("当前 年龄: %d\n新 年龄: ", s->age); read_line(buf,sizeof(buf)); if (strlen(buf)) s->age = atoi(buf); else printf("未修改\n"); break;
case 4: printf("当前 班级: %s\n新 班级: ", s->className); read_line(buf,sizeof(buf)); if (strlen(buf)) strncpy(s->className, buf, CLASS_LEN-1); else printf("未修改\n"); break;
case 5: printf("当前 语文: %.2f\n新 语文: ", s->chinese); read_line(buf,sizeof(buf)); if (strlen(buf)) { float v = strtof(buf,NULL); if (v>=0 && v<=100) s->chinese=v; else { set_color(COLOR_RED); printf("忽略无效成绩\n"); set_color(COLOR_RESET); } } else printf("未修改\n"); break;
case 6: printf("当前 数学: %.2f\n新 数学: ", s->math); read_line(buf,sizeof(buf)); if (strlen(buf)) { float v = strtof(buf,NULL); if (v>=0 && v<=100) s->math=v; else { set_color(COLOR_RED); printf("忽略无效成绩\n"); set_color(COLOR_RESET); } } else printf("未修改\n"); break;
case 7: printf("当前 英语: %.2f\n新 英语: ", s->english); read_line(buf,sizeof(buf)); if (strlen(buf)) { float v = strtof(buf,NULL); if (v>=0 && v<=100) s->english=v; else { set_color(COLOR_RED); printf("忽略无效成绩\n"); set_color(COLOR_RESET); } } else printf("未修改\n"); break;
default: printf("无效选择\n"); break;
}
calc_score(s); // 每次修改后重新计算统计
}
set_color(COLOR_GREEN); printf("修改完成\n"); set_color(COLOR_RESET);
}
void show_all_students() {
printf("共 %zu 名学生\n", student_count);
printf("%-10s %-10s %-4s %-3s %-12s %7s %7s %7s %8s %8s %5s\n",
"学号","姓名","性别","年龄","班级","语文","数学","英语","总分","平均","名次");
for (size_t i=0;i<student_count;i++) {
Student *s = &students[i];
printf("%-10s %-10s %-4s %-3d %-12s %7.2f %7.2f %7.2f %8.2f %8.2f %5d\n",
s->id, s->name, s->gender, s->age, s->className, s->chinese, s->math, s->english, s->total, s->average, s->rank);
}
}
void query_by_id() { char id[ID_LEN]; printf("输入学号: "); read_line(id, sizeof(id)); int idx = find_by_id(id); if (idx==-1) { printf("未找到\n"); return; } Student *s = &students[idx]; printf("%s %s %s %d %s 语:%.2f 数:%.2f 英:%.2f 总:%.2f 平:%.2f 排名:%d\n", s->id, s->name, s->gender, s->age, s->className, s->chinese, s->math, s->english, s->total, s->average, s->rank); }
void query_by_name() { char key[NAME_LEN]; printf("输入姓名关键字: "); read_line(key, sizeof(key)); for (size_t i=0;i<student_count;i++) if (strstr(students[i].name, key)) { Student *s=&students[i]; printf("%s %s %s %d %s 总:%.2f 平:%.2f 排名:%d\n", s->id, s->name, s->gender, s->age, s->className, s->total, s->average, s->rank); } }
void query_by_class() { char key[CLASS_LEN]; printf("输入班级: "); read_line(key, sizeof(key)); for (size_t i=0;i<student_count;i++) if (strcmp(students[i].className, key)==0) { Student *s=&students[i]; printf("%s %s %s %d 总:%.2f 平:%.2f 排名:%d\n", s->id, s->name, s->gender, s->age, s->total, s->average, s->rank); } }
void query_combined() { char classKey[CLASS_LEN]; char nameKey[NAME_LEN]; printf("按班级(留空忽略): "); read_line(classKey, sizeof(classKey)); printf("按姓名关键字(留空忽略): "); read_line(nameKey, sizeof(nameKey)); for (size_t i=0;i<student_count;i++) { int ok = 1; if (strlen(classKey) && strcmp(students[i].className, classKey)!=0) ok=0; if (ok && strlen(nameKey) && !strstr(students[i].name, nameKey)) ok=0; if (ok) { Student *s = &students[i]; printf("%s %s %s %d %s 总:%.2f 平:%.2f 排名:%d\n", s->id, s->name, s->gender, s->age, s->className, s->total, s->average, s->rank); } } }
void batch_score_entry() { printf("批量录入成绩(按学号录入,输入空学号结束)\n"); char id[ID_LEN]; while (1) { printf("学号: "); read_line(id, sizeof(id)); if (strlen(id)==0) break; int idx = find_by_id(id); if (idx==-1) { printf("未找到学号 %s\n", id); continue; } Student *s = &students[idx]; s->chinese = read_float_range("语文 (0-100): ", 0, 100); s->math = read_float_range("数学 (0-100): ", 0, 100); s->english = read_float_range("英语 (0-100): ", 0, 100); calc_score(s); printf("更新完成 %s\n", s->name); } }
void single_subject_entry() { char id[ID_LEN]; printf("学号: "); read_line(id,sizeof(id)); int idx = find_by_id(id); if (idx==-1) { printf("未找到\n"); return; } Student *s = &students[idx]; printf("选择科目: 1.语文 2.数学 3.英语\n"); int c = read_int("选择: "); if (c==1) s->chinese = read_float_range("语文 (0-100): ",0,100); else if (c==2) s->math = read_float_range("数学 (0-100): ",0,100); else if (c==3) s->english = read_float_range("英语 (0-100): ",0,100); else { printf("无效选择\n"); return; } calc_score(s); printf("修改并更新完成\n"); }
void subject_stats() { if (student_count==0) { printf("无数据\n"); return; } float sumC=0,sumM=0,sumE=0; float minC=1e9,minM=1e9,minE=1e9; float maxC=-1e9,maxM=-1e9,maxE=-1e9; for (size_t i=0;i<student_count;i++) { Student *s=&students[i]; sumC += s->chinese; sumM += s->math; sumE += s->english; if (s->chinese<minC) minC=s->chinese; if (s->chinese>maxC) maxC=s->chinese; if (s->math<minM) minM=s->math; if (s->math>maxM) maxM=s->math; if (s->english<minE) minE=s->english; if (s->english>maxE) maxE=s->english; } printf("语文: 平均 %.2f 最高 %.2f 最低 %.2f\n", sumC/student_count, maxC, minC); printf("数学: 平均 %.2f 最高 %.2f 最低 %.2f\n", sumM/student_count, maxM, minM); printf("英语: 平均 %.2f 最高 %.2f 最低 %.2f\n", sumE/student_count, maxE, minE); }
void class_stats() { if (student_count==0) { printf("无数据\n"); return; } for (size_t i=0;i<student_count;i++) { int seen = 0; for (size_t j=0;j<i;j++) if (strcmp(students[i].className, students[j].className)==0) { seen=1; break; } if (seen) continue; const char *cls = students[i].className; int cnt=0; float sum=0, max=-1e9, min=1e9; for (size_t k=0;k<student_count;k++) if (strcmp(students[k].className, cls)==0) { cnt++; sum += students[k].total; if (students[k].total>max) max=students[k].total; if (students[k].total<min) min=students[k].total; } printf("班级 %s: 学生=%d 平均总分=%.2f 最高=%.2f 最低=%.2f\n", cls, cnt, sum/cnt, max, min); } }
void range_stats() { int ranges[6] = {0}; for (size_t i=0;i<student_count;i++) { float t = students[i].average; if (t>=90) ranges[0]++; else if (t>=80) ranges[1]++; else if (t>=70) ranges[2]++; else if (t>=60) ranges[3]++; else ranges[4]++; } printf(">=90: %d\n80-89: %d\n70-79: %d\n60-69: %d\n<60: %d\n", ranges[0], ranges[1], ranges[2], ranges[3], ranges[4]); }
int cmp_total_desc(const void *a, const void *b) { const Student *A = a, *B = b; if (A->total < B->total) return 1; if (A->total > B->total) return -1; return 0; }
int cmp_total_asc(const void *a, const void *b) { return -cmp_total_desc(a,b); }
int cmp_chinese_desc(const void *a, const void *b) { const Student *A=a,*B=b; if (A->chinese<B->chinese) return 1; if (A->chinese>B->chinese) return -1; return 0; }
int cmp_math_desc(const void *a, const void *b) { const Student *A=a,*B=b; if (A->math<B->math) return 1; if (A->math>B->math) return -1; return 0; }
int cmp_english_desc(const void *a, const void *b) { const Student *A=a,*B=b; if (A->english<B->english) return 1; if (A->english>B->english) return -1; return 0; }
/*
* compute_ranks
* 根据指定字段计算名次并写回到原 `students` 数组的 `rank` 字段中。
* 参数 `field` 的含义:
* - 0: 按总分排序
* - 1: 按语文排序
* - 2: 按数学排序
* - 3: 按英语排序
* 实现细节:
* - 先拷贝一份学生数组到临时数组 `tmp`,对 `tmp` 进行 qsort 排序。
* - 处理并列分数:当当前与前一个记录的值相等时,视为并列并保留相同名次;否则名次更新为当前索引+1。
* 性能提示:当前实现为 O(n log n)(排序) + O(n^2)(写回时逐条匹配学号),当 student_count 很大时可通过哈希表将写回操作优化为 O(n)。
*/
void compute_ranks(int field) {
if (student_count==0) return;
Student *tmp = malloc(student_count * sizeof(Student));
for (size_t i=0;i<student_count;i++) tmp[i]=students[i];
if (field==0) qsort(tmp, student_count, sizeof(Student), cmp_total_desc);
else if (field==1) qsort(tmp, student_count, sizeof(Student), cmp_chinese_desc);
else if (field==2) qsort(tmp, student_count, sizeof(Student), cmp_math_desc);
else if (field==3) qsort(tmp, student_count, sizeof(Student), cmp_english_desc);
int rank = 1;
for (size_t i=0;i<student_count;i++) {
if (i>0) {
int same = 0;
if (field==0 && tmp[i].total == tmp[i-1].total) same=1;
if (field==1 && tmp[i].chinese == tmp[i-1].chinese) same=1;
if (field==2 && tmp[i].math == tmp[i-1].math) same=1;
if (field==3 && tmp[i].english == tmp[i-1].english) same=1;
if (!same) rank = (int)i+1;
}
// 将排序后的名次写回原数组(按学号匹配)
for (size_t j=0;j<student_count;j++) if (strcmp(tmp[i].id, students[j].id)==0) { students[j].rank = rank; break; }
}
free(tmp);
}
void sort_and_show() {
printf("选择排序字段: 1.总分 2.语文 3.数学 4.英语\n"); int f = read_int("选择: ");
printf("选择顺序: 1.降序 2.升序\n"); int ord = read_int("选择: ");
if (f==1) { if (ord==1) qsort(students, student_count, sizeof(Student), cmp_total_desc); else qsort(students, student_count, sizeof(Student), cmp_total_asc); }
else if (f==2) { if (ord==1) qsort(students, student_count, sizeof(Student), cmp_chinese_desc); else { /* 简单回退 */ qsort(students, student_count, sizeof(Student), cmp_total_desc); } }
else if (f==3) { if (ord==1) qsort(students, student_count, sizeof(Student), cmp_math_desc); }
else if (f==4) { if (ord==1) qsort(students, student_count, sizeof(Student), cmp_english_desc); }
show_all_students();
}
void student_info_menu() { while (1) { printf("---------- 学生信息管理 ----------\n"); printf("1. 添加学生信息\n2. 删除学生信息\n3. 修改学生信息\n4. 显示所有学生\n5. 返回主菜单\n"); printf("\n--------------------------------\n请选择操作(1-5): "); int c = read_int(""); switch(c) { case 1: add_student(); break; case 2: delete_student(); break; case 3: modify_student(); break; case 4: show_all_students(); pause_anykey(); break; case 5: return; default: printf("无效选择\n"); break; } } }
void grade_management_menu() { while (1) { printf("---- 成绩管理 ----\n1. 批量录入成绩\n2. 单科录入\n3. 统计(单科)\n4. 班级统计\n5. 分数段统计\n6. 排名计算\n7. 导出 CSV\n8. 返回主菜单\n请选择: "); int c = read_int(""); switch(c) { case 1: batch_score_entry(); break; case 2: single_subject_entry(); break; case 3: subject_stats(); break; case 4: class_stats(); break; case 5: range_stats(); break; case 6: { printf("按 0 总分 1 语文 2 数学 3 英语 排名: "); int f = read_int(""); compute_ranks(f); printf("排名已计算\n"); break; } case 7: { char fname[260]; printf("导出文件名 (默认 students.csv): "); read_line(fname,sizeof(fname)); if (strlen(fname)==0) strcpy(fname,"students.csv"); if (export_csv(fname)) printf("导出成功 %s\n", fname); break; } case 8: return; default: printf("无效选择\n"); break; } } }
void query_menu() { while (1) { printf("---- 数据查询 ----\n1. 按学号查询\n2. 按姓名模糊查询\n3. 按班级查询\n4. 多条件查询\n5. 返回主菜单\n请选择: "); int c = read_int(""); switch(c) { case 1: query_by_id(); break; case 2: query_by_name(); break; case 3: query_by_class(); break; case 4: query_combined(); break; case 5: return; default: printf("无效选择\n"); break; } } }
void analysis_menu() { while (1) { printf("---- 统计分析 ----\n1. 排序显示\n2. 单科统计\n3. 班级统计\n4. 分数段统计\n5. 导出 CSV\n6. 返回主菜单\n请选择: "); int c = read_int(""); switch(c) { case 1: sort_and_show(); break; case 2: subject_stats(); break; case 3: class_stats(); break; case 4: range_stats(); break; case 5: { char fname[260]; printf("导出文件名 (默认 students.csv): "); read_line(fname,sizeof(fname)); if (strlen(fname)==0) strcpy(fname,"students.csv"); if (export_csv(fname)) printf("导出成功 %s\n", fname); break; } case 6: return; default: printf("无效选择\n"); break; } } }
void settings_menu() { while (1) { printf("---- 系统设置 ----\n1. 保存数据\n2. 备份数据\n3. 加载数据文件\n4. 返回主菜单\n请选择: "); int c = read_int(""); if (c==1) { if (save_data(DATA_FILE)) printf("保存成功\n"); else printf("保存失败\n"); } else if (c==2) { char dest[260]; time_t t = time(NULL); struct tm *tm = localtime(&t); snprintf(dest, sizeof(dest), "backup_%04d%02d%02d_%02d%02d%02d.csv", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); if (backup_data(DATA_FILE, dest)) printf("备份成功: %s\n", dest); else printf("备份失败\n"); } else if (c==3) { char fname[260]; printf("输入要加载的文件名 (默认 students.csv): "); read_line(fname,sizeof(fname)); if (strlen(fname)==0) strcpy(fname, DATA_FILE); if (load_data(fname)) printf("加载成功\n"); else printf("加载失败或文件不存在\n"); } else if (c==4) return; else printf("无效选择\n"); } }
int main_menu() { while (1) { system("color 0B"); printf("====================================\n 学生信息管理系统 v1.0\n====================================\n"); printf("1. 学生信息管理\n2. 成绩管理\n3. 数据查询\n4. 统计分析\n5. 系统设置\n0. 退出系统\n"); printf("\n====================================\n请选择操作(0-5): "); int c = read_int(""); switch(c) { case 1: student_info_menu(); break; case 2: grade_management_menu(); break; case 3: query_menu(); break; case 4: analysis_menu(); break; case 5: settings_menu(); break; case 0: return 0; default: printf("无效选择\n"); break; } } }
/*
* main
* 启动流程:
* 1. 设置控制台颜色(Windows)并显示加载动画 `proc()`。
* 2. 尝试从默认 CSV 文件 `DATA_FILE` 加载数据(若存在则读取并填充内存)。
* 3. 进入 `main_menu()` 处理交互操作。
* 4. 退出前询问是否保存当前内存数据到 `DATA_FILE`。
* 5. 释放动态分配的内存并返回。
*/
int main() {
system("color 0B");
proc();
if (load_data(DATA_FILE)) { set_color(COLOR_GREEN); printf("已加载数据文件 %s,%zu 条记录\n", DATA_FILE, student_count); set_color(COLOR_RESET); }
else { set_color(COLOR_YELLOW); printf("未找到数据文件,使用空数据\n"); set_color(COLOR_RESET); }
main_menu();
printf("是否保存数据到 %s ? (Y/N): ", DATA_FILE);
char ans[8]; read_line(ans, sizeof(ans)); if (ans[0]=='Y' || ans[0]=='y') { if (save_data(DATA_FILE)) printf("保存成功\n"); else printf("保存失败\n"); }
if (students) free(students);
return 0;
}
/* 程序入口说明:设置颜色、显示加载动画、加载 CSV 数据、进入主菜单、退出前提示保存 */
总结
这个学生成绩管理系统展示了C语言在实际应用开发中的强大能力,涵盖了:
- 数据结构设计:合理的结构体定义
- 内存管理:动态数组的自动扩容
- 文件操作:CSV格式的读写解析
- 算法实现:排序、搜索、统计等核心算法
- 用户体验:友好的交互界面和输入验证
系统代码结构清晰,模块划分合理,具有良好的可读性和可维护性,适合作为C语言课程设计或初学者进阶学习的参考项目。
该系统不仅实现了基本的学生成绩管理功能,还通过多种技术优化提升了系统的实用性和健壮性,是一个功能完整、代码规范的C语言实践项目。

705

被折叠的 条评论
为什么被折叠?



