学生成绩管理系统 - C语言实现详解,附带源码

项目概述

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


界面展示

1. 主页面

在这里插入图片描述

2. 学生信息管理页面

在这里插入图片描述

3.统计分析页面

在这里插入图片描述

4.成绩管理页面

在这里插入图片描述

功能架构

1. 核心数据结构

typedef struct {
    char id[ID_LEN];          // 学号
    char name[NAME_LEN];      // 姓名
    char gender[GENDER_LEN];  // 性别
    int age;                  // 年龄
    char className[CLASS_LEN];// 班级
    float chinese;            // 语文
    float math;               // 数学
    float english;            // 英语
    float total;              // 总分
    float average;            // 平均分
    int rank;                 // 排名
} Student;

2. 系统模块划分

2.1 学生信息管理模块
  • 添加学生信息:完整录入学生基本信息和成绩
  • 删除学生信息:按学号删除指定学生记录
  • 修改学生信息:支持选择性修改各字段
  • 显示所有学生:以表格形式展示全部数据
2.2 成绩管理模块
  • 批量录入成绩:按学号循环录入多学生成绩
  • 单科录入:针对特定学生修改单科成绩
  • 成绩统计:各科目平均分、最高分、最低分
  • 班级统计:按班级统计成绩分布
  • 分数段统计:按分数区间统计学生人数
  • 排名计算:支持按总分或单科成绩排名
2.3 数据查询模块
  • 按学号查询:精确查询指定学生
  • 按姓名模糊查询:支持姓名关键字搜索
  • 按班级查询:筛选指定班级学生
  • 多条件组合查询:班级+姓名的复合查询
2.4 统计分析模块
  • 排序显示:支持按各字段升序/降序排列
  • 数据导出:将当前数据导出为CSV文件
2.5 系统设置模块
  • 数据保存:手动保存到默认文件
  • 数据备份:时间戳命名的备份文件
  • 数据加载:从指定文件加载数据

技术特色

1. 动态内存管理

void ensure_capacity() {
    if (student_capacity==0) {
        student_capacity = 16;
        students = malloc(student_capacity * sizeof(Student));
    } else if (student_count >= student_capacity) {
        student_capacity *= 2;
        students = realloc(students, student_capacity * sizeof(Student));
    }
}
  • 初始容量16,满时自动扩容为2倍
  • 避免频繁内存分配,提高性能

2. 数据持久化

采用CSV格式存储,兼容Excel等工具:

id,name,gender,age,class,chinese,math,english,total,average,rank
2023001,张三,男,18,计算机1班,85.00,92.00,78.00,255.00,85.00,1

3. 输入验证机制

float read_float_range(const char *prompt, float min, float max) {
    // 循环验证输入范围
    while (1) {
        printf("%s", prompt);
        if (!read_line(buf, sizeof(buf))) return 0.0f;
        char *end; float v = strtof(buf, &end);
        if (end!=buf && *end=='\0') {
            if (v < min || v > max) { 
                printf("值应在 %.2f - %.2f 之间\n", min, max); 
                continue; 
            }
            return v;
        }
        printf("输入无效,请输入数字。\n");
    }
}

4. 用户界面优化

  • 彩色输出:Windows平台支持彩色控制台输出
  • 加载动画:系统启动时的视觉效果
  • 暂停功能:关键操作后的暂停确认

核心算法详解

1. 排名计算算法

void compute_ranks(int field) {
    // 创建临时数组进行排序
    Student *tmp = malloc(student_count * sizeof(Student));
    for (size_t i=0;i<student_count;i++) tmp[i]=students[i];
    
    // 根据选择字段排序
    if (field==0) qsort(tmp, student_count, sizeof(Student), cmp_total_desc);
    else if (field==1) qsort(tmp, student_count, sizeof(Student), cmp_chinese_desc);
    
    // 处理并列排名
    int rank = 1;
    for (size_t i=0;i<student_count;i++) {
        if (i>0) {
            int same = check_same_score(tmp, i, field); // 检查分数是否相同
            if (!same) rank = (int)i+1;
        }
        // 写回排名到原数组
        update_rank(tmp[i].id, rank);
    }
    free(tmp);
}

2. 数据加载策略

int load_data(const char *filename) {
    // 读取CSV文件
    while (fgets(line, sizeof(line), f)) {
        // 解析CSV行数据
        // 检查学号是否已存在
        int idx = find_by_id(s.id);
        if (idx != -1) {
            students[idx] = s;  // 覆盖更新
        } else {
            ensure_capacity();   // 确保容量
            students[student_count++] = s; // 追加新记录
        }
    }
}

3. 快速查询优化

int find_by_id(const char *id) {
    for (size_t i=0;i<student_count;i++) {
        if (strcmp(students[i].id, id)==0) return (int)i;
    }
    return -1;
}

注:当前使用线性搜索,数据量大时可优化为哈希表


编译与运行

环境要求

  • Windows系统 + MinGW编译器
  • 或其他支持C99标准的C编译器

编译命令

gcc student_manager.c -o student_manager.exe

文件结构

项目根目录/
├── student_manager.c    # 主程序源文件
├── students.csv         # 数据文件(自动生成)
└── backup_*.csv        # 备份文件(按时间戳生成)

使用指南

1. 系统启动

  • 显示加载动画,自动加载students.csv数据文件
  • 进入彩色主菜单界面

2. 数据管理流程

  1. 信息录入:通过学生信息管理添加基础数据
  2. 成绩录入:使用批量录入或单科录入功能
  3. 排名计算:在成绩管理中计算各类排名
  4. 数据保存:定期保存防止数据丢失

3. 查询技巧

  • 多条件查询时,留空条件表示忽略该条件
  • 姓名查询支持模糊匹配(包含关系)
  • 导出功能可用于数据迁移或外部分析

4. 备份策略

  • 系统设置中提供手动备份功能
  • 备份文件包含时间戳,便于版本管理
  • 支持从任意CSV文件加载数据

扩展性与优化建议

1. 性能优化

  • 大数据量时可将线性搜索改为二分查找
  • 添加数据索引机制提高查询效率
  • 实现分页显示避免控制台溢出

2. 功能扩展

  • 增加用户登录和权限管理
  • 支持更多科目和自定义字段
  • 添加图表统计可视化输出
  • 实现网络版多用户访问

3. 代码优化

  • 添加更完善的错误处理机制
  • 实现配置文件和命令行参数
  • 增加单元测试和调试模式

源代码

/*
 * 学生成绩管理系统 v1.0
 * 功能摘要:学生信息管理 / 成绩管理 / 数据查询 / 统计分析 / 数据持久化 (CSV 文本)
 * 编译 (Windows, MinGW): gcc student_manager.c -o student_manager.exe
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#ifdef _WIN32
#include <windows.h>
#endif

#define ID_LEN 12
#define NAME_LEN 20
#define GENDER_LEN 4
#define CLASS_LEN 20

/*
 * Student 数据结构说明:
 * - 字段长度采用固定数组以简化输入验证与序列化。
 * - `total` 与 `average` 在内存中由程序计算并覆盖 CSV 中可能存在的值,
 *   因此 CSV 中的这两列仅作参考。
 * - `rank` 字段由 `compute_ranks()` 计算并写会到内存与导出文件中。
 * 注意:当前实现并未对字符串输入做完整的越界/编码检查,生产环境请加强验证。
 */

typedef struct {
    char id[ID_LEN];          // 学号
    char name[NAME_LEN];      // 姓名
    char gender[GENDER_LEN];  // 性别
    int age;                  // 年龄
    char className[CLASS_LEN];// 班级
    float chinese;            // 语文
    float math;               // 数学
    float english;            // 英语
    float total;              // 总分
    float average;            // 平均分
    int rank;                 // 排名
} Student;

// 动态学生数组
Student *students = NULL;
size_t student_count = 0;
size_t student_capacity = 0;

// 默认数据文件(CSV 文本)
const char *DATA_FILE = "students.csv";

// ------------ 辅助函数 ---------------
void pause_anykey() {
    printf("按回车继续...\n");
    getchar();
}

#ifdef _WIN32
void set_color(WORD attr) { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), attr); }
#define COLOR_RESET  (FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE)
#define COLOR_RED    (FOREGROUND_RED | FOREGROUND_INTENSITY)
#define COLOR_GREEN  (FOREGROUND_GREEN | FOREGROUND_INTENSITY)
#define COLOR_YELLOW (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY)
#define COLOR_CYAN   (FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY)
#else
void set_color(int attr) { (void)attr; }
#define COLOR_RESET  0
#define COLOR_RED    0
#define COLOR_GREEN  0
#define COLOR_YELLOW 0
#define COLOR_CYAN   0
#endif

int load_data(const char *filename);

/* 加载动画(视觉效果) */
void proc() {
    int i = 0;
    printf("\n\n\n\n\n");
    for (i = 1; i <= 20; i++) {
        printf("\t\t\t\t\t系统加载中:");
        printf("[%.1f%%]\r", (i + 1) * 5.0);
        fflush(stdout);
#ifdef _WIN32
        Sleep(100);
#else
        struct timespec ts = {0, 100 * 1000000}; nanosleep(&ts, NULL);
#endif
    }
    printf("\n");
    system("cls");
}

void trim_newline(char *s) { if (!s) return; size_t n = strlen(s); if (n>0 && s[n-1]=='\n') s[n-1]='\0'; }
char *read_line(char *buf, size_t sz) { if (!fgets(buf, (int)sz, stdin)) return NULL; trim_newline(buf); return buf; }

int read_int(const char *prompt) {
    char buf[64];
    while (1) {
        printf("%s", prompt);
        if (!read_line(buf, sizeof(buf))) return 0;
        if (strlen(buf)==0) return 0;
        char *end; long v = strtol(buf, &end, 10);
        if (end!=buf && *end=='\0') return (int)v;
        printf("输入无效,请输入整数。\n");
    }
}

float read_float_range(const char *prompt, float min, float max) {
    char buf[64];
    while (1) {
        printf("%s", prompt);
        if (!read_line(buf, sizeof(buf))) return 0.0f;
        if (strlen(buf)==0) return 0.0f;
        char *end; float v = strtof(buf, &end);
        if (end!=buf && *end=='\0') {
            if (v < min || v > max) { printf("值应在 %.2f - %.2f 之间\n", min, max); continue; }
            return v;
        }
        printf("输入无效,请输入数字。\n");
    }
}

/*
 * ensure_capacity
 * -----------------
 * 确保 `students` 动态数组具有足够容量以存放新条目。
 * 实现策略:
 * - 当 `student_capacity == 0` 时,分配初始容量(当前实现为 16)。
 * - 当 `student_count >= student_capacity` 时,将容量乘以 2 并调用 `realloc` 扩容。
 * 注意事项:
 * - 本函数为简洁实现,未对 `malloc`/`realloc` 失败做完整的恢复处理;在内存分配失败时
 *   程序可能会在后续写入时崩溃。可根据需要修改为返回错误码并在调用方做优雅处理。
 */
void ensure_capacity() {
    if (student_capacity==0) {
        student_capacity = 16;
        students = malloc(student_capacity * sizeof(Student));
        // 若 malloc 失败 students 为 NULL,调用端若尝试写入会出错(可以改进为返回错误码)。
    } else if (student_count >= student_capacity) {
        student_capacity *= 2;
        students = realloc(students, student_capacity * sizeof(Student));
        // realloc 若失败将返回 NULL 并保持原指针;这里为简洁实现未做细致错误处理。
    }
}

/* 线性查找:按学号查找,返回索引或 -1 */
int find_by_id(const char *id) {
    for (size_t i=0;i<student_count;i++) {
        if (strcmp(students[i].id, id)==0) return (int)i;
    }
    return -1;
}

/* 计算总分和平均分(覆盖文件中可能存在的 total/average 字段) */
void calc_score(Student *s) {
    s->total = s->chinese + s->math + s->english;
    s->average = s->total / 3.0f;
}

/*
 * save_data
 * 将当前内存中的学生数据保存为 CSV 文本文件。
 * - 文件以文本模式打开并写入表头:id,name,gender,age,class,chinese,math,english,total,average,rank
 * - 按照固定字段顺序输出每条记录(浮点数保留两位小数)。
 * - 若打开/写入失败,函数返回 0 并在 stderr 打印错误信息。
 * 返回值:保存成功返回 1,失败返回 0。
 */
int save_data(const char *filename) {
    FILE *f = fopen(filename, "w");
    if (!f) { perror("打开文件写入失败"); return 0; }
    fprintf(f, "id,name,gender,age,class,chinese,math,english,total,average,rank\n");
    for (size_t i=0;i<student_count;i++) {
        Student *s = &students[i];
        fprintf(f, "%s,%s,%s,%d,%s,%.2f,%.2f,%.2f,%.2f,%.2f,%d\n",
            s->id, s->name, s->gender, s->age, s->className,
            s->chinese, s->math, s->english, s->total, s->average, s->rank);
    }
    fclose(f);
    return 1;
}

/*
 * load_data
 * ---------
 * 从 CSV 文本文件读取学生数据并加载到内存:
 * - 跳过第一行表头(若检测到 header 包含 "id,name")。
 * - 使用简单的逗号分割(`strtok`)解析字段,注意该方法不支持带引号的复杂 CSV。
 * - 解析出学号、姓名、性别、年龄、班级、三科成绩等字段后,重新计算 `total` 与 `average`。
 * - 若内存中已存在相同学号,将覆盖原记录;否则追加新记录(调用 `ensure_capacity()` 扩容)。
 * 返回值:加载成功返回 1,文件不存在或打开失败返回 0。
 */
int load_data(const char *filename) {
    FILE *f = fopen(filename, "r");
    if (!f) return 0; // 文件不存在视为没有数据

    char line[1024];
    int line_no = 0;
    while (fgets(line, sizeof(line), f)) {
        line_no++;
        trim_newline(line);
        if (line[0] == '\0') continue; // 跳过空行
        if (line_no == 1 && (strncmp(line, "id,", 3) == 0 || strstr(line, "id,name") != NULL)) continue; // 跳过表头

        // 使用 strtok 简单分割(会修改 buf)
        char buf[1024]; strncpy(buf, line, sizeof(buf)-1); buf[sizeof(buf)-1]='\0';
        char *tokens[12] = {0}; int t = 0;
        char *p = strtok(buf, ",");
        while (p && t < 12) { tokens[t++] = p; p = strtok(NULL, ","); }
        if (t < 8) continue; // 至少需要 8 个字段

        Student s; memset(&s, 0, sizeof(s));
        strncpy(s.id, tokens[0], ID_LEN-1);
        strncpy(s.name, tokens[1], NAME_LEN-1);
        if (t>2 && tokens[2]) strncpy(s.gender, tokens[2], GENDER_LEN-1);
        if (t>3 && tokens[3]) s.age = atoi(tokens[3]);
        if (t>4 && tokens[4]) strncpy(s.className, tokens[4], CLASS_LEN-1);
        if (t>5 && tokens[5]) s.chinese = (float)atof(tokens[5]);
        if (t>6 && tokens[6]) s.math = (float)atof(tokens[6]);
        if (t>7 && tokens[7]) s.english = (float)atof(tokens[7]);
        // 重新计算以保证内部数据一致
        calc_score(&s);

        int idx = find_by_id(s.id);
        if (idx != -1) {
            // 覆盖已有记录
            students[idx] = s;
        } else {
            // 追加新记录:先扩容再写入并自增计数
            ensure_capacity();
            students[student_count++] = s;
        }
    }
    fclose(f);
    return 1;
}

int backup_data(const char *src, const char *dest) {
    FILE *f1 = fopen(src, "rb"); if (!f1) { perror("备份源打开失败"); return 0; }
    FILE *f2 = fopen(dest, "wb"); if (!f2) { perror("备份目标打开失败"); fclose(f1); return 0; }
    char buf[4096]; size_t n;
    while ((n = fread(buf, 1, sizeof(buf), f1))>0) fwrite(buf, 1, n, f2);
    fclose(f1); fclose(f2); return 1;
}

int export_csv(const char *filename) { return save_data(filename); }

/* ---------- 学生信息管理 ---------- */
void add_student() {
    char buf[128]; Student s; memset(&s,0,sizeof(s));
    printf("添加学生信息\n");
    while (1) {
        printf("学号: "); if (!read_line(buf, sizeof(buf))) return; if (strlen(buf)==0) { printf("学号不能为空\n"); continue; }
        if (find_by_id(buf) != -1) { printf("学号已存在,请重新输入\n"); continue; }
        strncpy(s.id, buf, ID_LEN-1); break;
    }
    printf("姓名: "); read_line(buf, sizeof(buf)); strncpy(s.name, buf, NAME_LEN-1);
    printf("性别: "); read_line(buf, sizeof(buf)); strncpy(s.gender, buf, GENDER_LEN-1);
    s.age = read_int("年龄: ");
    printf("班级: "); read_line(buf, sizeof(buf)); strncpy(s.className, buf, CLASS_LEN-1);
    s.chinese = read_float_range("语文成绩 (0-100): ", 0.0f, 100.0f);
    s.math = read_float_range("数学成绩 (0-100): ", 0.0f, 100.0f);
    s.english = read_float_range("英语成绩 (0-100): ", 0.0f, 100.0f);
    calc_score(&s);
    ensure_capacity();
    // 追加写入:先写入数组末尾,再增加计数(保持插入顺序)
    students[student_count++] = s;
    printf("添加成功\n");
}

void delete_student() {
    char id[ID_LEN]; printf("输入要删除的学号: "); read_line(id, sizeof(id));
    int idx = find_by_id(id); if (idx==-1) { printf("未找到学号 %s\n", id); return; }
    printf("确认删除学生 %s %s ? (Y/N): ", students[idx].id, students[idx].name);
    char ans[8]; read_line(ans, sizeof(ans)); if (ans[0]!='Y' && ans[0]!='y') { printf("已取消\n"); return; }
    // 将后续元素前移覆盖被删除元素
    for (size_t i=idx;i+1<student_count;i++) students[i]=students[i+1];
    student_count--;
    printf("删除成功\n");
}

void modify_student() {
    char id[ID_LEN]; char buf[128];
    printf("输入要修改的学号: "); read_line(id, sizeof(id));
    int idx = find_by_id(id); if (idx==-1) { set_color(COLOR_RED); printf("未找到学号 %s\n", id); set_color(COLOR_RESET); return; }
    Student *s = &students[idx]; set_color(COLOR_CYAN); printf("找到: %s %s\n", s->id, s->name); set_color(COLOR_RESET);
    while (1) {
        printf("选择要修改的字段:\n1. 姓名\n2. 性别\n3. 年龄\n4. 班级\n5. 语文\n6. 数学\n7. 英语\n0. 完成并返回\n");
        int choice = read_int("选择(0-7): "); if (choice==0) break;
        switch (choice) {
            case 1: printf("当前 姓名: %s\n新 姓名: ", s->name); read_line(buf,sizeof(buf)); if (strlen(buf)) strncpy(s->name, buf, NAME_LEN-1); else printf("未修改\n"); break;
            case 2: printf("当前 性别: %s\n新 性别: ", s->gender); read_line(buf,sizeof(buf)); if (strlen(buf)) strncpy(s->gender, buf, GENDER_LEN-1); else printf("未修改\n"); break;
            case 3: printf("当前 年龄: %d\n新 年龄: ", s->age); read_line(buf,sizeof(buf)); if (strlen(buf)) s->age = atoi(buf); else printf("未修改\n"); break;
            case 4: printf("当前 班级: %s\n新 班级: ", s->className); read_line(buf,sizeof(buf)); if (strlen(buf)) strncpy(s->className, buf, CLASS_LEN-1); else printf("未修改\n"); break;
            case 5: printf("当前 语文: %.2f\n新 语文: ", s->chinese); read_line(buf,sizeof(buf)); if (strlen(buf)) { float v = strtof(buf,NULL); if (v>=0 && v<=100) s->chinese=v; else { set_color(COLOR_RED); printf("忽略无效成绩\n"); set_color(COLOR_RESET); } } else printf("未修改\n"); break;
            case 6: printf("当前 数学: %.2f\n新 数学: ", s->math); read_line(buf,sizeof(buf)); if (strlen(buf)) { float v = strtof(buf,NULL); if (v>=0 && v<=100) s->math=v; else { set_color(COLOR_RED); printf("忽略无效成绩\n"); set_color(COLOR_RESET); } } else printf("未修改\n"); break;
            case 7: printf("当前 英语: %.2f\n新 英语: ", s->english); read_line(buf,sizeof(buf)); if (strlen(buf)) { float v = strtof(buf,NULL); if (v>=0 && v<=100) s->english=v; else { set_color(COLOR_RED); printf("忽略无效成绩\n"); set_color(COLOR_RESET); } } else printf("未修改\n"); break;
            default: printf("无效选择\n"); break;
        }
        calc_score(s); // 每次修改后重新计算统计
    }
    set_color(COLOR_GREEN); printf("修改完成\n"); set_color(COLOR_RESET);
}

void show_all_students() {
    printf("共 %zu 名学生\n", student_count);
    printf("%-10s %-10s %-4s %-3s %-12s %7s %7s %7s %8s %8s %5s\n",
        "学号","姓名","性别","年龄","班级","语文","数学","英语","总分","平均","名次");
    for (size_t i=0;i<student_count;i++) {
        Student *s = &students[i];
        printf("%-10s %-10s %-4s %-3d %-12s %7.2f %7.2f %7.2f %8.2f %8.2f %5d\n",
            s->id, s->name, s->gender, s->age, s->className, s->chinese, s->math, s->english, s->total, s->average, s->rank);
    }
}

void query_by_id() { char id[ID_LEN]; printf("输入学号: "); read_line(id, sizeof(id)); int idx = find_by_id(id); if (idx==-1) { printf("未找到\n"); return; } Student *s = &students[idx]; printf("%s %s %s %d %s 语:%.2f 数:%.2f 英:%.2f 总:%.2f 平:%.2f 排名:%d\n", s->id, s->name, s->gender, s->age, s->className, s->chinese, s->math, s->english, s->total, s->average, s->rank); }

void query_by_name() { char key[NAME_LEN]; printf("输入姓名关键字: "); read_line(key, sizeof(key)); for (size_t i=0;i<student_count;i++) if (strstr(students[i].name, key)) { Student *s=&students[i]; printf("%s %s %s %d %s 总:%.2f 平:%.2f 排名:%d\n", s->id, s->name, s->gender, s->age, s->className, s->total, s->average, s->rank); } }

void query_by_class() { char key[CLASS_LEN]; printf("输入班级: "); read_line(key, sizeof(key)); for (size_t i=0;i<student_count;i++) if (strcmp(students[i].className, key)==0) { Student *s=&students[i]; printf("%s %s %s %d 总:%.2f 平:%.2f 排名:%d\n", s->id, s->name, s->gender, s->age, s->total, s->average, s->rank); } }

void query_combined() { char classKey[CLASS_LEN]; char nameKey[NAME_LEN]; printf("按班级(留空忽略): "); read_line(classKey, sizeof(classKey)); printf("按姓名关键字(留空忽略): "); read_line(nameKey, sizeof(nameKey)); for (size_t i=0;i<student_count;i++) { int ok = 1; if (strlen(classKey) && strcmp(students[i].className, classKey)!=0) ok=0; if (ok && strlen(nameKey) && !strstr(students[i].name, nameKey)) ok=0; if (ok) { Student *s = &students[i]; printf("%s %s %s %d %s 总:%.2f 平:%.2f 排名:%d\n", s->id, s->name, s->gender, s->age, s->className, s->total, s->average, s->rank); } } }

void batch_score_entry() { printf("批量录入成绩(按学号录入,输入空学号结束)\n"); char id[ID_LEN]; while (1) { printf("学号: "); read_line(id, sizeof(id)); if (strlen(id)==0) break; int idx = find_by_id(id); if (idx==-1) { printf("未找到学号 %s\n", id); continue; } Student *s = &students[idx]; s->chinese = read_float_range("语文 (0-100): ", 0, 100); s->math = read_float_range("数学 (0-100): ", 0, 100); s->english = read_float_range("英语 (0-100): ", 0, 100); calc_score(s); printf("更新完成 %s\n", s->name); } }

void single_subject_entry() { char id[ID_LEN]; printf("学号: "); read_line(id,sizeof(id)); int idx = find_by_id(id); if (idx==-1) { printf("未找到\n"); return; } Student *s = &students[idx]; printf("选择科目: 1.语文 2.数学 3.英语\n"); int c = read_int("选择: "); if (c==1) s->chinese = read_float_range("语文 (0-100): ",0,100); else if (c==2) s->math = read_float_range("数学 (0-100): ",0,100); else if (c==3) s->english = read_float_range("英语 (0-100): ",0,100); else { printf("无效选择\n"); return; } calc_score(s); printf("修改并更新完成\n"); }

void subject_stats() { if (student_count==0) { printf("无数据\n"); return; } float sumC=0,sumM=0,sumE=0; float minC=1e9,minM=1e9,minE=1e9; float maxC=-1e9,maxM=-1e9,maxE=-1e9; for (size_t i=0;i<student_count;i++) { Student *s=&students[i]; sumC += s->chinese; sumM += s->math; sumE += s->english; if (s->chinese<minC) minC=s->chinese; if (s->chinese>maxC) maxC=s->chinese; if (s->math<minM) minM=s->math; if (s->math>maxM) maxM=s->math; if (s->english<minE) minE=s->english; if (s->english>maxE) maxE=s->english; } printf("语文: 平均 %.2f 最高 %.2f 最低 %.2f\n", sumC/student_count, maxC, minC); printf("数学: 平均 %.2f 最高 %.2f 最低 %.2f\n", sumM/student_count, maxM, minM); printf("英语: 平均 %.2f 最高 %.2f 最低 %.2f\n", sumE/student_count, maxE, minE); }

void class_stats() { if (student_count==0) { printf("无数据\n"); return; } for (size_t i=0;i<student_count;i++) { int seen = 0; for (size_t j=0;j<i;j++) if (strcmp(students[i].className, students[j].className)==0) { seen=1; break; } if (seen) continue; const char *cls = students[i].className; int cnt=0; float sum=0, max=-1e9, min=1e9; for (size_t k=0;k<student_count;k++) if (strcmp(students[k].className, cls)==0) { cnt++; sum += students[k].total; if (students[k].total>max) max=students[k].total; if (students[k].total<min) min=students[k].total; } printf("班级 %s: 学生=%d 平均总分=%.2f 最高=%.2f 最低=%.2f\n", cls, cnt, sum/cnt, max, min); } }

void range_stats() { int ranges[6] = {0}; for (size_t i=0;i<student_count;i++) { float t = students[i].average; if (t>=90) ranges[0]++; else if (t>=80) ranges[1]++; else if (t>=70) ranges[2]++; else if (t>=60) ranges[3]++; else ranges[4]++; } printf(">=90: %d\n80-89: %d\n70-79: %d\n60-69: %d\n<60: %d\n", ranges[0], ranges[1], ranges[2], ranges[3], ranges[4]); }

int cmp_total_desc(const void *a, const void *b) { const Student *A = a, *B = b; if (A->total < B->total) return 1; if (A->total > B->total) return -1; return 0; }
int cmp_total_asc(const void *a, const void *b) { return -cmp_total_desc(a,b); }
int cmp_chinese_desc(const void *a, const void *b) { const Student *A=a,*B=b; if (A->chinese<B->chinese) return 1; if (A->chinese>B->chinese) return -1; return 0; }
int cmp_math_desc(const void *a, const void *b) { const Student *A=a,*B=b; if (A->math<B->math) return 1; if (A->math>B->math) return -1; return 0; }
int cmp_english_desc(const void *a, const void *b) { const Student *A=a,*B=b; if (A->english<B->english) return 1; if (A->english>B->english) return -1; return 0; }

/*
 * compute_ranks
 * 根据指定字段计算名次并写回到原 `students` 数组的 `rank` 字段中。
 * 参数 `field` 的含义:
 *  - 0: 按总分排序
 *  - 1: 按语文排序
 *  - 2: 按数学排序
 *  - 3: 按英语排序
 * 实现细节:
 * - 先拷贝一份学生数组到临时数组 `tmp`,对 `tmp` 进行 qsort 排序。
 * - 处理并列分数:当当前与前一个记录的值相等时,视为并列并保留相同名次;否则名次更新为当前索引+1。
 * 性能提示:当前实现为 O(n log n)(排序) + O(n^2)(写回时逐条匹配学号),当 student_count 很大时可通过哈希表将写回操作优化为 O(n)。
 */
void compute_ranks(int field) {
    if (student_count==0) return;
    Student *tmp = malloc(student_count * sizeof(Student));
    for (size_t i=0;i<student_count;i++) tmp[i]=students[i];
    if (field==0) qsort(tmp, student_count, sizeof(Student), cmp_total_desc);
    else if (field==1) qsort(tmp, student_count, sizeof(Student), cmp_chinese_desc);
    else if (field==2) qsort(tmp, student_count, sizeof(Student), cmp_math_desc);
    else if (field==3) qsort(tmp, student_count, sizeof(Student), cmp_english_desc);
    int rank = 1;
    for (size_t i=0;i<student_count;i++) {
        if (i>0) {
            int same = 0;
            if (field==0 && tmp[i].total == tmp[i-1].total) same=1;
            if (field==1 && tmp[i].chinese == tmp[i-1].chinese) same=1;
            if (field==2 && tmp[i].math == tmp[i-1].math) same=1;
            if (field==3 && tmp[i].english == tmp[i-1].english) same=1;
            if (!same) rank = (int)i+1;
        }
        // 将排序后的名次写回原数组(按学号匹配)
        for (size_t j=0;j<student_count;j++) if (strcmp(tmp[i].id, students[j].id)==0) { students[j].rank = rank; break; }
    }
    free(tmp);
}

void sort_and_show() {
    printf("选择排序字段: 1.总分 2.语文 3.数学 4.英语\n"); int f = read_int("选择: ");
    printf("选择顺序: 1.降序 2.升序\n"); int ord = read_int("选择: ");
    if (f==1) { if (ord==1) qsort(students, student_count, sizeof(Student), cmp_total_desc); else qsort(students, student_count, sizeof(Student), cmp_total_asc); }
    else if (f==2) { if (ord==1) qsort(students, student_count, sizeof(Student), cmp_chinese_desc); else { /* 简单回退 */ qsort(students, student_count, sizeof(Student), cmp_total_desc); } }
    else if (f==3) { if (ord==1) qsort(students, student_count, sizeof(Student), cmp_math_desc); }
    else if (f==4) { if (ord==1) qsort(students, student_count, sizeof(Student), cmp_english_desc); }
    show_all_students();
}

void student_info_menu() { while (1) { printf("---------- 学生信息管理 ----------\n"); printf("1. 添加学生信息\n2. 删除学生信息\n3. 修改学生信息\n4. 显示所有学生\n5. 返回主菜单\n"); printf("\n--------------------------------\n请选择操作(1-5): "); int c = read_int(""); switch(c) { case 1: add_student(); break; case 2: delete_student(); break; case 3: modify_student(); break; case 4: show_all_students(); pause_anykey(); break; case 5: return; default: printf("无效选择\n"); break; } } }

void grade_management_menu() { while (1) { printf("---- 成绩管理 ----\n1. 批量录入成绩\n2. 单科录入\n3. 统计(单科)\n4. 班级统计\n5. 分数段统计\n6. 排名计算\n7. 导出 CSV\n8. 返回主菜单\n请选择: "); int c = read_int(""); switch(c) { case 1: batch_score_entry(); break; case 2: single_subject_entry(); break; case 3: subject_stats(); break; case 4: class_stats(); break; case 5: range_stats(); break; case 6: { printf("按 0 总分 1 语文 2 数学 3 英语 排名: "); int f = read_int(""); compute_ranks(f); printf("排名已计算\n"); break; } case 7: { char fname[260]; printf("导出文件名 (默认 students.csv): "); read_line(fname,sizeof(fname)); if (strlen(fname)==0) strcpy(fname,"students.csv"); if (export_csv(fname)) printf("导出成功 %s\n", fname); break; } case 8: return; default: printf("无效选择\n"); break; } } }

void query_menu() { while (1) { printf("---- 数据查询 ----\n1. 按学号查询\n2. 按姓名模糊查询\n3. 按班级查询\n4. 多条件查询\n5. 返回主菜单\n请选择: "); int c = read_int(""); switch(c) { case 1: query_by_id(); break; case 2: query_by_name(); break; case 3: query_by_class(); break; case 4: query_combined(); break; case 5: return; default: printf("无效选择\n"); break; } } }

void analysis_menu() { while (1) { printf("---- 统计分析 ----\n1. 排序显示\n2. 单科统计\n3. 班级统计\n4. 分数段统计\n5. 导出 CSV\n6. 返回主菜单\n请选择: "); int c = read_int(""); switch(c) { case 1: sort_and_show(); break; case 2: subject_stats(); break; case 3: class_stats(); break; case 4: range_stats(); break; case 5: { char fname[260]; printf("导出文件名 (默认 students.csv): "); read_line(fname,sizeof(fname)); if (strlen(fname)==0) strcpy(fname,"students.csv"); if (export_csv(fname)) printf("导出成功 %s\n", fname); break; } case 6: return; default: printf("无效选择\n"); break; } } }

void settings_menu() { while (1) { printf("---- 系统设置 ----\n1. 保存数据\n2. 备份数据\n3. 加载数据文件\n4. 返回主菜单\n请选择: "); int c = read_int(""); if (c==1) { if (save_data(DATA_FILE)) printf("保存成功\n"); else printf("保存失败\n"); } else if (c==2) { char dest[260]; time_t t = time(NULL); struct tm *tm = localtime(&t); snprintf(dest, sizeof(dest), "backup_%04d%02d%02d_%02d%02d%02d.csv", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); if (backup_data(DATA_FILE, dest)) printf("备份成功: %s\n", dest); else printf("备份失败\n"); } else if (c==3) { char fname[260]; printf("输入要加载的文件名 (默认 students.csv): "); read_line(fname,sizeof(fname)); if (strlen(fname)==0) strcpy(fname, DATA_FILE); if (load_data(fname)) printf("加载成功\n"); else printf("加载失败或文件不存在\n"); } else if (c==4) return; else printf("无效选择\n"); } }

int main_menu() { while (1) { system("color 0B"); printf("====================================\n      学生信息管理系统 v1.0\n====================================\n"); printf("1. 学生信息管理\n2. 成绩管理\n3. 数据查询\n4. 统计分析\n5. 系统设置\n0. 退出系统\n"); printf("\n====================================\n请选择操作(0-5): "); int c = read_int(""); switch(c) { case 1: student_info_menu(); break; case 2: grade_management_menu(); break; case 3: query_menu(); break; case 4: analysis_menu(); break; case 5: settings_menu(); break; case 0: return 0; default: printf("无效选择\n"); break; } } }

/*
 * main
 * 启动流程:
 * 1. 设置控制台颜色(Windows)并显示加载动画 `proc()`。 
 * 2. 尝试从默认 CSV 文件 `DATA_FILE` 加载数据(若存在则读取并填充内存)。
 * 3. 进入 `main_menu()` 处理交互操作。
 * 4. 退出前询问是否保存当前内存数据到 `DATA_FILE`。
 * 5. 释放动态分配的内存并返回。
 */
int main() {
    system("color 0B");
    proc();
    if (load_data(DATA_FILE)) { set_color(COLOR_GREEN); printf("已加载数据文件 %s,%zu 条记录\n", DATA_FILE, student_count); set_color(COLOR_RESET); }
    else { set_color(COLOR_YELLOW); printf("未找到数据文件,使用空数据\n"); set_color(COLOR_RESET); }
    main_menu();
    printf("是否保存数据到 %s ? (Y/N): ", DATA_FILE);
    char ans[8]; read_line(ans, sizeof(ans)); if (ans[0]=='Y' || ans[0]=='y') { if (save_data(DATA_FILE)) printf("保存成功\n"); else printf("保存失败\n"); }
    if (students) free(students);
    return 0;
}

/* 程序入口说明:设置颜色、显示加载动画、加载 CSV 数据、进入主菜单、退出前提示保存 */


总结

这个学生成绩管理系统展示了C语言在实际应用开发中的强大能力,涵盖了:

  • 数据结构设计:合理的结构体定义
  • 内存管理:动态数组的自动扩容
  • 文件操作:CSV格式的读写解析
  • 算法实现:排序、搜索、统计等核心算法
  • 用户体验:友好的交互界面和输入验证

系统代码结构清晰,模块划分合理,具有良好的可读性和可维护性,适合作为C语言课程设计或初学者进阶学习的参考项目。

该系统不仅实现了基本的学生成绩管理功能,还通过多种技术优化提升了系统的实用性和健壮性,是一个功能完整、代码规范的C语言实践项目。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员春至

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值