编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 01.学生管理系统

01.学生管理系统

目录介绍

  • 01.实现效果
    • 1.1 拆分和细化任务
    • 1.2 数据本地化
  • 02.实现过程
    • 2.1 创建头文件
    • 2.2 封装学生和结点数据
    • 2.3 创建头结点
    • 2.4 系统功能提示
    • 2.5 根据用户输入选择功能
    • 2.6 录入学生信息
    • 2.7 打印学生信息
    • 2.8 实现循环录入
    • 2.9 暂停和清空控制台
    • 2.10 统计学生人数
    • 2.11 查找学生信息
    • 2.12 学生信息持久化
    • 2.13 修改学生信息
    • 2.14 删除学生信息
    • 2.15 按成绩排序
  • 03.优化实践

01.实现效果

1.1 拆分和细化任务

  • 利用单链表实现学生管理系统,具体功能包括
    • 1.录入学生信息
    • 2.打印学生信息
    • 3.统计学生人数
    • 4.查找学生信息
    • 5.修改学生信息
    • 6.删除学生信息
    • 7.按照成绩排序
  • 通过该案例可以学习到什么内容
    • 数据的增删改查
    • 结构体,指针

1.2 数据本地化

  • 同时将学生信息保存至文件中,当再次启动程序时,自动读取学生数据。
    • 1.使用数据读写,那么这个就要使用到file

02.实现过程

2.1 头文件

  • 创建头文件
    //标准输入输出
    #include <stdio.h>
    //动态申请内存
    #include <stdlib.h>
  • 在.c文件中添加头文件
    //添加头文件
    #include "StudentManager.h"

2.2 封装学生和结点数据

  • 为了实现学生管理系统,要做两个事情。
    • 第一需要定义学生结构体(包含姓名,学号,分数等等);第二需要用数组或者其他容器装载学生实现增删改查操作。
  • 定义学生结构体
    • 用于保存学生的学号、姓名、成绩信息,并使用 typedef 给 struct _Student 类型起别名为 Student 方便使用。
    //定义学生结构体
    typedef struct _student {
        //学号
        int stuNum;
        //姓名
        char name[20];
        //分数
        int score;
    };
  • 定义节点结构体,用于保存链表(这里用链表装载学生数据)的节点数据,节点需要保存学生信息以及下一个节点的地址。
    //定义节点结构体
    typedef struct _Node {
        //学生
        Student stu;
        //指针
        struct _Node * next;
    } Node;

2.3 创建头结点

  • 创建 StudentManager.c ,加入main函数,并创建链表的头结点,定义head头指针指向头结点
    //添加头文件
    #include "StudentManager.h"
    
    int main() {
        //创建头节点
        Node *head = malloc(sizeof(Node));
        head->next = NULL;
        return 0;
    }
  • 这里面用到了malloc函数,在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。

2.4 系统功能提示

  • 定义welcome函数,用于提示用户系统功能
    void welcome() {
        printf("*********************************\n");
        printf("*\t学生成绩管理系统\t*\n");
        printf("*********************************\n");
        printf("*\t请选择功能列表\t\t*\n");
        printf("*********************************\n");
        printf("*\t1.录入学生信息\t\t*\n");
        printf("*\t2.打印学生信息\t\t*\n");
        printf("*\t3.统计学生人数\t\t*\n");
        printf("*\t4.查找学生信息\t\t*\n");
        printf("*\t5.修改学生信息\t\t*\n");
        printf("*\t6.删除学生信息\t\t*\n");
        printf("*\t7.学生成绩排序\t\t*\n");
        printf("*\t8.推出学生系统\t\t*\n");
        printf("*********************************\n");
    }
  • 在头文件中声明函数,并在main函数中调用,后面定义的其他函数,也应该先在头文件中声明,再调用。
  • StudentManager.h
    void welcome();
  • StudentManager.c
    int main() {
        //创建头节点
        Node *head = malloc(sizeof(Node));
        head->next = NULL;
        welcome();
        return 0;
    }

2.5 根据用户输入选择功能

  • StudentManager.h 在头文件中声明函数
    void scanChar(Node *head) ;
  • 从键盘获取输入值的方式目前有:
    • 第一种:getchar(),吸收回车
    • 第二种:getch:不需要回车,直接获取输入的字符,需要包含conio.h头文件,2022之前的版本需要使用 _getch()
  • 通过 getchar() 函数获取用户输入的单个字符,此函数不需要回车,并通过switch进行判断
    void scanChar(Node *head) {
        /* 等待从键盘接收一个字符, 输入字符后,回车才能录入,注意:回车符号会被存储至缓存区,下次 循环导致获取到回车
         * char c = getchar();
         * getchar(); 吸收回车
         * getch:不需要回车,直接获取输入的字符,需要包含conio.h头文件,2022之前的版本需要使用 _getch()
         */
        char c = getchar();
        switch (c) {
            //录入学生信息
            case '1':
                inputStudent(head);
                break;
                //打印学生信息
            case '2':
                printStudent(head);
                break;
                //统计学生人数
            case '3':
                countStudent(head);
                break;
                //查找学生信息
            case '4':
                findStudent(head);
                break;
                //修改学生信息
            case '5':
                modifyStudent(head);
                break;
                //删除学生信息
            case '6':
                deleteStudent(head);
                break;
                //按照成绩排序
            case '7':
                sortStudent(head);
                break;
                //推出系统
            case '8':
                exitSystem();
                break;
            default:
                printf("其他无效数字\n");
                break;
        }
    }

2.6 录入学生信息

  • 定义 inputStudent 函数,实现新增学生信息的功能,在main函数中调用。
    void inputStudent(Node *node) {
        //创建结点
        Node *fresh = malloc(sizeof(Node));
        fresh->next = NULL;
        //输入用户信息
        printf("请输入学生的学号、姓名、成绩: \n");
        scanf("%d%s%d", &fresh->stu.stuNum, fresh->stu.name, &fresh->stu.score);
        //定义指针指向头结点,用于遍历链表
        Node *move = node;
        while (move->next != NULL) {
            move = move->next;
        }
        //将学生插入到尾部
        move->next = fresh;
    }

2.7 打印学生信息

  • 定义 printStudent 函数,实现打印所有学生信息的功能
    void printStudent(Node *node) {
        Node *move = node->next;
        while (move != NULL) {
            printf("学号:%d 姓名:%s 成绩:%d \n", move->stu.stuNum, move->stu.name, move->stu.score);
            move = move->next;
        }
    }
  • 然后在main方法中,添加该函数调用
    //打印学生信息
    case '2':
        printStudent(head);
        break;

2.8 实现循环录入

  • 添加循环,将 welcome 函数及switch判断都加入到循环体,保持程序一直运行,实现循环录入学生信息功能。可以使用while实现循环!
    • 在c语言中,并没有为布尔值设置类型,使用0表示假,非0表示真!
    • 头文件stdbool.h定义了另一个类型别名bool,并且定义了true代表1、false代表0。只要加载这个头文件,就可以使用这几个关键字。
    int main() {
        welcome();
        while (1) {
            scanChar(head);
        }
        return 0;
    }
    void scanChar(Node *head) {
      /* 等待从键盘接收一个字符, 输入字符后,回车才能录入,注意:回车符号会被存储至缓存区,下次 循环导致获取到回车
       * char c = getchar();
       * getchar(); 吸收回车
       * getch:不需要回车,直接获取输入的字符,需要包含conio.h头文件,2022之前的版本需要使用 _getch()
       */
      char c = getchar();
      switch (c) {
      }
    }

2.9 暂停和清空控制台

  • 每次录入学生信息后,进入到下一次循环,会重新打印:其他无效数字,可以先使程序暂停,等待用户下一步指示
  • 当用户按下任意键后,可以先清空控制台,防止重复出现。
    system("pause");
    system("clear");

2.10 统计学生人数

  • 定义一个统计学生人数的方法countStudent,然后遍历链表
    void countStudent(Node *head) {
        int count = 0;
        Node *move = head->next;
        while (move != NULL) {
            move = move->next;
            count++;
        }
        printf("学生总人数为:%d\n", count);
    }
  • 然后调用统计学生人数方法
    //统计学生人数
    case '3':
        countStudent(head);
        break;

2.11 查找学生信息

  • 定义一个查找学生人数的方法 findStudent,然后遍历链表,通过学号去查找
    void findStudent(Node *head) {
        int number;
        printf("请输入要查找的学生学号:");
        scanf("%d", &number);
        Node *move = head->next;
        while (move != NULL) {
            if (move->stu.stuNum == number) {
                printf("学号:%d 姓名:%s 成绩:%d\n", move->stu.stuNum, move->stu.name, move->stu.score);
                return;
            }
            move = move->next;
        }
        printf("没有查找到学生信息\n");
    }
  • 然后调用查找学生人数方法
    //查找学生信息
    case '4':
        findStudent(head);
        break;

2.12 学生信息持久化

  • 定义saveStudent函数,将链表数据保存至文件中
    • 通过fopen打开文件。
    • 通过fwrite将数据写到file文件中。
    • 通过fclose关闭文件。
    void saveStudent(Node *head) {
        //打开文件
        FILE *file = fopen("./stu.info", "w");
        if (file == NULL) {
            printf("打开文件失败\n");
            return;
        }
        Node *move = head->next;
        while (move != NULL) {
            //将数据写到file文件中
            int i = fwrite(&move->stu, sizeof(Student), 1, file);
            if (i != 1) {
                printf("保存%s出现错误\n", move->stu.name);
            }
            move = move->next;
        }
        //关闭文件
        fclose(file);
    };
  • 下一次,定义 loadStudent 函数实现学生信息的读取
    • 通过fopen打开文件。
    • 通过fread将数据从文件,不断读取出来。
    • 通过fclose关闭文件。
    void loadStudent(Node *head) {
        FILE *file = fopen("./stu.info", "r");
        if (file == NULL) {
            printf("未找到学生文件,跳过读取\n");
            return;
        }
        //创建一个结点
        Node *fresh = malloc(sizeof(Node));
        fresh->next = NULL;
        Node *move = head;
        //不断读取文件
        while (fread(&fresh->stu, sizeof(Student), 1, file) == 1) {
            move->next = fresh;
            move = fresh;
            fresh = malloc(sizeof(Node));
            fresh->next = NULL;
        }
        free(fresh);
        //最后多定义一个fresh,要将它释放掉。关闭文件
        fclose(file);
        printf("读取成功\n");
    };

2.13 修改学生信息

  • 定义 modifyStudent 函数,用于根据学生学号修改学生信息。
    void modifyStudent(Node *head) {
        int number;
        printf("请输入要修改的学生学号:");
        scanf("%d", &number);
        Node *move = head;
        while (move != NULL) {
            if (move->stu.stuNum == number) {
                printf("请输入学生姓名,成绩\n");
                scanf("%s%d", move->stu.name, &move->stu.score);
                printf("修改学生信息成功\n");
                break;
            }
            move = move->next;
        }
        if (move == NULL) {
            printf("未找到学生信息\n");
        }
        saveStudent(head);
    }

2.14 删除学生信息

  • 定义 deleteStudent 函数,用于根据学号删除学生。
    void deleteStudent(Node *head) {
        int number;
        printf("请输入要删除的学生学号:");
        scanf("%d", &number);
        Node *move = head;
        //如何删除链表
        while (move != NULL) {
            if (move->stu.stuNum == number) {
                //删除节点
                Node *temp = move->next;
                move->next = move->next->next;
                free(temp);
                //将节点设置成null
                temp = NULL;
                break;
            }
            move = move->next;
        }
        if (move == NULL) {
            printf("未找到学生信息\n");
        }
    }
贡献者: yangchong211