用链表知识总结回顾学生成绩管理系统的实现步骤详解
本次,我要用学到的相关链表知识,对学生成绩管理系统的实现进行总结回顾。

首先创建一个项目,接着分别创建头文件和源文件,头文件用于声明函数,源文件用于定义函数以及实现学生成绩管理系统。
创建完成后如上图。
先创建一个结构体,这个结构体用于存放学生信息,学生信息包括学号、姓名、成绩。在这里,学号使用的是字符数组,因为整型可能无法满足学号长度的需求。
//学生信息
typedef struct student
{
char number[15];
char name[20];
float score;
}stu;
接着就是创建节点,目的是用来链接学生信息,节点情况如下,常规的做法是放置一个结构体,结构体内容为学生信息,以及放置下一个结点的地址 。
//节点信息
typedef struct Node
{
stu student;
struct Node* next;
}node;
基本步骤已经完成。接下来是菜单,它的作用是指引用户完成程序。菜单将通过函数来实现,要记得先在头文件里声明 。
void menu()
{
输出“ *****************学生成绩管理系统***************”,换行符为制表符,引号为英文引号 。
打印输出“ **************请选择功能列表********************”,并换行
输出制表符,内容为“*******1.添加学生信息 2.打印学生信息*******”,换行
打印,制表符,“*******3.统计学生人数 4.查找学生信息*******”,换行符
输出制表符“\t”,内容为“*******5.修改学生信息 6.删除学生信息*******”,换行
输出制表符“\t”,接着输出“*******7.按成绩排序 8.退出系统***********”,然后换行
}
菜单里的内容便是学生成绩系统即将达成的操作,接下来我会对每个步骤的实现予以详细说明。
1.添加学生信息
同样用函数实现,为方便实现链表的增删查改,需使用一个头结点,头结点不存放任何内容,只为方便链表操作。添加学生信息即给链表尾插一个新节点,添加节点时要先向内存申请空间,因申请的是节点空间,所以要用结构体指针(节点地址)接收,之后初始化新建节点,用scanf输入要添加的学生信息,接着进行尾插操作。判断是否只有一个节点,若是,则直接将头结点的next指向新节点,若不是,需找到尾结点,这就需要引出一个新节点用来找尾结点,循环遍历cur直至到最后一个节点,将最后一个节点指向新建节点即可。
void inputstudent(node* head)
{
创建一个指针变量fresh,使其指向通过调用malloc函数分配的内存,该内存的大小为结构体node的大小 。
fresh->next = NULL;
printf("请输入学生的学号、姓名、成绩:");
从标准输入读取一个字符串,存入fresh指向的结构体中student成员的number数组,再读取一个字符串,存入fresh指向的结构体中student成员的name数组,接着读取一个浮点数,存入fresh指向的结构体中student成员的score变量 。
if(head->next==NULL)
head->next = fresh;
else
{
node*cur = head;
while (cur->next != NULL)
{
cur = cur->next;
}
cur ->next= fresh;
}
}
2.打印学生信息
打印学生信息,这需要一个可移动的节点,利用该节点对链表进行遍历,然后依次进行打印 。
void printstudent(node* head)
{
node* cur = head;
while (cur->next != NULL)
{
输出“学号:”,紧接着输出cur指向的下一个节点的学生的学号,再输出“ 姓名:”,接着输出cur指向的下一个节点的学生的姓名,最后输出“ 成绩:”,并保留两位小数输出cur指向的下一个节点的学生的成绩,换行。
cur = cur->next;
}
}
3.统计学生人数
统计人数就是在遍历链表的过程中引入一个变量进行计数即可。
void countstudent(node* head)
{
int count = 0;
node* cur = head;
while (cur->next != NULL)
{
count++;
cur = cur->next;
}
输出“学生的总人数为:”,紧跟着变量count的值,然后换行
}
4.查找学生信息
查找学生信息时,是依据学生学号找出其各项信息。因为要让用户输入需查找学生的学号,所以要定义一个字符数组。之后,将节点里学生信息中的学号依次与用户输入的信息作比较。比对成功后,输入该项学生的各项信息即可。比较时,由于是字符串之间的比较,所以要使用字符串比较函数strcmp,不可直接用”==“ 。
void findstudent(node* head)
{
char stu[20];
printf("请输入要查找的学生的学号:");
scanf("%s", stu);
node* cur = head;
while (cur->next != NULL)
{
如果当前节点的下一个节点的学生编号与传入的学生编号相等 ,则执行后续操作 。
{
输出“学号:”,接着输出当前节点下一个节点的学生学号,再输出“ 姓名:”,然后输出当前节点下一个节点的学生姓名,最后输出“ 成绩:”以及当前节点下一个节点的学生成绩,成绩保留两位小数,末尾换行。
return;
}
cur = cur->next;
}
printf("未找到该学生\n");
}
使学生信息更持久化
这个操作与文件操作相关,首先要创建或打开文件(fopen),接着向文件里写入数据(fwrite),然后遍历链表把每个节点保存到文件中,把该函数放在添加学生信息的函数后面就行。
void savestudent(node* head)
{
FILE类型的指针file,通过调用fopen函数,以写入模式打开文件"./stu.info",其中"w"表示可写 。
node* cur = head;
while (cur->next != NULL)
{
如果向文件写入当前节点下一个节点的学生信息时失败,即fwrite(要写入文件的数据的地址,数据的字节大小,传入数据的个数,传入的目标文件)函数返回值不等于1,这里要写入的数据是当前节点下一个节点的学生信息,数据的字节大小为结构体stu的大小,传入数据个数为1,目标文件为file
{
printf("写入失败\n");
return;
}
cur = cur->next;
}
fclose(file);//关闭文件
}
读取文件
打开曾存放过数据的文件,读取文件里的每个学生信息,把这些信息连接到新链表中,使用可移动的节点从链表头结点开始循环,先申请一个空间来存放读取的第一个节点,接着将可移动节点的next链接到新申请空间里存放读取数据的节点,可移动节点向后遍历,继续申请空间,直到文件中的数据都被读取 。注意,要释放掉最后一次申请的空间,循环之前已经申请过一次空间,在最后一次循环中,申请的空间未被利用到。
void loadstudent(node* head)
{
FILE类型的指针file,通过调用fopen函数,以只读模式打开名为"./stu.info"的文件 ,这里的"r"表示read即只读 。
if (!file)
{
printf("没有学生文件,跳过读取\n");
return;
}
node* fresh = (node*)malloc(sizeof(node));
node* cur = head;
fresh->next = NULL;
当从文件中读取数据时,每次读取一个学生结构体大小的数据,若读取成功则继续循环,读取的数据存放在fresh指向的学生结构体中,读取数据的来源是文件 。
{
cur->next = fresh;
cur = fresh;
创建一个指针变量fresh,使其指向通过调用malloc函数分配内存后得到的地址,该内存大小为结构体node的大小 。
fresh->next = NULL;
}
free(fresh);
fclose(file);
printf("读取成功!\n");
}
5.修改学生信息
先让用户输入要修改的学生的学号,此操作和查找学生信息有点相似,接着循环遍历查找此学生并进行修改,修改完成后记得将其存到文件中。
定义一个名为modifystudent的函数,该函数接收一个指向node类型的指针head作为参数 。
{
printf("请输入要修改的学生的学号:");
char stu[15];
scanf("%s", stu);
node* cur = head->next;
while (cur != NULL)
{
如果将当前结构体中学生的编号与传入的学生编号进行字符串比较的结果等于0 ,
{
printf("请输入学生姓名,成绩:");
从标准输入读取一个字符串,存储到cur指向的结构体中student成员的name字段,再读取一个浮点数,存储到cur指向的结构体中student成员的score字段 。
savestudent(head);//持久化学生信息
printf("修改成功\n");
return;
}
cur = cur->next;
}
printf("未找到该学生\n");
}
6.删除学生信息
先找到要删除的学生,注意要使用删除学生的前一个节点,将前一个节点与删除学生的下一个节点进行链接,此时删除学生的节点就不在链表中了,释放掉删除学生的空间即可。注意:不可直接将该节点删除,否则删除学生后面的节点就无法找到。
定义一个名为deletestudent的函数,该函数带有一个指向节点的指针head作为参数 。
{
printf("请输入要删除的学生的学号:");
char stu[15];
scanf("%s", stu);
node* cur = head;
while (cur->next!=NULL)
{
如果字符串 stu 与 cur 指向的下一个节点的 student 结构体中的 number 成员相等 ,则执行后续操作 。
{
定义一个节点指针tmp ,使其指向当前节点cur的下一个节点 ,以此来记录要删除的学生节点 。
cur->next = cur->next->next;
free(tmp);
tmp = NULL;
savestudent(head);
printf("删除成功\n");
return;
}
cur = cur->next;
}
printf("未找到该学生\n");
}
7.根据学生成绩将学生进行排序
对链表中的节点数据进行排序,采用冒泡排序的方式,链表中结点的数据是学生信息,直接交换学生信息就行,不用交换节点。
结点的冒泡排序:
void sortstudent(node* head)
{
node* save = NULL;
node* cur = NULL;
对于节点指针turn,其初始值为head的下一个节点,当turn的下一个节点不为空时,执行循环,每次循环将turn更新为其下一个节点 。
{
让当前指针cur指向头节点的下一个节点,只要当前节点的下一个节点不等于保存的节点,就将当前指针cur指向下一个节点 。
{
if (cur->next->student.score < cur->student.score)
{
stu temp = cur->student;
cur的学生等于cur下一个的学生;
cur->next->student = temp;
}
}
简化比较次数,将cur赋值给save,对于最后面已经排序过的数据,就无需再进行比较。
}
printstudent(head);
}
整体代码如下:
StudentSystem.h:
#include
#include
#include
#include
//学生信息
typedef struct student
{
char number[15];
char name[20];
float score;
}stu;
//节点信息
typedef struct Node
{
stu student;
struct Node* next;
}node;
void menu();
//输入学生信息
定义一个名为inputstudent的函数,该函数接受一个指向node类型的指针作为参数 。
//打印学生信息
定义一个函数,函数名为printstudent ,参数为node* head ;
//统计学生人数
定义一个名为countstudent的函数,该函数接受一个指向node类型的指针head作为参数;
//查找学生信息
void findstudent(node* head);
//使学生信息持久化 写入文件
void savestudent(node* head);
//读取文件
void loadstudent(node* head);
//修改学生信息
定义一个名为modifystudent的函数,该函数接受一个指向node类型的指针作为参数 。
//删除学生信息
定义一个名为deletestudent的函数,该函数接受一个指向node类型的指针head作为参数;
//按照学生成绩排序
void sortstudent(node* head);
StudentStystem.c:
#include"StudentStystem.h"
void menu()
{
printf("\t*****************学生成绩管理系统***************\n");
printf("\t**************请选择功能列表********************\n");
printf("\t*******1.添加学生信息 2.打印学生信息*******\n");
printf("\t*******3.统计学生人数 4.查找学生信息*******\n");
printf("\t*******5.修改学生信息 6.删除学生信息*******\n");
printf("\t*******7.按成绩排序 8.退出系统***********\n");
}
void inputstudent(node* head)
{
node* fresh = (node*)malloc(sizeof(node));
fresh->next = NULL;
printf("请输入学生的学号、姓名、成绩:");
scanf("%s%s%f", fresh->student.number, fresh->student.name, &fresh->student.score);
if(head->next==NULL)
head->next = fresh;
else
{
node*cur = head;
while (cur->next != NULL)
{
cur = cur->next;
}
cur ->next= fresh;
}
savestudent(head);
}
void printstudent(node* head)
{
node* cur = head;
while (cur->next != NULL)
{
printf("学号:%s 姓名:%s 成绩:%.2f\n", cur->next->student.number, cur->next->student.name, cur->next->student.score);
cur = cur->next;
}
}
void countstudent(node* head)
{
int count = 0;
node* cur = head;
while (cur->next != NULL)
{
count++;
cur = cur->next;
}
printf("学生的总人数为:%d\n", count);
}
void findstudent(node* head)
{
char stu[20];
printf("请输入要查找的学生的学号:");
scanf("%s", stu);
node* cur = head;
while (cur->next != NULL)
{
if (strcmp(cur->next->student.number,stu) == 0)
{
printf("学号:%s 姓名:%s 成绩:%.2f\n", cur->next->student.number, cur->next->student.name, cur->next->student.score);
return;
}
cur = cur->next;
}
printf("未找到该学生\n");
}
void savestudent(node* head)
{
FILE* file = fopen("./stu.info", "w");//w即write可写
node* cur = head;
while (cur->next != NULL)
{
if (fwrite(&cur->next->student, sizeof(stu), 1, file) != 1)//fwrite(要写入文件的数据的地址,数据的字节大小,传入数据的个数,传入的目标文件)
{
printf("写入失败\n");
return;
}
cur = cur->next;
}
fclose(file);//关闭文件
}
void loadstudent(node* head)
{
FILE* file = fopen("./stu.info", "r");//r即read只读
if (!file)
{
printf("没有学生文件,跳过读取\n");
return;
}
node* fresh = (node*)malloc(sizeof(node));
node* cur = head;
fresh->next = NULL;
while (fread(&fresh->student, sizeof(stu), 1, file) == 1)//fread(读取出的数据存放的地址,读取出数据的字节大小,读取出数据的个数,读取数据的来源)
{
cur->next = fresh;
cur = fresh;
fresh = (node*)malloc(sizeof(node));
fresh->next = NULL;
}
free(fresh);
fclose(file);
printf("读取成功!\n");
}
void modifystudent(node* head)
{
printf("请输入要修改的学生的学号:");
char stu[15];
scanf("%s", stu);
node* cur = head->next;
while (cur != NULL)
{
if (strcmp(cur->student.number, stu) == 0)
{
printf("请输入学生姓名,成绩:");
scanf("%s%f", cur->student.name, &cur->student.score);
savestudent(head);//持久化学生信息
printf("修改成功\n");
return;
}
cur = cur->next;
}
printf("未找到该学生\n");
}
void deletestudent(node* head)
{
printf("请输入要删除的学生的学号:");
char stu[15];
scanf("%s", stu);
node* cur = head;
while (cur->next!=NULL)
{
if (strcmp(stu, cur->next->student.number) == 0)
{
node* tmp = cur->next;
cur->next = cur->next->next;
free(tmp);
tmp = NULL;
savestudent(head);
printf("删除成功\n");
return;
}
cur = cur->next;
}
printf("未找到该学生\n");
}
void sortstudent(node* head)
{
node* save = NULL;
node* cur = NULL;
for (node* turn = head->next; turn->next != NULL;turn=turn->next)
{
for ( cur = head->next; cur->next != save; cur = cur->next)
{
if (cur->next->student.score < cur->student.score)
{
stu temp = cur->student;
cur->student = cur->next->student;
cur->next->student = temp;
}
}
save = cur;
}
printstudent(head);
}
test.c :
#include"StudentStystem.h"
int main()
{
//创建头结点
创建一个指针变量head,使其指向通过调用malloc函数分配的内存区域,该内存区域的大小为结构体node的大小 。
head->next = NULL;
loadstudent(head);
menu();
while (1)
{
char c = _getch();
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':
system("cls");
printf("欢迎下次使用\n");
exit(0);
break;
default:
printf("请重新输入:");
break;
}
}
return 0;
}
学生成绩管理系统至此已完成,该系统与单链表的增加、删除、查找、修改关系紧密,大家若想多了解,点击以下链接便可。
有关单链表更多接口函数的详细说明:
C语言实现单链表的各个接口函数