学生信息管理系统课程设计报告

1. 课程设计概述

1.1. 设计背景

在教育信息化快速发展的背景下,高校学生数量不断增长,学生信息(如基本信息、课程成绩等)的管理工作日益繁杂。传统的人工管理方式存在效率低下、数据易出错、查询统计不便等问题,已无法满足现代化教学管理的需求。因此,开发一个功能完善、操作便捷、数据安全的学生信息管理系统,能够有效提升学生信息管理的效率和准确性,减轻管理人员的工作负担,为教学管理提供有力支持。

1.2. 设计目标

本课程设计旨在开发一款基于 C 语言的学生信息管理系统,实现学生信息的规范化管理。具体目标如下:

  1. 数据管理功能:支持学生基本信息(学号、姓名、学院、专业)和成绩信息(语文、数学、英语、总分)的添加、删除、修改、查询操作。

  2. 数据存储功能:采用 CSV 文件作为数据存储介质,支持数据的持久化保存,确保系统关闭后数据不丢失,且支持 Excel 直接打开查看。

  3. 数据处理功能:提供按学号、各科成绩、总分的排序功能,以及平均分计算、成绩极值查找等统计分析功能。

  4. 数据验证功能:对输入的学号、成绩等数据进行合法性校验,确保数据的准确性和完整性。

  5. 操作便捷性:设计简洁直观的菜单界面,让用户能够快速上手操作,所有数据操作后自动保存,无需手动干预。

1.3. 开发环境

  • 编程语言:C 语言

  • 编译环境:GCC(GNU Compiler Collection)

  • 运行平台:Windows/macOS/Linux

  • 开发工具:Visual Studio Code、Code::Blocks

  • 数据存储:CSV(Comma-Separated Values)文件

2. 系统总体设计

2.1. 系统架构

本系统采用模块化的设计思想,将功能划分为多个独立的模块,各模块之间通过函数调用实现协作,具体架构如下:

  1. 数据存储模块:负责 CSV 文件的读写操作,实现数据的持久化存储和加载。

  2. 数据管理模块:包含学生信息的添加、删除、修改、查询等核心操作。

  3. 数据处理模块:实现学生信息的排序、平均分计算、成绩极值查找等功能。

  4. 界面交互模块:提供菜单显示、用户输入处理、信息输出等交互功能。

  5. 工具辅助模块:包含数据验证、输入缓冲区清理、内存管理等辅助功能。

系统的工作流程为:启动系统后,首先从 CSV 文件加载已有学生数据,然后显示主菜单供用户选择操作,用户完成相应操作后,系统自动将数据保存到 CSV 文件,直至用户选择退出系统。

2.2. 核心类设计

本系统基于 C 语言的结构体实现数据封装,核心结构体设计如下:

  1. 学生结构体(Student):用于存储单个学生的完整信息,包括基本信息和成绩信息,各字段采用字符数组存储,确保数据存储的安全性和兼容性。
// 学生结构体
typedef struct {
char id[MAX_ID_LEN];
char name[MAX_NAME_LEN];
char college[MAX_COLLEGE_LEN];
char major[MAX_MAJOR_LEN];
char chinese[MAX_SCORE_LEN];
char math[MAX_SCORE_LEN];
char english[MAX_SCORE_LEN];
char total[MAX_SCORE_LEN];
} Student;
  1. 学生管理器结构体(StudentManager):用于管理学生数据集合,包含学生数组指针、当前学生数量和容量,实现动态内存管理,支持学生数据的动态扩容。
typedef struct {
Student\* students; // 学生数组指针
int size; // 当前学生数量
int capacity; // 数组容量
} StudentManager;

2.3. 数据存储设计

系统采用 CSV 文件(student_info.csv)作为数据存储介质,CSV 文件具有结构简单、通用性强、可被 Excel 等办公软件直接打开的特点。文件格式设计如下:

  1. 表头行:存储字段名称,依次为 “学号”“姓名”“学院”“专业”“语文”“数学”“英语”“总分”。

  2. 数据行:每行存储一个学生的完整信息,各字段之间用逗号分隔,成绩字段支持小数格式(保留 1 位小数)。

示例数据如下:

学号,姓名,学院,专业,语文,数学,英语,总分
2023001,张三,计算机学院,计算机科学与技术,85.5,92.0,78.5,256.0
2023002,李四,电子信息学院,电子工程,90.0,88.5,93.0,271.5

3. 系统详细实现

3.1. CSV 文件读写实现

  1. 数据加载(loadFromCSV 函数):
  • 以只读模式打开 CSV 文件,若文件不存在则提示创建新文件。

  • 跳过表头行,逐行读取数据,使用 strtok 函数按逗号分割字段。

  • 对读取的字段进行换行符清理和数据验证,确保成绩字段为合法数字(0-100 分)。

  • 若学生数量超过当前容量,自动扩容(容量翻倍),并将数据存入学生数组。

  1. 数据保存(autoSaveToCSV 函数):
  • 以写入模式打开 CSV 文件,覆盖原有内容。

  • 先写入表头信息,再逐行写入学生数据,各字段用逗号分隔。

  • 所有数据操作(添加、删除、修改、排序)后自动调用该函数,确保数据实时保存。

3.2. 核心功能实现

3.3. 数据管理功能

  1. 添加学生(addStudent 函数):
  • 校验学号唯一性和非空性,避免重复学号。

  • 依次输入姓名、学院、专业(均校验非空)。

  • 输入语文、数学、英语成绩,通过 validateScore 函数校验成绩为 0-100 的数字。

  • 自动计算总分并更新,扩容学生数组(如需),增加学生数量并保存数据。

  1. 删除学生(deleteStudent 函数):
  • 支持按学号(数字输入)或姓名(非数字输入)查找学生。

  • 找到学生后显示其信息,提示用户确认删除操作。

  • 确认删除后,通过数组元素移动覆盖待删除数据,减少学生数量并保存数据。

  1. 修改学生(modifyStudent 函数):
  • 按学号或姓名查找学生,显示当前信息。

  • 提供修改选项(学院、专业、语文成绩、数学成绩、英语成绩),选择后输入新数据并校验。

  • 若修改成绩,自动重新计算总分,保存修改后的数据并显示更新结果。

  1. 查找学生(searchStudent 函数):
  • 支持按学号或姓名查找学生,找到后显示完整信息,未找到则提示错误。

3.4. 数据处理功能

  1. 排序功能:
  • 按学号排序(sortById 函数):先按学号长度排序,再按字典序升序排列,使用 qsort 函数和 compareById 比较函数。

  • 按成绩排序(sortByChinese、sortByMath、sortByEnglish、sortByTotal 函数):按对应成绩降序排列,使用 qsort 函数和相应比较函数。

  1. 统计分析功能:
  • 计算平均分(calculateAverage 函数):遍历所有学生,累加各科成绩和总分,计算并输出平均分(保留 2 位小数)。

  • 查找极值(findExtremeValues 函数):遍历所有学生,找到各科成绩和总分的最高分、最低分对应的学生,输出成绩及学生基本信息。

3.5. 辅助功能实现

  1. 数据验证(isNumeric 和 validateScore 函数):
  • isNumeric 函数:校验字符串是否为合法数字(支持整数和小数)。

  • validateScore 函数:校验成绩是否为 0-100 的合法数字,不合法则提示错误。

  1. 输入处理(clearInputBuffer 函数):
  • 清理输入缓冲区中的残留字符(如换行符),避免影响后续输入操作。
  1. 信息显示(displayHeader 和 displayStudent 函数):
  • displayHeader 函数:按固定格式显示表头,确保字段对齐。

  • displayStudent 函数:按表头格式显示单个学生信息,保证输出整齐美观。

4. 系统测试

4.1. 功能测试

测试功能 测试用例 预期结果 测试结果
添加学生 输入合法学号、姓名、学院、专业及 0-100 分成绩 学生添加成功,总分自动计算,CSV 文件更新 符合预期
添加学生 输入重复学号 提示学号已存在,要求重新输入 符合预期
添加学生 输入负数或 100 分以上成绩 提示成绩非法,要求重新输入 符合预期
删除学生 输入存在的学号 / 姓名 找到学生并确认后删除,CSV 文件更新 符合预期
删除学生 输入不存在的学号 / 姓名 提示未找到学生 符合预期
修改学生 修改学院 / 专业 信息更新成功,CSV 文件保存 符合预期
修改学生 修改成绩为 85.5 成绩更新,总分重新计算,CSV 文件保存 符合预期
查找学生 输入存在的学号 / 姓名 显示学生完整信息 符合预期
排序功能 按总分排序 学生按总分降序排列,CSV 文件更新 符合预期
统计功能 计算平均分 正确输出各科及总分平均分 符合预期
统计功能 查找极值 正确输出各科及总分的最高 / 最低分及对应学生 符合预期

4.2. 运行截图

  1. 系统启动界面:显示数据加载完成提示,主菜单界面。
    image.png

  2. 添加学生界面:输入学生信息过程及添加成功提示。
    image.png

  3. 显示所有学生界面:按格式整齐显示所有学生信息。
    image.png

  4. 统计分析界面:平均分计算结果和成绩极值查找结果。
    image.png

4.3. 边界测试

  1. 极限容量测试:添加 1000 名学生(MAX_STUDENTS),系统自动扩容,无内存溢出,数据正常保存。

  2. 空数据测试:系统无学生数据时,执行删除、修改、查找、排序等操作,提示 “当前没有学生数据”。

  3. 成绩边界测试:输入 0 分、100 分、99.9 分等边界成绩,系统正常接收并计算总分。

  4. 输入异常测试:输入非数字成绩、空姓名 / 学院 / 专业,系统提示错误并要求重新输入。

4.4. 用户体验测试

  1. 界面友好性:菜单清晰明了,操作提示简洁易懂,输出信息格式整齐。

  2. 操作便捷性:支持按学号 / 姓名快速查找和删除,修改功能针对性强,无需重复输入无关信息。

  3. 数据安全性:所有操作自动保存,避免数据丢失;数据验证机制确保输入数据合法。

  4. 兼容性:CSV 文件可直接用 Excel 打开编辑,编辑后重新加载系统可正常识别(需确保格式正确)。

5. 总结与展望

5.1. 课程设计总结

本课程设计基于 C 语言成功实现了学生信息管理系统,完成了预期的所有功能目标。系统采用模块化设计,结构清晰,各功能模块独立且协作良好;通过结构体封装学生数据,结合动态内存管理实现了学生数组的灵活扩容;采用 CSV 文件存储数据,兼顾了数据持久化和通用性;完善的数据验证机制和自动保存功能确保了数据的准确性和安全性。

在开发过程中,重点解决了以下问题:

  1. CSV 文件的解析与写入,处理了字段分隔、换行符清理等细节。

  2. 动态内存管理,通过扩容机制支持大量学生数据存储。

  3. 数据验证与输入处理,避免非法数据和输入缓冲区残留问题。

  4. 排序算法的实现,利用 qsort 函数提高排序效率。

同时,系统也存在一些不足:

  1. CSV 文件解析仅支持简单格式,不支持字段包含逗号的情况。

  2. 查找功能仅支持精确匹配,不支持模糊查询(如按姓名前缀查找)。

  3. 界面为命令行界面,缺乏图形化交互,用户体验有待提升。

5.2. 未来展望

针对系统存在的不足,未来可从以下方面进行优化和扩展:

  1. 增强 CSV 文件兼容性:支持字段包含逗号、引号等特殊字符的解析,提高数据存储的灵活性。

  2. 扩展查询功能:增加模糊查询(按姓名、学院、专业模糊匹配)、多条件查询(如学院 + 专业筛选)。

  3. 优化界面交互:开发图形化界面(如基于 GTK 或 Qt),提升操作便捷性和视觉体验。

  4. 增加更多统计功能:如成绩分布统计(及格率、优秀率)、学生信息导出(PDF、Excel 格式)。

  5. 数据安全优化:添加密码登录功能,对敏感数据进行加密存储,防止未授权访问。

6. 附录

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>

#define MAX_STUDENTS 1000
#define MAX_NAME_LEN 50
#define MAX_COLLEGE_LEN 50
#define MAX_MAJOR_LEN 50
#define MAX_ID_LEN 20
#define MAX_SCORE_LEN 10
#define CSV_FILENAME "student_info.csv"

// 学生结构体
typedef struct {
char id[MAX_ID_LEN];
char name[MAX_NAME_LEN];
char college[MAX_COLLEGE_LEN];
char major[MAX_MAJOR_LEN];
char chinese[MAX_SCORE_LEN];
char math[MAX_SCORE_LEN];
char english[MAX_SCORE_LEN];
char total[MAX_SCORE_LEN];
} Student;

// 学生管理器结构体
typedef struct {
Student* students;
int size;
int capacity;
} StudentManager;

// 全局变量
const char* HEADERS[] = {"学号", "姓名", "学院", "专业", "语文", "数学", "英语", "总分"};

// 函数声明
void initStudentManager(StudentManager* manager);
void freeStudentManager(StudentManager* manager);
int isNumeric(const char* str);
void loadFromCSV(StudentManager* manager);
void autoSaveToCSV(StudentManager* manager);
int validateScore(const char* score, const char* subject);
void updateTotal(Student* s);
int findById(StudentManager* manager, const char* id);
int findByName(StudentManager* manager, const char* name);
void addStudent(StudentManager* manager);
void deleteStudent(StudentManager* manager);
void modifyStudent(StudentManager* manager);
void searchStudent(StudentManager* manager);
void displayAll(StudentManager* manager);
void displayHeader();
void displayStudent(Student* s);
void sortById(StudentManager* manager);
void sortByChinese(StudentManager* manager);
void sortByMath(StudentManager* manager);
void sortByEnglish(StudentManager* manager);
void sortByTotal(StudentManager* manager);
void calculateAverage(StudentManager* manager);
void findExtremeValues(StudentManager* manager);
void displayMenu();
void clearInputBuffer();

// 初始化学生管理器
void initStudentManager(StudentManager* manager) {
manager->capacity = 100;
manager->size = 0;
manager->students = (Student*)malloc(manager->capacity * sizeof(Student));
if (manager->students == NULL) {
printf("内存分配失败!\n");
exit(1);
}
}

// 释放学生管理器内存
void freeStudentManager(StudentManager* manager) {
free(manager->students);
manager->students = NULL;
manager->size = 0;
manager->capacity = 0;
}

// 检查字符串是否为数字
int isNumeric(const char* str) {
if (str == NULL || *str == '\0') return 0;

int dotCount = 0;
for (int i = 0; str[i] != '\0'; i++) {
if (str[i] == '.') {
dotCount++;
if (dotCount > 1) return 0;
} else if (!isdigit(str[i])) {
return 0;
}
}
return 1;
}

// 从CSV文件加载数据
void loadFromCSV(StudentManager* manager) {
FILE* file = fopen(CSV_FILENAME, "r");
if (file == NULL) {
printf("未找到CSV数据文件,将创建新文件。\n");
return;
}

char line[1024];
fgets(line, sizeof(line), file); // 跳过表头

while (fgets(line, sizeof(line), file)) {
if (line[0] == '\0' || line[0] == '\n') continue;

// 简单的CSV解析(假设字段不包含逗号)
char* tokens[8];
char* token = strtok(line, ",");
int tokenCount = 0;

while (token != NULL && tokenCount < 8) {
tokens[tokenCount++] = token;
token = strtok(NULL, ",");
}

if (tokenCount >= 8) {
if (manager->size >= manager->capacity) {
manager->capacity *= 2;
manager->students = (Student*)realloc(manager->students, manager->capacity * sizeof(Student));
if (manager->students == NULL) {
printf("内存分配失败!\n");
fclose(file);
return;
}
}

Student* s = &manager->students[manager->size];
strncpy(s->id, tokens[0], MAX_ID_LEN - 1);
strncpy(s->name, tokens[1], MAX_NAME_LEN - 1);
strncpy(s->college, tokens[2], MAX_COLLEGE_LEN - 1);
strncpy(s->major, tokens[3], MAX_MAJOR_LEN - 1);
strncpy(s->chinese, tokens[4], MAX_SCORE_LEN - 1);
strncpy(s->math, tokens[5], MAX_SCORE_LEN - 1);
strncpy(s->english, tokens[6], MAX_SCORE_LEN - 1);
strncpy(s->total, tokens[7], MAX_SCORE_LEN - 1);

// 移除可能的换行符
s->id[strcspn(s->id, "\n")] = 0;
s->name[strcspn(s->name, "\n")] = 0;
s->college[strcspn(s->college, "\n")] = 0;
s->major[strcspn(s->major, "\n")] = 0;
s->chinese[strcspn(s->chinese, "\n")] = 0;
s->math[strcspn(s->math, "\n")] = 0;
s->english[strcspn(s->english, "\n")] = 0;
s->total[strcspn(s->total, "\n")] = 0;

// 验证成绩
if (!validateScore(s->chinese, "语文")) strcpy(s->chinese, "0.0");
if (!validateScore(s->math, "数学")) strcpy(s->math, "0.0");
if (!validateScore(s->english, "英语")) strcpy(s->english, "0.0");
updateTotal(s);

manager->size++;
}
}

fclose(file);
printf("CSV数据加载完成,共 %d 名学生。\n", manager->size);
}

// 自动保存到CSV文件
void autoSaveToCSV(StudentManager* manager) {
FILE* file = fopen(CSV_FILENAME, "w");
if (file == NULL) {
printf("CSV文件保存失败!\n");
return;
}

// 写入表头
for (int i = 0; i < 8; i++) {
fprintf(file, "%s", HEADERS[i]);
if (i < 7) fprintf(file, ",");
}
fprintf(file, "\n");

// 写入学生数据
for (int i = 0; i < manager->size; i++) {
Student* s = &manager->students[i];
fprintf(file, "%s,%s,%s,%s,%s,%s,%s,%s\n",
s->id, s->name, s->college, s->major,
s->chinese, s->math, s->english, s->total);
}

fclose(file);
}

// 验证成绩合法性
int validateScore(const char* score, const char* subject) {
if (!isNumeric(score)) {
printf("错误:%s成绩必须是数字!请重新输入。\n", subject);
return 0;
}

double scoreValue = atof(score);
if (scoreValue < 0 || scoreValue > 100) {
printf("错误:%s成绩必须在0-100之间!请重新输入。\n", subject);
return 0;
}

return 1;
}

// 更新总分
void updateTotal(Student* s) {
double chi = atof(s->chinese);
double math = atof(s->math);
double eng = atof(s->english);
double total = chi + math + eng;
snprintf(s->total, MAX_SCORE_LEN, "%.1f", total);
}

// 根据学号查找学生索引
int findById(StudentManager* manager, const char* id) {
for (int i = 0; i < manager->size; i++) {
if (strcmp(manager->students[i].id, id) == 0) {
return i;
}
}
return -1;
}

// 根据姓名查找学生索引
int findByName(StudentManager* manager, const char* name) {
for (int i = 0; i < manager->size; i++) {
if (strcmp(manager->students[i].name, name) == 0) {
return i;
}
}
return -1;
}

// 添加学生
void addStudent(StudentManager* manager) {
printf("\n===================== 添加学生 =====================\n");

if (manager->size >= manager->capacity) {
manager->capacity *= 2;
manager->students = (Student*)realloc(manager->students, manager->capacity * sizeof(Student));
if (manager->students == NULL) {
printf("内存分配失败!\n");
return;
}
}

Student* s = &manager->students[manager->size];

// 学号验证
while (1) {
printf("请输入学号:");
scanf("%s", s->id);
clearInputBuffer();

if (findById(manager, s->id) != -1) {
printf("错误:该学号已存在!请重新输入。\n");
} else if (strlen(s->id) == 0) {
printf("错误:学号不能为空!请重新输入。\n");
} else {
break;
}
}

// 姓名
printf("请输入姓名:");
fgets(s->name, MAX_NAME_LEN, stdin);
s->name[strcspn(s->name, "\n")] = 0; // 移除换行符
while (strlen(s->name) == 0) {
printf("错误:姓名不能为空!请重新输入:");
fgets(s->name, MAX_NAME_LEN, stdin);
s->name[strcspn(s->name, "\n")] = 0;
}

// 学院
printf("请输入学院:");
fgets(s->college, MAX_COLLEGE_LEN, stdin);
s->college[strcspn(s->college, "\n")] = 0;
while (strlen(s->college) == 0) {
printf("错误:学院不能为空!请重新输入:");
fgets(s->college, MAX_COLLEGE_LEN, stdin);
s->college[strcspn(s->college, "\n")] = 0;
}

// 专业
printf("请输入专业:");
fgets(s->major, MAX_MAJOR_LEN, stdin);
s->major[strcspn(s->major, "\n")] = 0;
while (strlen(s->major) == 0) {
printf("错误:专业不能为空!请重新输入:");
fgets(s->major, MAX_MAJOR_LEN, stdin);
s->major[strcspn(s->major, "\n")] = 0;
}

// 成绩输入
while (1) {
printf("请输入语文成绩:");
scanf("%s", s->chinese);
clearInputBuffer();
if (validateScore(s->chinese, "语文")) break;
}

while (1) {
printf("请输入数学成绩:");
scanf("%s", s->math);
clearInputBuffer();
if (validateScore(s->math, "数学")) break;
}

while (1) {
printf("请输入英语成绩:");
scanf("%s", s->english);
clearInputBuffer();
if (validateScore(s->english, "英语")) break;
}

updateTotal(s);
manager->size++;
autoSaveToCSV(manager);
printf("\n✅ 学生添加成功!数据已保存到 %s\n", CSV_FILENAME);
}

// 删除学生
void deleteStudent(StudentManager* manager) {
printf("\n===================== 删除学生 =====================\n");
if (manager->size == 0) {
printf("提示:当前没有学生数据!\n");
return;
}

char input[100];
printf("请输入学号或姓名:");
scanf("%s", input);
clearInputBuffer();

int index = isNumeric(input) ? findById(manager, input) : findByName(manager, input);

if (index == -1) {
printf("错误:未找到该学生!\n");
return;
}

printf("找到学生:\n");
displayHeader();
displayStudent(&manager->students[index]);

char confirm;
printf("确定要删除吗?(y/n):");
scanf(" %c", &confirm);
clearInputBuffer();

if (confirm == 'y' || confirm == 'Y') {
// 移动数组元素
for (int i = index; i < manager->size - 1; i++) {
manager->students[i] = manager->students[i + 1];
}
manager->size--;
autoSaveToCSV(manager);
printf("\n✅ 学生删除成功!数据已保存到 %s\n", CSV_FILENAME);
} else {
printf("❌ 删除操作已取消\n");
}
}

// 修改学生信息
void modifyStudent(StudentManager* manager) {
printf("\n===================== 修改学生 =====================\n");
if (manager->size == 0) {
printf("提示:当前没有学生数据!\n");
return;
}

char input[100];
printf("请输入学号或姓名:");
scanf("%s", input);
clearInputBuffer();

int index = isNumeric(input) ? findById(manager, input) : findByName(manager, input);

if (index == -1) {
printf("错误:未找到该学生!\n");
return;
}

Student* s = &manager->students[index];
printf("当前学生信息:\n");
displayHeader();
displayStudent(s);

printf("\n请选择要修改的项目:\n");
printf("1. 学院\n");
printf("2. 专业\n");
printf("3. 语文成绩\n");
printf("4. 数学成绩\n");
printf("5. 英语成绩\n");
printf("6. 取消修改\n");
printf("请选择(1-6):");

int choice;
scanf("%d", &choice);
clearInputBuffer();

switch (choice) {
case 1:
printf("请输入新的学院:");
fgets(s->college, MAX_COLLEGE_LEN, stdin);
s->college[strcspn(s->college, "\n")] = 0;
while (strlen(s->college) == 0) {
printf("错误:学院不能为空!请重新输入:");
fgets(s->college, MAX_COLLEGE_LEN, stdin);
s->college[strcspn(s->college, "\n")] = 0;
}
break;
case 2:
printf("请输入新的专业:");
fgets(s->major, MAX_MAJOR_LEN, stdin);
s->major[strcspn(s->major, "\n")] = 0;
while (strlen(s->major) == 0) {
printf("错误:专业不能为空!请重新输入:");
fgets(s->major, MAX_MAJOR_LEN, stdin);
s->major[strcspn(s->major, "\n")] = 0;
}
break;
case 3:
while (1) {
printf("请输入新的语文成绩:");
scanf("%s", s->chinese);
clearInputBuffer();
if (validateScore(s->chinese, "语文")) {
updateTotal(s);
break;
}
}
break;
case 4:
while (1) {
printf("请输入新的数学成绩:");
scanf("%s", s->math);
clearInputBuffer();
if (validateScore(s->math, "数学")) {
updateTotal(s);
break;
}
}
break;
case 5:
while (1) {
printf("请输入新的英语成绩:");
scanf("%s", s->english);
clearInputBuffer();
if (validateScore(s->english, "英语")) {
updateTotal(s);
break;
}
}
break;
case 6:
printf("❌ 修改操作已取消\n");
return;
default:
printf("错误:无效选择!\n");
return;
}

autoSaveToCSV(manager);
printf("\n✅ 学生信息修改成功!数据已保存到 %s\n", CSV_FILENAME);
printf("修改后的信息:\n");
displayHeader();
displayStudent(s);
}

// 查找学生
void searchStudent(StudentManager* manager) {
printf("\n===================== 查找学生 =====================\n");
if (manager->size == 0) {
printf("提示:当前没有学生数据!\n");
return;
}

char input[100];
printf("请输入学号或姓名:");
scanf("%s", input);
clearInputBuffer();

int index = isNumeric(input) ? findById(manager, input) : findByName(manager, input);

if (index == -1) {
printf("错误:未找到该学生!\n");
return;
}

printf("✅ 找到学生:\n");
displayHeader();
displayStudent(&manager->students[index]);
}

// 显示所有学生
void displayAll(StudentManager* manager) {
if (manager->size == 0) {
printf("提示:当前没有学生数据!\n");
return;
}

printf("\n===================== 所有学生信息 (%d 名) =====================\n", manager->size);
displayHeader();
for (int i = 0; i < manager->size; i++) {
displayStudent(&manager->students[i]);
}
printf("==========================================================================\n");
}

// 显示表头
void displayHeader() {
printf("%-10s%-20s%-30s%-30s%-10s%-10s%-10s%-10s\n",
HEADERS[0], HEADERS[1], HEADERS[2], HEADERS[3],
HEADERS[4], HEADERS[5], HEADERS[6], HEADERS[7]);
printf("----------------------------------------------------------------------------------------------------\n");
}

// 显示单个学生信息
void displayStudent(Student* s) {
printf("%-10s%-20s%-30s%-30s%-10s%-10s%-10s%-10s\n",
s->id, s->name, s->college, s->major,
s->chinese, s->math, s->english, s->total);
}

// 比较函数 - 按学号
int compareById(const void* a, const void* b) {
const Student* s1 = (const Student*)a;
const Student* s2 = (const Student*)b;

// 先按长度排序,再按字典序
int len1 = strlen(s1->id);
int len2 = strlen(s2->id);
if (len1 != len2) return len1 - len2;
return strcmp(s1->id, s2->id);
}

// 比较函数 - 按语文成绩
int compareByChinese(const void* a, const void* b) {
const Student* s1 = (const Student*)a;
const Student* s2 = (const Student*)b;
double score1 = atof(s1->chinese);
double score2 = atof(s2->chinese);
return (score2 > score1) ? 1 : (score2 < score1) ? -1 : 0;
}

// 比较函数 - 按数学成绩
int compareByMath(const void* a, const void* b) {
const Student* s1 = (const Student*)a;
const Student* s2 = (const Student*)b;
double score1 = atof(s1->math);
double score2 = atof(s2->math);
return (score2 > score1) ? 1 : (score2 < score1) ? -1 : 0;
}

// 比较函数 - 按英语成绩
int compareByEnglish(const void* a, const void* b) {
const Student* s1 = (const Student*)a;
const Student* s2 = (const Student*)b;
double score1 = atof(s1->english);
double score2 = atof(s2->english);
return (score2 > score1) ? 1 : (score2 < score1) ? -1 : 0;
}

// 比较函数 - 按总分
int compareByTotal(const void* a, const void* b) {
const Student* s1 = (const Student*)a;
const Student* s2 = (const Student*)b;
double score1 = atof(s1->total);
double score2 = atof(s2->total);
return (score2 > score1) ? 1 : (score2 < score1) ? -1 : 0;
}

// 排序函数
void sortById(StudentManager* manager) {
if (manager->size == 0) {
printf("提示:当前没有学生数据可排序!\n");
return;
}
qsort(manager->students, manager->size, sizeof(Student), compareById);
autoSaveToCSV(manager);
printf("\n✅ 已按学号升序排序!数据已保存到 %s\n", CSV_FILENAME);
}

void sortByChinese(StudentManager* manager) {
if (manager->size == 0) {
printf("提示:当前没有学生数据可排序!\n");
return;
}
qsort(manager->students, manager->size, sizeof(Student), compareByChinese);
autoSaveToCSV(manager);
printf("\n✅ 已按语文成绩降序排序!数据已保存到 %s\n", CSV_FILENAME);
}

void sortByMath(StudentManager* manager) {
if (manager->size == 0) {
printf("提示:当前没有学生数据可排序!\n");
return;
}
qsort(manager->students, manager->size, sizeof(Student), compareByMath);
autoSaveToCSV(manager);
printf("\n✅ 已按数学成绩降序排序!数据已保存到 %s\n", CSV_FILENAME);
}

void sortByEnglish(StudentManager* manager) {
if (manager->size == 0) {
printf("提示:当前没有学生数据可排序!\n");
return;
}
qsort(manager->students, manager->size, sizeof(Student), compareByEnglish);
autoSaveToCSV(manager);
printf("\n✅ 已按英语成绩降序排序!数据已保存到 %s\n", CSV_FILENAME);
}

void sortByTotal(StudentManager* manager) {
if (manager->size == 0) {
printf("提示:当前没有学生数据可排序!\n");
return;
}
qsort(manager->students, manager->size, sizeof(Student), compareByTotal);
autoSaveToCSV(manager);
printf("\n✅ 已按总分降序排序!数据已保存到 %s\n", CSV_FILENAME);
}

// 计算平均分
void calculateAverage(StudentManager* manager) {
if (manager->size == 0) {
printf("提示:当前没有学生数据!\n");
return;
}

double chiTotal = 0.0, mathTotal = 0.0, engTotal = 0.0, totalTotal = 0.0;
int validCount = 0;

for (int i = 0; i < manager->size; i++) {
Student* s = &manager->students[i];
chiTotal += atof(s->chinese);
mathTotal += atof(s->math);
engTotal += atof(s->english);
totalTotal += atof(s->total);
validCount++;
}

if (validCount == 0) {
printf("错误:没有有效的成绩数据可计算!\n");
return;
}

printf("\n===================== 成绩平均分 =====================\n");
printf("语文平均分:%.2f\n", chiTotal / validCount);
printf("数学平均分:%.2f\n", mathTotal / validCount);
printf("英语平均分:%.2f\n", engTotal / validCount);
printf("总分平均分:%.2f\n", totalTotal / validCount);
}

// 查找极值
void findExtremeValues(StudentManager* manager) {
if (manager->size == 0) {
printf("提示:当前没有学生数据!\n");
return;
}

// 简单的极值查找实现
int chiMinIndex = 0, chiMaxIndex = 0;
int mathMinIndex = 0, mathMaxIndex = 0;
int engMinIndex = 0, engMaxIndex = 0;
int totalMinIndex = 0, totalMaxIndex = 0;

for (int i = 1; i < manager->size; i++) {
Student* current = &manager->students[i];

if (atof(current->chinese) < atof(manager->students[chiMinIndex].chinese)) chiMinIndex = i;
if (atof(current->chinese) > atof(manager->students[chiMaxIndex].chinese)) chiMaxIndex = i;

if (atof(current->math) < atof(manager->students[mathMinIndex].math)) mathMinIndex = i;
if (atof(current->math) > atof(manager->students[mathMaxIndex].math)) mathMaxIndex = i;

if (atof(current->english) < atof(manager->students[engMinIndex].english)) engMinIndex = i;
if (atof(current->english) > atof(manager->students[engMaxIndex].english)) engMaxIndex = i;

if (atof(current->total) < atof(manager->students[totalMinIndex].total)) totalMinIndex = i;
if (atof(current->total) > atof(manager->students[totalMaxIndex].total)) totalMaxIndex = i;
}

printf("\n===================== 成绩极值 =====================\n");
printf("语文最低分:%s (学号:%s,姓名:%s)\n",
manager->students[chiMinIndex].chinese,
manager->students[chiMinIndex].id,
manager->students[chiMinIndex].name);
printf("语文最高分:%s (学号:%s,姓名:%s)\n",
manager->students[chiMaxIndex].chinese,
manager->students[chiMaxIndex].id,
manager->students[chiMaxIndex].name);
printf("数学最低分:%s (学号:%s,姓名:%s)\n",
manager->students[mathMinIndex].math,
manager->students[mathMinIndex].id,
manager->students[mathMinIndex].name);
printf("数学最高分:%s (学号:%s,姓名:%s)\n",
manager->students[mathMaxIndex].math,
manager->students[mathMaxIndex].id,
manager->students[mathMaxIndex].name);
printf("英语最低分:%s (学号:%s,姓名:%s)\n",
manager->students[engMinIndex].english,
manager->students[engMinIndex].id,
manager->students[engMinIndex].name);
printf("英语最高分:%s (学号:%s,姓名:%s)\n",
manager->students[engMaxIndex].english,
manager->students[engMaxIndex].id,
manager->students[engMaxIndex].name);
printf("总分最低分:%s (学号:%s,姓名:%s)\n",
manager->students[totalMinIndex].total,
manager->students[totalMinIndex].id,
manager->students[totalMinIndex].name);
printf("总分最高分:%s (学号:%s,姓名:%s)\n",
manager->students[totalMaxIndex].total,
manager->students[totalMaxIndex].id,
manager->students[totalMaxIndex].name);
}

// 显示菜单
void displayMenu() {
printf("\n==========================================================\n");
printf(" 学生信息管理系统 \n");
printf("==========================================================\n");
printf("1. 添加学生 2. 删除学生 3. 修改学生\n");
printf("4. 查找学生 5. 显示所有 6. 按学号排序\n");
printf("7. 按语文排序 8. 按数学排序 9. 按英语排序\n");
printf("10. 按总分排序 11. 计算平均分 12. 查找极值\n");
printf(" 14. 退出系统 \n");
printf("==========================================================\n");
printf("提示:数据存储在 %s,支持Excel直接打开\n", CSV_FILENAME);
printf("请选择功能(1-14):");
}

// 清空输入缓冲区
void clearInputBuffer() {
int c;
while ((c = getchar()) != '\n' && c != EOF);
}

// 主函数
int main() {
StudentManager manager;
initStudentManager(&manager);

printf("正在加载CSV学生数据...\n");
loadFromCSV(&manager);

int choice = 0;
do {
displayMenu();

if (scanf("%d", &choice) != 1) {
clearInputBuffer();
printf("\n❌ 错误:输入无效!请输入数字1-14。\n");
continue;
}

clearInputBuffer();

switch (choice) {
case 1: addStudent(&manager); break;
case 2: deleteStudent(&manager); break;
case 3: modifyStudent(&manager); break;
case 4: searchStudent(&manager); break;
case 5: displayAll(&manager); break;
case 6: sortById(&manager); break;
case 7: sortByChinese(&manager); break;
case 8: sortByMath(&manager); break;
case 9: sortByEnglish(&manager); break;
case 10: sortByTotal(&manager); break;
case 11: calculateAverage(&manager); break;
case 12: findExtremeValues(&manager); break;
case 13:
printf("\n✅ 感谢使用学生信息管理系统!数据已保存到 %s\n", CSV_FILENAME);
break;
default:
printf("\n❌ 错误:选择无效!请输入1-14之间的数字。\n");
}

if (choice != 14) {
printf("\n按Enter键继续...");
getchar();
}

} while (choice != 14);

freeStudentManager(&manager);
return 0;
}