diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9074f0e --- /dev/null +++ b/Makefile @@ -0,0 +1,72 @@ +# 学生成绩管理系统 Makefile +# 编译器设置 +CC = gcc +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) + +# 头文件 +HEADERS = config.h globals.h auxiliary.h main_menu.h user_manage.h stu.data.h statistical_analysis.h + +# 默认目标 +all: $(TARGET) + +# 链接目标文件 +$(TARGET): $(OBJECTS) + $(CC) $(OBJECTS) -o $(TARGET) + +# 编译源文件 +%.o: %.c $(HEADERS) + $(CC) $(CFLAGS) -c $< -o $@ + +# 清理编译文件 +clean: + rm -f $(OBJECTS) $(TARGET) + rm -f *.exe + +# 创建必要的目录 +setup: + mkdir -p data + mkdir -p backup + +# 运行程序 +run: $(TARGET) + ./$(TARGET) + +# Windows 特定目标 +windows: CFLAGS += -DWINDOWS +windows: $(TARGET) + +# 调试版本 +debug: CFLAGS += -DDEBUG -O0 +debug: $(TARGET) + +# 发布版本 +release: CFLAGS += -O2 -DNDEBUG +release: $(TARGET) + +# 安装(可选) +install: $(TARGET) + cp $(TARGET) /usr/local/bin/ + +# 帮助信息 +help: + @echo "可用的目标:" + @echo " all - 编译程序(默认)" + @echo " clean - 清理编译文件" + @echo " setup - 创建必要的目录" + @echo " run - 编译并运行程序" + @echo " windows - 编译Windows版本" + @echo " debug - 编译调试版本" + @echo " release - 编译发布版本" + @echo " install - 安装程序到系统" + @echo " help - 显示此帮助信息" + +.PHONY: all clean setup run windows debug release install help \ No newline at end of file diff --git a/auxiliary.c b/auxiliary.c new file mode 100644 index 0000000..9166e3f --- /dev/null +++ b/auxiliary.c @@ -0,0 +1,449 @@ +/** + * @file auxiliary.c + * @brief 辅助函数实现文件 + * @note 实现系统中使用的各种辅助函数 + */ + +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#include +#include +#else +#include +#include +#include +#endif + +#include "auxiliary.h" +#include "config.h" +#include "globals.h" +#include "stu.data.h" +#include "user_manage.h" + +/** + * @brief 清理输入缓冲区 + */ +void clearInputBuffer() +{ + int c; + while ((c = getchar()) != '\n' && c != EOF) + ; +} + +/** + * @brief 暂停系统,等待用户按键 + */ +void pauseSystem() +{ + printf("\n按任意键继续..."); +#ifdef _WIN32 + _getch(); +#else + getchar(); +#endif + printf("\n"); +} + +/** + * @brief 清屏 + */ +void clearScreen() +{ +#ifdef _WIN32 + system("cls"); +#else + system("clear"); +#endif +} + +/** + * @brief 打印分隔线 + */ +void printSeparator() +{ + printf("========================================\n"); +} + +/** + * @brief 打印标题头 + * @param title 标题文本 + */ +void printHeader(const char *title) +{ + printSeparator(); + printf(" %s\n", title); + printSeparator(); +} + +/** + * @brief 验证分数是否合法 + * @param score 分数 + * @return 合法返回true,否则返回false + */ +bool isValidScore(float score) +{ + return score >= MIN_SCORE && score <= MAX_SCORE; +} + +/** + * @brief 验证学号格式 + * @param id 学号 + * @return 合法返回true,否则返回false + */ +bool isValidStudentID(const char *id) +{ + if (id == NULL || strlen(id) == 0 || strlen(id) >= MAX_ID_LENGTH) + { + return false; + } + + // 检查是否只包含数字和字母 + for (int i = 0; id[i] != '\0'; i++) + { + if (!isalnum(id[i])) + { + return false; + } + } + + return true; +} + +/** + * @brief 验证姓名格式 + * @param name 姓名 + * @return 合法返回true,否则返回false + */ +bool isValidName(const char *name) +{ + if (name == NULL || strlen(name) == 0 || strlen(name) >= MAX_NAME_LENGTH) + { + return false; + } + + // 检查是否包含非法字符 + for (int i = 0; name[i] != '\0'; i++) + { + if (!isalpha(name[i]) && name[i] != ' ' && name[i] != '-' && + (unsigned char)name[i] < 128) + { // 允许中文字符 + return false; + } + } + + return true; +} + +/** + * @brief 验证性别 + * @param gender 性别字符 + * @return 合法返回true,否则返回false + */ +bool isValidGender(char gender) +{ + return gender == GENDER_MALE || gender == GENDER_FEMALE; +} + +/** + * @brief 验证年龄 + * @param age 年龄 + * @return 合法返回true,否则返回false + */ +bool isValidAge(int age) +{ + return age >= 10 && age <= 100; +} + +/** + * @brief 去除字符串首尾空格 + * @param str 字符串 + */ +void trimString(char *str) +{ + if (str == NULL) + return; + + // 去除开头空格 + char *start = str; + while (isspace(*start)) + start++; + + // 去除结尾空格 + char *end = str + strlen(str) - 1; + while (end > start && isspace(*end)) + end--; + + // 移动字符串 + int len = end - start + 1; + memmove(str, start, len); + str[len] = '\0'; +} + +/** + * @brief 判断字符串是否为空 + * @param str 字符串 + * @return 为空返回true,否则返回false + */ +bool isEmptyString(const char *str) +{ + if (str == NULL) + return true; + + while (*str) + { + if (!isspace(*str)) + return false; + str++; + } + return true; +} + +/** + * @brief 检查文件是否存在 + * @param filename 文件名 + * @return 存在返回true,否则返回false + */ +bool fileExists(const char *filename) +{ + FILE *file = fopen(filename, "r"); + if (file) + { + fclose(file); + return true; + } + return false; +} + +/** + * @brief 创建目录 + * @param path 目录路径 + * @return 成功返回true,否则返回false + */ +bool createDirectory(const char *path) +{ +#ifdef _WIN32 + return _mkdir(path) == 0 || errno == EEXIST; +#else + return mkdir(path, 0755) == 0 || errno == EEXIST; +#endif +} + +/** + * @brief 计算平均分 + * @param scores 分数数组 + * @param count 分数个数 + * @return 平均分 + */ +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; +} + +/** + * @brief 安全输入整数 + * @param prompt 提示信息 + * @param min 最小值 + * @param max 最大值 + * @return 输入的整数 + */ +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 安全输入浮点数 + * @param prompt 提示信息 + * @param min 最小值 + * @param max 最大值 + * @return 输入的浮点数 + */ +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 安全输入字符串 + * @param prompt 提示信息 + * @param buffer 缓冲区 + * @param maxLen 最大长度 + */ +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 彩色输出 + * @param text 文本 + * @param color 颜色代码 + */ +void printColored(const char *text, const char *color) +{ + printf("%s%s%s", color, text, COLOR_RESET); +} + +/** + * @brief 成功消息 + * @param message 消息 + */ +void printSuccess(const char *message) +{ + printColored(message, COLOR_GREEN); + printf("\n"); +} + +/** + * @brief 错误消息 + * @param message 消息 + */ +void printError(const char *message) +{ + printColored(message, COLOR_RED); + printf("\n"); +} + +/** + * @brief 警告消息 + * @param message 消息 + */ +void printWarning(const char *message) +{ + printColored(message, COLOR_YELLOW); + printf("\n"); +} + +/** + * @brief 信息消息 + * @param message 消息 + */ +void printInfo(const char *message) +{ + printColored(message, COLOR_CYAN); + printf("\n"); +} + +/** + * @brief 初始化系统 + * @return 成功返回true,否则返回false + */ +bool initializeSystem() +{ + // 创建数据目录 + if (!createDataDirectories()) + { + return false; + } + + // 加载用户数据 + loadUsersFromFile(); + + // 加载学生数据 + loadStudentsFromFile(); + + systemInitialized = true; + return true; +} + +/** + * @brief 创建数据目录 + * @return 成功返回true,否则返回false + */ +bool createDataDirectories() +{ + if (!createDirectory("data")) + { + return false; + } + + if (!createDirectory(BACKUP_DIR)) + { + return false; + } + + return true; +} + +/** + * @brief 清理系统资源 + */ +void cleanupSystem() +{ + // 保存数据 + if (dataModified) + { + saveStudentsToFile(); + saveUsersToFile(); + } + + systemInitialized = false; +} \ No newline at end of file diff --git a/auxiliary.h b/auxiliary.h new file mode 100644 index 0000000..2a2e5cf --- /dev/null +++ b/auxiliary.h @@ -0,0 +1,62 @@ +/** + * @file auxiliary.h + * @brief 辅助函数头文件 + * @note 包含系统中使用的各种辅助函数声明 + */ + +#ifndef AUXILIARY_H +#define AUXILIARY_H + +#include +#include "main_menu.h" + +// 输入输出辅助函数 +void clearInputBuffer(); // 清理输入缓冲区 +void pauseSystem(); // 暂停系统,等待用户按键 +void clearScreen(); // 清屏 +void printSeparator(); // 打印分隔线 +void printHeader(const char* title); // 打印标题头 + +// 数据验证函数 +bool isValidScore(float score); // 验证分数是否合法 (0-100) +bool isValidStudentID(const char* id); // 验证学号格式 +bool isValidName(const char* name); // 验证姓名格式 +bool isValidGender(char gender); // 验证性别 +bool isValidAge(int age); // 验证年龄 + +// 字符串处理函数 +void trimString(char* str); // 去除字符串首尾空格 +bool isEmptyString(const char* str); // 判断字符串是否为空 +void toLowerCase(char* str); // 转换为小写 +void toUpperCase(char* str); // 转换为大写 + +// 文件操作辅助函数 +bool fileExists(const char* filename); // 检查文件是否存在 +bool createDirectory(const char* path); // 创建目录 +bool backupFile(const char* source, const char* backup); // 备份文件 + +// 数学计算辅助函数 +float calculateAverage(float scores[], int count); // 计算平均分 +float findMaxScore(float scores[], int count); // 找最高分 +float findMinScore(float scores[], int count); // 找最低分 + + +// 安全输入函数 +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); // 安全输入字符串 +char safeInputChar(const char* prompt, const char* validChars); // 安全输入字符 + +// 系统初始化和清理函数 +bool initializeSystem(); // 初始化系统 +void cleanupSystem(); // 清理系统资源 +bool createDataDirectories(); // 创建数据目录 + +// 颜色输出函数 +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 // AUXILIARY_H \ No newline at end of file diff --git a/config.h b/config.h new file mode 100644 index 0000000..d9036d5 --- /dev/null +++ b/config.h @@ -0,0 +1,108 @@ +/** + * @file config.h + * @brief 学生成绩管理系统参数配置头文件 + * @note 本文件集中定义了学生成绩管理系统的所有参数配置,便于统一管理和修改 + */ + +#ifndef CONFIG_H +#define CONFIG_H + +// 系统配置参数 +#define MAX_STUDENTS 1000 // 最大学生数量 +#define MAX_COURSES 10 // 每个学生最多课程数 +#define MAX_USERS 50 // 最大用户数量 +#define MAX_LOGIN_ATTEMPTS 3 // 最大登录尝试次数 + +// 字符串长度限制 +#define MAX_ID_LENGTH 20 // 学号最大长度 +#define MAX_NAME_LENGTH 50 // 姓名最大长度 +#define MAX_COURSE_NAME_LENGTH 50 // 课程名称最大长度 +#define MAX_USERNAME_LENGTH 30 // 用户名最大长度 +#define MAX_PASSWORD_LENGTH 30 // 密码最大长度 + +// 分数相关配置 +#define MIN_SCORE 0.0 // 最低分数 +#define MAX_SCORE 100.0 // 最高分数 +#define PASS_SCORE 60.0 // 及格分数 +#define EXCELLENT_SCORE 90.0 // 优秀分数 + +// 文件路径配置 +#define STUDENTS_FILE "data/students.csv" // 学生数据文件 +#define USERS_FILE "data/users.txt" // 用户数据文件 +#define BACKUP_DIR "backup/" // 备份目录 + +// 菜单选项定义 +#define MENU_EXIT 0 +#define MENU_BASIC_FUNCTIONS 1 +#define MENU_STATISTICS 2 +#define MENU_ADMIN 3 + +// 基本功能菜单选项 +#define BASIC_BACK 0 +#define BASIC_ADD_STUDENT 1 +#define BASIC_DELETE_STUDENT 2 +#define BASIC_MODIFY_STUDENT 3 +#define BASIC_SEARCH_BY_ID 4 +#define BASIC_SEARCH_BY_NAME 5 +#define BASIC_DISPLAY_ALL 6 +#define BASIC_SORT_STUDENTS 7 + +// 统计功能菜单选项 +#define STATS_BACK 0 +#define STATS_COURSE_ANALYSIS 1 +#define STATS_SCORE_DISTRIBUTION 2 +#define STATS_SCORE_RANGES 3 +#define STATS_OVERALL_ANALYSIS 4 + +// 管理功能菜单选项 +#define ADMIN_BACK 0 +#define ADMIN_ADD_USER 1 +#define ADMIN_DELETE_USER 2 +#define ADMIN_MODIFY_PASSWORD 3 +#define ADMIN_VIEW_USERS 4 + +// 排序选项 +#define SORT_BY_ID 1 +#define SORT_BY_NAME 2 +#define SORT_BY_TOTAL_SCORE 3 +#define SORT_BY_AVERAGE_SCORE 4 + +// 排序顺序 +#define SORT_ASCENDING 1 +#define SORT_DESCENDING 2 + +// 性别定义 +#define GENDER_MALE 'M' +#define GENDER_FEMALE 'F' + +// 颜色代码(用于美化输出) +#define COLOR_RESET "\033[0m" +#define COLOR_RED "\033[31m" +#define COLOR_GREEN "\033[32m" +#define COLOR_YELLOW "\033[33m" +#define COLOR_BLUE "\033[34m" +#define COLOR_MAGENTA "\033[35m" +#define COLOR_CYAN "\033[36m" +#define COLOR_WHITE "\033[37m" + +// 系统消息 +#define MSG_SUCCESS "操作成功!" +#define MSG_FAILURE "操作失败!" +#define MSG_NOT_FOUND "未找到相关记录!" +#define MSG_INVALID_INPUT "输入无效,请重新输入!" +#define MSG_FILE_ERROR "文件操作错误!" + +// 数据结构定义 +typedef struct { + char studentID[MAX_ID_LENGTH]; // 学号 + char name[MAX_NAME_LENGTH]; // 姓名 + int age; // 年龄 + char gender; // 性别 ('M'/'F') + char courses[MAX_COURSES][MAX_COURSE_NAME_LENGTH]; // 课程名称 + float scores[MAX_COURSES]; // 各科成绩 + int courseCount; // 课程数量 + float totalScore; // 总分 + float averageScore; // 平均分 +} Student; + +#endif // CONFIG_H \ No newline at end of file diff --git a/core_handlers.c b/core_handlers.c new file mode 100644 index 0000000..4f4ab5d --- /dev/null +++ b/core_handlers.c @@ -0,0 +1,154 @@ +/** + * @file core_handlers.c + * @brief 核心处理函数实现文件 + * @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 diff --git a/core_handlers.h b/core_handlers.h new file mode 100644 index 0000000..c5e7118 --- /dev/null +++ b/core_handlers.h @@ -0,0 +1,18 @@ +/** + * @file core_handlers.h + * @brief 核心处理函数头文件 + * @note 包含主要的功能处理函数声明 + */ + +#ifndef CORE_HANDLERS_H +#define CORE_HANDLERS_H + +#include "config.h" + +// 核心处理函数声明 +void handleBasicFunctions(); // 处理基本功能菜单 +void handleStatistics(); // 处理统计功能菜单 +void handleAdminFunctions(); // 处理管理功能菜单 +void handleSortStudents(); // 处理学生排序功能 + +#endif // CORE_HANDLERS_H \ No newline at end of file diff --git a/globals.c b/globals.c new file mode 100644 index 0000000..f355d04 --- /dev/null +++ b/globals.c @@ -0,0 +1,25 @@ +/** + * @file globals.c + * @brief 全局变量定义文件 + * @note 定义所有全局变量的实际存储空间 + */ + +#include "globals.h" + +// 全局变量定义 +Student students[MAX_STUDENTS]; // 学生数组 +User users[MAX_USERS]; // 用户数组 +int studentCount = 0; // 当前学生数量 +int userCount = 0; // 当前用户数量 +char currentUser[MAX_USERNAME_LENGTH] = ""; // 当前登录用户 +bool isCurrentUserAdmin = false; // 当前用户是否为管理员 + +// 系统状态变量 +bool systemInitialized = false; // 系统是否已初始化 +bool dataModified = false; // 数据是否已修改 + +// 统计信息缓存 +float overallAverageScore = 0.0; // 全体学生平均分 +float highestScore = 0.0; // 最高分 +float lowestScore = 100.0; // 最低分 +bool statsNeedUpdate = true; // 统计信息是否需要更新 \ No newline at end of file diff --git a/globals.h b/globals.h new file mode 100644 index 0000000..9f36f8f --- /dev/null +++ b/globals.h @@ -0,0 +1,39 @@ +/** + * @file globals.h + * @brief 全局变量声明头文件 + * @note 集中管理所有全局变量的声明,提高代码可维护性 + */ + +#ifndef GLOBALS_H +#define GLOBALS_H + +#include +#include "config.h" +#include "main_menu.h" + +// 用户结构体定义 +typedef struct { + char username[MAX_USERNAME_LENGTH]; + char password[MAX_PASSWORD_LENGTH]; + bool isAdmin; // 是否为管理员 +} User; + +// 全局变量声明 +extern Student students[MAX_STUDENTS]; // 学生数组 +extern User users[MAX_USERS]; // 用户数组 +extern int studentCount; // 当前学生数量 +extern int userCount; // 当前用户数量 +extern char currentUser[MAX_USERNAME_LENGTH]; // 当前登录用户 +extern bool isCurrentUserAdmin; // 当前用户是否为管理员 + +// 系统状态变量 +extern bool systemInitialized; // 系统是否已初始化 +extern bool dataModified; // 数据是否已修改(用于判断是否需要保存) + +// 统计信息缓存(可选,用于提高性能) +extern float overallAverageScore; // 全体学生平均分 +extern float highestScore; // 最高分 +extern float lowestScore; // 最低分 +extern bool statsNeedUpdate; // 统计信息是否需要更新 + +#endif // GLOBALS_H \ No newline at end of file diff --git a/main.c b/main.c new file mode 100644 index 0000000..6d004a7 --- /dev/null +++ b/main.c @@ -0,0 +1,146 @@ +/** + * @file main.c + * @brief 学生成绩管理系统主程序 + * @note 系统入口点,包含主要的程序流程控制 + */ + +#include +#include +#include +#ifdef _WIN32 +#include +#include +#else +#include +#include +#endif + +#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" +#include "core_handlers.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 + */ +int main(int argc, char *argv[]) +{ + // 设置控制台编码为UTF-8 +#ifdef _WIN32 + system("chcp 65001 > nul"); // 设置控制台编码为UTF-8 + SetConsoleOutputCP(65001); // 设置控制台输出编码 + SetConsoleCP(65001); // 设置控制台输入编码 +#endif + + // 初始化系统 + if (!initializeSystem()) + { + printError("系统初始化失败!"); + return -1; + } + + // 显示欢迎信息 + clearScreen(); + printHeader("学生成绩管理系统"); + printf("\n"); + printInfo("欢迎使用学生成绩管理系统!"); + printf("\n"); + pauseSystem(); + + // 用户登录 + int loginAttempts = 0; + while (loginAttempts < MAX_LOGIN_ATTEMPTS) + { + clearScreen(); + printHeader("用户登录"); + + if (loginSystem()) + { + printSuccess("登录成功!"); + printf("欢迎您,%s%s\n", currentUser, + isCurrentUserAdmin ? " (管理员)" : " (普通用户)"); + pauseSystem(); + break; + } + else + { + loginAttempts++; + if (loginAttempts < MAX_LOGIN_ATTEMPTS) + { + printError("登录失败!请检查用户名和密码。"); + printf("剩余尝试次数:%d\n", MAX_LOGIN_ATTEMPTS - loginAttempts); + pauseSystem(); + } + } + } + + // 登录失败次数过多,退出程序 + if (loginAttempts >= MAX_LOGIN_ATTEMPTS) + { + printError("登录失败次数过多,程序将退出!"); + cleanupSystem(); + return -1; + } + + // 主程序循环 + int choice; + do + { + clearScreen(); + displayMainMenu(); + choice = safeInputInt("请选择功能", MENU_EXIT, MENU_ADMIN); + + switch (choice) + { + case MENU_BASIC_FUNCTIONS: + handleBasicFunctions(); + break; + case MENU_STATISTICS: + handleStatistics(); + break; + case MENU_ADMIN: + if (isCurrentUserAdmin) + { + handleAdminFunctions(); + } + else + { + printError("您没有管理员权限!"); + pauseSystem(); + } + break; + case MENU_EXIT: + // 保存数据并退出 + if (dataModified) + { + printInfo("正在保存数据..."); + saveStudentsToFile(); + saveUsersToFile(); + printSuccess("数据保存完成!"); + } + printInfo("感谢使用学生成绩管理系统!"); + break; + default: + printError("无效的选择!"); + pauseSystem(); + break; + } + } while (choice != MENU_EXIT); + + // 清理系统资源 + cleanupSystem(); + return 0; +} \ No newline at end of file diff --git a/main_menu.c b/main_menu.c new file mode 100644 index 0000000..19dac7d --- /dev/null +++ b/main_menu.c @@ -0,0 +1,92 @@ +/** + * @file main_menu.c + * @brief 主菜单实现文件 + * @note 实现各种菜单显示功能 + */ + + #include + #include "main_menu.h" + #include "config.h" + #include "auxiliary.h" + #include "globals.h" + + /** + * @brief 显示主菜单 + */ + void displayMainMenu() + { + printHeader("学生成绩管理系统 - 主菜单"); + printf("\n"); + printf("当前用户: %s %s\n", currentUser, + isCurrentUserAdmin ? "(管理员)" : "(普通用户)"); + printf("\n"); + printf("1. 基本功能管理\n"); + printf("2. 统计分析功能\n"); + if (isCurrentUserAdmin) + { + printf("3. 系统管理功能\n"); + } + printf("0. 退出系统\n"); + printf("\n"); + printSeparator(); + } + + /** + * @brief 显示基本功能菜单 + */ + void displayBasicFunctionsMenu() + { + printHeader("基本功能管理"); + printf("\n"); + printf("1. 添加学生信息\n"); + printf("2. 删除学生信息\n"); + printf("3. 修改学生信息\n"); + printf("4. 按学号查找学生\n"); + printf("5. 按姓名查找学生\n"); + printf("6. 显示所有学生\n"); + printf("7. 学生信息排序\n"); + printf("0. 返回主菜单\n"); + printf("\n"); + printf("当前学生总数: %d\n", studentCount); + printf("\n"); + printSeparator(); + } + + /** + * @brief 显示统计功能菜单 + */ + void displayStatisticsMenu() + { + printHeader("统计分析功能"); + printf("\n"); + printf("1. 课程统计分析\n"); + printf("2. 成绩分布统计\n"); + printf("3. 分数段统计\n"); + printf("4. 综合统计分析\n"); + printf("0. 返回主菜单\n"); + printf("\n"); + printf("当前学生总数: %d\n", studentCount); + if (studentCount > 0) { + printf("系统平均分: %.2f\n", overallAverageScore); + } + printf("\n"); + printSeparator(); + } + + /** + * @brief 显示管理功能菜单 + */ + void displayAdminMenu() + { + printHeader("系统管理功能"); + printf("\n"); + printf("1. 添加用户账户\n"); + printf("2. 删除用户账户\n"); + printf("3. 修改用户密码\n"); + printf("4. 查看所有用户\n"); + printf("0. 返回主菜单\n"); + printf("\n"); + printf("当前用户总数: %d\n", userCount); + printf("\n"); + printSeparator(); + } \ No newline at end of file diff --git a/main_menu.h b/main_menu.h new file mode 100644 index 0000000..9eff268 --- /dev/null +++ b/main_menu.h @@ -0,0 +1,12 @@ +#ifndef MAIN_MENU_H +#define MAIN_MENU_H + +#include "config.h" + +// 主菜单和子菜单显示函数 +void displayMainMenu(); // 显示主菜单 +void displayBasicFunctionsMenu(); // 显示基本功能菜单 +void displayStatisticsMenu(); // 显示统计功能菜单 +void displayAdminMenu(); // 显示管理功能菜单 + +#endif // MAIN_MENU_H \ No newline at end of file diff --git a/statistical_analysis.c b/statistical_analysis.c new file mode 100644 index 0000000..8a134d1 --- /dev/null +++ b/statistical_analysis.c @@ -0,0 +1,488 @@ +/** + * @file statistical_analysis.c + * @brief 统计分析功能实现文件 + * @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 diff --git a/statistical_analysis.h b/statistical_analysis.h new file mode 100644 index 0000000..b34b9eb --- /dev/null +++ b/statistical_analysis.h @@ -0,0 +1,71 @@ +/** + * @file statistical_analysis.h + * @brief 统计分析功能头文件 + * @note 包含各种统计分析功能的函数声明 + */ + +#ifndef STATISTICAL_ANALYSIS_H +#define STATISTICAL_ANALYSIS_H + +#include "config.h" +#include "main_menu.h" + +// 课程统计结构体 +typedef struct { + int studentCount; + float maxScore; + float minScore; + float totalScore; + float averageScore; + float passRate; +} CourseStats; + +// 分数分布结构体 +typedef struct { + int excellent; // 90-100分 + int good; // 80-89分 + int medium; // 70-79分 + int pass; // 60-69分 + int fail; // 0-59分 +} ScoreDistribution; + +// 学生排名结构体 +typedef struct { + int studentIndex; + float averageScore; + float totalScore; +} StudentRank; + +// 总体统计结构体 +typedef struct { + int totalStudents; + int maleCount; + int femaleCount; + float averageAge; + float highestAverage; + float lowestAverage; + float overallAverageScore; + float standardDeviation; + int totalCourses; + float averageCoursesPerStudent; +} OverallStats; + +// 主要统计分析函数 +void displayCourseStatistics(); // 显示课程统计信息 +void displayScoreDistribution(); // 显示分数分布 +void displayStudentRanking(); // 显示学生排名 +void displayOverallStatistics(); // 显示系统总体统计 + +// 查找功能 +void findTopStudent(); // 查找最高分学生 +void findBottomStudent(); // 查找最低分学生 +void findTopScoreInCourse(); // 按课程查找最高分 + +// 计算函数 +CourseStats calculateCourseStats(const char* courseName); +ScoreDistribution calculateScoreDistribution(); +OverallStats calculateOverallStats(); +void calculateStudentStats(Student* student); +void updateGlobalStats(); + +#endif // STATISTICAL_ANALYSIS_H \ No newline at end of file diff --git a/stu.data.h b/stu.data.h new file mode 100644 index 0000000..d4c96fa --- /dev/null +++ b/stu.data.h @@ -0,0 +1,18 @@ +#ifndef STU_DATA_H +#define STU_DATA_H + +#include "config.h" + +// 学生数据管理相关函数 (CRUD) +void loadStudentsFromFile(); // 从文件加载学生数据 +void saveStudentsToFile(); // 将学生数据保存到文件 +void addStudent(); // 增加学生信息 +void deleteStudent(); // 删除学生信息 +void modifyStudent(); // 修改学生信息 +void searchStudentByID(); // 按学号查找学生 +void searchStudentByName(); // 按姓名查找学生 +void displayAllStudents(); // 显示所有学生信息 +void displayStudentInfo(const Student* student); // 显示单个学生信息 +void sortStudents(int criteria, int order); // 统一排序函数,根据参数选择排序依据和升降序 + +#endif // STU_DATA_H \ No newline at end of file diff --git a/stu_data.c b/stu_data.c new file mode 100644 index 0000000..1b8ffd2 --- /dev/null +++ b/stu_data.c @@ -0,0 +1,614 @@ +/** + * @file stu_data.c + * @brief 学生数据管理实现文件 + * @note 实现学生信息的增删改查功能 + */ + +#include +#include +#include +#include "stu.data.h" +#include "config.h" +#include "globals.h" +#include "auxiliary.h" +#include "statistical_analysis.h" + +/** + * @brief 从CSV文件加载学生数据 + */ +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]; + memset(student, 0, sizeof(Student)); + + // 解析CSV行 + char* token = strtok(line, ","); + if (token == NULL) continue; + + // 学号 + strncpy(student->studentID, token, MAX_ID_LENGTH - 1); + + // 姓名 + token = strtok(NULL, ","); + if (token == NULL) continue; + strncpy(student->name, token, MAX_NAME_LENGTH - 1); + + // 年龄 + token = strtok(NULL, ","); + if (token == NULL) continue; + student->age = atoi(token); + + // 性别 + token = strtok(NULL, ","); + if (token == NULL) continue; + student->gender = token[0]; + + // 课程数量 + token = strtok(NULL, ","); + if (token == NULL) continue; + student->courseCount = atoi(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); + } + + // 总分 + token = strtok(NULL, ","); + if (token != NULL) { + student->totalScore = atof(token); + } + + // 平均分 + token = strtok(NULL, ","); + if (token != NULL) { + student->averageScore = atof(token); + } + + studentCount++; + } + + fclose(file); + + // 更新统计信息 + statsNeedUpdate = true; +} + +/** + * @brief 将学生数据保存到CSV文件 + */ +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文件!"); +} + +/** + * @brief 添加学生信息 + */ +void addStudent() { + clearScreen(); + printHeader("添加学生信息"); + + if (studentCount >= MAX_STUDENTS) { + printError("学生数量已达上限!"); + pauseSystem(); + return; + } + + Student newStudent; + memset(&newStudent, 0, sizeof(Student)); + + printf("\n"); + + // 输入学号 + while (1) { + safeInputString("请输入学号", newStudent.studentID, MAX_ID_LENGTH); + + if (!isValidStudentID(newStudent.studentID)) { + printError("学号格式无效!"); + continue; + } + + // 检查学号是否已存在 + bool exists = false; + for (int i = 0; i < studentCount; i++) { + if (strcmp(students[i].studentID, newStudent.studentID) == 0) { + printError("学号已存在!"); + exists = true; + break; + } + } + + if (!exists) break; + } + + // 输入姓名 + while (1) { + safeInputString("请输入姓名", newStudent.name, MAX_NAME_LENGTH); + if (isValidName(newStudent.name)) break; + printError("姓名格式无效!"); + } + + // 输入年龄 + newStudent.age = safeInputInt("请输入年龄", 10, 100); + + // 输入性别 + while (1) { + printf("请输入性别 (M/F): "); + char gender; + scanf(" %c", &gender); + clearInputBuffer(); + + if (isValidGender(gender)) { + newStudent.gender = gender; + break; + } + printError("性别输入无效!请输入 M 或 F"); + } + + // 输入课程信息 + printf("\n开始输入课程信息:\n"); + newStudent.courseCount = 0; + + while (newStudent.courseCount < MAX_COURSES) { + printf("\n第 %d 门课程:\n", newStudent.courseCount + 1); + + safeInputString("课程名称", + newStudent.courses[newStudent.courseCount], + MAX_COURSE_NAME_LENGTH); + + newStudent.scores[newStudent.courseCount] = + safeInputFloat("课程分数", MIN_SCORE, MAX_SCORE); + + newStudent.courseCount++; + + if (newStudent.courseCount < MAX_COURSES) { + printf("\n是否继续添加课程?(y/n): "); + char choice; + scanf(" %c", &choice); + clearInputBuffer(); + + if (choice != 'y' && choice != 'Y') { + break; + } + } + } + + // 计算总分和平均分 + calculateStudentStats(&newStudent); + + // 添加到数组 + students[studentCount] = newStudent; + studentCount++; + + // 标记数据已修改 + dataModified = true; + statsNeedUpdate = true; + + printSuccess("学生信息添加成功!"); + printf("学号: %s\n", newStudent.studentID); + printf("姓名: %s\n", newStudent.name); + printf("总分: %.2f\n", newStudent.totalScore); + printf("平均分: %.2f\n", newStudent.averageScore); + + pauseSystem(); +} + +/** + * @brief 删除学生信息 + */ +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 修改学生信息 + */ +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) { + printf("\n找到学生信息:\n"); + printf("学号: %s\n", students[i].studentID); + printf("姓名: %s\n", students[i].name); + printf("年龄: %d\n", students[i].age); + printf("性别: %c\n", students[i].gender); + + printf("\n修改选项:\n"); + printf("1. 修改姓名\n"); + printf("2. 修改年龄\n"); + printf("3. 修改性别\n"); + printf("4. 修改课程成绩\n"); + printf("0. 返回\n"); + + int choice = safeInputInt("请选择修改项", 0, 4); + + switch (choice) { + case 1: // 修改姓名 + safeInputString("请输入新姓名", students[i].name, MAX_NAME_LENGTH); + break; + case 2: + students[i].age = safeInputInt("请输入新年龄", 10, 100); + break; + case 3: + while (1) { + printf("请输入新性别 (M/F): "); + char gender; + scanf(" %c", &gender); + clearInputBuffer(); + + if (isValidGender(gender)) { + students[i].gender = gender; + break; + } + printError("性别输入无效!"); + } + break; + case 4: + printf("\n当前课程列表:\n"); + for (int j = 0; j < students[i].courseCount; j++) { + printf("%d. %s: %.2f\n", j + 1, students[i].courses[j], students[i].scores[j]); + } + + printf("\n修改选项:\n"); + printf("1. 修改现有课程成绩\n"); + printf("2. 添加新课程\n"); + printf("3. 删除课程\n"); + printf("0. 返回\n"); + + int courseChoice = safeInputInt("请选择操作", 0, 3); + switch (courseChoice) { + case 1: // 修改现有课程成绩 + if (students[i].courseCount == 0) { + printWarning("该学生没有课程记录!"); + break; + } + + int courseIndex = safeInputInt("请选择要修改的课程", 1, students[i].courseCount) - 1; + students[i].scores[courseIndex] = safeInputFloat("新成绩", MIN_SCORE, MAX_SCORE); + break; + + case 2: // 添加新课程 + if (students[i].courseCount >= MAX_COURSES) { + printWarning("课程数量已达上限!"); + break; + } + + safeInputString("课程名称", students[i].courses[students[i].courseCount], MAX_COURSE_NAME_LENGTH); + students[i].scores[students[i].courseCount] = safeInputFloat("课程成绩", MIN_SCORE, MAX_SCORE); + students[i].courseCount++; + break; + + case 3: // 删除课程 + if (students[i].courseCount == 0) { + printWarning("该学生没有课程记录!"); + break; + } + + courseIndex = safeInputInt("请选择要删除的课程", 1, students[i].courseCount) - 1; + + // 移动数组元素 + for (int k = courseIndex; k < students[i].courseCount - 1; k++) { + strcpy(students[i].courses[k], students[i].courses[k + 1]); + students[i].scores[k] = students[i].scores[k + 1]; + } + students[i].courseCount--; + break; + } + break; + case 0: + return; + } + + if (choice == 4) { + calculateStudentStats(&students[i]); + } + + dataModified = true; + statsNeedUpdate = true; + + printSuccess("学生信息修改成功!"); + pauseSystem(); + return; + } + } + + printError("未找到该学号的学生!"); + pauseSystem(); +} + +/** + * @brief 修改学生课程成绩 + */ +/** + * @brief 按学号查找学生 + */ +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 按姓名查找学生 + */ +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 显示所有学生信息 + */ +void displayAllStudents() { + clearScreen(); + printHeader("所有学生信息"); + + if (studentCount == 0) { + printWarning("暂无学生数据!"); + pauseSystem(); + return; + } + + printf("\n"); + // 调整中文表头的对齐格式,考虑中文字符的显示宽度 + printf("%-10s %-12s %-6s %-6s %-8s %-8s\n", + "学号", "姓名", "年龄", "性别", "总分", "平均分"); + printf("========================================\n"); + + for (int i = 0; i < studentCount; i++) { + printf("%-10s %-12s %-6d %-6c %-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 显示单个学生详细信息 + */ +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); + } +} + +/** + * @brief 排序学生信息 + */ +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_system.exe b/student_system.exe new file mode 100644 index 0000000..b6c3975 Binary files /dev/null and b/student_system.exe differ diff --git a/student_system_new.exe b/student_system_new.exe new file mode 100644 index 0000000..011f1f3 Binary files /dev/null and b/student_system_new.exe differ diff --git a/test_ranking.txt b/test_ranking.txt new file mode 100644 index 0000000..683a0ac --- /dev/null +++ b/test_ranking.txt @@ -0,0 +1,6 @@ +admin +123456 +2 +3 +0 +0 \ No newline at end of file diff --git a/user_manage.c b/user_manage.c new file mode 100644 index 0000000..1857a9d --- /dev/null +++ b/user_manage.c @@ -0,0 +1,265 @@ +/** + * @file user_manage.c + * @brief 用户管理实现文件 + * @note 实现用户认证和管理功能 + */ + +#include +#include +#include +#include "user_manage.h" +#include "config.h" +#include "globals.h" +#include "auxiliary.h" + +/** + * @brief 处理用户登录 + * @return 登录成功返回1,失败返回0 + */ +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) { + // 登录成功 + strcpy(currentUser, username); + isCurrentUserAdmin = users[i].isAdmin; + return 1; + } + } + + return 0; // 登录失败 +} + +/** + * @brief 从文件加载用户数据 + */ +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("已创建默认用户账户:"); + printInfo("管理员 - 用户名: admin, 密码: 123456"); + printInfo("普通用户 - 用户名: teacher, 密码: password"); + return; + } + + userCount = 0; + char line[200]; + + 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) { + strcpy(users[userCount].username, username); + strcpy(users[userCount].password, password); + users[userCount].isAdmin = (strcmp(adminFlag, "1") == 0); + userCount++; + } + } + + fclose(file); +} + +/** + * @brief 将用户数据保存到文件 + */ +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, + users[i].isAdmin ? 1 : 0); + } + + fclose(file); +} + +/** + * @brief 增加用户账户 + */ +void addUserAccount() { + clearScreen(); + printHeader("添加用户账户"); + + 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) { + 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 删除用户账户 + */ +void deleteUserAccount() { + clearScreen(); + printHeader("删除用户账户"); + + if (userCount <= 1) { + printError("至少需要保留一个用户账户!"); + pauseSystem(); + return; + } + + char username[MAX_USERNAME_LENGTH]; + printf("\n"); + safeInputString("请输入要删除的用户名", username, MAX_USERNAME_LENGTH); + + // 不能删除当前登录用户 + if (strcmp(username, currentUser) == 0) { + printError("不能删除当前登录的用户!"); + pauseSystem(); + return; + } + + // 查找并删除用户 + for (int i = 0; i < userCount; i++) { + if (strcmp(users[i].username, username) == 0) { + // 移动后面的用户向前 + for (int j = i; j < userCount - 1; j++) { + users[j] = users[j + 1]; + } + userCount--; + + // 保存到文件 + saveUsersToFile(); + dataModified = true; + + printSuccess("用户删除成功!"); + pauseSystem(); + return; + } + } + + printError("用户不存在!"); + pauseSystem(); +} + +/** + * @brief 修改用户密码 + */ +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) { + safeInputString("请输入新密码", newPassword, MAX_PASSWORD_LENGTH); + + strcpy(users[i].password, newPassword); + + // 保存到文件 + saveUsersToFile(); + dataModified = true; + + printSuccess("密码修改成功!"); + pauseSystem(); + return; + } + } + + printError("用户不存在!"); + pauseSystem(); +} + +/** + * @brief 查看所有用户 + */ +void viewAllUsers() { + clearScreen(); + printHeader("所有用户列表"); + + 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", + 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 new file mode 100644 index 0000000..ce4a0f8 --- /dev/null +++ b/user_manage.h @@ -0,0 +1,13 @@ +#ifndef USER_MANAGE_H +#define USER_MANAGE_H + +// 用户认证与管理相关函数 +int loginSystem(); // 处理用户登录 +void loadUsersFromFile(); // 从文件加载用户数据 +void saveUsersToFile(); // 将用户数据保存到文件 +void addUserAccount(); // 增加用户 +void deleteUserAccount(); // 删除用户 +void modifyUserPassword(); // 修改用户密码 +void viewAllUsers(); // 查看所有用户 + +#endif // USER_MANAGE_H \ No newline at end of file