diff --git a/Makefile b/Makefile index 9074f0e..c7ef470 100644 --- a/Makefile +++ b/Makefile @@ -7,29 +7,23 @@ CFLAGS = -Wall -Wextra -std=c99 -g TARGET = student_system # 源文件 -SOURCES = main.c globals.c auxiliary.c main_menu.c user_manage.c stu_data.c statistical_analysis.c - -# 目标文件 -OBJECTS = $(SOURCES:.c=.o) +SOURCES = main.c globals.c main_menu.c user_manage.c core_handlers.c statistical_analysis.c student_io.c student_crud.c student_search.c student_sort.c io_utils.c validation.c string_utils.c file_utils.c math_utils.c system_utils.c # 头文件 -HEADERS = config.h globals.h auxiliary.h main_menu.h user_manage.h stu.data.h statistical_analysis.h +HEADERS = config.h globals.h main_menu.h user_manage.h core_handlers.h statistical_analysis.h student_io.h student_crud.h student_search.h student_sort.h io_utils.h validation.h string_utils.h file_utils.h math_utils.h system_utils.h # 默认目标 all: $(TARGET) -# 链接目标文件 -$(TARGET): $(OBJECTS) - $(CC) $(OBJECTS) -o $(TARGET) - -# 编译源文件 -%.o: %.c $(HEADERS) - $(CC) $(CFLAGS) -c $< -o $@ +# 直接编译链接(不生成.o文件) +$(TARGET): $(SOURCES) $(HEADERS) + $(CC) $(CFLAGS) $(SOURCES) -o $(TARGET) # 清理编译文件 clean: - rm -f $(OBJECTS) $(TARGET) + rm -f $(TARGET) rm -f *.exe + rm -f *.o # 创建必要的目录 setup: diff --git a/config.h b/config.h index d9036d5..8a6c948 100644 --- a/config.h +++ b/config.h @@ -26,6 +26,10 @@ #define PASS_SCORE 60.0 // 及格分数 #define EXCELLENT_SCORE 90.0 // 优秀分数 +// 年龄相关配置 +#define MIN_AGE 10 // 最小年龄 +#define MAX_AGE 100 // 最大年龄 + // 文件路径配置 #define STUDENTS_FILE "data/students.csv" // 学生数据文件 #define USERS_FILE "data/users.txt" // 用户数据文件 diff --git a/core_handlers.c b/core_handlers.c index 4f4ab5d..867b6cf 100644 --- a/core_handlers.c +++ b/core_handlers.c @@ -4,151 +4,202 @@ * @note 实现主要的功能处理函数 */ - #include - #include - #include - - #include "core_handlers.h" - #include "config.h" - #include "globals.h" - #include "main_menu.h" - #include "user_manage.h" - #include "stu.data.h" - #include "statistical_analysis.h" - #include "auxiliary.h" - - /** - * @brief 处理基本功能菜单 - */ - void handleBasicFunctions() - { - int choice; - do { - clearScreen(); - displayBasicFunctionsMenu(); - choice = safeInputInt("请选择功能", BASIC_BACK, BASIC_SORT_STUDENTS); - - switch (choice) { - case BASIC_ADD_STUDENT: - addStudent(); - break; - case BASIC_DELETE_STUDENT: - deleteStudent(); - break; - case BASIC_MODIFY_STUDENT: - modifyStudent(); - break; - case BASIC_SEARCH_BY_ID: - searchStudentByID(); - break; - case BASIC_SEARCH_BY_NAME: - searchStudentByName(); - break; - case BASIC_DISPLAY_ALL: - displayAllStudents(); - break; - case BASIC_SORT_STUDENTS: - handleSortStudents(); - break; - case BASIC_BACK: - break; - default: - printError("无效的选择!"); - pauseSystem(); - break; - } - } while (choice != BASIC_BACK); - } - - /** - * @brief 处理统计功能菜单 - */ - void handleStatistics() { - int choice; - do { - clearScreen(); - displayStatisticsMenu(); - choice = safeInputInt("请选择功能", STATS_BACK, STATS_OVERALL_ANALYSIS); - - switch (choice) { - case STATS_COURSE_ANALYSIS: - displayCourseStatistics(); - break; - case STATS_SCORE_DISTRIBUTION: - displayScoreDistribution(); - break; - case STATS_SCORE_RANGES: - displayStudentRanking(); - break; - case STATS_OVERALL_ANALYSIS: - displayOverallStatistics(); - break; - case STATS_BACK: - break; - default: - printError("无效的选择!"); - pauseSystem(); - break; - } - } while (choice != STATS_BACK); - } - - /** - * @brief 处理管理功能菜单 - */ - void handleAdminFunctions() { - int choice; - do { - clearScreen(); - displayAdminMenu(); - choice = safeInputInt("请选择功能", ADMIN_BACK, ADMIN_VIEW_USERS); - - switch (choice) { - case ADMIN_ADD_USER: - addUserAccount(); - break; - case ADMIN_DELETE_USER: - deleteUserAccount(); - break; - case ADMIN_MODIFY_PASSWORD: - modifyUserPassword(); - break; - case ADMIN_VIEW_USERS: - viewAllUsers(); - break; - case ADMIN_BACK: - break; - default: - printError("无效的选择!"); - pauseSystem(); - break; - } - } while (choice != ADMIN_BACK); - } - - /** - * @brief 处理学生排序功能 - */ - void handleSortStudents() { - clearScreen(); - printHeader("学生排序"); - - printf("排序依据:\n"); - printf("1. 按学号排序\n"); - printf("2. 按姓名排序\n"); - printf("3. 按总分排序\n"); - printf("4. 按平均分排序\n"); - - int criteria = safeInputInt("请选择排序依据", SORT_BY_ID, SORT_BY_AVERAGE_SCORE); - - printf("\n排序顺序:\n"); - printf("1. 升序\n"); - printf("2. 降序\n"); - - int order = safeInputInt("请选择排序顺序", SORT_ASCENDING, SORT_DESCENDING); - - sortStudents(criteria, order); - - printf("\n排序完成!\n"); - displayAllStudents(); - } \ No newline at end of file +#include +#include +#include + +#include "core_handlers.h" +#include "config.h" +#include "globals.h" +#include "main_menu.h" +#include "user_manage.h" +#include "student_io.h" +#include "student_crud.h" +#include "student_search.h" +#include "student_sort.h" +#include "statistical_analysis.h" +#include "io_utils.h" + +/** + * @brief 处理基本功能菜单 + * @details 显示并处理学生信息管理的基本功能菜单循环 + * 提供学生信息的增删改查、排序等核心功能 + * 循环显示菜单直到用户选择返回主菜单 + * @note 包含的功能: + * - 添加学生信息 + * - 删除学生信息 + * - 修改学生信息 + * - 按学号查找学生 + * - 按姓名查找学生 + * - 显示所有学生 + * - 学生信息排序 + */ +void handleBasicFunctions() +{ + int choice; + do + { + clearScreen(); + displayBasicFunctionsMenu(); + choice = safeInputInt("请选择功能", BASIC_BACK, BASIC_SORT_STUDENTS); + + switch (choice) + { + case BASIC_ADD_STUDENT: + addStudent(); + break; + case BASIC_DELETE_STUDENT: + deleteStudent(); + break; + case BASIC_MODIFY_STUDENT: + modifyStudent(); + break; + case BASIC_SEARCH_BY_ID: + searchStudentByID(); + break; + case BASIC_SEARCH_BY_NAME: + searchStudentByName(); + break; + case BASIC_DISPLAY_ALL: + displayAllStudents(); + break; + case BASIC_SORT_STUDENTS: + handleSortStudents(); + break; + case BASIC_BACK: + break; + default: + printError("无效的选择!"); + pauseSystem(); + break; + } + } while (choice != BASIC_BACK); +} + +/** + * @brief 处理统计功能菜单 + * @details 显示并处理统计分析功能菜单循环 + * 提供各种学生成绩的统计分析功能 + * 循环显示菜单直到用户选择返回主菜单 + * @note 包含的功能: + * - 课程统计分析 + * - 成绩分布统计 + * - 学生排名统计 + * - 综合统计分析 + */ +void handleStatistics() +{ + int choice; + do + { + clearScreen(); + displayStatisticsMenu(); + choice = safeInputInt("请选择功能", STATS_BACK, STATS_OVERALL_ANALYSIS); + + switch (choice) + { + case STATS_COURSE_ANALYSIS: + displayCourseStatistics(); + break; + case STATS_SCORE_DISTRIBUTION: + displayScoreDistribution(); + break; + case STATS_SCORE_RANGES: + displayStudentRanking(); + break; + case STATS_OVERALL_ANALYSIS: + displayOverallStatistics(); + break; + case STATS_BACK: + break; + default: + printError("无效的选择!"); + pauseSystem(); + break; + } + } while (choice != STATS_BACK); +} + +/** + * @brief 处理管理功能菜单 + * @details 显示并处理系统管理功能菜单循环,仅限管理员用户访问 + * 提供用户账户管理的各项功能 + * 循环显示菜单直到用户选择返回主菜单 + * @note 包含的功能: + * - 添加用户账户 + * - 删除用户账户 + * - 修改用户密码 + * - 查看所有用户 + * @warning 此函数仅应在验证用户为管理员后调用 + */ +void handleAdminFunctions() +{ + int choice; + do + { + clearScreen(); + displayAdminMenu(); + choice = safeInputInt("请选择功能", ADMIN_BACK, ADMIN_VIEW_USERS); + + switch (choice) + { + case ADMIN_ADD_USER: + addUserAccount(); + break; + case ADMIN_DELETE_USER: + deleteUserAccount(); + break; + case ADMIN_MODIFY_PASSWORD: + modifyUserPassword(); + break; + case ADMIN_VIEW_USERS: + viewAllUsers(); + break; + case ADMIN_BACK: + break; + default: + printError("无效的选择!"); + pauseSystem(); + break; + } + } while (choice != ADMIN_BACK); +} + +/** + * @brief 处理学生排序功能 + * @details 提供交互式的学生信息排序功能 + * 用户可选择排序依据(学号、姓名、总分、平均分)和排序顺序(升序、降序) + * 排序完成后自动显示排序结果 + * @note 排序依据选项: + * 1. 按学号排序 + * 2. 按姓名排序 + * 3. 按总分排序 + * 4. 按平均分排序 + * @note 排序顺序选项: + * 1. 升序 + * 2. 降序 + */ +void handleSortStudents() +{ + clearScreen(); + printHeader("学生排序"); + + printf("排序依据:\n"); + printf("1. 按学号排序\n"); + printf("2. 按姓名排序\n"); + printf("3. 按总分排序\n"); + printf("4. 按平均分排序\n"); + + int criteria = safeInputInt("请选择排序依据", SORT_BY_ID, SORT_BY_AVERAGE_SCORE); + + printf("\n排序顺序:\n"); + printf("1. 升序\n"); + printf("2. 降序\n"); + + int order = safeInputInt("请选择排序顺序", SORT_ASCENDING, SORT_DESCENDING); + + sortStudents(criteria, order); + + printf("\n排序完成!\n"); + displayAllStudents(); +} \ No newline at end of file diff --git a/file_utils.c b/file_utils.c new file mode 100644 index 0000000..2f55f59 --- /dev/null +++ b/file_utils.c @@ -0,0 +1,67 @@ +/** + * @file file_utils.c + * @brief 文件操作工具函数实现文件 + * @note 实现文件和目录操作相关函数 + */ + +#include +#include +#include +#ifdef _WIN32 +#include +#include +#else +#include +#endif + +#include "file_utils.h" + +/** + * @brief 检查文件是否存在 + * @details 使用access函数(Unix/Linux)或_access函数(Windows)检查文件是否存在且可读 + * @param filename 要检查的文件路径 + * @return 如果文件存在且可读返回true,否则返回false + * @note 函数只检查文件是否存在,不检查文件内容 + * @warning 如果filename为NULL,返回false + */ +bool fileExists(const char *filename) +{ + if (filename == NULL) + return false; + +#ifdef _WIN32 + return _access(filename, 0) == 0; +#else + return access(filename, F_OK) == 0; +#endif +} + +/** + * @brief 创建目录 + * @details 使用mkdir函数创建指定路径的目录 + * 在Windows下使用_mkdir,在Unix/Linux下使用mkdir + * @param path 要创建的目录路径 + * @return 如果目录创建成功或已存在返回true,否则返回false + * @note 如果目录已存在,函数返回true + * @note 函数不会递归创建父目录 + * @warning 如果path为NULL,返回false + */ +bool createDirectory(const char *path) +{ + if (path == NULL) + return false; + + // 检查目录是否已存在 + struct stat st = {0}; + if (stat(path, &st) == 0) + { + return true; // 目录已存在 + } + + // 创建目录 +#ifdef _WIN32 + return _mkdir(path) == 0; +#else + return mkdir(path, 0755) == 0; +#endif +} \ No newline at end of file diff --git a/file_utils.h b/file_utils.h new file mode 100644 index 0000000..ab4b9d2 --- /dev/null +++ b/file_utils.h @@ -0,0 +1,16 @@ +/** + * @file file_utils.h + * @brief 文件操作工具函数头文件 + * @note 包含文件和目录操作相关函数声明 + */ + +#ifndef FILE_UTILS_H +#define FILE_UTILS_H + +#include + +// 文件操作函数 +bool fileExists(const char* filename); // 检查文件是否存在 +bool createDirectory(const char* path); // 创建目录 + +#endif // FILE_UTILS_H \ No newline at end of file diff --git a/globals.h b/globals.h index 9f36f8f..b69f1d5 100644 --- a/globals.h +++ b/globals.h @@ -9,7 +9,6 @@ #include #include "config.h" -#include "main_menu.h" // 用户结构体定义 typedef struct { diff --git a/io_utils.c b/io_utils.c new file mode 100644 index 0000000..c280875 --- /dev/null +++ b/io_utils.c @@ -0,0 +1,255 @@ +/** + * @file io_utils.c + * @brief 输入输出工具函数实现文件 + * @note 实现界面显示、用户输入等相关函数 + */ + +#include +#include +#include +#include +#ifdef _WIN32 +#include +#endif + +#include "io_utils.h" +#include "config.h" +#include "string_utils.h" + +/** + * @brief 清理输入缓冲区 + * @details 清除标准输入流中的所有剩余字符,直到遇到换行符或文件结束符 + * 主要用于防止输入缓冲区中的残留字符影响后续输入操作 + * @note 在使用scanf等函数后调用此函数可以避免输入问题 + */ +void clearInputBuffer() +{ + int c; + while ((c = getchar()) != '\n' && c != EOF) + ; +} + +/** + * @brief 暂停系统,等待用户按键 + * @details 显示提示信息并等待用户按下任意键后继续执行 + * 在Windows系统下使用_getch()函数,在其他系统下使用getchar()函数 + * @note 用于在菜单操作完成后暂停,让用户有时间查看结果 + * @warning 在非Windows系统下需要按回车键才能继续 + */ +void pauseSystem() +{ + printf("\n按任意键继续..."); +#ifdef _WIN32 + _getch(); +#else + getchar(); +#endif + printf("\n"); +} + +/** + * @brief 清屏 + * @details 根据操作系统类型调用相应的清屏命令 + * Windows系统使用"cls"命令,其他系统使用"clear"命令 + * @note 用于清除终端屏幕内容,提供更好的用户界面体验 + * @warning 依赖于系统命令,在某些受限环境下可能无法正常工作 + */ +void clearScreen() +{ +#ifdef _WIN32 + system("cls"); +#else + system("clear"); +#endif +} + +/** + * @brief 打印分隔线 + * @details 输出一行由等号组成的分隔线,用于美化界面显示 + * @note 分隔线长度为40个字符,用于分隔不同的界面区域 + */ +void printSeparator() +{ + printf("========================================\n"); +} + +/** + * @brief 打印标题头 + * @details 以美观的格式显示标题,标题上下各有一条分隔线 + * @param title 要显示的标题文本,不能为NULL + * @note 标题会居中显示,前面有10个空格的缩进 + * @warning 如果title为NULL,可能导致程序崩溃 + */ +void printHeader(const char *title) +{ + printSeparator(); + printf(" %s\n", title); + printSeparator(); +} + +/** + * @brief 安全输入整数 + * @details 提供安全的整数输入功能,包含范围验证和错误处理 + * 使用fgets和sscanf组合避免缓冲区溢出,循环直到获得有效输入 + * @param prompt 显示给用户的提示信息 + * @param min 允许输入的最小值(包含) + * @param max 允许输入的最大值(包含) + * @return 返回用户输入的有效整数 + * @note 函数会一直循环直到用户输入有效的整数 + * @note 自动显示输入范围提示 + * @warning 如果prompt为NULL,printf可能出现问题 + */ +int safeInputInt(const char *prompt, int min, int max) +{ + int value; + char buffer[100]; + + while (1) + { + printf("%s (%d-%d): ", prompt, min, max); + + if (fgets(buffer, sizeof(buffer), stdin) != NULL) + { + if (sscanf(buffer, "%d", &value) == 1) + { + if (value >= min && value <= max) + { + return value; + } + } + } + + printError("输入无效,请重新输入!"); + } +} + +/** + * @brief 安全输入浮点数 + * @details 提供安全的浮点数输入功能,包含范围验证和错误处理 + * 使用fgets和sscanf组合避免缓冲区溢出,循环直到获得有效输入 + * @param prompt 显示给用户的提示信息 + * @param min 允许输入的最小值(包含) + * @param max 允许输入的最大值(包含) + * @return 返回用户输入的有效浮点数 + * @note 函数会一直循环直到用户输入有效的浮点数 + * @note 自动显示输入范围提示,精度为小数点后1位 + * @warning 如果prompt为NULL,printf可能出现问题 + */ +float safeInputFloat(const char *prompt, float min, float max) +{ + float value; + char buffer[100]; + + while (1) + { + printf("%s (%.1f-%.1f): ", prompt, min, max); + + if (fgets(buffer, sizeof(buffer), stdin) != NULL) + { + if (sscanf(buffer, "%f", &value) == 1) + { + if (value >= min && value <= max) + { + return value; + } + } + } + + printError("输入无效,请重新输入!"); + } +} + +/** + * @brief 安全输入字符串 + * @details 提供安全的字符串输入功能,包含空值检查和自动去除首尾空格 + * 使用fgets避免缓冲区溢出,自动移除换行符并处理空白字符 + * @param prompt 显示给用户的提示信息 + * @param buffer 存储输入字符串的缓冲区 + * @param maxLen 缓冲区的最大长度(包含终止符) + * @note 函数会一直循环直到用户输入非空字符串 + * @note 自动移除输入字符串的首尾空白字符 + * @warning 如果buffer为NULL或maxLen<=0,可能导致程序崩溃 + */ +void safeInputString(const char *prompt, char *buffer, int maxLen) +{ + while (1) + { + printf("%s: ", prompt); + + if (fgets(buffer, maxLen, stdin) != NULL) + { + // 移除换行符 + buffer[strcspn(buffer, "\n")] = '\0'; + trimString(buffer); + + if (!isEmptyString(buffer)) + { + return; + } + } + + printError("输入不能为空,请重新输入!"); + } +} + +/** + * @brief 彩色输出 + * @details 使用ANSI转义序列在终端中输出彩色文本 + * 输出格式为:颜色代码 + 文本 + 重置代码 + * @param text 要输出的文本内容 + * @param color ANSI颜色代码字符串(如COLOR_RED、COLOR_GREEN等) + * @note 输出后会自动重置颜色为默认值 + * @warning 如果终端不支持ANSI转义序列,可能显示乱码 + */ +void printColored(const char *text, const char *color) +{ + printf("%s%s%s", color, text, COLOR_RESET); +} + +/** + * @brief 成功消息 + * @details 以绿色显示成功消息,用于提示操作成功完成 + * @param message 要显示的成功消息文本 + * @note 消息会以绿色显示,并在末尾自动添加换行符 + */ +void printSuccess(const char *message) +{ + printColored(message, COLOR_GREEN); + printf("\n"); +} + +/** + * @brief 错误消息 + * @details 以红色显示错误消息,用于提示操作失败或出现错误 + * @param message 要显示的错误消息文本 + * @note 消息会以红色显示,并在末尾自动添加换行符 + */ +void printError(const char *message) +{ + printColored(message, COLOR_RED); + printf("\n"); +} + +/** + * @brief 警告消息 + * @details 以黄色显示警告消息,用于提示需要注意的情况 + * @param message 要显示的警告消息文本 + * @note 消息会以黄色显示,并在末尾自动添加换行符 + */ +void printWarning(const char *message) +{ + printColored(message, COLOR_YELLOW); + printf("\n"); +} + +/** + * @brief 信息消息 + * @details 以青色显示信息消息,用于提示一般性信息 + * @param message 要显示的信息消息文本 + * @note 消息会以青色显示,并在末尾自动添加换行符 + */ +void printInfo(const char *message) +{ + printColored(message, COLOR_CYAN); + printf("\n"); +} \ No newline at end of file diff --git a/io_utils.h b/io_utils.h new file mode 100644 index 0000000..49b1a0a --- /dev/null +++ b/io_utils.h @@ -0,0 +1,31 @@ +/** + * @file io_utils.h + * @brief 输入输出工具函数头文件 + * @note 包含界面显示、用户输入等相关函数声明 + */ + +#ifndef IO_UTILS_H +#define IO_UTILS_H + +#include + +// 界面显示函数 +void clearInputBuffer(); // 清理输入缓冲区 +void pauseSystem(); // 暂停系统,等待用户按键 +void clearScreen(); // 清屏 +void printSeparator(); // 打印分隔线 +void printHeader(const char* title); // 打印标题头 + +// 安全输入函数 +int safeInputInt(const char* prompt, int min, int max); // 安全输入整数 +float safeInputFloat(const char* prompt, float min, float max); // 安全输入浮点数 +void safeInputString(const char* prompt, char* buffer, int maxLen); // 安全输入字符串 + +// 颜色输出函数 +void printColored(const char* text, const char* color); // 彩色输出 +void printSuccess(const char* message); // 成功消息 +void printError(const char* message); // 错误消息 +void printWarning(const char* message); // 警告消息 +void printInfo(const char* message); // 信息消息 + +#endif // IO_UTILS_H \ No newline at end of file diff --git a/main.c b/main.c index 6d004a7..fb1ea6d 100644 --- a/main.c +++ b/main.c @@ -19,24 +19,29 @@ #include "globals.h" #include "main_menu.h" #include "user_manage.h" -#include "stu.data.h" #include "statistical_analysis.h" -#include "auxiliary.h" +#include "io_utils.h" +#include "system_utils.h" #include "core_handlers.h" +#include "student_io.h" /** * @brief 主程序入口 - * @param argc 命令行参数个数 - * @param argv 命令行参数数组 - * @return 程序退出状态码 - * @note 系统中有两个用户: - 1. admin - 密码是 123456 - 2. teacher - 密码是 password - * @note - gcc -o student_system.exe main.c stu_data.c auxiliary.c statistical_analysis.c main_menu.c globals.c user_manage.c core_handlers.c - .\student_system.exe + * @details 学生成绩管理系统的主入口函数,负责系统初始化、用户登录验证、 + * 主菜单循环处理和系统清理等核心流程 + * 程序流程:设置编码 -> 系统初始化 -> 用户登录 -> 主菜单循环 -> 数据保存 -> 系统清理 + * @param argc 命令行参数个数(当前未使用) + * @param argv 命令行参数数组(当前未使用) + * @return 程序退出状态码:0表示正常退出,-1表示异常退出 + * @note 系统预设用户账户: + * 1. admin - 密码:123456(管理员权限) + * 2. teacher - 密码:password(普通用户权限) + * @note 编译运行命令: + * gcc -Wall -Wextra -std=c99 -g main.c globals.c main_menu.c user_manage.c core_handlers.c statistical_analysis.c student_io.c student_crud.c student_search.c student_sort.c io_utils.c validation.c string_utils.c file_utils.c math_utils.c system_utils.c -o student_system + ./student_system + * @warning 登录失败超过MAX_LOGIN_ATTEMPTS次会强制退出程序 */ -int main(int argc, char *argv[]) +int main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) { // 设置控制台编码为UTF-8 #ifdef _WIN32 diff --git a/main_menu.c b/main_menu.c index 19dac7d..fd24811 100644 --- a/main_menu.c +++ b/main_menu.c @@ -7,14 +7,21 @@ #include #include "main_menu.h" #include "config.h" - #include "auxiliary.h" + #include "io_utils.h" #include "globals.h" /** - * @brief 显示主菜单 - */ - void displayMainMenu() - { + * @brief 显示主菜单 + * @details 显示学生成绩管理系统的主菜单界面,包括当前用户信息和可用功能选项 + * 根据用户权限动态显示菜单项(管理员可看到系统管理功能) + * @note 菜单选项: + * 1. 基本功能管理(所有用户) + * 2. 统计分析功能(所有用户) + * 3. 系统管理功能(仅管理员) + * 0. 退出系统 + */ +void displayMainMenu() +{ printHeader("学生成绩管理系统 - 主菜单"); printf("\n"); printf("当前用户: %s %s\n", currentUser, @@ -32,10 +39,21 @@ } /** - * @brief 显示基本功能菜单 - */ - void displayBasicFunctionsMenu() - { + * @brief 显示基本功能菜单 + * @details 显示学生信息管理的基本功能菜单,包括增删改查和排序功能 + * 同时显示当前系统中的学生总数 + * @note 菜单功能: + * 1. 添加学生信息 + * 2. 删除学生信息 + * 3. 修改学生信息 + * 4. 按学号查找学生 + * 5. 按姓名查找学生 + * 6. 显示所有学生 + * 7. 学生信息排序 + * 0. 返回主菜单 + */ +void displayBasicFunctionsMenu() +{ printHeader("基本功能管理"); printf("\n"); printf("1. 添加学生信息\n"); @@ -53,10 +71,18 @@ } /** - * @brief 显示统计功能菜单 - */ - void displayStatisticsMenu() - { + * @brief 显示统计功能菜单 + * @details 显示统计分析功能菜单,提供各种数据统计和分析选项 + * 显示当前学生总数和系统平均分(如果有学生数据) + * @note 菜单功能: + * 1. 课程统计分析 + * 2. 成绩分布统计 + * 3. 分数段统计 + * 4. 综合统计分析 + * 0. 返回主菜单 + */ +void displayStatisticsMenu() +{ printHeader("统计分析功能"); printf("\n"); printf("1. 课程统计分析\n"); @@ -74,10 +100,19 @@ } /** - * @brief 显示管理功能菜单 - */ - void displayAdminMenu() - { + * @brief 显示管理功能菜单 + * @details 显示系统管理功能菜单,仅管理员可访问 + * 提供用户账户管理功能,显示当前用户总数 + * @note 菜单功能: + * 1. 添加用户账户 + * 2. 删除用户账户 + * 3. 修改用户密码 + * 4. 查看所有用户 + * 0. 返回主菜单 + * @warning 此菜单仅限管理员用户访问 + */ +void displayAdminMenu() +{ printHeader("系统管理功能"); printf("\n"); printf("1. 添加用户账户\n"); diff --git a/math_utils.c b/math_utils.c new file mode 100644 index 0000000..108040e --- /dev/null +++ b/math_utils.c @@ -0,0 +1,31 @@ +/** + * @file math_utils.c + * @brief 数学计算工具函数实现文件 + * @note 实现数学计算相关函数 + */ + +#include "math_utils.h" + +/** + * @brief 计算平均值 + * @details 计算浮点数数组的算术平均值 + * 遍历数组求和,然后除以元素个数 + * @param scores 浮点数数组 + * @param count 数组元素个数 + * @return 返回数组的平均值,如果count为0返回0.0 + * @note 如果count为0,函数返回0.0避免除零错误 + * @warning 如果scores为NULL且count>0,可能导致程序崩溃 + */ +float calculateAverage(float scores[], int count) +{ + if (count <= 0) + return 0.0; + + float sum = 0.0; + for (int i = 0; i < count; i++) + { + sum += scores[i]; + } + + return sum / count; +} \ No newline at end of file diff --git a/math_utils.h b/math_utils.h new file mode 100644 index 0000000..d09984e --- /dev/null +++ b/math_utils.h @@ -0,0 +1,13 @@ +/** + * @file math_utils.h + * @brief 数学计算工具函数头文件 + * @note 包含数学计算相关函数声明 + */ + +#ifndef MATH_UTILS_H +#define MATH_UTILS_H + +// 数学计算函数 +float calculateAverage(float scores[], int count); // 计算平均值 + +#endif // MATH_UTILS_H \ No newline at end of file diff --git a/statistical_analysis.c b/statistical_analysis.c index 8a134d1..7f17e3a 100644 --- a/statistical_analysis.c +++ b/statistical_analysis.c @@ -4,485 +4,651 @@ * @note 实现各种统计分析功能 */ - #include - #include - #include - #include - #include "statistical_analysis.h" - #include "config.h" - #include "globals.h" - #include "auxiliary.h" - #include "stu.data.h" - - /** - * @brief 显示课程统计信息 - */ - void displayCourseStatistics() - { - clearScreen(); - printHeader("课程统计信息"); - - if (studentCount == 0) - { - printWarning("暂无学生数据!"); - pauseSystem(); - return; - } - - // 收集所有课程名称 - char courses[MAX_STUDENTS * MAX_COURSES][MAX_COURSE_NAME_LENGTH]; - int courseCount = 0; - - for (int i = 0; i < studentCount; i++) { - for (int j = 0; j < students[i].courseCount; j++) { - bool exists = false; - for (int k = 0; k < courseCount; k++) { - if (strcmp(courses[k], students[i].courses[j]) == 0) { - exists = true; - break; - } - } - if (!exists) { - strcpy(courses[courseCount], students[i].courses[j]); - courseCount++; - } - } - } - - if (courseCount == 0) { - printWarning("暂无课程数据!"); - pauseSystem(); - return; - } - - printf("\n"); - printf("%-20s %-8s %-8s %-8s %-8s %-8s\n", - "课程名称", "人数", "最高分", "最低分", "平均分", "及格率"); - printSeparator(); - - for (int i = 0; i < courseCount; i++) { - CourseStats stats = calculateCourseStats(courses[i]); - printf("%-20s %-8d %-8.2f %-8.2f %-8.2f %-8.2f%%\n", - courses[i], stats.studentCount, stats.maxScore, - stats.minScore, stats.averageScore, stats.passRate); - } - - pauseSystem(); - } - - /** - * @brief 计算课程统计信息 - */ - CourseStats calculateCourseStats(const char* courseName) { - CourseStats stats = {0}; - float scores[MAX_STUDENTS]; - int count = 0; - - // 收集该课程的所有分数 - for (int i = 0; i < studentCount; i++) { - for (int j = 0; j < students[i].courseCount; j++) { - if (strcmp(students[i].courses[j], courseName) == 0) { - scores[count] = students[i].scores[j]; - count++; - break; - } - } - } - - if (count == 0) return stats; - - stats.studentCount = count; - stats.maxScore = scores[0]; - stats.minScore = scores[0]; - stats.totalScore = 0; - - int passCount = 0; - - for (int i = 0; i < count; i++) { - if (scores[i] > stats.maxScore) stats.maxScore = scores[i]; - if (scores[i] < stats.minScore) stats.minScore = scores[i]; - stats.totalScore += scores[i]; - - if (scores[i] >= PASS_SCORE) passCount++; - } - - stats.averageScore = stats.totalScore / count; - stats.passRate = (float)passCount / count * 100; - - return stats; - } - - /** - * @brief 显示分数分布 - */ - void displayScoreDistribution() { - clearScreen(); - printHeader("分数分布统计"); - - if (studentCount == 0) { - printWarning("暂无学生数据!"); - pauseSystem(); - return; - } - - ScoreDistribution dist = calculateScoreDistribution(); - - printf("\n分数段分布:\n"); - printSeparator(); - printf("90-100分: %d人 (%.2f%%)\n", dist.excellent, - (float)dist.excellent / studentCount * 100); - printf("80-89分: %d人 (%.2f%%)\n", dist.good, - (float)dist.good / studentCount * 100); - printf("70-79分: %d人 (%.2f%%)\n", dist.medium, - (float)dist.medium / studentCount * 100); - printf("60-69分: %d人 (%.2f%%)\n", dist.pass, - (float)dist.pass / studentCount * 100); - printf("0-59分: %d人 (%.2f%%)\n", dist.fail, - (float)dist.fail / studentCount * 100); - - printf("\n总体统计:\n"); - printf("总人数: %d\n", studentCount); - printf("及格人数: %d (%.2f%%)\n", - studentCount - dist.fail, - (float)(studentCount - dist.fail) / studentCount * 100); - printf("不及格人数: %d (%.2f%%)\n", - dist.fail, (float)dist.fail / studentCount * 100); - - pauseSystem(); - } - - /** - * @brief 计算分数分布 - */ - ScoreDistribution calculateScoreDistribution() { - ScoreDistribution dist = {0}; - - for (int i = 0; i < studentCount; i++) { - float avgScore = students[i].averageScore; - - if (avgScore >= 90) { - dist.excellent++; - } else if (avgScore >= 80) { - dist.good++; - } else if (avgScore >= 70) { - dist.medium++; - } else if (avgScore >= 60) { - dist.pass++; - } else { - dist.fail++; - } - } - - return dist; - } - - /** - * @brief 显示学生排名 - */ - void displayStudentRanking() { - clearScreen(); - printHeader("学生排名"); - - if (studentCount == 0) { - printWarning("暂无学生数据!"); - pauseSystem(); - return; - } - - // 创建排名数组 - StudentRank rankings[MAX_STUDENTS]; - for (int i = 0; i < studentCount; i++) { - rankings[i].studentIndex = i; - rankings[i].averageScore = students[i].averageScore; - rankings[i].totalScore = students[i].totalScore; - } - - // 按平均分排序(降序) - for (int i = 0; i < studentCount - 1; i++) { - for (int j = 0; j < studentCount - 1 - i; j++) { - if (rankings[j].averageScore < rankings[j + 1].averageScore) { - StudentRank temp = rankings[j]; - rankings[j] = rankings[j + 1]; - rankings[j + 1] = temp; - } - } - } - - printf("\n"); - // 调整中文表头的对齐格式,考虑中文字符的显示宽度 - printf("%-5s %-10s %-12s %-8s %-8s\n", - "排名", "学号", "姓名", "总分", "平均分"); - printf("========================================\n"); - - for (int i = 0; i < studentCount; i++) { - int idx = rankings[i].studentIndex; - printf("%-5d %-10s %-12s %-8.2f %-8.2f\n", - i + 1, - students[idx].studentID, - students[idx].name, - students[idx].totalScore, - students[idx].averageScore); - } - - pauseSystem(); - } - - /** - * @brief 显示系统总体统计 - */ - void displayOverallStatistics() { - clearScreen(); - printHeader("系统总体统计"); - - if (studentCount == 0) { - printWarning("暂无学生数据!"); - pauseSystem(); - return; - } - - OverallStats stats = calculateOverallStats(); - - printf("\n学生信息统计:\n"); - printSeparator(); - printf("总学生数: %d\n", stats.totalStudents); - printf("男学生数: %d (%.2f%%)\n", stats.maleCount, - (float)stats.maleCount / stats.totalStudents * 100); - printf("女学生数: %d (%.2f%%)\n", stats.femaleCount, - (float)stats.femaleCount / stats.totalStudents * 100); - printf("平均年龄: %.1f岁\n", stats.averageAge); - - printf("\n成绩统计:\n"); - printSeparator(); - printf("最高平均分: %.2f\n", stats.highestAverage); - printf("最低平均分: %.2f\n", stats.lowestAverage); - printf("全体平均分: %.2f\n", stats.overallAverageScore); - printf("标准差: %.2f\n", stats.standardDeviation); - - printf("\n课程统计:\n"); - printSeparator(); - printf("总课程数: %d\n", stats.totalCourses); - printf("平均课程数/人: %.1f\n", stats.averageCoursesPerStudent); - - pauseSystem(); - } - - /** - * @brief 计算系统总体统计 - */ - OverallStats calculateOverallStats() { - OverallStats stats = {0}; - - if (studentCount == 0) return stats; - - stats.totalStudents = studentCount; - - float totalAge = 0; - float totalAverage = 0; - int totalCourseCount = 0; - - // 收集所有课程名称 - char courses[MAX_STUDENTS * MAX_COURSES][MAX_COURSE_NAME_LENGTH]; - int uniqueCourseCount = 0; - - stats.highestAverage = students[0].averageScore; - stats.lowestAverage = students[0].averageScore; - - for (int i = 0; i < studentCount; i++) { - // 性别统计 - if (students[i].gender == GENDER_MALE) { - stats.maleCount++; - } else if (students[i].gender == GENDER_FEMALE) { - stats.femaleCount++; - } - - // 年龄统计 - totalAge += students[i].age; - - // 成绩统计 - totalAverage += students[i].averageScore; - totalCourseCount += students[i].courseCount; - - if (students[i].averageScore > stats.highestAverage) { - stats.highestAverage = students[i].averageScore; - } - if (students[i].averageScore < stats.lowestAverage) { - stats.lowestAverage = students[i].averageScore; - } - - // 课程统计 - for (int j = 0; j < students[i].courseCount; j++) { - bool exists = false; - for (int k = 0; k < uniqueCourseCount; k++) { - if (strcmp(courses[k], students[i].courses[j]) == 0) { - exists = true; - break; - } - } - if (!exists) { - strcpy(courses[uniqueCourseCount], students[i].courses[j]); - uniqueCourseCount++; - } - } - } - - stats.averageAge = totalAge / studentCount; - stats.overallAverageScore = totalAverage / studentCount; - stats.totalCourses = uniqueCourseCount; - stats.averageCoursesPerStudent = (float)totalCourseCount / studentCount; - - // 计算标准差 - float variance = 0; - for (int i = 0; i < studentCount; i++) { - float diff = students[i].averageScore - stats.overallAverageScore; - variance += diff * diff; - } - variance /= studentCount; - stats.standardDeviation = sqrt(variance); - - return stats; - } - - /** - * @brief 查找最高分学生 - */ - void findTopStudent() { - clearScreen(); - printHeader("最高分学生"); - - if (studentCount == 0) { - printWarning("暂无学生数据!"); - pauseSystem(); - return; - } - - int topIndex = 0; - for (int i = 1; i < studentCount; i++) { - if (students[i].averageScore > students[topIndex].averageScore) { - topIndex = i; - } - } - - printf("\n最高平均分学生:\n"); - displayStudentInfo(&students[topIndex]); - - pauseSystem(); - } - - /** - * @brief 查找最低分学生 - */ - void findBottomStudent() { - clearScreen(); - printHeader("最低分学生"); - - if (studentCount == 0) { - printWarning("暂无学生数据!"); - pauseSystem(); - return; - } - - int bottomIndex = 0; - for (int i = 1; i < studentCount; i++) { - if (students[i].averageScore < students[bottomIndex].averageScore) { - bottomIndex = i; - } - } - - printf("\n最低平均分学生:\n"); - displayStudentInfo(&students[bottomIndex]); - - pauseSystem(); - } - - /** - * @brief 按课程查找最高分 - */ - void findTopScoreInCourse() { - clearScreen(); - printHeader("课程最高分查询"); - - if (studentCount == 0) { - printWarning("暂无学生数据!"); - pauseSystem(); - return; - } - - char courseName[MAX_COURSE_NAME_LENGTH]; - printf("\n"); - safeInputString("请输入课程名称", courseName, MAX_COURSE_NAME_LENGTH); - - float maxScore = -1; - int maxIndex = -1; - - for (int i = 0; i < studentCount; i++) { - for (int j = 0; j < students[i].courseCount; j++) { - if (strcmp(students[i].courses[j], courseName) == 0) { - if (students[i].scores[j] > maxScore) { - maxScore = students[i].scores[j]; - maxIndex = i; - } - } - } - } - - if (maxIndex == -1) { - printError("未找到该课程!"); - } else { - printf("\n课程 \"%s\" 最高分:\n", courseName); - printSeparator(); - printf("学号: %s\n", students[maxIndex].studentID); - printf("姓名: %s\n", students[maxIndex].name); - printf("分数: %.2f\n", maxScore); - } - - pauseSystem(); - } - - /** - * @brief 计算学生统计信息 - */ - void calculateStudentStats(Student* student) { - if (student->courseCount == 0) { - student->totalScore = 0; - student->averageScore = 0; - return; - } - - student->totalScore = 0; - for (int i = 0; i < student->courseCount; i++) { - student->totalScore += student->scores[i]; - } - - student->averageScore = student->totalScore / student->courseCount; - } - - /** - * @brief 更新全局统计缓存 - */ - void updateGlobalStats() { - if (studentCount == 0) { - overallAverageScore = 0; - highestScore = 0; - lowestScore = 0; - statsNeedUpdate = false; - return; - } - - float total = 0; - highestScore = students[0].averageScore; - lowestScore = students[0].averageScore; - - for (int i = 0; i < studentCount; i++) { - total += students[i].averageScore; - - if (students[i].averageScore > highestScore) { - highestScore = students[i].averageScore; - } - if (students[i].averageScore < lowestScore) { - lowestScore = students[i].averageScore; - } - } - - overallAverageScore = total / studentCount; - statsNeedUpdate = false; - } \ No newline at end of file +#include +#include +#include +#include +#include "statistical_analysis.h" +#include "config.h" +#include "globals.h" +#include "io_utils.h" +#include "math_utils.h" +#include "student_io.h" +#include "student_crud.h" +#include "student_search.h" +#include "student_sort.h" + +/** + * @brief 显示课程统计信息 + * @details 统计并显示所有课程的详细信息,包括每门课程的人数、最高分、最低分、平均分和及格率 + * 自动收集系统中所有不重复的课程名称,并为每门课程计算统计数据 + * @note 显示内容包括: + * - 课程名称 + * - 选课人数 + * - 最高分、最低分、平均分 + * - 及格率(基于PASS_SCORE阈值) + * @warning 如果没有学生数据或课程数据,将显示相应警告信息 + */ +void displayCourseStatistics() +{ + clearScreen(); + printHeader("课程统计信息"); + + if (studentCount == 0) + { + printWarning("暂无学生数据!"); + pauseSystem(); + return; + } + + // 收集所有课程名称 + char courses[MAX_STUDENTS * MAX_COURSES][MAX_COURSE_NAME_LENGTH]; + int courseCount = 0; + + for (int i = 0; i < studentCount; i++) + { + for (int j = 0; j < students[i].courseCount; j++) + { + bool exists = false; + for (int k = 0; k < courseCount; k++) + { + if (strcmp(courses[k], students[i].courses[j]) == 0) + { + exists = true; + break; + } + } + if (!exists) + { + strcpy(courses[courseCount], students[i].courses[j]); + courseCount++; + } + } + } + + if (courseCount == 0) + { + printWarning("暂无课程数据!"); + pauseSystem(); + return; + } + + printf("\n"); + printf("%-14s %-8s %-11s %-11s %-11s %-11s\n", + "课程名称", "人数", "最高分", "最低分", "平均分", "及格率"); + printf("========================================\n"); + + for (int i = 0; i < courseCount; i++) + { + CourseStats stats = calculateCourseStats(courses[i]); + printf("%-12s %-6d %-8.2f %-8.2f %-8.2f %-7.2f%%\n", + courses[i], stats.studentCount, stats.maxScore, + stats.minScore, stats.averageScore, stats.passRate); + } + + pauseSystem(); +} + +/** + * @brief 计算课程统计信息 + * @details 计算指定课程的详细统计数据,包括选课人数、分数统计和及格率 + * @param courseName 要统计的课程名称 + * @return CourseStats 包含课程统计信息的结构体 + * @note 统计内容包括: + * - studentCount: 选课学生数量 + * - maxScore, minScore: 最高分和最低分 + * - totalScore, averageScore: 总分和平均分 + * - passRate: 及格率(百分比) + * @warning 如果课程不存在,返回全零的统计结构体 + */ +CourseStats calculateCourseStats(const char *courseName) +{ + CourseStats stats = {0}; + float scores[MAX_STUDENTS]; + int count = 0; + + // 收集该课程的所有分数 + for (int i = 0; i < studentCount; i++) + { + for (int j = 0; j < students[i].courseCount; j++) + { + if (strcmp(students[i].courses[j], courseName) == 0) + { + scores[count] = students[i].scores[j]; + count++; + break; + } + } + } + + if (count == 0) + return stats; + + stats.studentCount = count; + stats.maxScore = scores[0]; + stats.minScore = scores[0]; + stats.totalScore = 0; + + int passCount = 0; + + for (int i = 0; i < count; i++) + { + if (scores[i] > stats.maxScore) + stats.maxScore = scores[i]; + if (scores[i] < stats.minScore) + stats.minScore = scores[i]; + stats.totalScore += scores[i]; + + if (scores[i] >= PASS_SCORE) + passCount++; + } + + stats.averageScore = stats.totalScore / count; + stats.passRate = (float)passCount / count * 100; + + return stats; +} + +/** + * @brief 显示分数分布 + * @details 统计并显示学生平均分的分布情况,按分数段进行分类统计 + * 显示各分数段的人数和百分比,以及总体及格情况 + * @note 分数段划分: + * - 90-100分:优秀 + * - 80-89分:良好 + * - 70-79分:中等 + * - 60-69分:及格 + * - 0-59分:不及格 + * @warning 如果没有学生数据,将显示警告信息 + */ +void displayScoreDistribution() +{ + clearScreen(); + printHeader("分数分布统计"); + + if (studentCount == 0) + { + printWarning("暂无学生数据!"); + pauseSystem(); + return; + } + + ScoreDistribution dist = calculateScoreDistribution(); + + printf("\n分数段分布:\n"); + printSeparator(); + printf("90-100分: %d人 (%.2f%%)\n", dist.excellent, + (float)dist.excellent / studentCount * 100); + printf("80-89分: %d人 (%.2f%%)\n", dist.good, + (float)dist.good / studentCount * 100); + printf("70-79分: %d人 (%.2f%%)\n", dist.medium, + (float)dist.medium / studentCount * 100); + printf("60-69分: %d人 (%.2f%%)\n", dist.pass, + (float)dist.pass / studentCount * 100); + printf("0-59分: %d人 (%.2f%%)\n", dist.fail, + (float)dist.fail / studentCount * 100); + + printf("\n总体统计:\n"); + printf("总人数: %d\n", studentCount); + printf("及格人数: %d (%.2f%%)\n", + studentCount - dist.fail, + (float)(studentCount - dist.fail) / studentCount * 100); + printf("不及格人数: %d (%.2f%%)\n", + dist.fail, (float)dist.fail / studentCount * 100); + + pauseSystem(); +} + +/** + * @brief 计算分数分布 + * @details 根据学生的平均分计算各分数段的人数分布 + * @return ScoreDistribution 包含各分数段人数的结构体 + * @note 分数段定义: + * - excellent: 90-100分 + * - good: 80-89分 + * - medium: 70-79分 + * - pass: 60-69分 + * - fail: 0-59分 + */ +ScoreDistribution calculateScoreDistribution() +{ + ScoreDistribution dist = {0}; + + for (int i = 0; i < studentCount; i++) + { + float avgScore = students[i].averageScore; + + if (avgScore >= 90) + { + dist.excellent++; + } + else if (avgScore >= 80) + { + dist.good++; + } + else if (avgScore >= 70) + { + dist.medium++; + } + else if (avgScore >= 60) + { + dist.pass++; + } + else + { + dist.fail++; + } + } + + return dist; +} + +/** + * @brief 显示学生排名 + * @details 按学生平均分进行降序排序,显示学生排名列表 + * 包括排名、学号、姓名、总分和平均分信息 + * @note 排序规则:按平均分从高到低排序 + * @note 显示格式:排名 | 学号 | 姓名 | 总分 | 平均分 + * @warning 如果没有学生数据,将显示警告信息 + */ +void displayStudentRanking() +{ + clearScreen(); + printHeader("学生排名"); + + if (studentCount == 0) + { + printWarning("暂无学生数据!"); + pauseSystem(); + return; + } + + // 创建排名数组 + StudentRank rankings[MAX_STUDENTS]; + for (int i = 0; i < studentCount; i++) + { + rankings[i].studentIndex = i; + rankings[i].averageScore = students[i].averageScore; + rankings[i].totalScore = students[i].totalScore; + } + + // 按平均分排序(降序) + for (int i = 0; i < studentCount - 1; i++) + { + for (int j = 0; j < studentCount - 1 - i; j++) + { + if (rankings[j].averageScore < rankings[j + 1].averageScore) + { + StudentRank temp = rankings[j]; + rankings[j] = rankings[j + 1]; + rankings[j + 1] = temp; + } + } + } + + printf("\n"); + // 调整中文表头的对齐格式,考虑中文字符的显示宽度 + printf("%-7s %-12s %-12s %-10s %-8s\n", + "排名", "学号", "姓名", "总分", "平均分"); + printf("==========================================\n"); + + for (int i = 0; i < studentCount; i++) + { + int idx = rankings[i].studentIndex; + printf("%-5d %-10s %-12s %-8.2f %-8.2f\n", + i + 1, + students[idx].studentID, + students[idx].name, + students[idx].totalScore, + students[idx].averageScore); + } + + pauseSystem(); +} + +/** + * @brief 显示系统总体统计 + * @details 显示系统的综合统计信息,包括学生信息统计、成绩统计和课程统计 + * 提供系统整体数据的全面概览 + * @note 统计内容包括: + * - 学生信息:总数、性别分布、平均年龄 + * - 成绩统计:最高/最低/平均分、标准差 + * - 课程统计:总课程数、平均课程数 + * @warning 如果没有学生数据,将显示警告信息 + */ +void displayOverallStatistics() +{ + clearScreen(); + printHeader("系统总体统计"); + + if (studentCount == 0) + { + printWarning("暂无学生数据!"); + pauseSystem(); + return; + } + + OverallStats stats = calculateOverallStats(); + + printf("\n学生信息统计:\n"); + printSeparator(); + printf("总学生数: %d\n", stats.totalStudents); + printf("男学生数: %d (%.2f%%)\n", stats.maleCount, + (float)stats.maleCount / stats.totalStudents * 100); + printf("女学生数: %d (%.2f%%)\n", stats.femaleCount, + (float)stats.femaleCount / stats.totalStudents * 100); + printf("平均年龄: %.1f岁\n", stats.averageAge); + + printf("\n成绩统计:\n"); + printSeparator(); + printf("最高平均分: %.2f\n", stats.highestAverage); + printf("最低平均分: %.2f\n", stats.lowestAverage); + printf("全体平均分: %.2f\n", stats.overallAverageScore); + printf("标准差: %.2f\n", stats.standardDeviation); + + printf("\n课程统计:\n"); + printSeparator(); + printf("总课程数: %d\n", stats.totalCourses); + printf("平均课程数/人: %.1f\n", stats.averageCoursesPerStudent); + + pauseSystem(); +} + +/** + * @brief 计算系统总体统计 + * @details 计算系统的综合统计数据,包括学生、成绩和课程的各项统计指标 + * @return OverallStats 包含系统总体统计信息的结构体 + * @note 计算内容包括: + * - 学生统计:总数、性别分布、平均年龄 + * - 成绩统计:最高/最低/平均分、标准差 + * - 课程统计:总课程数、人均课程数 + * @note 标准差计算使用总体标准差公式 + */ +OverallStats calculateOverallStats() +{ + OverallStats stats = {0}; + + if (studentCount == 0) + return stats; + + stats.totalStudents = studentCount; + + float totalAge = 0; + float totalAverage = 0; + int totalCourseCount = 0; + + // 收集所有课程名称 + char courses[MAX_STUDENTS * MAX_COURSES][MAX_COURSE_NAME_LENGTH]; + int uniqueCourseCount = 0; + + stats.highestAverage = students[0].averageScore; + stats.lowestAverage = students[0].averageScore; + + for (int i = 0; i < studentCount; i++) + { + // 性别统计 + if (students[i].gender == GENDER_MALE) + { + stats.maleCount++; + } + else if (students[i].gender == GENDER_FEMALE) + { + stats.femaleCount++; + } + + // 年龄统计 + totalAge += students[i].age; + + // 成绩统计 + totalAverage += students[i].averageScore; + totalCourseCount += students[i].courseCount; + + if (students[i].averageScore > stats.highestAverage) + { + stats.highestAverage = students[i].averageScore; + } + if (students[i].averageScore < stats.lowestAverage) + { + stats.lowestAverage = students[i].averageScore; + } + + // 课程统计 + for (int j = 0; j < students[i].courseCount; j++) + { + bool exists = false; + for (int k = 0; k < uniqueCourseCount; k++) + { + if (strcmp(courses[k], students[i].courses[j]) == 0) + { + exists = true; + break; + } + } + if (!exists) + { + strcpy(courses[uniqueCourseCount], students[i].courses[j]); + uniqueCourseCount++; + } + } + } + + stats.averageAge = totalAge / studentCount; + stats.overallAverageScore = totalAverage / studentCount; + stats.totalCourses = uniqueCourseCount; + stats.averageCoursesPerStudent = (float)totalCourseCount / studentCount; + + // 计算标准差 + float variance = 0; + for (int i = 0; i < studentCount; i++) + { + float diff = students[i].averageScore - stats.overallAverageScore; + variance += diff * diff; + } + variance /= studentCount; + stats.standardDeviation = sqrt(variance); + + return stats; +} + +/** + * @brief 查找最高分学生 + * @details 查找并显示平均分最高的学生信息 + * 遍历所有学生,找出平均分最高者并显示其详细信息 + * @note 比较依据:学生的平均分(averageScore) + * @warning 如果没有学生数据,将显示警告信息 + */ +void findTopStudent() +{ + clearScreen(); + printHeader("最高分学生"); + + if (studentCount == 0) + { + printWarning("暂无学生数据!"); + pauseSystem(); + return; + } + + int topIndex = 0; + for (int i = 1; i < studentCount; i++) + { + if (students[i].averageScore > students[topIndex].averageScore) + { + topIndex = i; + } + } + + printf("\n最高平均分学生:\n"); + displayStudentInfo(&students[topIndex]); + + pauseSystem(); +} + +/** + * @brief 查找最低分学生 + * @details 查找并显示平均分最低的学生信息 + * 遍历所有学生,找出平均分最低者并显示其详细信息 + * @note 比较依据:学生的平均分(averageScore) + * @warning 如果没有学生数据,将显示警告信息 + */ +void findBottomStudent() +{ + clearScreen(); + printHeader("最低分学生"); + + if (studentCount == 0) + { + printWarning("暂无学生数据!"); + pauseSystem(); + return; + } + + int bottomIndex = 0; + for (int i = 1; i < studentCount; i++) + { + if (students[i].averageScore < students[bottomIndex].averageScore) + { + bottomIndex = i; + } + } + + printf("\n最低平均分学生:\n"); + displayStudentInfo(&students[bottomIndex]); + + pauseSystem(); +} + +/** + * @brief 按课程查找最高分 + * @details 在指定课程中查找并显示最高分学生的信息 + * 用户输入课程名称,系统查找该课程的最高分获得者 + * @note 查找过程: + * 1. 用户输入课程名称 + * 2. 遍历所有学生的该课程成绩 + * 3. 找出最高分及对应学生 + * 4. 显示学生信息和分数 + * @warning 如果课程不存在,将显示错误信息 + */ +void findTopScoreInCourse() +{ + clearScreen(); + printHeader("课程最高分查询"); + + if (studentCount == 0) + { + printWarning("暂无学生数据!"); + pauseSystem(); + return; + } + + char courseName[MAX_COURSE_NAME_LENGTH]; + printf("\n"); + safeInputString("请输入课程名称", courseName, MAX_COURSE_NAME_LENGTH); + + float maxScore = -1; + int maxIndex = -1; + + for (int i = 0; i < studentCount; i++) + { + for (int j = 0; j < students[i].courseCount; j++) + { + if (strcmp(students[i].courses[j], courseName) == 0) + { + if (students[i].scores[j] > maxScore) + { + maxScore = students[i].scores[j]; + maxIndex = i; + } + } + } + } + + if (maxIndex == -1) + { + printError("未找到该课程!"); + } + else + { + printf("\n课程 \"%s\" 最高分:\n", courseName); + printSeparator(); + printf("学号: %s\n", students[maxIndex].studentID); + printf("姓名: %s\n", students[maxIndex].name); + printf("分数: %.2f\n", maxScore); + } + + pauseSystem(); +} + +/** + * @brief 计算学生统计信息 + * @details 计算指定学生的总分和平均分 + * 根据学生的所有课程成绩计算统计数据 + * @param student 指向要计算统计信息的学生结构体的指针 + * @note 计算内容: + * - totalScore: 所有课程成绩的总和 + * - averageScore: 平均成绩(总分/课程数) + * @note 如果学生没有课程,总分和平均分都设为0 + * @warning 传入的student指针不能为NULL + */ +void calculateStudentStats(Student *student) +{ + if (student->courseCount == 0) + { + student->totalScore = 0; + student->averageScore = 0; + return; + } + + student->totalScore = 0; + for (int i = 0; i < student->courseCount; i++) + { + student->totalScore += student->scores[i]; + } + + student->averageScore = student->totalScore / student->courseCount; +} + +/** + * @brief 更新全局统计缓存 + * @details 更新系统的全局统计缓存变量,包括全体平均分、最高分和最低分 + * 当学生数据发生变化时调用此函数更新缓存 + * @note 更新的全局变量: + * - overallAverageScore: 全体学生平均分 + * - highestScore: 最高平均分 + * - lowestScore: 最低平均分 + * - statsNeedUpdate: 统计更新标志(设为false) + * @note 如果没有学生数据,所有统计值都设为0 + * @see overallAverageScore, highestScore, lowestScore, statsNeedUpdate + */ +void updateGlobalStats() +{ + if (studentCount == 0) + { + overallAverageScore = 0; + highestScore = 0; + lowestScore = 0; + statsNeedUpdate = false; + return; + } + + float total = 0; + highestScore = students[0].averageScore; + lowestScore = students[0].averageScore; + + for (int i = 0; i < studentCount; i++) + { + total += students[i].averageScore; + + if (students[i].averageScore > highestScore) + { + highestScore = students[i].averageScore; + } + if (students[i].averageScore < lowestScore) + { + lowestScore = students[i].averageScore; + } + } + + overallAverageScore = total / studentCount; + statsNeedUpdate = false; +} \ No newline at end of file diff --git a/statistical_analysis.h b/statistical_analysis.h index b34b9eb..e086285 100644 --- a/statistical_analysis.h +++ b/statistical_analysis.h @@ -8,7 +8,6 @@ #define STATISTICAL_ANALYSIS_H #include "config.h" -#include "main_menu.h" // 课程统计结构体 typedef struct { diff --git a/string_utils.c b/string_utils.c new file mode 100644 index 0000000..4375fe4 --- /dev/null +++ b/string_utils.c @@ -0,0 +1,76 @@ +/** + * @file string_utils.c + * @brief 字符串处理工具函数实现文件 + * @note 实现字符串操作相关函数 + */ + +#include +#include +#include +#include "string_utils.h" + +/** + * @brief 去除字符串首尾空白字符 + * @details 移除字符串开头和结尾的空格、制表符、换行符等空白字符 + * 使用双指针技术,从两端向中间处理,原地修改字符串 + * @param str 要处理的字符串,函数会直接修改此字符串 + * @note 函数会直接修改传入的字符串,不会分配新内存 + * @note 如果整个字符串都是空白字符,结果将是空字符串 + * @warning 如果str为NULL,可能导致程序崩溃 + */ +void trimString(char *str) +{ + if (str == NULL) + return; + + // 去除开头的空白字符 + char *start = str; + while (isspace((unsigned char)*start)) + { + start++; + } + + // 如果字符串全是空白字符 + if (*start == '\0') + { + str[0] = '\0'; + return; + } + + // 去除结尾的空白字符 + char *end = str + strlen(str) - 1; + while (end > start && isspace((unsigned char)*end)) + { + end--; + } + + // 移动字符串到开头 + int len = end - start + 1; + memmove(str, start, len); + str[len] = '\0'; +} + +/** + * @brief 检查字符串是否为空 + * @details 检查字符串是否为NULL、空字符串或只包含空白字符 + * 使用isspace函数检查每个字符是否为空白字符 + * @param str 要检查的字符串 + * @return 如果字符串为空或只包含空白字符返回true,否则返回false + * @note 空白字符包括空格、制表符、换行符等 + * @note 如果str为NULL,返回true + */ +bool isEmptyString(const char *str) +{ + if (str == NULL) + return true; + + while (*str) + { + if (!isspace((unsigned char)*str)) + { + return false; + } + str++; + } + return true; +} \ No newline at end of file diff --git a/string_utils.h b/string_utils.h new file mode 100644 index 0000000..10a8afb --- /dev/null +++ b/string_utils.h @@ -0,0 +1,16 @@ +/** + * @file string_utils.h + * @brief 字符串处理工具函数头文件 + * @note 包含字符串操作相关函数声明 + */ + +#ifndef STRING_UTILS_H +#define STRING_UTILS_H + +#include + +// 字符串处理函数 +void trimString(char* str); // 去除字符串首尾空白字符 +bool isEmptyString(const char* str); // 检查字符串是否为空 + +#endif // STRING_UTILS_H \ No newline at end of file diff --git a/student_crud.c b/student_crud.c new file mode 100644 index 0000000..405d1d1 --- /dev/null +++ b/student_crud.c @@ -0,0 +1,499 @@ +/** + * @file student_crud.c + * @brief 学生数据增删改操作实现 + * @note 负责学生信息的添加、删除、修改等CRUD操作 + */ + +#include +#include +#include +#include "config.h" +#include "globals.h" +#include "io_utils.h" +#include "validation.h" +#include "statistical_analysis.h" + +/** + * @brief 输入并验证学生学号 + * @details 循环输入学号直到格式正确且不重复 + * @param student 指向要填充的学生结构体的指针 + */ +static void inputStudentID(Student *student) +{ + while (1) + { + safeInputString("请输入学号", student->studentID, MAX_ID_LENGTH); + + if (!isValidStudentId(student->studentID)) + { + printError("学号格式无效!"); + continue; + } + + // 检查学号是否已存在 + bool exists = false; + for (int i = 0; i < studentCount; i++) + { + if (strcmp(students[i].studentID, student->studentID) == 0) + { + printError("学号已存在!"); + exists = true; + break; + } + } + + if (!exists) + break; + } +} + +/** + * @brief 输入并验证学生基本信息 + * @details 输入学生的姓名、年龄和性别信息 + * @param student 指向要填充的学生结构体的指针 + */ +static void inputBasicInfo(Student *student) +{ + // 输入姓名 + while (1) + { + safeInputString("请输入姓名", student->name, MAX_NAME_LENGTH); + if (isValidName(student->name)) + break; + printError("姓名格式无效!"); + } + + // 输入年龄 + student->age = safeInputInt("请输入年龄", 10, 100); + + // 输入性别 + while (1) + { + printf("请输入性别 (M/F): "); + char gender; + scanf(" %c", &gender); + clearInputBuffer(); + + if (isValidGender(gender)) + { + student->gender = gender; + break; + } + printError("性别输入无效!请输入 M 或 F"); + } +} + +/** + * @brief 输入学生课程信息 + * @details 循环输入课程名称和成绩,支持添加多门课程 + * @param student 指向要填充的学生结构体的指针 + */ +static void inputCourseInfo(Student *student) +{ + printf("\n开始输入课程信息:\n"); + student->courseCount = 0; + + while (student->courseCount < MAX_COURSES) + { + printf("\n第 %d 门课程:\n", student->courseCount + 1); + + safeInputString("课程名称", + student->courses[student->courseCount], + MAX_COURSE_NAME_LENGTH); + + student->scores[student->courseCount] = + safeInputFloat("课程分数", MIN_SCORE, MAX_SCORE); + + student->courseCount++; + + if (student->courseCount < MAX_COURSES) + { + printf("\n是否继续添加课程?(y/n): "); + char choice; + scanf(" %c", &choice); + clearInputBuffer(); + + if (choice != 'y' && choice != 'Y') + { + break; + } + } + } +} + +/** + * @brief 显示添加成功的学生信息 + * @details 显示新添加学生的基本信息和统计数据 + * @param student 指向学生结构体的指针 + */ +static void displayAddedStudentInfo(const Student *student) +{ + printSuccess("学生信息添加成功!"); + printf("学号: %s\n", student->studentID); + printf("姓名: %s\n", student->name); + printf("总分: %.2f\n", student->totalScore); + printf("平均分: %.2f\n", student->averageScore); +} + +/** + * @brief 添加学生信息 + * @details 交互式地添加新学生信息,包括基本信息和课程成绩 + * 验证学号唯一性、姓名格式、年龄范围等 + * 自动计算总分和平均分 + * @note 会检查学生数量是否已达上限MAX_STUDENTS + * @note 学号必须唯一,不能与现有学生重复 + * @warning 如果学生数量已满,会显示错误信息并返回 + * @see MAX_STUDENTS, isValidStudentId(), isValidName() + */ +void addStudent() +{ + clearScreen(); + printHeader("添加学生信息"); + + if (studentCount >= MAX_STUDENTS) + { + printError("学生数量已达上限!"); + pauseSystem(); + return; + } + + Student newStudent; + memset(&newStudent, 0, sizeof(Student)); + + printf("\n"); + + // 输入学号 + inputStudentID(&newStudent); + + // 输入基本信息 + inputBasicInfo(&newStudent); + + // 输入课程信息 + inputCourseInfo(&newStudent); + + // 计算总分和平均分 + calculateStudentStats(&newStudent); + + // 添加到数组 + students[studentCount] = newStudent; + studentCount++; + + // 标记数据已修改 + dataModified = true; + statsNeedUpdate = true; + + // 显示添加成功信息 + displayAddedStudentInfo(&newStudent); + + pauseSystem(); +} + +/** + * @brief 删除学生信息 + * @details 提供交互式界面删除指定学号的学生信息 + * 包含确认机制,防止误删除操作 + * @note 删除流程: + * 1. 输入要删除的学生学号 + * 2. 查找并显示学生信息 + * 3. 用户确认删除操作 + * 4. 删除学生并重新排列数组 + * 5. 更新数据修改和统计更新标志 + * @warning 删除操作不可逆,请谨慎操作 + * @warning 如果没有学生数据,将显示警告信息 + */ +void deleteStudent() +{ + clearScreen(); + printHeader("删除学生信息"); + + if (studentCount == 0) + { + printWarning("暂无学生数据!"); + pauseSystem(); + return; + } + + char studentID[MAX_ID_LENGTH]; + printf("\n"); + safeInputString("请输入要删除的学生学号", studentID, MAX_ID_LENGTH); + + // 查找学生 + for (int i = 0; i < studentCount; i++) + { + if (strcmp(students[i].studentID, studentID) == 0) + { + printf("\n找到学生信息:\n"); + printf("学号: %s\n", students[i].studentID); + printf("姓名: %s\n", students[i].name); + + printf("\n确认删除?(y/n): "); + char choice; + scanf(" %c", &choice); + clearInputBuffer(); + + if (choice == 'y' || choice == 'Y') + { + // 移动后面的学生向前 + for (int j = i; j < studentCount - 1; j++) + { + students[j] = students[j + 1]; + } + studentCount--; + + dataModified = true; + statsNeedUpdate = true; + + printSuccess("学生信息删除成功!"); + } + else + { + printInfo("删除操作已取消。"); + } + + pauseSystem(); + return; + } + } + + printError("未找到该学号的学生!"); + pauseSystem(); +} + +/** + * @brief 显示学生基本信息 + * @details 显示学生的学号、姓名、年龄、性别等基本信息 + * @param student 指向学生结构体的指针 + */ +static void displayStudentBasicInfo(const Student *student) +{ + printf("\n找到学生信息:\n"); + printf("学号: %s\n", student->studentID); + printf("姓名: %s\n", student->name); + printf("年龄: %d\n", student->age); + printf("性别: %c\n", student->gender); +} + +/** + * @brief 修改学生性别 + * @details 循环输入直到获得有效的性别值 + * @param student 指向学生结构体的指针 + */ +static void modifyStudentGender(Student *student) +{ + while (1) + { + printf("请输入新性别 (M/F): "); + char gender; + scanf(" %c", &gender); + clearInputBuffer(); + + if (isValidGender(gender)) + { + student->gender = gender; + break; + } + printError("性别输入无效!"); + } +} + +/** + * @brief 修改现有课程成绩 + * @details 选择并修改学生的现有课程成绩 + * @param student 指向学生结构体的指针 + */ +static void modifyExistingCourse(Student *student) +{ + if (student->courseCount == 0) + { + printWarning("该学生没有课程记录!"); + return; + } + + int courseIndex = safeInputInt("请选择要修改的课程", 1, student->courseCount) - 1; + student->scores[courseIndex] = safeInputFloat("新成绩", MIN_SCORE, MAX_SCORE); +} + +/** + * @brief 添加新课程 + * @details 为学生添加新的课程和成绩 + * @param student 指向学生结构体的指针 + */ +static void addNewCourse(Student *student) +{ + if (student->courseCount >= MAX_COURSES) + { + printWarning("课程数量已达上限!"); + return; + } + + safeInputString("课程名称", student->courses[student->courseCount], MAX_COURSE_NAME_LENGTH); + student->scores[student->courseCount] = safeInputFloat("课程成绩", MIN_SCORE, MAX_SCORE); + student->courseCount++; +} + +/** + * @brief 删除课程 + * @details 删除学生的指定课程和成绩 + * @param student 指向学生结构体的指针 + */ +static void deleteCourse(Student *student) +{ + if (student->courseCount == 0) + { + printWarning("该学生没有课程记录!"); + return; + } + + int courseIndex = safeInputInt("请选择要删除的课程", 1, student->courseCount) - 1; + + // 移动数组元素 + for (int k = courseIndex; k < student->courseCount - 1; k++) + { + strcpy(student->courses[k], student->courses[k + 1]); + student->scores[k] = student->scores[k + 1]; + } + student->courseCount--; +} + +/** + * @brief 显示课程列表 + * @details 显示学生的所有课程和成绩 + * @param student 指向学生结构体的指针 + */ +static void displayCourseList(const Student *student) +{ + printf("\n当前课程列表:\n"); + for (int j = 0; j < student->courseCount; j++) + { + printf("%d. %s: %.2f\n", j + 1, student->courses[j], student->scores[j]); + } +} + +/** + * @brief 修改课程信息 + * @details 提供课程修改的子菜单,包括修改、添加、删除课程 + * @param student 指向学生结构体的指针 + * @return true 如果进行了课程修改,false 如果没有修改 + */ +static bool modifyCourseInfo(Student *student) +{ + displayCourseList(student); + + printf("\n修改选项:\n"); + printf("1. 修改现有课程成绩\n"); + printf("2. 添加新课程\n"); + printf("3. 删除课程\n"); + printf("0. 返回\n"); + + int courseChoice = safeInputInt("请选择操作", 0, 3); + switch (courseChoice) + { + case 1: // 修改现有课程成绩 + modifyExistingCourse(student); + return true; + case 2: // 添加新课程 + addNewCourse(student); + return true; + case 3: // 删除课程 + deleteCourse(student); + return true; + case 0: + return false; + } + return false; +} + +/** + * @brief 处理学生信息修改 + * @details 处理找到学生后的修改操作 + * @param student 指向学生结构体的指针 + */ +static void handleStudentModification(Student *student) +{ + displayStudentBasicInfo(student); + + printf("\n修改选项:\n"); + printf("1. 修改姓名\n"); + printf("2. 修改年龄\n"); + printf("3. 修改性别\n"); + printf("4. 修改课程成绩\n"); + printf("0. 返回\n"); + + int choice = safeInputInt("请选择修改项", 0, 4); + + bool courseModified = false; + + switch (choice) + { + case 1: // 修改姓名 + safeInputString("请输入新姓名", student->name, MAX_NAME_LENGTH); + break; + case 2: // 修改年龄 + student->age = safeInputInt("请输入新年龄", 10, 100); + break; + case 3: // 修改性别 + modifyStudentGender(student); + break; + case 4: // 修改课程成绩 + courseModified = modifyCourseInfo(student); + break; + case 0: + return; + } + + if (courseModified) + { + calculateStudentStats(student); + } + + dataModified = true; + statsNeedUpdate = true; + + printSuccess("学生信息修改成功!"); +} + +/** + * @brief 修改学生信息 + * @details 提供交互式界面修改指定学生的各项信息 + * 支持修改姓名、年龄、性别和课程成绩等信息 + * @note 修改选项: + * 1. 修改姓名 + * 2. 修改年龄 + * 3. 修改性别 + * 4. 修改课程成绩(包括修改现有成绩、添加新课程、删除课程) + * @note 课程成绩修改包含: + * - 修改现有课程成绩 + * - 添加新课程 + * - 删除课程 + * @warning 修改课程信息后会自动重新计算总分和平均分 + */ +void modifyStudent() +{ + clearScreen(); + printHeader("修改学生信息"); + + if (studentCount == 0) + { + printWarning("暂无学生数据!"); + pauseSystem(); + return; + } + + char studentID[MAX_ID_LENGTH]; + printf("\n"); + safeInputString("请输入要修改的学生学号", studentID, MAX_ID_LENGTH); + + // 查找学生 + for (int i = 0; i < studentCount; i++) + { + if (strcmp(students[i].studentID, studentID) == 0) + { + handleStudentModification(&students[i]); + pauseSystem(); + return; + } + } + + printError("未找到该学号的学生!"); + pauseSystem(); +} \ No newline at end of file diff --git a/student_crud.h b/student_crud.h new file mode 100644 index 0000000..089333c --- /dev/null +++ b/student_crud.h @@ -0,0 +1,30 @@ +/** + * @file student_crud.h + * @brief 学生数据增删改操作头文件 + * @note 声明学生数据的创建、删除、修改等CRUD功能 + */ + +#ifndef STUDENT_CRUD_H +#define STUDENT_CRUD_H + +#include "config.h" + +/** + * @brief 添加新学生 + * @details 交互式添加新学生信息,包括基本信息和课程成绩 + */ +void addStudent(); + +/** + * @brief 删除学生 + * @details 根据学号删除指定学生的所有信息 + */ +void deleteStudent(); + +/** + * @brief 修改学生信息 + * @details 交互式修改学生的基本信息和课程成绩 + */ +void modifyStudent(); + +#endif // STUDENT_CRUD_H \ No newline at end of file diff --git a/student_io.c b/student_io.c new file mode 100644 index 0000000..9a11064 --- /dev/null +++ b/student_io.c @@ -0,0 +1,234 @@ +/** + * @file student_io.c + * @brief 学生数据文件输入输出操作实现 + * @note 负责学生数据的文件读写、CSV解析等功能 + */ + +#include +#include +#include +#include "config.h" +#include "globals.h" +#include "io_utils.h" +#include "string_utils.h" +#include "statistical_analysis.h" + +/** + * @brief 解析CSV行中的基本学生信息 + * @details 从CSV行中解析学号、姓名、年龄、性别等基本信息 + * @param student 指向要填充的学生结构体的指针 + * @param token 当前CSV token指针的指针 + * @return true 解析成功,false 解析失败 + */ +static bool parseBasicStudentInfo(Student *student, char **token) +{ + // 学号 + if (*token == NULL) return false; + strncpy(student->studentID, *token, MAX_ID_LENGTH - 1); + + // 姓名 + *token = strtok(NULL, ","); + if (*token == NULL) return false; + strncpy(student->name, *token, MAX_NAME_LENGTH - 1); + + // 年龄 + *token = strtok(NULL, ","); + if (*token == NULL) return false; + student->age = atoi(*token); + + // 性别 + *token = strtok(NULL, ","); + if (*token == NULL) return false; + student->gender = (*token)[0]; + + // 课程数量 + *token = strtok(NULL, ","); + if (*token == NULL) return false; + student->courseCount = atoi(*token); + + return true; +} + +/** + * @brief 解析CSV行中的课程信息 + * @details 从CSV行中解析课程名称和成绩信息 + * @param student 指向要填充的学生结构体的指针 + * @param token 当前CSV token指针的指针 + */ +static void parseCourseInfo(Student *student, char **token) +{ + // 课程和成绩 + for (int i = 0; i < student->courseCount && i < MAX_COURSES; i++) + { + // 课程名称 + *token = strtok(NULL, ","); + if (*token == NULL) break; + strncpy(student->courses[i], *token, MAX_COURSE_NAME_LENGTH - 1); + + // 成绩 + *token = strtok(NULL, ","); + if (*token == NULL) break; + student->scores[i] = atof(*token); + } +} + +/** + * @brief 解析CSV行中的统计信息 + * @details 从CSV行中解析总分和平均分信息 + * @param student 指向要填充的学生结构体的指针 + * @param token 当前CSV token指针的指针 + */ +static void parseStatisticsInfo(Student *student, char **token) +{ + // 总分 + *token = strtok(NULL, ","); + if (*token != NULL) + { + student->totalScore = atof(*token); + } + + // 平均分 + *token = strtok(NULL, ","); + if (*token != NULL) + { + student->averageScore = atof(*token); + } +} + +/** + * @brief 解析单行CSV学生数据 + * @details 解析一行CSV数据并填充学生结构体 + * @param line CSV行数据 + * @param student 指向要填充的学生结构体的指针 + * @return true 解析成功,false 解析失败 + */ +static bool parseStudentLine(char *line, Student *student) +{ + memset(student, 0, sizeof(Student)); + + // 解析CSV行 + char *token = strtok(line, ","); + if (token == NULL) return false; + + // 解析基本信息 + if (!parseBasicStudentInfo(student, &token)) return false; + + // 解析课程信息 + parseCourseInfo(student, &token); + + // 解析统计信息 + parseStatisticsInfo(student, &token); + + return true; +} + +/** + * @brief 从CSV文件加载学生数据 + * @details 从STUDENTS_FILE指定的CSV文件中读取学生信息并加载到内存中 + * 解析CSV格式数据,包括学号、姓名、年龄、性别、课程信息等 + * 如果文件不存在,会初始化为空的学生列表 + * @note 会跳过CSV文件的头部行,最多加载MAX_STUDENTS个学生 + * @note 加载完成后会设置statsNeedUpdate标志为true + * @warning 如果CSV格式不正确,可能导致数据解析错误 + * @see STUDENTS_FILE, MAX_STUDENTS, Student结构体 + */ +void loadStudentsFromFile() +{ + FILE *file = fopen(STUDENTS_FILE, "r"); + if (file == NULL) + { + studentCount = 0; + printInfo("学生数据文件不存在,将创建新文件。"); + return; + } + + char line[1024]; + studentCount = 0; + + // 跳过CSV头部 + if (fgets(line, sizeof(line), file) == NULL) + { + fclose(file); + studentCount = 0; + return; + } + + // 读取学生数据 + while (fgets(line, sizeof(line), file) != NULL && studentCount < MAX_STUDENTS) + { + Student *student = &students[studentCount]; + + if (parseStudentLine(line, student)) + { + studentCount++; + } + } + + fclose(file); + + // 更新统计信息 + statsNeedUpdate = true; +} + +/** + * @brief 将学生数据保存到CSV文件 + * @details 将内存中的所有学生数据以CSV格式保存到STUDENTS_FILE文件中 + * 包含完整的CSV头部和所有学生的详细信息 + * 保存成功后会重置dataModified标志 + * @note CSV格式包括:学号、姓名、年龄、性别、课程数量、各课程名称和成绩、总分、平均分 + * @note 对于课程数量不足MAX_COURSES的学生,会用空值填充 + * @warning 如果文件无法创建或写入,会显示错误信息 + * @see STUDENTS_FILE, MAX_COURSES, dataModified + */ +void saveStudentsToFile() +{ + FILE *file = fopen(STUDENTS_FILE, "w"); + if (file == NULL) + { + printError("无法保存学生数据!"); + return; + } + + // 写入CSV头部 + fprintf(file, "学号,姓名,年龄,性别,课程数量"); + for (int i = 0; i < MAX_COURSES; i++) + { + fprintf(file, ",课程%d,成绩%d", i + 1, i + 1); + } + fprintf(file, ",总分,平均分\n"); + + // 写入学生数据 + for (int i = 0; i < studentCount; i++) + { + Student *student = &students[i]; + + // 基本信息 + fprintf(file, "%s,%s,%d,%c,%d", + student->studentID, + student->name, + student->age, + student->gender, + student->courseCount); + + // 课程和成绩 + for (int j = 0; j < MAX_COURSES; j++) + { + if (j < student->courseCount) + { + fprintf(file, ",%s,%.2f", student->courses[j], student->scores[j]); + } + else + { + fprintf(file, ",,"); // 空的课程和成绩 + } + } + + // 总分和平均分 + fprintf(file, ",%.2f,%.2f\n", student->totalScore, student->averageScore); + } + + fclose(file); + dataModified = false; + + printSuccess("学生数据已保存到CSV文件!"); +} \ No newline at end of file diff --git a/student_io.h b/student_io.h new file mode 100644 index 0000000..e8040d9 --- /dev/null +++ b/student_io.h @@ -0,0 +1,24 @@ +/** + * @file student_io.h + * @brief 学生数据文件输入输出操作头文件 + * @note 声明学生数据的文件读写、CSV解析等功能 + */ + +#ifndef STUDENT_IO_H +#define STUDENT_IO_H + +#include "config.h" + +/** + * @brief 从CSV文件加载学生数据 + * @details 从STUDENTS_FILE指定的CSV文件中读取学生信息并加载到内存中 + */ +void loadStudentsFromFile(); + +/** + * @brief 将学生数据保存到CSV文件 + * @details 将内存中的所有学生数据以CSV格式保存到STUDENTS_FILE文件中 + */ +void saveStudentsToFile(); + +#endif // STUDENT_IO_H \ No newline at end of file diff --git a/student_search.c b/student_search.c new file mode 100644 index 0000000..14373c7 --- /dev/null +++ b/student_search.c @@ -0,0 +1,176 @@ +/** + * @file student_search.c + * @brief 学生数据搜索和显示功能实现 + * @note 负责学生信息的查找、显示等功能 + */ + +#include +#include +#include +#include "config.h" +#include "globals.h" +#include "io_utils.h" + +// 函数前向声明 +void displayStudentInfo(const Student *student); + +/** + * @brief 按学号查找学生 + * @details 根据用户输入的学号精确查找学生信息 + * 找到后显示该学生的详细信息 + * @note 查找方式:精确匹配学号 + * @warning 如果没有学生数据或未找到匹配学生,将显示相应提示信息 + */ +void searchStudentByID() +{ + clearScreen(); + printHeader("按学号查找学生"); + + if (studentCount == 0) + { + printWarning("暂无学生数据!"); + pauseSystem(); + return; + } + + char studentID[MAX_ID_LENGTH]; + printf("\n"); + safeInputString("请输入学号", studentID, MAX_ID_LENGTH); + + for (int i = 0; i < studentCount; i++) + { + if (strcmp(students[i].studentID, studentID) == 0) + { + displayStudentInfo(&students[i]); + pauseSystem(); + return; + } + } + + printError("未找到该学号的学生!"); + pauseSystem(); +} + +/** + * @brief 按姓名查找学生 + * @details 根据用户输入的姓名进行模糊查找学生信息 + * 支持部分姓名匹配,显示所有匹配的学生详细信息 + * @note 查找方式:模糊匹配(包含子字符串) + * @note 如果找到多个匹配学生,将全部显示 + * @warning 如果没有学生数据或未找到匹配学生,将显示相应提示信息 + */ +void searchStudentByName() +{ + clearScreen(); + printHeader("按姓名查找学生"); + + if (studentCount == 0) + { + printWarning("暂无学生数据!"); + pauseSystem(); + return; + } + + char name[MAX_NAME_LENGTH]; + printf("\n"); + safeInputString("请输入姓名(支持模糊查找)", name, MAX_NAME_LENGTH); + + bool found = false; + for (int i = 0; i < studentCount; i++) + { + if (strstr(students[i].name, name) != NULL) + { + if (!found) + { + printf("\n找到以下匹配的学生:\n"); + printSeparator(); + found = true; + } + displayStudentInfo(&students[i]); + printf("\n"); + } + } + + if (!found) + { + printError("未找到匹配的学生!"); + } + + pauseSystem(); +} + +/** + * @brief 显示所有学生信息 + * @details 以表格形式显示系统中所有学生的基本信息 + * 包括学号、姓名、年龄、性别、总分和平均分 + * @note 显示格式:表格形式,便于查看和比较 + * @note 显示内容:学号、姓名、年龄、性别、总分、平均分 + * @note 同时显示总学生数统计 + * @warning 如果没有学生数据,将显示警告信息 + */ +void displayAllStudents() +{ + clearScreen(); + printHeader("所有学生信息"); + + if (studentCount == 0) + { + printWarning("暂无学生数据!"); + pauseSystem(); + return; + } + + printf("\n"); + // 调整中文表头的对齐格式,考虑中文字符的显示宽度 + printf("%-12s %-10s %-6s %-6s %-10s %-8s\n", + "学号", "姓名", "年龄", "性别", "总分", "平均分"); + printf("==========================================\n"); + + for (int i = 0; i < studentCount; i++) + { + printf("%-10s %-10s %-4d %-4c %-8.2f %-8.2f\n", + students[i].studentID, + students[i].name, + students[i].age, + students[i].gender, + students[i].totalScore, + students[i].averageScore); + } + + printf("\n总学生数: %d\n", studentCount); + pauseSystem(); +} + +/** + * @brief 显示单个学生详细信息 + * @details 显示指定学生的完整详细信息,包括基本信息和所有课程成绩 + * @param student 指向要显示信息的学生结构体的常量指针 + * @note 显示内容: + * - 基本信息:学号、姓名、年龄、性别、课程数量 + * - 课程成绩:每门课程的名称和分数 + * - 统计信息:总分和平均分 + * @warning 传入的student指针不能为NULL + */ +void displayStudentInfo(const Student *student) +{ + printf("\n学生详细信息:\n"); + printSeparator(); + printf("学号: %s\n", student->studentID); + printf("姓名: %s\n", student->name); + printf("年龄: %d\n", student->age); + printf("性别: %c\n", student->gender); + printf("课程数量: %d\n", student->courseCount); + + if (student->courseCount > 0) + { + printf("\n课程成绩:\n"); + for (int i = 0; i < student->courseCount; i++) + { + printf(" %s: %.2f分\n", + student->courses[i], + student->scores[i]); + } + printf("\n总分: %.2f\n", student->totalScore); + printf("平均分: %.2f\n", student->averageScore); + } +} \ No newline at end of file diff --git a/student_search.h b/student_search.h new file mode 100644 index 0000000..abef232 --- /dev/null +++ b/student_search.h @@ -0,0 +1,36 @@ +/** + * @file student_search.h + * @brief 学生数据搜索和显示操作头文件 + * @note 声明学生数据的查找、显示等功能 + */ + +#ifndef STUDENT_SEARCH_H +#define STUDENT_SEARCH_H + +#include "config.h" + +/** + * @brief 按学号查找学生 + * @details 根据用户输入的学号精确查找学生信息 + */ +void searchStudentByID(); + +/** + * @brief 按姓名查找学生 + * @details 根据用户输入的姓名进行模糊查找学生信息 + */ +void searchStudentByName(); + +/** + * @brief 显示所有学生信息 + * @details 以表格形式显示系统中所有学生的基本信息 + */ +void displayAllStudents(); + +/** + * @brief 显示单个学生详细信息 + * @param student 指向要显示信息的学生结构体的常量指针 + */ +void displayStudentInfo(const Student *student); + +#endif // STUDENT_SEARCH_H \ No newline at end of file diff --git a/student_sort.c b/student_sort.c new file mode 100644 index 0000000..a4108cc --- /dev/null +++ b/student_sort.c @@ -0,0 +1,65 @@ +/** + * @file student_sort.c + * @brief 学生数据排序功能实现 + * @note 负责学生信息的排序操作 + */ + +#include +#include +#include +#include "config.h" +#include "globals.h" + +/** + * @brief 排序学生信息 + * @details 根据指定的排序依据和顺序对学生数组进行排序 + * 使用冒泡排序算法实现 + * @param criteria 排序依据(SORT_BY_ID, SORT_BY_NAME, SORT_BY_TOTAL_SCORE, SORT_BY_AVERAGE_SCORE) + * @param order 排序顺序(SORT_ASCENDING升序, SORT_DESCENDING降序) + * @note 排序依据选项: + * - SORT_BY_ID: 按学号排序 + * - SORT_BY_NAME: 按姓名排序 + * - SORT_BY_TOTAL_SCORE: 按总分排序 + * - SORT_BY_AVERAGE_SCORE: 按平均分排序 + * @note 排序算法:冒泡排序(适合小规模数据) + * @note 排序完成后会设置dataModified标志 + */ +void sortStudents(int criteria, int order) +{ + if (studentCount <= 1) + return; + + // 使用冒泡排序 + for (int i = 0; i < studentCount - 1; i++) + { + for (int j = 0; j < studentCount - 1 - i; j++) + { + bool shouldSwap = false; + + switch (criteria) + { + case SORT_BY_ID: + shouldSwap = (order == SORT_ASCENDING) ? strcmp(students[j].studentID, students[j + 1].studentID) > 0 : strcmp(students[j].studentID, students[j + 1].studentID) < 0; + break; + case SORT_BY_NAME: + shouldSwap = (order == SORT_ASCENDING) ? strcmp(students[j].name, students[j + 1].name) > 0 : strcmp(students[j].name, students[j + 1].name) < 0; + break; + case SORT_BY_TOTAL_SCORE: + shouldSwap = (order == SORT_ASCENDING) ? students[j].totalScore > students[j + 1].totalScore : students[j].totalScore < students[j + 1].totalScore; + break; + case SORT_BY_AVERAGE_SCORE: + shouldSwap = (order == SORT_ASCENDING) ? students[j].averageScore > students[j + 1].averageScore : students[j].averageScore < students[j + 1].averageScore; + break; + } + + if (shouldSwap) + { + Student temp = students[j]; + students[j] = students[j + 1]; + students[j + 1] = temp; + } + } + } + + dataModified = true; +} \ No newline at end of file diff --git a/student_sort.h b/student_sort.h new file mode 100644 index 0000000..a9d90d4 --- /dev/null +++ b/student_sort.h @@ -0,0 +1,20 @@ +/** + * @file student_sort.h + * @brief 学生数据排序操作头文件 + * @note 声明学生数据的排序功能 + */ + +#ifndef STUDENT_SORT_H +#define STUDENT_SORT_H + +#include "config.h" + +/** + * @brief 排序学生信息 + * @details 根据指定的排序依据和顺序对学生数组进行排序 + * @param criteria 排序依据(SORT_BY_ID, SORT_BY_NAME, SORT_BY_TOTAL_SCORE, SORT_BY_AVERAGE_SCORE) + * @param order 排序顺序(SORT_ASCENDING升序, SORT_DESCENDING降序) + */ +void sortStudents(int criteria, int order); + +#endif // STUDENT_SORT_H \ No newline at end of file diff --git a/system_utils.c b/system_utils.c new file mode 100644 index 0000000..489608d --- /dev/null +++ b/system_utils.c @@ -0,0 +1,68 @@ +/** + * @file system_utils.c + * @brief 系统工具函数实现文件 + * @note 实现系统初始化和清理相关函数 + */ + +#include +#include +#include "system_utils.h" +#include "file_utils.h" +#include "io_utils.h" +#include "config.h" + +/** + * @brief 初始化系统 + * @details 执行系统启动时的初始化操作,包括创建必要的数据目录 + * 调用createDataDirectories函数创建数据存储目录 + * @return 如果初始化成功返回true,否则返回false + * @note 此函数应在程序启动时调用 + * @note 如果初始化失败,会输出错误信息 + */ +bool initializeSystem() +{ + printInfo("正在初始化系统..."); + + if (!createDataDirectories()) + { + printError("系统初始化失败:无法创建数据目录"); + return false; + } + + printSuccess("系统初始化完成"); + return true; +} + +/** + * @brief 创建数据目录 + * @details 创建程序运行所需的数据存储目录 + * 目前创建"data"目录用于存储学生数据文件 + * @return 如果目录创建成功或已存在返回true,否则返回false + * @note 如果目录已存在,函数仍返回true + * @note 可以根据需要扩展创建更多目录 + */ +bool createDataDirectories() +{ + // 创建data目录 + if (!createDirectory("data")) + { + printError("无法创建data目录"); + return false; + } + + return true; +} + +/** + * @brief 清理系统资源 + * @details 执行程序退出前的清理操作 + * 目前主要输出清理完成的提示信息 + * @note 此函数应在程序退出前调用 + * @note 可以根据需要添加更多清理操作,如关闭文件、释放内存等 + */ +void cleanupSystem() +{ + printInfo("正在清理系统资源..."); + // 这里可以添加其他清理操作 + printSuccess("系统清理完成"); +} \ No newline at end of file diff --git a/system_utils.h b/system_utils.h new file mode 100644 index 0000000..b83c0a3 --- /dev/null +++ b/system_utils.h @@ -0,0 +1,17 @@ +/** + * @file system_utils.h + * @brief 系统工具函数头文件 + * @note 包含系统初始化和清理相关函数声明 + */ + +#ifndef SYSTEM_UTILS_H +#define SYSTEM_UTILS_H + +#include + +// 系统初始化和清理函数 +bool initializeSystem(); // 初始化系统 +bool createDataDirectories(); // 创建数据目录 +void cleanupSystem(); // 清理系统资源 + +#endif // SYSTEM_UTILS_H \ No newline at end of file diff --git a/user_manage.c b/user_manage.c index 1857a9d..331d972 100644 --- a/user_manage.c +++ b/user_manage.c @@ -10,51 +10,74 @@ #include "user_manage.h" #include "config.h" #include "globals.h" -#include "auxiliary.h" +#include "io_utils.h" +#include "string_utils.h" /** * @brief 处理用户登录 - * @return 登录成功返回1,失败返回0 + * @details 提供用户登录验证功能,验证用户名和密码的正确性 + * 登录成功后设置当前用户信息和管理员权限 + * @return int 登录成功返回1,失败返回0 + * @note 登录过程: + * 1. 获取用户输入的用户名和密码 + * 2. 遍历用户数组进行验证 + * 3. 验证成功则设置currentUser和isCurrentUserAdmin + * @note 设置的全局变量: + * - currentUser: 当前登录用户名 + * - isCurrentUserAdmin: 当前用户是否为管理员 */ -int loginSystem() { +int loginSystem() +{ char username[MAX_USERNAME_LENGTH]; char password[MAX_PASSWORD_LENGTH]; - + printf("\n"); safeInputString("请输入用户名", username, MAX_USERNAME_LENGTH); safeInputString("请输入密码", password, MAX_PASSWORD_LENGTH); - + // 验证用户名和密码 - for (int i = 0; i < userCount; i++) { - if (strcmp(users[i].username, username) == 0 && - strcmp(users[i].password, password) == 0) { + for (int i = 0; i < userCount; i++) + { + if (strcmp(users[i].username, username) == 0 && + strcmp(users[i].password, password) == 0) + { // 登录成功 strcpy(currentUser, username); isCurrentUserAdmin = users[i].isAdmin; return 1; } } - + return 0; // 登录失败 } /** * @brief 从文件加载用户数据 + * @details 从USERS_FILE文件中读取用户数据到内存 + * 如果文件不存在,则创建默认的管理员和普通用户账户 + * @note 文件格式:每行格式为 "username:password:isAdmin" + * 其中isAdmin为1表示管理员,0表示普通用户 + * @note 默认账户: + * - 管理员:用户名admin,密码123456 + * - 普通用户:用户名teacher,密码password + * @warning 如果文件格式错误,可能导致数据加载不完整 */ -void loadUsersFromFile() { - FILE* file = fopen(USERS_FILE, "r"); - if (file == NULL) { +void loadUsersFromFile() +{ + FILE *file = fopen(USERS_FILE, "r"); + if (file == NULL) + { // 文件不存在,创建默认管理员账户 strcpy(users[0].username, "admin"); strcpy(users[0].password, "123456"); users[0].isAdmin = true; - + strcpy(users[1].username, "teacher"); strcpy(users[1].password, "password"); users[1].isAdmin = false; - + userCount = 2; - + // 保存默认用户到文件 saveUsersToFile(); printInfo("已创建默认用户账户:"); @@ -62,204 +85,270 @@ void loadUsersFromFile() { printInfo("普通用户 - 用户名: teacher, 密码: password"); return; } - + userCount = 0; char line[200]; - - while (fgets(line, sizeof(line), file) && userCount < MAX_USERS) { + + while (fgets(line, sizeof(line), file) && userCount < MAX_USERS) + { // 移除换行符 line[strcspn(line, "\n")] = '\0'; - + // 解析格式:username:password:isAdmin - char* username = strtok(line, ":"); - char* password = strtok(NULL, ":"); - char* adminFlag = strtok(NULL, ":"); - - if (username && password && adminFlag) { + char *username = strtok(line, ":"); + char *password = strtok(NULL, ":"); + char *adminFlag = strtok(NULL, ":"); + + if (username && password && adminFlag) + { strcpy(users[userCount].username, username); strcpy(users[userCount].password, password); users[userCount].isAdmin = (strcmp(adminFlag, "1") == 0); userCount++; } } - + fclose(file); } /** * @brief 将用户数据保存到文件 + * @details 将内存中的用户数据写入到USERS_FILE文件中 + * 采用文本格式存储,每个用户占一行 + * @note 文件格式:每行格式为 "username:password:isAdmin" + * 其中isAdmin为1表示管理员,0表示普通用户 + * @warning 如果文件无法打开,将显示错误信息 */ -void saveUsersToFile() { - FILE* file = fopen(USERS_FILE, "w"); - if (file == NULL) { +void saveUsersToFile() +{ + FILE *file = fopen(USERS_FILE, "w"); + if (file == NULL) + { printError("无法保存用户数据!"); return; } - - for (int i = 0; i < userCount; i++) { - fprintf(file, "%s:%s:%d\n", - users[i].username, - users[i].password, + + for (int i = 0; i < userCount; i++) + { + fprintf(file, "%s:%s:%d\n", + users[i].username, + users[i].password, users[i].isAdmin ? 1 : 0); } - + fclose(file); } /** * @brief 增加用户账户 + * @details 提供交互式界面添加新的用户账户 + * 包括用户名唯一性检查、密码设置和用户类型选择 + * @note 添加流程: + * 1. 检查用户数量是否达到上限 + * 2. 输入新用户名并检查唯一性 + * 3. 设置密码 + * 4. 选择用户类型(普通用户/管理员) + * 5. 保存到文件并更新数据修改标志 + * @warning 如果用户数量已达MAX_USERS上限,将拒绝添加 + * @warning 如果用户名已存在,将拒绝添加 */ -void addUserAccount() { +void addUserAccount() +{ clearScreen(); printHeader("添加用户账户"); - - if (userCount >= MAX_USERS) { + + if (userCount >= MAX_USERS) + { printError("用户数量已达上限!"); pauseSystem(); return; } - + char username[MAX_USERNAME_LENGTH]; char password[MAX_PASSWORD_LENGTH]; - + printf("\n"); safeInputString("请输入新用户名", username, MAX_USERNAME_LENGTH); - + // 检查用户名是否已存在 - for (int i = 0; i < userCount; i++) { - if (strcmp(users[i].username, username) == 0) { + for (int i = 0; i < userCount; i++) + { + if (strcmp(users[i].username, username) == 0) + { printError("用户名已存在!"); pauseSystem(); return; } } - + safeInputString("请输入密码", password, MAX_PASSWORD_LENGTH); - + printf("\n用户类型:\n"); printf("1. 普通用户\n"); printf("2. 管理员\n"); int userType = safeInputInt("请选择用户类型", 1, 2); - + // 添加新用户 strcpy(users[userCount].username, username); strcpy(users[userCount].password, password); users[userCount].isAdmin = (userType == 2); userCount++; - + // 保存到文件 saveUsersToFile(); dataModified = true; - + printSuccess("用户添加成功!"); pauseSystem(); } /** * @brief 删除用户账户 + * @details 提供交互式界面删除指定的用户账户 + * 包含安全检查,防止删除当前登录用户和最后一个用户 + * @note 删除限制: + * - 不能删除当前登录的用户 + * - 系统至少需要保留一个用户账户 + * @note 删除过程: + * 1. 输入要删除的用户名 + * 2. 进行安全检查 + * 3. 查找用户并删除 + * 4. 重新排列用户数组 + * 5. 保存到文件并更新数据修改标志 + * @warning 删除操作不可逆,请谨慎操作 */ -void deleteUserAccount() { +void deleteUserAccount() +{ clearScreen(); printHeader("删除用户账户"); - - if (userCount <= 1) { + + if (userCount <= 1) + { printError("至少需要保留一个用户账户!"); pauseSystem(); return; } - + char username[MAX_USERNAME_LENGTH]; printf("\n"); safeInputString("请输入要删除的用户名", username, MAX_USERNAME_LENGTH); - + // 不能删除当前登录用户 - if (strcmp(username, currentUser) == 0) { + if (strcmp(username, currentUser) == 0) + { printError("不能删除当前登录的用户!"); pauseSystem(); return; } - + // 查找并删除用户 - for (int i = 0; i < userCount; i++) { - if (strcmp(users[i].username, username) == 0) { + for (int i = 0; i < userCount; i++) + { + if (strcmp(users[i].username, username) == 0) + { // 移动后面的用户向前 - for (int j = i; j < userCount - 1; j++) { + for (int j = i; j < userCount - 1; j++) + { users[j] = users[j + 1]; } userCount--; - + // 保存到文件 saveUsersToFile(); dataModified = true; - + printSuccess("用户删除成功!"); pauseSystem(); return; } } - + printError("用户不存在!"); pauseSystem(); } /** * @brief 修改用户密码 + * @details 提供交互式界面修改指定用户的密码 + * 管理员可以修改任何用户的密码 + * @note 修改流程: + * 1. 输入要修改密码的用户名 + * 2. 查找用户是否存在 + * 3. 输入新密码 + * 4. 更新用户密码 + * 5. 保存到文件并更新数据修改标志 + * @warning 密码修改后立即生效,用户需要使用新密码登录 */ -void modifyUserPassword() { +void modifyUserPassword() +{ clearScreen(); printHeader("修改用户密码"); - + char username[MAX_USERNAME_LENGTH]; char newPassword[MAX_PASSWORD_LENGTH]; - + printf("\n"); safeInputString("请输入用户名", username, MAX_USERNAME_LENGTH); - + // 查找用户 - for (int i = 0; i < userCount; i++) { - if (strcmp(users[i].username, username) == 0) { + for (int i = 0; i < userCount; i++) + { + if (strcmp(users[i].username, username) == 0) + { safeInputString("请输入新密码", newPassword, MAX_PASSWORD_LENGTH); - + strcpy(users[i].password, newPassword); - + // 保存到文件 saveUsersToFile(); dataModified = true; - + printSuccess("密码修改成功!"); pauseSystem(); return; } } - + printError("用户不存在!"); pauseSystem(); } /** * @brief 查看所有用户 + * @details 显示系统中所有用户的信息列表 + * 包括用户名、用户类型和当前登录状态 + * @note 显示内容: + * - 用户名 + * - 用户类型(管理员/普通用户) + * - 状态(标识当前登录用户) + * - 总用户数统计 + * @note 表格格式显示,便于查看和管理 + * @warning 如果没有用户数据,将显示警告信息 */ -void viewAllUsers() { +void viewAllUsers() +{ clearScreen(); printHeader("所有用户列表"); - - if (userCount == 0) { + + if (userCount == 0) + { printWarning("暂无用户数据!"); pauseSystem(); return; } - + printf("\n"); printf("%-20s %-15s %-10s\n", "用户名", "用户类型", "状态"); printSeparator(); - - for (int i = 0; i < userCount; i++) { - printf("%-20s %-15s %-10s\n", + + for (int i = 0; i < userCount; i++) + { + printf("%-20s %-15s %-10s\n", users[i].username, users[i].isAdmin ? "管理员" : "普通用户", strcmp(users[i].username, currentUser) == 0 ? "当前用户" : ""); } - + printf("\n总用户数: %d\n", userCount); pauseSystem(); } \ No newline at end of file diff --git a/user_manage.h b/user_manage.h index ce4a0f8..686c911 100644 --- a/user_manage.h +++ b/user_manage.h @@ -1,6 +1,8 @@ #ifndef USER_MANAGE_H #define USER_MANAGE_H +#include "config.h" + // 用户认证与管理相关函数 int loginSystem(); // 处理用户登录 void loadUsersFromFile(); // 从文件加载用户数据 diff --git a/validation.c b/validation.c new file mode 100644 index 0000000..042ffaf --- /dev/null +++ b/validation.c @@ -0,0 +1,86 @@ +/** + * @file validation.c + * @brief 数据验证函数实现文件 + * @note 实现学生信息各字段的验证函数 + */ + +#include +#include +#include +#include "validation.h" +#include "config.h" +#include "string_utils.h" + +/** + * @brief 验证成绩是否有效 + * @details 检查成绩是否在有效范围内(0-100分) + * @param score 要验证的成绩值 + * @return 如果成绩有效返回true,否则返回false + * @note 有效成绩范围为0.0到100.0(包含边界值) + */ +bool isValidScore(float score) +{ + return score >= 0.0 && score <= 100.0; +} + +/** + * @brief 验证学号是否有效 + * @details 检查学号格式是否符合要求:非空且长度在合理范围内 + * @param id 要验证的学号字符串 + * @return 如果学号有效返回true,否则返回false + * @note 学号不能为空,长度必须在1到MAX_ID_LENGTH之间 + * @warning 如果id为NULL,返回false + */ +bool isValidStudentId(const char *id) +{ + if (id == NULL || isEmptyString(id)) + { + return false; + } + + int len = strlen(id); + return len > 0 && len <= MAX_ID_LENGTH; +} + +/** + * @brief 验证姓名是否有效 + * @details 检查姓名格式是否符合要求:非空且长度在合理范围内 + * @param name 要验证的姓名字符串 + * @return 如果姓名有效返回true,否则返回false + * @note 姓名不能为空,长度必须在1到MAX_NAME_LENGTH之间 + * @warning 如果name为NULL,返回false + */ +bool isValidName(const char *name) +{ + if (name == NULL || isEmptyString(name)) + { + return false; + } + + int len = strlen(name); + return len > 0 && len <= MAX_NAME_LENGTH; +} + +/** + * @brief 验证性别是否有效 + * @details 检查性别是否为'M'(男)或'F'(女) + * @param gender 要验证的性别字符 + * @return 如果性别有效返回true,否则返回false + * @note 只接受'M'或'F'两个值 + */ +bool isValidGender(char gender) +{ + return gender == GENDER_MALE || gender == GENDER_FEMALE; +} + +/** + * @brief 验证年龄是否有效 + * @details 检查年龄是否在合理范围内 + * @param age 要验证的年龄值 + * @return 如果年龄有效返回true,否则返回false + * @note 有效年龄范围为MIN_AGE到MAX_AGE(包含边界值) + */ +bool isValidAge(int age) +{ + return age >= MIN_AGE && age <= MAX_AGE; +} \ No newline at end of file diff --git a/validation.h b/validation.h new file mode 100644 index 0000000..4d4a80f --- /dev/null +++ b/validation.h @@ -0,0 +1,19 @@ +/** + * @file validation.h + * @brief 数据验证函数头文件 + * @note 包含学生信息各字段的验证函数声明 + */ + +#ifndef VALIDATION_H +#define VALIDATION_H + +#include + +// 数据验证函数 +bool isValidScore(float score); // 验证成绩是否有效 +bool isValidStudentId(const char* id); // 验证学号是否有效 +bool isValidName(const char* name); // 验证姓名是否有效 +bool isValidGender(char gender); // 验证性别是否有效 +bool isValidAge(int age); // 验证年龄是否有效 + +#endif // VALIDATION_H \ No newline at end of file