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"); } }