谪仙阁-记录美好瞬间

C语言课设———学生信息管理系统

作者头像
谪仙 本文作者

2025-3-24 阅读 105 约 9分钟读完

评论0

好的,我将逐句详细解析这个C语言程序,并解释其功能和实现细节。

1. 预处理指令和头文件包含

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_STUDENTS 100  // 最大学生数量
  • #include <stdio.h>:包含标准输入输出库,提供 printfscanf 等函数,用于控制台输入输出。
  • #include <stdlib.h>:包含标准库,提供 mallocfreeexit 等函数,用于内存管理和程序控制。
  • #include <string.h>:包含字符串处理库,提供 strcpystrcmp 等函数,用于字符串操作。
  • #define MAX_STUDENTS 100:定义一个宏 MAX_STUDENTS,值为 100。这表示程序最多可以存储 100 个学生的信息。使用宏可以方便地修改最大学生数量。

2. 学生结构体定义

typedef struct Student {
    char id[20];         // 学号
    char name[50];       // 姓名
    float scores[3];     // 三门成绩
    float total;         // 总分
    float average;       // 平均分
} Student;
  • typedef struct Student { ... } Student;:定义一个名为 Student 的结构体类型。typedef 关键字用于为结构体创建一个别名,这样就可以直接使用 Student 来声明结构体变量,而不需要写 struct Student
  • char id[20];:定义一个字符数组 id,用于存储学生的学号。最多可以存储 19 个字符加上一个空字符 \0
  • char name[50];:定义一个字符数组 name,用于存储学生的姓名。最多可以存储 49 个字符加上一个空字符 \0
  • float scores[3];:定义一个浮点数数组 scores,用于存储学生的三门成绩。
  • float total;:定义一个浮点数变量 total,用于存储学生的总分。
  • float average;:定义一个浮点数变量 average,用于存储学生的平均分。

3. 全局变量声明

Student students[MAX_STUDENTS];  // 学生数组
int student_count = 0;           // 当前学生数量
  • Student students[MAX_STUDENTS];:声明一个 Student 类型的数组 students,大小为 MAX_STUDENTS(100)。这个数组用于存储所有学生的信息。
  • int student_count = 0;:声明一个整型变量 student_count,用于记录当前存储的学生数量。初始值为 0,表示当前没有学生信息。

4. 菜单显示函数

void print_menu() {
    printf("\n======== 学生信息管理系统 ========\n");
    printf("1. 添加学生\n");
    printf("2. 显示所有学生\n");
    printf("3. 删除学生\n");
    printf("4. 修改学生信息\n");
    printf("5. 搜索学生\n");
    printf("6. 成绩排序\n");
    printf("7. 保存到文件\n");
    printf("8. 从文件加载\n");
    printf("0. 退出系统\n");
    printf("==================================\n");
    printf("请输入选项:");
}
  • void print_menu() { ... }:定义一个名为 print_menu 的函数,用于显示学生信息管理系统的菜单。
  • printf(...):使用 printf 函数在控制台输出菜单选项。

5. 添加学生函数

void add_student() {
    if (student_count >= MAX_STUDENTS) {
        printf("学生数量已达上限,无法添加!\n");
        return;
    }

    Student new_stu;
    printf("\n请输入学号:");
    scanf("%s", new_stu.id);

    // 检查学号是否重复
    for (int i = 0; i < student_count; i++) {
        if (strcmp(students[i].id, new_stu.id) == 0) {
            printf("学号已存在!\n");
            return;
        }
    }

    printf("请输入姓名:");
    scanf("%s", new_stu.name);
    printf("请输入三门成绩(用空格分隔):");
    scanf("%f %f %f", &new_stu.scores[0], &new_stu.scores[1], &new_stu.scores[2]);

    new_stu.total = new_stu.scores[0] + new_stu.scores[1] + new_stu.scores[2];
    new_stu.average = new_stu.total / 3;

    students[student_count++] = new_stu;
    printf("学生添加成功!\n");
}
  • void add_student() { ... }:定义一个名为 add_student 的函数,用于添加学生信息。
  • if (student_count >= MAX_STUDENTS) { ... }:检查当前学生数量是否已达到最大限制。如果已达到,则输出错误信息并返回。
  • Student new_stu;:声明一个 Student 类型的变量 new_stu,用于存储新学生的信息。
  • printf("\n请输入学号:");:提示用户输入学号。
  • scanf("%s", new_stu.id);:使用 scanf 函数从控制台读取用户输入的学号,并存储到 new_stu.id 中。注意: scanf("%s", ...) 在读取字符串时存在缓冲区溢出的风险。建议使用 fgets 函数来代替,并限制读取的字符数。
  • 学号重复检查: 循环遍历已存在的学生信息,使用 strcmp 函数比较新输入的学号与已存在的学号是否相同。如果相同,则输出错误信息并返回。
  • printf("请输入姓名:");:提示用户输入姓名。
  • scanf("%s", new_stu.name);:使用 scanf 函数从控制台读取用户输入的姓名,并存储到 new_stu.name 中。注意: 同样存在缓冲区溢出的风险,建议使用 fgets
  • printf("请输入三门成绩(用空格分隔):");:提示用户输入三门成绩。
  • scanf("%f %f %f", &new_stu.scores[0], &new_stu.scores[1], &new_stu.scores[2]);:使用 scanf 函数从控制台读取用户输入的三门成绩,并存储到 new_stu.scores 数组中。
  • new_stu.total = new_stu.scores[0] + new_stu.scores[1] + new_stu.scores[2];:计算学生的总分。
  • new_stu.average = new_stu.total / 3;:计算学生的平均分。
  • students[student_count++] = new_stu;:将新学生的信息存储到 students 数组中,并将 student_count 的值加 1。student_count++ 是后置自增运算符,表示先使用 student_count 的值,然后再将 student_count 的值加 1。
  • printf("学生添加成功!\n");:输出添加成功的提示信息。

6. 显示学生信息函数

void display_students() {
    if (student_count == 0) {
        printf("没有学生记录!\n");
        return;
    }

    printf("\n%-15s%-20s%-10s%-10s%-10s%-10s%-10s\n",
           "学号", "姓名", "成绩1", "成绩2", "成绩3", "总分", "平均分");
    printf("------------------------------------------------------------\n");

    for (int i = 0; i < student_count; i++) {
        printf("%-15s%-20s%-10.1f%-10.1f%-10.1f%-10.1f%-10.1f\n",
               students[i].id, students[i].name,
               students[i].scores[0], students[i].scores[1], students[i].scores[2],
               students[i].total, students[i].average);
    }
}
  • void display_students() { ... }:定义一个名为 display_students 的函数,用于显示所有学生的信息。
  • if (student_count == 0) { ... }:检查当前学生数量是否为 0。如果为 0,则输出没有学生记录的提示信息并返回。
  • printf("\n%-15s%-20s%-10s%-10s%-10s%-10s%-10s\n", ...);:使用 printf 函数输出表头。%-15s%-20s 等是格式化字符串,用于控制输出的对齐方式和宽度。- 表示左对齐,1520 等表示字段宽度。
  • printf("------------------------------------------------------------\n");:输出分隔线。
  • 循环遍历学生信息: 循环遍历 students 数组,使用 printf 函数输出每个学生的信息。%-10.1f 表示输出浮点数,字段宽度为 10,保留 1 位小数。

7. 删除学生函数

void delete_student() {
    char target[20];
    printf("\n请输入要删除的学号:");
    scanf("%s", target);

    for (int i = 0; i < student_count; i++) {
        if (strcmp(students[i].id, target) == 0) {
            // 将后面的学生前移
            for (int j = i; j < student_count - 1; j++) {
                students[j] = students[j + 1];
            }
            student_count--;
            printf("删除成功!\n");
            return;
        }
    }
    printf("未找到该学生!\n");
}
  • void delete_student() { ... }:定义一个名为 delete_student 的函数,用于删除学生信息。
  • char target[20];:声明一个字符数组 target,用于存储要删除的学生的学号。
  • printf("\n请输入要删除的学号:");:提示用户输入要删除的学号。
  • scanf("%s", target);:使用 scanf 函数从控制台读取用户输入的学号,并存储到 target 中。注意: 同样存在缓冲区溢出的风险,建议使用 fgets
  • 循环查找学生: 循环遍历 students 数组,使用 strcmp 函数比较输入的学号与数组中学生的学号是否相同。
  • 删除学生: 如果找到匹配的学生,则将后面的学生信息依次前移一位,覆盖要删除的学生信息。然后将 student_count 的值减 1。
  • printf("删除成功!\n");:输出删除成功的提示信息。
  • printf("未找到该学生!\n");:如果没有找到匹配的学生,则输出未找到的提示信息。

8. 修改学生信息函数

void modify_student() {
    char target[20];
    printf("\n请输入要修改的学号:");
    scanf("%s", target);

    for (int i = 0; i < student_count; i++) {
        if (strcmp(students[i].id, target) == 0) {
            printf("请输入新姓名:");
            scanf("%s", students[i].name);
            printf("请输入新成绩(三门):");
            scanf("%f %f %f", &students[i].scores[0], &students[i].scores[1], &students[i].scores[2]);

            students[i].total = students[i].scores[0] + students[i].scores[1] + students[i].scores[2];
            students[i].average = students[i].total / 3;
            printf("修改成功!\n");
            return;
        }
    }
    printf("未找到该学生!\n");
}
  • void modify_student() { ... }:定义一个名为 modify_student 的函数,用于修改学生信息。
  • char target[20];:声明一个字符数组 target,用于存储要修改的学生的学号。
  • printf("\n请输入要修改的学号:");:提示用户输入要修改的学号。
  • scanf("%s", target);:使用 scanf 函数从控制台读取用户输入的学号,并存储到 target 中。注意: 同样存在缓冲区溢出的风险,建议使用 fgets
  • 循环查找学生: 循环遍历 students 数组,使用 strcmp 函数比较输入的学号与数组中学生的学号是否相同。
  • 修改学生信息: 如果找到匹配的学生,则提示用户输入新的姓名和成绩,并更新学生信息。
  • printf("修改成功!\n");:输出修改成功的提示信息。
  • printf("未找到该学生!\n");:如果没有找到匹配的学生,则输出未找到的提示信息。

9. 搜索学生函数

void search_student() {
    int choice;
    char keyword[50];
    printf("\n请选择搜索方式:\n1.按学号\n2.按姓名\n");
    scanf("%d", &choice);
    printf("请输入关键字:");
    scanf("%s", keyword);

    int found = 0;
    for (int i = 0; i < student_count; i++) {
        if ((choice == 1 && strcmp(students[i].id, keyword) == 0) ||
            (choice == 2 && strcmp(students[i].name, keyword) == 0)) {
            printf("\n%-15s%-20s%-10s%-10s%-10s%-10s%-10s\n",
                   "学号", "姓名", "成绩1", "成绩2", "成绩3", "总分", "平均分");
            printf("%-15s%-20s%-10.1f%-10.1f%-10.1f%-10.1f%-10.1f\n",
                   students[i].id, students[i].name,
                   students[i].scores[0], students[i].scores[1], students[i].scores[2],
                   students[i].total, students[i].average);
            found = 1;
            break;
        }
    }
    if (!found) printf("未找到匹配的学生!\n");
}
  • void search_student() { ... }:定义一个名为 search_student 的函数,用于搜索学生信息。
  • int choice;:声明一个整型变量 choice,用于存储用户选择的搜索方式。
  • char keyword[50];:声明一个字符数组 keyword,用于存储用户输入的搜索关键字。
  • printf("\n请选择搜索方式:\n1.按学号\n2.按姓名\n");:提示用户选择搜索方式。
  • scanf("%d", &choice);:使用 scanf 函数从控制台读取用户选择的搜索方式,并存储到 choice 中。
  • printf("请输入关键字:");:提示用户输入搜索关键字。
  • scanf("%s", keyword);:使用 scanf 函数从控制台读取用户输入的搜索关键字,并存储到 keyword 中。注意: 同样存在缓冲区溢出的风险,建议使用 fgets
  • int found = 0;:声明一个整型变量 found,用于标记是否找到匹配的学生。
  • 循环查找学生: 循环遍历 students 数组,根据用户选择的搜索方式,使用 strcmp 函数比较输入的关键字与数组中学生的学号或姓名是否相同。
  • 显示学生信息: 如果找到匹配的学生,则输出学生的信息,并将 found 的值设置为 1,然后跳出循环。
  • if (!found) printf("未找到匹配的学生!\n");:如果没有找到匹配的学生,则输出未找到的提示信息。

10. 成绩排序函数

void sort_students() {
    if (student_count == 0) {
        printf("没有学生记录!\n");
        return;
    }

    if (student_count == 1) {
        printf("当前只有一个学生,成绩如下:\n");
        display_students();  // 直接显示当前学生
        return;
    }

    // 学生数量大于 1,执行排序
    for (int i = 0; i < student_count - 1; i++) {
        for (int j = 0; j < student_count - i - 1; j++) {
            if (students[j].total < students[j + 1].total) {
                // 交换学生信息
                Student temp = students[j];
                students[j] = students[j + 1];
                students[j + 1] = temp;
            }
        }
    }
    printf("排序完成!\n");
    display_students();  // 显示排序结果
}
  • void sort_students() { ... }:定义一个名为 sort_students 的函数,用于按总分对学生信息进行排序。
  • if (student_count == 0) { ... }:检查当前学生数量是否为 0。如果为 0,则输出没有学生记录的提示信息并返回。
  • if (student_count == 1) { ... }:检查当前学生数量是否为 1。如果为 1,则输出只有一个学生的提示信息,并直接显示该学生的信息。
  • 冒泡排序: 使用冒泡排序算法对学生信息进行排序。外层循环控制排序的轮数,内层循环控制每轮比较的次数。如果前一个学生的总分小于后一个学生的总分,则交换两个学生的信息。
  • printf("排序完成!\n");:输出排序完成的提示信息。
  • display_students();:显示排序后的学生信息。

11. 保存到文件函数

void save_to_file() {
    FILE* fp = fopen("students.dat", "w");
    if (!fp) {
        printf("文件保存失败!\n");
        return;
    }

    for (int i = 0; i < student_count; i++) {
        fprintf(fp, "%s %s %.1f %.1f %.1f\n",
                students[i].id, students[i].name,
                students[i].scores[0], students[i].scores[1], students[i].scores[2]);
    }
    fclose(fp);
    printf("数据已保存到文件!\n");
}
  • void save_to_file() { ... }:定义一个名为 save_to_file 的函数,用于将学生信息保存到文件中。
  • FILE* fp = fopen("students.dat", "w");:使用 fopen 函数打开名为 "students.dat" 的文件,以写入模式打开。"w" 表示写入模式,如果文件不存在则创建,如果文件存在则覆盖。fp 是一个文件指针,用于指向打开的文件。
  • if (!fp) { ... }:检查文件是否成功打开。如果打开失败,则输出文件保存失败的提示信息并返回。
  • 循环写入学生信息: 循环遍历 students 数组,使用 fprintf 函数将每个学生的信息写入到文件中。fprintf 函数类似于 printf 函数,但是它可以将数据写入到文件中。
  • fclose(fp);:使用 fclose 函数关闭文件。
  • printf("数据已保存到文件!\n");:输出数据已保存到文件的提示信息。

12. 从文件加载函数

void load_from_file() {
    FILE* fp = fopen("students.dat", "r");
    if (!fp) {
        printf("文件读取失败!\n");
        return;
    }

    student_count = 0;  // 清空当前数据
    while (fscanf(fp, "%s %s %f %f %f",
                 students[student_count].id,
                 students[student_count].name,
                 &students[student_count].scores[0],
                 &students[student_count].scores[1],
                 &students[student_count].scores[2]) != EOF) {
        students[student_count].total = students[student_count].scores[0] +
                                        students[student_count].scores[1] +
                                        students[student_count].scores[2];
        students[student_count].average = students[student_count].total / 3;
        student_count++;
    }
    fclose(fp);
    printf("数据加载成功!\n");
}
  • void load_from_file() { ... }:定义一个名为 load_from_file 的函数,用于从文件中加载学生信息。
  • FILE* fp = fopen("students.dat", "r");:使用 fopen 函数打开名为 "students.dat" 的文件,以读取模式打开。"r" 表示读取模式,如果文件不存在则返回 NULL。
  • if (!fp) { ... }:检查文件是否成功打开。如果打开失败,则输出文件读取失败的提示信息并返回。
  • student_count = 0;:将 student_count 的值设置为 0,清空当前的学生信息。
  • 循环读取学生信息: 使用 fscanf 函数从文件中读取学生信息,直到文件结束。fscanf 函数类似于 scanf 函数,但是它可以从文件中读取数据。EOF (End Of File) 是一个宏,表示文件结束。
  • students[student_count].total = students[student_count].scores[0] + ...;:计算学生的总分。
  • students[student_count].average = students[student_count].total / 3;:计算学生的平均分。
  • student_count++;:将 student_count 的值加 1。
  • fclose(fp);:使用 fclose 函数关闭文件。
  • printf("数据加载成功!\n");:输出数据加载成功的提示信息。

13. 主函数

int main() {
    int choice;
    print_menu();  // 程序启动时先显示一次菜单
    do {
        scanf("%d", &choice);
        switch (choice) {
            case 1: add_student(); break;
            case 2: display_students(); break;
            case 3: delete_student(); break;
            case 4: modify_student(); break;
            case 5: search_student(); break;
            case 6: sort_students(); break;
            case 7: save_to_file(); break;
            case 8: load_from_file(); break;
            case 0: printf("感谢使用!\n"); break;
            default: printf("无效选项!\n");
        }
        if (choice != 0) {
            print_menu();  // 每次操作后重新显示菜单
        }
    } while (choice != 0);

    return 0;
}
  • int main() { ... }:定义主函数。
  • int choice;:声明一个整型变量 choice,用于存储用户选择的菜单选项。
  • print_menu();:调用 print_menu 函数显示菜单。
  • 循环处理用户输入: 使用 do...while 循环,不断提示用户输入菜单选项,并根据用户的选择执行相应的操作。
  • scanf("%d", &choice);:使用 scanf 函数从控制台读取用户输入的菜单选项,并存储到 choice 中。
  • switch 语句: 使用 switch 语句根据 choice 的值执行相应的操作。
  • case 1: add_student(); break;:如果 choice 的值为 1,则调用 add_student 函数添加学生信息。
  • case 2: display_students(); break;:如果 choice 的值为 2,则调用 display_students 函数显示学生信息。
  • case 3: delete_student(); break;:如果 choice 的值为 3,则调用 delete_student 函数删除学生信息。
  • case 4: modify_student(); break;:如果 choice 的值为 4,则调用 modify_student 函数修改学生信息。
  • case 5: search_student(); break;:如果 choice 的值为 5,则调用 search_student 函数搜索学生信息。
  • case 6: sort_students(); break;:如果 choice 的值为 6,则调用 sort_students 函数对学生信息进行排序。
  • case 7: save_to_file(); break;:如果 choice 的值为 7,则调用 save_to_file 函数将学生信息保存到文件中。
  • case 8: load_from_file(); break;:如果 choice 的值为 8,则调用 load_from_file 函数从文件中加载学生信息。
  • case 0: printf("感谢使用!\n"); break;:如果 choice 的值为 0,则输出感谢使用的提示信息,并退出循环。
  • default: printf("无效选项!\n");:如果 choice 的值不是 0-8,则输出无效选项的提示信息。
  • if (choice != 0) { print_menu(); }:如果用户选择的不是退出选项,则重新显示菜单。
  • while (choice != 0);:当用户选择的菜单选项为 0 时,退出循环。
  • return 0;:程序正常结束。

总结

这个程序实现了一个简单的学生信息管理系统,可以添加、显示、删除、修改、搜索和排序学生信息,并将学生信息保存到文件或从文件中加载。程序使用了结构体来存储学生信息,使用了数组来存储多个学生的信息,使用了函数来实现不同的功能模块。

改进建议

  • 输入验证: 应该对用户输入的数据进行验证,例如检查学号是否符合格式,成绩是否在有效范围内。
  • 错误处理: 应该对可能出现的错误进行处理,例如文件打开失败,内存分配失败等。
  • 缓冲区溢出: scanf("%s", ...) 在读取字符串时存在缓冲区溢出的风险。建议使用 fgets 函数来代替,并限制读取的字符数。
  • 内存管理: 如果学生数量超过 MAX_STUDENTS,可以考虑使用动态内存分配来扩展 students 数组的大小。
  • 排序算法: 可以使用更高效的排序算法,例如快速排序或归并排序。
  • 用户界面: 可以使用图形用户界面 (GUI) 来改善用户体验。

希望这个详细的解析对您有所帮助!



手机扫码阅读
上一篇 没有了 下一篇 大学宿舍管理系统 - 宿舍展示一站式网站功能策划书
评论