v4.1.0: 目录结构标准化 - 创建include/src目录分离头文件和源文件
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
/**
|
||||
* @file core_handlers.c
|
||||
* @brief 核心处理函数实现文件
|
||||
* @note 实现主要的功能处理函数
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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();
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* @file file_utils.c
|
||||
* @brief 文件操作工具函数实现文件
|
||||
* @note 实现文件和目录操作相关函数
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
#include <io.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#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
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* @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; // 统计信息是否需要更新
|
||||
|
||||
// 排序参数
|
||||
int currentSortCriteria = 0; // 当前排序依据
|
||||
int currentSortOrder = 0; // 当前排序顺序
|
||||
|
||||
// 统计缓存
|
||||
StatisticsCache statsCache = {false, {0}, {0}, {{0}}, 0, 0}; // 统计分析缓存
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
/**
|
||||
* @file io_utils.c
|
||||
* @brief 输入输出工具函数实现文件
|
||||
* @note 实现界面显示、用户输入等相关函数
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#ifdef _WIN32
|
||||
#include <conio.h>
|
||||
#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");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 安全输入单个字符
|
||||
* @details 提供安全的字符输入功能,避免缓冲区溢出
|
||||
* @param prompt 显示给用户的提示信息
|
||||
* @return 返回用户输入的字符
|
||||
* @note 自动清理输入缓冲区
|
||||
*/
|
||||
char safeInputChar(const char *prompt)
|
||||
{
|
||||
char buffer[10];
|
||||
char result;
|
||||
|
||||
while (1)
|
||||
{
|
||||
printf("%s: ", prompt);
|
||||
|
||||
if (fgets(buffer, sizeof(buffer), stdin) != NULL)
|
||||
{
|
||||
// 移除换行符
|
||||
buffer[strcspn(buffer, "\n")] = '\0';
|
||||
trimString(buffer);
|
||||
|
||||
if (strlen(buffer) == 1)
|
||||
{
|
||||
result = buffer[0];
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
printError("请输入单个字符!");
|
||||
}
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* @brief 主程序入口
|
||||
* @brief 学生成绩管理系统主程序
|
||||
* @details 学生成绩管理系统的主入口函数,负责系统初始化、用户登录验证、
|
||||
* 主菜单循环处理和系统清理等核心流程
|
||||
* 程序流程:设置编码 -> 系统初始化 -> 用户登录 -> 主菜单循环 -> 数据保存 -> 系统清理
|
||||
* @return 程序退出状态码:0表示正常退出,-1表示异常退出
|
||||
* @note 系统预设用户账户:
|
||||
* 1. admin - 密码:123456(管理员权限)
|
||||
* 2. teacher - 密码:password(普通用户权限)
|
||||
* @note 编译运行命令:
|
||||
* gcc -std=c17 -o student_system.exe src/*.c -Iinclude
|
||||
./student_system
|
||||
或者用Makefile编译
|
||||
make run
|
||||
make clean
|
||||
* @warning 登录失败超过MAX_LOGIN_ATTEMPTS次会强制退出程序
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <direct.h>
|
||||
#else
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "config.h"
|
||||
#include "globals.h"
|
||||
#include "main_menu.h"
|
||||
#include "user_manage.h"
|
||||
#include "statistical_analysis.h"
|
||||
#include "io_utils.h"
|
||||
#include "system_utils.h"
|
||||
#include "core_handlers.h"
|
||||
#include "student_io.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
// 设置控制台编码为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;
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* @file main_menu.c
|
||||
* @brief 主菜单实现文件
|
||||
* @note 实现各种菜单显示功能
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "main_menu.h"
|
||||
#include "config.h"
|
||||
#include "io_utils.h"
|
||||
#include "globals.h"
|
||||
|
||||
/**
|
||||
* @brief 显示主菜单
|
||||
* @details 显示学生成绩管理系统的主菜单界面,包括当前用户信息和可用功能选项
|
||||
* 根据用户权限动态显示菜单项(管理员可看到系统管理功能)
|
||||
* @note 菜单选项:
|
||||
* 1. 基本功能管理(所有用户)
|
||||
* 2. 统计分析功能(所有用户)
|
||||
* 3. 系统管理功能(仅管理员)
|
||||
* 0. 退出系统
|
||||
*/
|
||||
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 显示基本功能菜单
|
||||
* @details 显示学生信息管理的基本功能菜单,包括增删改查和排序功能
|
||||
* 同时显示当前系统中的学生总数
|
||||
* @note 菜单功能:
|
||||
* 1. 添加学生信息
|
||||
* 2. 删除学生信息
|
||||
* 3. 修改学生信息
|
||||
* 4. 按学号查找学生
|
||||
* 5. 按姓名查找学生
|
||||
* 6. 显示所有学生
|
||||
* 7. 学生信息排序
|
||||
* 0. 返回主菜单
|
||||
*/
|
||||
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 显示统计功能菜单
|
||||
* @details 显示统计分析功能菜单,提供各种数据统计和分析选项
|
||||
* 显示当前学生总数和系统平均分(如果有学生数据)
|
||||
* @note 菜单功能:
|
||||
* 1. 课程统计分析
|
||||
* 2. 成绩分布统计
|
||||
* 3. 分数段统计
|
||||
* 4. 综合统计分析
|
||||
* 0. 返回主菜单
|
||||
*/
|
||||
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 显示管理功能菜单
|
||||
* @details 显示系统管理功能菜单,仅管理员可访问
|
||||
* 提供用户账户管理功能,显示当前用户总数
|
||||
* @note 菜单功能:
|
||||
* 1. 添加用户账户
|
||||
* 2. 删除用户账户
|
||||
* 3. 修改用户密码
|
||||
* 4. 查看所有用户
|
||||
* 0. 返回主菜单
|
||||
* @warning 此菜单仅限管理员用户访问
|
||||
*/
|
||||
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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* @file security_utils.c
|
||||
* @brief 安全工具函数实现文件
|
||||
* @note 实现密码哈希、验证等安全相关功能
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include "security_utils.h"
|
||||
|
||||
// SHA-256常量
|
||||
static const uint32_t k[64] = {
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
||||
};
|
||||
|
||||
// 右旋转函数
|
||||
#define ROTR(n,x) (((x) >> (n)) | ((x) << (32 - (n))))
|
||||
|
||||
// SHA-256辅助函数
|
||||
#define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z)))
|
||||
#define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
|
||||
#define EP0(x) (ROTR(2,x) ^ ROTR(13,x) ^ ROTR(22,x))
|
||||
#define EP1(x) (ROTR(6,x) ^ ROTR(11,x) ^ ROTR(25,x))
|
||||
#define SIG0(x) (ROTR(7,x) ^ ROTR(18,x) ^ ((x) >> 3))
|
||||
#define SIG1(x) (ROTR(17,x) ^ ROTR(19,x) ^ ((x) >> 10))
|
||||
|
||||
/**
|
||||
* @brief SHA-256哈希计算核心函数
|
||||
* @param data 输入数据
|
||||
* @param len 数据长度
|
||||
* @param hash 输出哈希值(32字节)
|
||||
*/
|
||||
static void sha256_compute(const uint8_t *data, size_t len, uint8_t *hash)
|
||||
{
|
||||
uint32_t h[8] = {
|
||||
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
||||
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
|
||||
};
|
||||
|
||||
size_t new_len = len + 1;
|
||||
while (new_len % 64 != 56) {
|
||||
new_len++;
|
||||
}
|
||||
new_len += 8;
|
||||
|
||||
uint8_t *msg = calloc(new_len, 1);
|
||||
if (!msg) return;
|
||||
|
||||
memcpy(msg, data, len);
|
||||
msg[len] = 0x80;
|
||||
|
||||
uint64_t bits_len = len * 8;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
msg[new_len - 1 - i] = (bits_len >> (i * 8)) & 0xff;
|
||||
}
|
||||
|
||||
for (size_t chunk = 0; chunk < new_len; chunk += 64) {
|
||||
uint32_t w[64];
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
w[i] = (msg[chunk + i * 4] << 24) |
|
||||
(msg[chunk + i * 4 + 1] << 16) |
|
||||
(msg[chunk + i * 4 + 2] << 8) |
|
||||
(msg[chunk + i * 4 + 3]);
|
||||
}
|
||||
|
||||
for (int i = 16; i < 64; i++) {
|
||||
w[i] = SIG1(w[i - 2]) + w[i - 7] + SIG0(w[i - 15]) + w[i - 16];
|
||||
}
|
||||
|
||||
uint32_t a = h[0], b = h[1], c = h[2], d = h[3];
|
||||
uint32_t e = h[4], f = h[5], g = h[6], h_val = h[7];
|
||||
|
||||
for (int i = 0; i < 64; i++) {
|
||||
uint32_t t1 = h_val + EP1(e) + CH(e, f, g) + k[i] + w[i];
|
||||
uint32_t t2 = EP0(a) + MAJ(a, b, c);
|
||||
h_val = g;
|
||||
g = f;
|
||||
f = e;
|
||||
e = d + t1;
|
||||
d = c;
|
||||
c = b;
|
||||
b = a;
|
||||
a = t1 + t2;
|
||||
}
|
||||
|
||||
h[0] += a; h[1] += b; h[2] += c; h[3] += d;
|
||||
h[4] += e; h[5] += f; h[6] += g; h[7] += h_val;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
hash[i * 4] = (h[i] >> 24) & 0xff;
|
||||
hash[i * 4 + 1] = (h[i] >> 16) & 0xff;
|
||||
hash[i * 4 + 2] = (h[i] >> 8) & 0xff;
|
||||
hash[i * 4 + 3] = h[i] & 0xff;
|
||||
}
|
||||
|
||||
free(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算字符串的SHA-256哈希值
|
||||
* @param input 输入字符串
|
||||
* @param output 输出缓冲区,至少需要SHA256_HEX_LENGTH字节
|
||||
*/
|
||||
void sha256_hash(const char *input, char *output)
|
||||
{
|
||||
if (!input || !output) return;
|
||||
|
||||
uint8_t hash[SHA256_DIGEST_LENGTH];
|
||||
sha256_compute((const uint8_t *)input, strlen(input), hash);
|
||||
|
||||
for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
|
||||
snprintf(output + i * 2, 3, "%02x", hash[i]);
|
||||
}
|
||||
output[SHA256_HEX_LENGTH - 1] = '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 验证密码是否匹配哈希值
|
||||
* @param password 明文密码
|
||||
* @param hash 存储的哈希值
|
||||
* @return 匹配返回1,不匹配返回0
|
||||
*/
|
||||
int verify_password(const char *password, const char *hash)
|
||||
{
|
||||
if (!password || !hash) return 0;
|
||||
|
||||
char computed_hash[SHA256_HEX_LENGTH];
|
||||
sha256_hash(password, computed_hash);
|
||||
|
||||
return strcmp(computed_hash, hash) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 生成密码哈希值
|
||||
* @param password 明文密码
|
||||
* @param hash_output 输出缓冲区,至少需要SHA256_HEX_LENGTH字节
|
||||
*/
|
||||
void hash_password(const char *password, char *hash_output)
|
||||
{
|
||||
sha256_hash(password, hash_output);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 安全地清除内存中的敏感数据
|
||||
* @param ptr 指向敏感数据的指针
|
||||
* @param size 数据大小
|
||||
*/
|
||||
void secure_memset(void *ptr, size_t size)
|
||||
{
|
||||
if (ptr) {
|
||||
volatile char *p = (volatile char *)ptr;
|
||||
while (size--) {
|
||||
*p++ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,843 @@
|
||||
/**
|
||||
* @file statistical_analysis.c
|
||||
* @brief 统计分析功能实现文件
|
||||
* @note 实现各种统计分析功能
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#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)
|
||||
{
|
||||
strncpy(courses[courseCount], students[i].courses[j], MAX_COURSE_NAME_LENGTH - 1);
|
||||
courses[courseCount][MAX_COURSE_NAME_LENGTH - 1] = '\0';
|
||||
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 = getCachedScoreDistribution();
|
||||
|
||||
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];
|
||||
int rankingCount = getCachedStudentRankings(rankings);
|
||||
|
||||
printf("\n");
|
||||
// 调整中文表头的对齐格式,考虑中文字符的显示宽度
|
||||
printf("%-7s %-12s %-12s %-10s %-8s\n",
|
||||
"排名", "学号", "姓名", "总分", "平均分");
|
||||
printf("==========================================\n");
|
||||
|
||||
for (int i = 0; i < rankingCount; 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 = getCachedOverallStats();
|
||||
|
||||
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)
|
||||
{
|
||||
strncpy(courses[uniqueCourseCount], students[i].courses[j], MAX_COURSE_NAME_LENGTH - 1);
|
||||
courses[uniqueCourseCount][MAX_COURSE_NAME_LENGTH - 1] = '\0';
|
||||
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;
|
||||
|
||||
// 使统计缓存无效
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
// ==================== 缓存管理函数实现 ====================
|
||||
|
||||
/**
|
||||
* @brief 初始化统计缓存
|
||||
* @details 初始化统计缓存系统,清空所有缓存数据
|
||||
* @note 在系统启动时调用,确保缓存处于干净状态
|
||||
*/
|
||||
// 快速排序辅助函数:分区
|
||||
int partitionRankings(StudentRank arr[], int low, int high) {
|
||||
float pivot = arr[high].averageScore;
|
||||
int i = (low - 1);
|
||||
|
||||
for (int j = low; j <= high - 1; j++) {
|
||||
// 降序排列:如果当前元素大于等于基准值
|
||||
if (arr[j].averageScore >= pivot) {
|
||||
i++;
|
||||
StudentRank temp = arr[i];
|
||||
arr[i] = arr[j];
|
||||
arr[j] = temp;
|
||||
}
|
||||
}
|
||||
StudentRank temp = arr[i + 1];
|
||||
arr[i + 1] = arr[high];
|
||||
arr[high] = temp;
|
||||
return (i + 1);
|
||||
}
|
||||
|
||||
// 快速排序主函数
|
||||
void quickSortRankings(StudentRank arr[], int low, int high) {
|
||||
if (low < high) {
|
||||
int pi = partitionRankings(arr, low, high);
|
||||
quickSortRankings(arr, low, pi - 1);
|
||||
quickSortRankings(arr, pi + 1, high);
|
||||
}
|
||||
}
|
||||
|
||||
void initStatisticsCache()
|
||||
{
|
||||
statsCache.isValid = false;
|
||||
statsCache.lastStudentCount = 0;
|
||||
statsCache.lastDataHash = 0;
|
||||
memset(&statsCache.overallStats, 0, sizeof(OverallStats));
|
||||
memset(&statsCache.scoreDistribution, 0, sizeof(ScoreDistribution));
|
||||
memset(statsCache.rankings, 0, sizeof(statsCache.rankings));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查缓存是否有效
|
||||
* @details 检查统计缓存是否仍然有效,通过比较学生数量和数据哈希值
|
||||
* @return bool 如果缓存有效返回true,否则返回false
|
||||
* @note 当学生数据发生变化时,缓存会被标记为无效
|
||||
*/
|
||||
bool isCacheValid()
|
||||
{
|
||||
if (!statsCache.isValid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查学生数量是否变化
|
||||
if (statsCache.lastStudentCount != studentCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查数据哈希值是否变化
|
||||
unsigned long currentHash = calculateDataHash();
|
||||
if (statsCache.lastDataHash != currentHash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新统计缓存
|
||||
* @details 重新计算并更新所有统计缓存数据
|
||||
* @note 当缓存无效时调用,重新计算所有统计信息并更新缓存
|
||||
* @note 包括总体统计、分数分布和学生排名的缓存更新
|
||||
*/
|
||||
void updateStatisticsCache()
|
||||
{
|
||||
if (studentCount == 0) {
|
||||
initStatisticsCache();
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新总体统计缓存
|
||||
statsCache.overallStats = calculateOverallStats();
|
||||
|
||||
// 更新分数分布缓存
|
||||
statsCache.scoreDistribution = calculateScoreDistribution();
|
||||
|
||||
// 更新学生排名缓存
|
||||
for (int i = 0; i < studentCount; i++) {
|
||||
statsCache.rankings[i].studentIndex = i;
|
||||
statsCache.rankings[i].averageScore = students[i].averageScore;
|
||||
statsCache.rankings[i].totalScore = students[i].totalScore;
|
||||
}
|
||||
|
||||
// 使用快速排序按平均分排序(降序)
|
||||
quickSortRankings(statsCache.rankings, 0, studentCount - 1);
|
||||
|
||||
// 更新缓存状态
|
||||
statsCache.isValid = true;
|
||||
statsCache.lastStudentCount = studentCount;
|
||||
statsCache.lastDataHash = calculateDataHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 使缓存无效
|
||||
* @details 将统计缓存标记为无效,强制下次访问时重新计算
|
||||
* @note 当学生数据被修改时调用,确保统计数据的准确性
|
||||
*/
|
||||
void invalidateCache()
|
||||
{
|
||||
statsCache.isValid = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算数据哈希值
|
||||
* @details 计算当前学生数据的哈希值,用于检测数据变化
|
||||
* @return unsigned long 当前数据的哈希值
|
||||
* @note 基于学生数量、学号、成绩等关键数据计算哈希值
|
||||
*/
|
||||
unsigned long calculateDataHash()
|
||||
{
|
||||
unsigned long hash = 5381; // DJB2 哈希算法初始值
|
||||
|
||||
// 包含学生数量
|
||||
hash = ((hash << 5) + hash) + studentCount;
|
||||
|
||||
for (int i = 0; i < studentCount; i++) {
|
||||
// 包含学号
|
||||
for (int j = 0; students[i].studentID[j] != '\0'; j++) {
|
||||
hash = ((hash << 5) + hash) + students[i].studentID[j];
|
||||
}
|
||||
|
||||
// 包含总分和平均分
|
||||
hash = ((hash << 5) + hash) + (unsigned long)(students[i].totalScore * 100);
|
||||
hash = ((hash << 5) + hash) + (unsigned long)(students[i].averageScore * 100);
|
||||
|
||||
// 包含课程数量
|
||||
hash = ((hash << 5) + hash) + students[i].courseCount;
|
||||
|
||||
// 包含各科成绩
|
||||
for (int j = 0; j < students[i].courseCount; j++) {
|
||||
hash = ((hash << 5) + hash) + (unsigned long)(students[i].scores[j] * 100);
|
||||
}
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取缓存的总体统计
|
||||
* @details 获取缓存的总体统计数据,如果缓存无效则先更新缓存
|
||||
* @return OverallStats 总体统计数据
|
||||
* @note 优先使用缓存数据,提高查询效率
|
||||
*/
|
||||
OverallStats getCachedOverallStats()
|
||||
{
|
||||
if (!isCacheValid()) {
|
||||
updateStatisticsCache();
|
||||
}
|
||||
return statsCache.overallStats;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取缓存的分数分布
|
||||
* @details 获取缓存的分数分布数据,如果缓存无效则先更新缓存
|
||||
* @return ScoreDistribution 分数分布数据
|
||||
* @note 优先使用缓存数据,避免重复计算
|
||||
*/
|
||||
ScoreDistribution getCachedScoreDistribution()
|
||||
{
|
||||
if (!isCacheValid()) {
|
||||
updateStatisticsCache();
|
||||
}
|
||||
return statsCache.scoreDistribution;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取缓存的学生排名
|
||||
* @details 获取缓存的学生排名数据,如果缓存无效则先更新缓存
|
||||
* @param rankings 输出参数,存储排名数据的数组
|
||||
* @return int 返回排名数据的数量
|
||||
* @note 排名按平均分降序排列,优先使用缓存数据
|
||||
*/
|
||||
int getCachedStudentRankings(StudentRank* rankings)
|
||||
{
|
||||
if (!isCacheValid()) {
|
||||
updateStatisticsCache();
|
||||
}
|
||||
|
||||
if (rankings != NULL && studentCount > 0) {
|
||||
memcpy(rankings, statsCache.rankings, studentCount * sizeof(StudentRank));
|
||||
}
|
||||
|
||||
return studentCount;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* @file string_utils.c
|
||||
* @brief 字符串处理工具函数实现文件
|
||||
* @note 实现字符串操作相关函数
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,557 @@
|
||||
/**
|
||||
* @file student_crud.c
|
||||
* @brief 学生数据增删改操作实现
|
||||
* @note 负责学生信息的添加、删除、修改等CRUD操作
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#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("姓名格式无效!");
|
||||
}
|
||||
|
||||
// 输入年龄
|
||||
while (1)
|
||||
{
|
||||
student->age = safeInputInt("请输入年龄", 10, 100);
|
||||
if (isValidAge(student->age))
|
||||
break;
|
||||
printError("年龄输入无效!年龄必须在合理范围内。");
|
||||
}
|
||||
|
||||
// 输入性别
|
||||
while (1)
|
||||
{
|
||||
char gender = safeInputChar("请输入性别 (M/F)");
|
||||
|
||||
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);
|
||||
|
||||
// 输入课程名称并验证
|
||||
while (1)
|
||||
{
|
||||
safeInputString("课程名称",
|
||||
student->courses[student->courseCount],
|
||||
MAX_COURSE_NAME_LENGTH);
|
||||
|
||||
if (isValidCourseName(student->courses[student->courseCount]))
|
||||
break;
|
||||
printError("课程名称格式无效!课程名称不能为空。");
|
||||
}
|
||||
|
||||
// 输入课程分数并验证
|
||||
while (1)
|
||||
{
|
||||
student->scores[student->courseCount] =
|
||||
safeInputFloat("课程分数", MIN_SCORE, MAX_SCORE);
|
||||
|
||||
if (isValidScore(student->scores[student->courseCount]))
|
||||
break;
|
||||
printError("成绩输入无效!成绩必须在0-100分之间。");
|
||||
}
|
||||
|
||||
student->courseCount++;
|
||||
|
||||
if (student->courseCount < MAX_COURSES)
|
||||
{
|
||||
char choice = safeInputChar("\n是否继续添加课程?(y/n)");
|
||||
|
||||
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;
|
||||
|
||||
// 使统计缓存无效
|
||||
invalidateCache();
|
||||
|
||||
// 显示添加成功信息
|
||||
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);
|
||||
|
||||
char choice = safeInputChar("\n确认删除?(y/n)");
|
||||
|
||||
if (choice == 'y' || choice == 'Y')
|
||||
{
|
||||
// 移动后面的学生向前
|
||||
for (int j = i; j < studentCount - 1; j++)
|
||||
{
|
||||
students[j] = students[j + 1];
|
||||
}
|
||||
studentCount--;
|
||||
|
||||
dataModified = true;
|
||||
statsNeedUpdate = true;
|
||||
|
||||
// 使统计缓存无效
|
||||
invalidateCache();
|
||||
|
||||
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)
|
||||
{
|
||||
char gender = safeInputChar("请输入新性别 (M/F)");
|
||||
|
||||
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;
|
||||
|
||||
// 验证索引有效性
|
||||
if (!isValidIndex(courseIndex, student->courseCount))
|
||||
{
|
||||
printError("课程索引无效!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 输入新成绩并验证
|
||||
while (1)
|
||||
{
|
||||
student->scores[courseIndex] = safeInputFloat("新成绩", MIN_SCORE, MAX_SCORE);
|
||||
if (isValidScore(student->scores[courseIndex]))
|
||||
break;
|
||||
printError("成绩输入无效!成绩必须在0-100分之间。");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 添加新课程
|
||||
* @details 为学生添加新的课程和成绩
|
||||
* @param student 指向学生结构体的指针
|
||||
*/
|
||||
static void addNewCourse(Student *student)
|
||||
{
|
||||
if (student->courseCount >= MAX_COURSES)
|
||||
{
|
||||
printWarning("课程数量已达上限!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 输入课程名称并验证
|
||||
while (1)
|
||||
{
|
||||
safeInputString("课程名称", student->courses[student->courseCount], MAX_COURSE_NAME_LENGTH);
|
||||
if (isValidCourseName(student->courses[student->courseCount]))
|
||||
break;
|
||||
printError("课程名称格式无效!课程名称不能为空。");
|
||||
}
|
||||
|
||||
// 输入课程成绩并验证
|
||||
while (1)
|
||||
{
|
||||
student->scores[student->courseCount] = safeInputFloat("课程成绩", MIN_SCORE, MAX_SCORE);
|
||||
if (isValidScore(student->scores[student->courseCount]))
|
||||
break;
|
||||
printError("成绩输入无效!成绩必须在0-100分之间。");
|
||||
}
|
||||
|
||||
student->courseCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 删除课程
|
||||
* @details 删除学生的指定课程和成绩
|
||||
* @param student 指向学生结构体的指针
|
||||
*/
|
||||
static void deleteCourse(Student *student)
|
||||
{
|
||||
if (student->courseCount == 0)
|
||||
{
|
||||
printWarning("该学生没有课程记录!");
|
||||
return;
|
||||
}
|
||||
|
||||
int courseIndex = safeInputInt("请选择要删除的课程", 1, student->courseCount) - 1;
|
||||
|
||||
// 验证索引有效性
|
||||
if (!isValidIndex(courseIndex, student->courseCount))
|
||||
{
|
||||
printError("课程索引无效!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 移动数组元素
|
||||
for (int k = courseIndex; k < student->courseCount - 1; k++)
|
||||
{
|
||||
strncpy(student->courses[k], student->courses[k + 1], MAX_COURSE_NAME_LENGTH - 1);
|
||||
student->courses[k][MAX_COURSE_NAME_LENGTH - 1] = '\0';
|
||||
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;
|
||||
|
||||
// 使统计缓存无效
|
||||
invalidateCache();
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
/**
|
||||
* @file student_io.c
|
||||
* @brief 学生数据文件输入输出操作实现
|
||||
* @note 负责学生数据的文件读写、CSV解析等功能
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#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;
|
||||
|
||||
// 使统计缓存无效
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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文件!");
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* @file student_search.c
|
||||
* @brief 学生数据搜索和显示功能实现
|
||||
* @note 负责学生信息的查找、显示等功能
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "config.h"
|
||||
#include "globals.h"
|
||||
#include "io_utils.h"
|
||||
|
||||
// 函数前向声明
|
||||
void displayStudentInfo(const Student *student);
|
||||
static int binarySearchByID(const char *studentID);
|
||||
static void ensureSortedByID();
|
||||
|
||||
/**
|
||||
* @brief 按学号查找学生
|
||||
* @details 根据用户输入的学号精确查找学生信息
|
||||
* 使用二分搜索算法提高查找效率,时间复杂度O(log n)
|
||||
* 找到后显示该学生的详细信息
|
||||
* @note 查找方式:精确匹配学号,使用二分搜索算法
|
||||
* @note 算法优化:从线性搜索O(n)优化为二分搜索O(log n)
|
||||
* @warning 如果没有学生数据或未找到匹配学生,将显示相应提示信息
|
||||
*/
|
||||
void searchStudentByID()
|
||||
{
|
||||
clearScreen();
|
||||
printHeader("按学号查找学生");
|
||||
|
||||
if (studentCount == 0)
|
||||
{
|
||||
printWarning("暂无学生数据!");
|
||||
pauseSystem();
|
||||
return;
|
||||
}
|
||||
|
||||
char studentID[MAX_ID_LENGTH];
|
||||
printf("\n");
|
||||
safeInputString("请输入学号", studentID, MAX_ID_LENGTH);
|
||||
|
||||
// 确保数组按学号排序,以支持二分搜索
|
||||
ensureSortedByID();
|
||||
|
||||
// 使用二分搜索查找学生
|
||||
int index = binarySearchByID(studentID);
|
||||
|
||||
if (index != -1)
|
||||
{
|
||||
displayStudentInfo(&students[index]);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 确保学生数组按学号排序
|
||||
* @details 检查学生数组是否按学号排序,如果没有则进行排序
|
||||
* 用于支持二分搜索算法
|
||||
* @note 只在需要时进行排序,避免不必要的性能开销
|
||||
*/
|
||||
static void ensureSortedByID()
|
||||
{
|
||||
// 检查是否已经按学号排序
|
||||
bool isSorted = true;
|
||||
for (int i = 0; i < studentCount - 1; i++)
|
||||
{
|
||||
if (strcmp(students[i].studentID, students[i + 1].studentID) > 0)
|
||||
{
|
||||
isSorted = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有排序,则按学号排序
|
||||
if (!isSorted)
|
||||
{
|
||||
// 使用外部排序函数
|
||||
extern void sortStudents(int criteria, int order);
|
||||
sortStudents(SORT_BY_ID, SORT_ASCENDING);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 二分搜索按学号查找学生
|
||||
* @details 使用二分搜索算法在已排序的学生数组中查找指定学号
|
||||
* 时间复杂度O(log n),比线性搜索O(n)更高效
|
||||
* @param studentID 要查找的学号
|
||||
* @return 找到的学生索引,如果未找到返回-1
|
||||
* @note 要求学生数组必须按学号排序
|
||||
*/
|
||||
static int binarySearchByID(const char *studentID)
|
||||
{
|
||||
int left = 0;
|
||||
int right = studentCount - 1;
|
||||
|
||||
while (left <= right)
|
||||
{
|
||||
int mid = left + (right - left) / 2;
|
||||
int cmp = strcmp(students[mid].studentID, studentID);
|
||||
|
||||
if (cmp == 0)
|
||||
{
|
||||
return mid; // 找到了
|
||||
}
|
||||
else if (cmp < 0)
|
||||
{
|
||||
left = mid + 1; // 在右半部分搜索
|
||||
}
|
||||
else
|
||||
{
|
||||
right = mid - 1; // 在左半部分搜索
|
||||
}
|
||||
}
|
||||
|
||||
return -1; // 未找到
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* @file student_sort.c
|
||||
* @brief 学生数据排序功能实现
|
||||
* @note 负责学生信息的排序操作
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "config.h"
|
||||
#include "globals.h"
|
||||
|
||||
/**
|
||||
* @brief 比较函数 - 按学号排序
|
||||
* @param a 指向第一个学生的指针
|
||||
* @param b 指向第二个学生的指针
|
||||
* @return 比较结果:负数表示a<b,0表示a=b,正数表示a>b
|
||||
*/
|
||||
static int compareByID(const void *a, const void *b)
|
||||
{
|
||||
const Student *studentA = (const Student *)a;
|
||||
const Student *studentB = (const Student *)b;
|
||||
int result = strcmp(studentA->studentID, studentB->studentID);
|
||||
return (currentSortOrder == SORT_ASCENDING) ? result : -result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 比较函数 - 按姓名排序
|
||||
* @param a 指向第一个学生的指针
|
||||
* @param b 指向第二个学生的指针
|
||||
* @return 比较结果:负数表示a<b,0表示a=b,正数表示a>b
|
||||
*/
|
||||
static int compareByName(const void *a, const void *b)
|
||||
{
|
||||
const Student *studentA = (const Student *)a;
|
||||
const Student *studentB = (const Student *)b;
|
||||
int result = strcmp(studentA->name, studentB->name);
|
||||
return (currentSortOrder == SORT_ASCENDING) ? result : -result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 比较函数 - 按总分排序
|
||||
* @param a 指向第一个学生的指针
|
||||
* @param b 指向第二个学生的指针
|
||||
* @return 比较结果:负数表示a<b,0表示a=b,正数表示a>b
|
||||
*/
|
||||
static int compareByTotalScore(const void *a, const void *b)
|
||||
{
|
||||
const Student *studentA = (const Student *)a;
|
||||
const Student *studentB = (const Student *)b;
|
||||
if (studentA->totalScore < studentB->totalScore)
|
||||
return (currentSortOrder == SORT_ASCENDING) ? -1 : 1;
|
||||
else if (studentA->totalScore > studentB->totalScore)
|
||||
return (currentSortOrder == SORT_ASCENDING) ? 1 : -1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 比较函数 - 按平均分排序
|
||||
* @param a 指向第一个学生的指针
|
||||
* @param b 指向第二个学生的指针
|
||||
* @return 比较结果:负数表示a<b,0表示a=b,正数表示a>b
|
||||
*/
|
||||
static int compareByAverageScore(const void *a, const void *b)
|
||||
{
|
||||
const Student *studentA = (const Student *)a;
|
||||
const Student *studentB = (const Student *)b;
|
||||
if (studentA->averageScore < studentB->averageScore)
|
||||
return (currentSortOrder == SORT_ASCENDING) ? -1 : 1;
|
||||
else if (studentA->averageScore > studentB->averageScore)
|
||||
return (currentSortOrder == SORT_ASCENDING) ? 1 : -1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 排序学生信息
|
||||
* @details 根据指定的排序依据和顺序对学生数组进行排序
|
||||
* 使用qsort标准库函数实现,时间复杂度O(n log n)
|
||||
* @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 排序算法:快速排序(qsort标准库函数,时间复杂度O(n log n))
|
||||
* @note 排序完成后会设置dataModified标志
|
||||
*/
|
||||
void sortStudents(int criteria, int order)
|
||||
{
|
||||
if (studentCount <= 1)
|
||||
return;
|
||||
|
||||
// 设置全局排序参数
|
||||
currentSortCriteria = criteria;
|
||||
currentSortOrder = order;
|
||||
|
||||
// 选择对应的比较函数并使用qsort进行排序
|
||||
switch (criteria)
|
||||
{
|
||||
case SORT_BY_ID:
|
||||
qsort(students, studentCount, sizeof(Student), compareByID);
|
||||
break;
|
||||
case SORT_BY_NAME:
|
||||
qsort(students, studentCount, sizeof(Student), compareByName);
|
||||
break;
|
||||
case SORT_BY_TOTAL_SCORE:
|
||||
qsort(students, studentCount, sizeof(Student), compareByTotalScore);
|
||||
break;
|
||||
case SORT_BY_AVERAGE_SCORE:
|
||||
qsort(students, studentCount, sizeof(Student), compareByAverageScore);
|
||||
break;
|
||||
default:
|
||||
return; // 无效的排序依据
|
||||
}
|
||||
|
||||
dataModified = true;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* @file system_utils.c
|
||||
* @brief 系统工具函数实现文件
|
||||
* @note 实现系统初始化和清理相关函数
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "system_utils.h"
|
||||
#include "file_utils.h"
|
||||
#include "io_utils.h"
|
||||
#include "config.h"
|
||||
#include "user_manage.h"
|
||||
#include "student_io.h"
|
||||
#include "statistical_analysis.h"
|
||||
|
||||
/**
|
||||
* @brief 初始化系统
|
||||
* @details 执行系统启动时的初始化操作,包括创建必要的数据目录
|
||||
* 调用createDataDirectories函数创建数据存储目录
|
||||
* @return 如果初始化成功返回true,否则返回false
|
||||
* @note 此函数应在程序启动时调用
|
||||
* @note 如果初始化失败,会输出错误信息
|
||||
*/
|
||||
bool initializeSystem()
|
||||
{
|
||||
printInfo("正在初始化系统...");
|
||||
|
||||
if (!createDataDirectories())
|
||||
{
|
||||
printError("系统初始化失败:无法创建数据目录");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 加载用户数据
|
||||
loadUsersFromFile();
|
||||
|
||||
// 加载学生数据
|
||||
loadStudentsFromFile();
|
||||
|
||||
// 初始化统计缓存
|
||||
initStatisticsCache();
|
||||
|
||||
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("系统清理完成");
|
||||
}
|
||||
@@ -0,0 +1,387 @@
|
||||
/**
|
||||
* @file user_manage.c
|
||||
* @brief 用户管理实现文件
|
||||
* @note 实现用户认证和管理功能
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "user_manage.h"
|
||||
#include "config.h"
|
||||
#include "globals.h"
|
||||
#include "io_utils.h"
|
||||
#include "string_utils.h"
|
||||
#include "security_utils.h"
|
||||
#include "validation.h"
|
||||
|
||||
/**
|
||||
* @brief 处理用户登录
|
||||
* @details 提供用户登录验证功能,验证用户名和密码的正确性
|
||||
* 登录成功后设置当前用户信息和管理员权限
|
||||
* @return int 登录成功返回1,失败返回0
|
||||
* @note 登录过程:
|
||||
* 1. 获取用户输入的用户名和密码
|
||||
* 2. 遍历用户数组进行验证
|
||||
* 3. 验证成功则设置currentUser和isCurrentUserAdmin
|
||||
* @note 设置的全局变量:
|
||||
* - currentUser: 当前登录用户名
|
||||
* - isCurrentUserAdmin: 当前用户是否为管理员
|
||||
*/
|
||||
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 &&
|
||||
verify_password(password, users[i].passwordHash))
|
||||
{
|
||||
// 登录成功
|
||||
strncpy(currentUser, username, MAX_USERNAME_LENGTH - 1);
|
||||
currentUser[MAX_USERNAME_LENGTH - 1] = '\0';
|
||||
isCurrentUserAdmin = users[i].isAdmin;
|
||||
// 安全清除密码
|
||||
secure_memset(password, strlen(password));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 安全清除密码
|
||||
secure_memset(password, strlen(password));
|
||||
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)
|
||||
{
|
||||
// 文件不存在,创建默认管理员账户
|
||||
strncpy(users[0].username, "admin", MAX_USERNAME_LENGTH - 1);
|
||||
users[0].username[MAX_USERNAME_LENGTH - 1] = '\0';
|
||||
hash_password("123456", users[0].passwordHash);
|
||||
users[0].isAdmin = true;
|
||||
|
||||
strncpy(users[1].username, "teacher", MAX_USERNAME_LENGTH - 1);
|
||||
users[1].username[MAX_USERNAME_LENGTH - 1] = '\0';
|
||||
hash_password("password", users[1].passwordHash);
|
||||
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:passwordHash:isAdmin
|
||||
char *username = strtok(line, ":");
|
||||
char *passwordHash = strtok(NULL, ":");
|
||||
char *adminFlag = strtok(NULL, ":");
|
||||
|
||||
if (username && passwordHash && adminFlag)
|
||||
{
|
||||
strncpy(users[userCount].username, username, MAX_USERNAME_LENGTH - 1);
|
||||
users[userCount].username[MAX_USERNAME_LENGTH - 1] = '\0';
|
||||
strncpy(users[userCount].passwordHash, passwordHash, SHA256_HEX_LENGTH - 1);
|
||||
users[userCount].passwordHash[SHA256_HEX_LENGTH - 1] = '\0';
|
||||
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)
|
||||
{
|
||||
printError("无法保存用户数据!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < userCount; i++)
|
||||
{
|
||||
fprintf(file, "%s:%s:%d\n",
|
||||
users[i].username,
|
||||
users[i].passwordHash,
|
||||
users[i].isAdmin ? 1 : 0);
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 增加用户账户
|
||||
* @details 提供交互式界面添加新的用户账户
|
||||
* 包括用户名唯一性检查、密码设置和用户类型选择
|
||||
* @note 添加流程:
|
||||
* 1. 检查用户数量是否达到上限
|
||||
* 2. 输入新用户名并检查唯一性
|
||||
* 3. 设置密码
|
||||
* 4. 选择用户类型(普通用户/管理员)
|
||||
* 5. 保存到文件并更新数据修改标志
|
||||
* @warning 如果用户数量已达MAX_USERS上限,将拒绝添加
|
||||
* @warning 如果用户名已存在,将拒绝添加
|
||||
*/
|
||||
void addUserAccount()
|
||||
{
|
||||
clearScreen();
|
||||
printHeader("添加用户账户");
|
||||
|
||||
if (userCount >= MAX_USERS)
|
||||
{
|
||||
printError("用户数量已达上限!");
|
||||
pauseSystem();
|
||||
return;
|
||||
}
|
||||
|
||||
char username[MAX_USERNAME_LENGTH];
|
||||
char password[MAX_PASSWORD_LENGTH];
|
||||
|
||||
printf("\n");
|
||||
do {
|
||||
safeInputString("请输入新用户名", username, MAX_USERNAME_LENGTH);
|
||||
if (!isValidUsername(username)) {
|
||||
printError("用户名格式无效!用户名长度必须在3-20字符之间,只能包含字母和数字。");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查用户名是否已存在
|
||||
bool exists = false;
|
||||
for (int i = 0; i < userCount; i++)
|
||||
{
|
||||
if (strcmp(users[i].username, username) == 0)
|
||||
{
|
||||
printError("用户名已存在!");
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!exists) break;
|
||||
} while (true);
|
||||
|
||||
do {
|
||||
safeInputString("请输入密码", password, MAX_PASSWORD_LENGTH);
|
||||
if (!isValidPassword(password)) {
|
||||
printError("密码格式无效!密码长度必须在6-50字符之间。");
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
} while (true);
|
||||
|
||||
printf("\n用户类型:\n");
|
||||
printf("1. 普通用户\n");
|
||||
printf("2. 管理员\n");
|
||||
int userType = safeInputInt("请选择用户类型", 1, 2);
|
||||
|
||||
// 添加新用户
|
||||
strncpy(users[userCount].username, username, MAX_USERNAME_LENGTH - 1);
|
||||
users[userCount].username[MAX_USERNAME_LENGTH - 1] = '\0';
|
||||
hash_password(password, users[userCount].passwordHash);
|
||||
users[userCount].isAdmin = (userType == 2);
|
||||
userCount++;
|
||||
|
||||
// 安全清除明文密码
|
||||
secure_memset(password, strlen(password));
|
||||
|
||||
// 保存到文件
|
||||
saveUsersToFile();
|
||||
dataModified = true;
|
||||
|
||||
printSuccess("用户添加成功!");
|
||||
pauseSystem();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 删除用户账户
|
||||
* @details 提供交互式界面删除指定的用户账户
|
||||
* 包含安全检查,防止删除当前登录用户和最后一个用户
|
||||
* @note 删除限制:
|
||||
* - 不能删除当前登录的用户
|
||||
* - 系统至少需要保留一个用户账户
|
||||
* @note 删除过程:
|
||||
* 1. 输入要删除的用户名
|
||||
* 2. 进行安全检查
|
||||
* 3. 查找用户并删除
|
||||
* 4. 重新排列用户数组
|
||||
* 5. 保存到文件并更新数据修改标志
|
||||
* @warning 删除操作不可逆,请谨慎操作
|
||||
*/
|
||||
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 修改用户密码
|
||||
* @details 提供交互式界面修改指定用户的密码
|
||||
* 管理员可以修改任何用户的密码
|
||||
* @note 修改流程:
|
||||
* 1. 输入要修改密码的用户名
|
||||
* 2. 查找用户是否存在
|
||||
* 3. 输入新密码
|
||||
* 4. 更新用户密码
|
||||
* 5. 保存到文件并更新数据修改标志
|
||||
* @warning 密码修改后立即生效,用户需要使用新密码登录
|
||||
*/
|
||||
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);
|
||||
|
||||
hash_password(newPassword, users[i].passwordHash);
|
||||
|
||||
// 安全清除明文密码
|
||||
secure_memset(newPassword, strlen(newPassword));
|
||||
|
||||
// 保存到文件
|
||||
saveUsersToFile();
|
||||
dataModified = true;
|
||||
|
||||
printSuccess("密码修改成功!");
|
||||
pauseSystem();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
printError("用户不存在!");
|
||||
pauseSystem();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查看所有用户
|
||||
* @details 显示系统中所有用户的信息列表
|
||||
* 包括用户名、用户类型和当前登录状态
|
||||
* @note 显示内容:
|
||||
* - 用户名
|
||||
* - 用户类型(管理员/普通用户)
|
||||
* - 状态(标识当前登录用户)
|
||||
* - 总用户数统计
|
||||
* @note 表格格式显示,便于查看和管理
|
||||
* @warning 如果没有用户数据,将显示警告信息
|
||||
*/
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* @file validation.c
|
||||
* @brief 数据验证函数实现文件
|
||||
* @note 实现学生信息各字段的验证函数
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 验证课程名称是否有效
|
||||
* @details 检查课程名称格式是否符合要求:非空且长度在合理范围内
|
||||
* @param courseName 要验证的课程名称字符串
|
||||
* @return 如果课程名称有效返回true,否则返回false
|
||||
* @note 课程名称不能为空,长度必须在1到MAX_COURSE_NAME_LENGTH之间
|
||||
* @warning 如果courseName为NULL,返回false
|
||||
*/
|
||||
bool isValidCourseName(const char *courseName)
|
||||
{
|
||||
if (courseName == NULL || isEmptyString(courseName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int len = strlen(courseName);
|
||||
return len > 0 && len <= MAX_COURSE_NAME_LENGTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 验证用户名是否有效
|
||||
* @details 检查用户名格式是否符合要求:非空、长度合理且只包含字母数字
|
||||
* @param username 要验证的用户名字符串
|
||||
* @return 如果用户名有效返回true,否则返回false
|
||||
* @note 用户名不能为空,长度必须在3到MAX_USERNAME_LENGTH之间,只能包含字母和数字
|
||||
* @warning 如果username为NULL,返回false
|
||||
*/
|
||||
bool isValidUsername(const char *username)
|
||||
{
|
||||
if (username == NULL || isEmptyString(username))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int len = strlen(username);
|
||||
if (len < 3 || len > MAX_USERNAME_LENGTH)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否只包含字母和数字
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
if (!isalnum(username[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 验证密码强度是否符合要求
|
||||
* @details 检查密码是否满足最低安全要求
|
||||
* @param password 要验证的密码字符串
|
||||
* @return 如果密码有效返回true,否则返回false
|
||||
* @note 密码长度必须在6到MAX_PASSWORD_LENGTH之间
|
||||
* @warning 如果password为NULL,返回false
|
||||
*/
|
||||
bool isValidPassword(const char *password)
|
||||
{
|
||||
if (password == NULL || isEmptyString(password))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int len = strlen(password);
|
||||
return len >= 6 && len <= MAX_PASSWORD_LENGTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 验证数组索引是否在有效范围内
|
||||
* @details 检查索引是否在指定范围内,防止数组越界
|
||||
* @param index 要验证的索引值
|
||||
* @param maxIndex 最大有效索引值(不包含)
|
||||
* @return 如果索引有效返回true,否则返回false
|
||||
* @note 有效索引范围为0到maxIndex-1
|
||||
*/
|
||||
bool isValidIndex(int index, int maxIndex)
|
||||
{
|
||||
return index >= 0 && index < maxIndex;
|
||||
}
|
||||
Reference in New Issue
Block a user