C语言实现简易学生成绩管理系统:结构体、菜单与增删改查

林见深
林见深
发布时间:2026-10-15 | 3人浏览过
原创

在C语言学习过程中,学生成绩管理系统是一个涵盖结构体、函数、指针及基本数据结构的经典练手项目。本文通过拆解功能模块,详细演示如何利用静态数组(顺序表)配合菜单交互,完成对学生信息的录入、查询、删除、修改、插入、排序及统计。

系统功能需求与界面设计

一个基础的学生管理系统至少应包含以下功能:成绩录入、成绩查询、成绩删除、成绩修改、成绩插入、成绩排序以及学生人数统计。为了实现良好的交互体验,我们需要设计一个控制台循环菜单,让用户通过数字选项调用对应功能,并在每次操作后保持程序运行状态直至用户选择退出。

系统主界面的效果展示如下:

菜单的核心逻辑是通过 while(1) 无限循环配合 switch-case 语句来实现。每次循环时,打印菜单选项,读取用户输入的 choice,根据输入值调用相应的处理函数。使用 system("cls") 清屏和 system("pause") 暂停可以使界面更清晰,便于观察结果。

void Menu()
{
    while(1)
    {
        printf("\t\t\t---------------STUDENT---------------\t\t\t\n");
        printf("\t\t\t0.退出\t\t\t\n");
        printf("\t\t\t1.录入成绩\t\t\t\n");
        printf("\t\t\t2.查询成绩\t\t\t\n"); 
        printf("\t\t\t3.删除成绩\t\t\t\n");
        printf("\t\t\t4.修改成绩\t\t\t\n");
        printf("\t\t\t5.插入成绩\t\t\t\n");
        printf("\t\t\t6.排序\t\t\t\n");
        printf("\t\t\t7.统计学生人数\t\t\t\n");
        printf("\t\t\t-------------------------------------\t\t\t\n"); 
        printf("\t\t\t选择(0---7)\t\t\t"); 
        
        int choice;
        scanf("%d", &choice);
        switch(choice)
        {
            case 0: exit(0); break;
            case 1: in(); save(); system("pause"); system("cls"); break;
            case 2: show(); system("pause"); system("cls"); break;
            case 3: del(); save(); system("pause"); system("cls"); break;
            case 4: modify(); system("pause"); system("cls"); break;
            case 5: insert(); save(); system("pause"); system("cls"); break;
            case 6: order(); system("pause"); system("cls"); break;
            case 7: total(); system("pause"); system("cls"); break;
            default: printf("\n输入错误,请输入0---7\n\n"); system("pause"); system("cls"); break;
        }
    }
}

数据结构定义:结构体与全局变量

由于这是一个简易系统,我们采用定长静态数组来模拟数据库的存储。每个学生对象需要包含学号、姓名以及各科成绩信息。我们将这些信息封装在一个 struct 结构体中,并定义一个足够大的数组 student[N] 来存储多个学生实例。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define N 100 // 最大学生数量

// 定义学生结构体
typedef struct student
{
    char num[20];   // 学号
    char name[20];  // 姓名
    float elec;     // 选修课成绩
    float expe;     // 实验课成绩
    float requ;     // 必修课成绩
    float sum;      // 总成绩
    float ave;      // 平均成绩
} Student;

Student student[N]; // 定义全局结构体数组
int count = 0;      // 当前实际学生人数

第1步:成绩的录入 (in)

目的:从控制台获取学生信息,自动计算总分和平均分,并存入数组。

操作逻辑:

  1. 询问用户要录入多少个学生。
  2. 遍历指定次数,依次提示输入学号、姓名及三门成绩。
  3. 在输入完成后,立即计算 sum 和 ave。
  4. 更新全局计数器 count。
void in()
{
    int i, n;
    printf("输入录取学生的数量:\n");
    scanf("%d", &n);
    
    for (i = 1; i <= n; i++)
    {
        printf("请输入第%d位学生学号:\n", i);
        scanf("%s", student[i].num);
        
        printf("请输入第%d位学生姓名:\n", i);
        scanf("%s", student[i].name);
        
        printf("请输入第%d位学生选修课成绩:\n", i);
        scanf("%f", &student[i].elec);
        
        printf("请输入第%d位学生实验课成绩:\n", i);
        scanf("%f", &student[i].expe);
        
        printf("请输入第%d位学生必修课成绩:\n", i);
        scanf("%f", &student[i].requ);
        
        // 自动计算总分和平均分
        student[i].sum = student[i].elec + student[i].expe + student[i].requ;
        student[i].ave = student[i].sum / 3;
        
        count++; // 增加实际人数
    }
    printf("录入成功\n");
}

第2步:成绩的查询 (show)

目的:根据输入的学号,在数组中查找并显示对应的学生详细信息。

操作逻辑:

  1. 接收用户输入的待查询学号字符串。
  2. 遍历数组,利用 strcmp() 对比当前学生的 num 字段。
  3. 如果匹配,则格式化输出该学生的所有成绩信息。
void show()
{
    int i;
    char xh[20];
    
    printf("请输入要查询的学生学号:\n");
    getchar(); // 消耗上一次输入残留的回车符
    gets(xh);
    
    for (i = 0; i < N; i++)
    {
        if (student[i].num[0] != '\0' && strcmp(student[i].num, xh) == 0)
        {
            printf("\n学号:%-5s  姓名:%-5s  选修课成绩:%-5.1f  实验课成绩:%-5.1f  必修课成绩:%-5.1f  总分:%-5.1f  平均分:%-5.1f\n",
                   student[i].num, student[i].name, student[i].elec,
                   student[i].expe, student[i].requ, student[i].sum, student[i].ave);
            return; // 找到后可直接退出函数,避免重复打印
        }
    }
    printf("未找到该学号的学生\n");
}

第3步:成绩的删除 (del)

目的:定位目标学生并从数组中移除,后续学生前移填补空缺。

操作逻辑:

  1. 输入要删除的学号。
  2. 查找该学号在数组中的索引 a。
  3. 从该位置开始,将后一个元素赋值给当前元素,直到最后一个学生被移到倒数第二位。
  4. 全局计数 count 减 1。
void del()
{
    int j, a = 0, k = count;
    char xh[25];
    
    printf("所要删除的学生学号为:\n");
    scanf("%s", xh);
    flush(stdin); // 清理缓冲区
    
    for (j = 0; j < count; j++)
    {
        if (strcmp(student[j].num, xh) == 0)
        {
            // 找到后,将该位置之后的所有元素向前挪动一位
            for (a = j; a < count - 1; a++)
            {
                student[a] = student[a + 1];
            }
            count--; // 人数减一
            printf("删除成功\n");
            return; // 删除后无需继续查找
        }
    }
    
    if (j == count) // 修正原逻辑判断,防止越界或错误提示
    {
        printf("没有你所要删除的学生的信息\n");
    }
}

第4步:成绩的修改 (modify)

目的:先锁定特定学生,再允许修改单科成绩,最后更新总分与均分。

操作逻辑:

  1. 输入学号找到目标索引 j。
  2. 提供子菜单,让选择修改哪一门课程的成绩。
  3. 输入新成绩数值,存入对应字段。
  4. 重新计算该生的 sum 和 ave。
  5. 显示修改后的完整记录以供确认。
void modify()
{
    int i, j, h;
    float score;
    char xh[20];
    
    printf("请输入修改的学生的学号:\n");
    scanf("%s", xh);
    
    // 查找目标学生
    j = -1;
    for(i = 0; i < N; i++)
    {
        if (strcmp(student[i].num, xh) == 0)
        {
            j = i;
            break;
        }
    }
    
    if (j == -1) {
        printf("找不到该学生\n");
        return;
    }
    
    printf("请选择修改内容\n");
    printf("1.修改该学生的选修课成绩\n");
    printf("2.修改该学生的实验课成绩\n");
    printf("3.修改该学生的必修课成绩\n");
    scanf("%d", &h);
    
    switch (h)
    {
        case 1:
            printf("请输入该学生的选修课成绩\n");
            scanf("%f", &score);
            student[j].elec = score; 
            break;
        case 2:
            printf("请输入该学生的实验课成绩\n");
            scanf("%f", &score);
            student[j].expe = score;
            break;
        case 3:
            printf("请输入该学生的必修课成绩\n");
            scanf("%f", &score);
            student[j].requ = score;
            break;
        default: 
            printf("\n输入错误,请输入1---3\n\n"); 
            return;
    }
    
    // 重新计算总分和平均分
    student[j].sum = student[j].elec + student[j].expe + student[j].requ;
    student[j].ave = student[j].sum / 3;
    
    printf("修改后为:\n");
    printf("\n学号:%-5s  姓名:%-5s  选修课成绩:%-5.1f  实验课成绩:%-5.1f  必修课成绩:%-5.1f  总分:%-5.1f  平均分:%-5.1f\n",
           student[j].num, student[j].name, student[j].elec,
           student[j].expe, student[j].requ, student[j].sum, student[j].ave);
    printf("\n修改成功\n");
}

第5步:成绩的插入 (insert)

目的:在数组末尾添加新学生,并处理潜在的学号重复问题。

操作逻辑:

  1. 输入要插入的人数 m。
  2. 在填入每个新学生信息前,检查其学号是否与现有学号冲突。
  3. 若不冲突,则正常录入;若冲突,要求重新输入。
  4. 录入完成后,更新 count。
void insert()
{
    int m, flag, i, j, k;
    
    printf("请输入待添加的学生人数:\n");
    scanf("%d", &m);
    
    if (m > 0)
    {
        k = count + 1;
        for(i = k; i <= count + m; i++)
        {
            flag = 1;
            while (flag != 0)
            {
                flag = 0;
                printf("请输入第%d位学生的学号:\n", i);
                getchar();
                gets(student[i].num);
                
                // 检查学号唯一性
                for (j = 0; j < count; j++)
                {
                    if (strcmp(student[i].num, student[j].num) == 0)
                    {
                        printf("该学号已存在,请重新输入!\n");
                        flag = 1;
                        break;
                    }
                }
            }
            
            printf("请输入第%d位学生姓名:\n", i);
            gets(student[i].name);
            
            printf("请输入第%d位学生选修课成绩:\n", i);
            scanf("%f", &student[i].elec);
            printf("请输入第%d位学生实验课成绩:\n", i);
            scanf("%f", &student[i].expe);
            printf("请输入第%d位学生必修课成绩:\n", i);
            scanf("%f", &student[i].requ);
            
            student[i].sum = student[i].elec + student[i].expe + student[i].requ;
            student[i].ave = student[i].sum / 3;
        }
        count += m;
        printf("添加完成!\n");
    }
}

第6步:成绩的排序 (order)

目的:按照学生的总分从高到低进行排列,方便排名查看。

操作逻辑:

这里使用简单的交换排序(Selection Sort 变体)。外层循环控制轮次,内层循环寻找剩余部分中的最低分(或最高分),通过临时结构体变量交换位置。

void order()
{
    int i, j, m;
    // 冒泡/选择排序:按总分升序
    for(j = 1; j <= count - 1; j++)
    {
        m = j;
        for(i = j + 1; i <= count; i++)
        {
            // 如果需要降序,改为 > 符号
            if(student[i].sum < student[m].sum)
            {
                Student temp = student[i];
                student[i] = student[m];
                student[m] = temp;
            }
        }
    }
    
    printf("排序后的成绩列表:\n");
    for(j = 1; j <= count; j++)
    {
        printf("\n学号:%-5s  姓名:%-5s  选修课成绩:%-5.1f  实验课成绩:%-5.1f  必修课成绩:%-5.1f  总分:%-5.1f  平均分:%-5.1f\n",
               student[j].num, student[j].name, student[j].elec,
               student[j].expe, student[j].requ, student[j].sum, student[j].ave);
    }
}

第7步:统计学生人数 (total)

目的:显示当前系统中存在的实时学生数量。

void total()
{
    printf("当前学生总人数: %d\n", count);
}

文件保存功能 (save)

为了防止数据在程序关闭后丢失,我们需要实现一个简单的文件保存功能。使用标准库中的文件操作函数 fopen, fwrite, fclose 将结构体数组的内容写入磁盘二进制文件。

void save()
{
    FILE* fp;
    char filename[100];
    int i;
    
    printf("请输入要保存的文件名(例如 students.dat):\n");
    scanf("%s", filename);
    
    if ((fp = fopen(filename, "wb")) == NULL) // 使用 wb 模式以二进制写入
    {
        printf("打开文件失败!\n");
        return;
    }
    
    // 写入从第1个到第count个学生
    for (i = 1; i <= count; i++)
    {
        if (fwrite(&student[i], sizeof(Student), 1, fp) != 1)
        {
            printf("保存失败!\n");
            fclose(fp);
            return;
        }
    }
    
    printf("数据保存成功至: %s\n", filename);
    fclose(fp);
}

总结

本案例完整地展示了C语言中面向过程编程的典型结构:通过全局数组维护状态,模块化编写各个功能函数。在实际开发中,建议在此基础上增加“从文件加载数据 (load)”的功能,以实现数据的持久化保留。以上就是C语言实现简易学生管理系统的详细内容,更多关于C语言结构体应用和基础算法的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表