学生信息管理系统(完整版)
代码实现还是比较简单的,学习数据结构的话,应该多动手画画图,便于理解,在这个过程中,你会对指针的使用更上一步,当然也有很多地方有很多坑,比如指针总是指向一些未知区域,这是在指针移动的时候,你没有做好结束条件的判断,倘若在这之后你还在修改里面的内容的话,那么后果是无法想象的。以上是这个项目的简单流程,一些整理学生表的功能我就不赘述了,这个功能一般使用在删除了学生之后的时候,我可以使用这个函数整理一下
对于刚开始接触数据结构,里面的很多知识都很难理解,需要自己多动手敲代码才能提升自己的能力。先简单说说这个学生管理系统,因为考虑到总是会发生改查等功能,并且考虑到资源的合理利用,采用数组或者使用顺序表都不是最佳选(开大了,会造成资源浪费,开小了无法满足正常的使用),所以我们应该使用动态内存,需要用多少我开多少,既不浪费,也能做到持续录入学生信息。
话不多说,上源码:
link.h文件
typedef int data_t;
typedef struct student//定义一个结构体,分别存学号,成绩,平均分等
{
int num;
float Chinese;
float Math;
float English;
float Average;
struct student *next;
}stu,*stup;
stup creat_list();//建立头节点函数
stup getnode(stup H,data_t x);//返回节点函数。这个函数作用很大,功能是返回x学号的这个学生的节点
stup freelist(stup H);//释放链表函数。传一个NULL给调用的节点
void help();//帮助函数
void clear();//清屏函数
int Type(stup H);//录入学生函数
int count(stup H);//计算平均分函数
int sort(stup H);//排序函数
int search(stup H);//查找学生函数
int del(stup H);//删除学生函数
int List(stup H);//打印学生信息函数
int listempty();//链表为空打印函数
int length(stup H);//求链表长度函数
int arrange(stup H);//整理学生信息函数
int modify(stup H);//修改学生信息函数
link.c文件
#include <stdio.h>
#include"link.h"
#include <stdlib.h>
void help()//帮助手册函数
{
printf("**********************************\n");
printf("* 学生成绩管理系统——帮助菜单 *\n");
printf("**********************************\n");
printf("* H = 显示帮助菜单 *\n");
printf("* T = 成绩录入 *\n");
printf("* A = 计算学生平均分 *\n");
printf("* L = 列出成绩表 *\n");
printf("* P = 按平均成绩由高到低排序*\n");
printf("* S = 按学号查询学生成绩 *\n");
printf("* F = 整理学生表 *\n");
printf("* X = 删除学生信息 *\n");
printf("* M = 修改学生信息 *\n");
printf("* C = 清屏 *\n");
printf("* Q = 退出程序 *\n");
printf("**********************************\n");
printf("* <C> 2023.0809 By 小企鹅 *\n");
printf("**********************************\n");
printf("请输入命令=");
return ;
}
stup creat_list()//建立头节点函数
{
stup H;
H=(stup)malloc(sizeof(stu));//动态分配节点
if(H==NULL)
{
printf("H malloc is error\n");
return H ;
}
//给定初始值,因为学生学号从1开始,头节点设置全零
H->num=0;
H->Chinese=0;
H->Math=0;
H->English=0;
H->Average=0;
H->next=NULL;//后继设为NULL
return H;
}
int Type(stup H)//录入学生函数
{
data_t n;
if(H==NULL) return -1;
int a;
float b,c,d,e;
stup q,p;
q=H;
printf("请输入即将录入学生的人数:");
scanf("%d",&n);
printf("请输入%d名学生的三門课的成绩:\n",n);
printf("学号\t语文\t数学\t外语\n");
for(int i=0;i<n;i++)//循环录入,按照输入的学生人数录入
{
p=(stup)malloc(sizeof(stu));//开辟新节点
if(p==NULL)
{
printf("p malloc is error\n");
return -1;
}
scanf("%d%f%f%f",&a,&b,&c,&d);
//对节点赋值
p->num=a;
p->Chinese=b,
p->Math=c;
p->English=d;
p->Average=0;
p->next=NULL;//节点指向空
while(q->next)//遍历链表,实现尾部插入
{
q=q->next;
}
q->next=p;//实现尾部插入
}
printf("请输入命令=");
return 0;
}
stup getnode(stup H,data_t x)//获取对应学号的节点函数
{
if(H==NULL) return NULL;
if(x<0)
{
return NULL;
}
if(x==0) return H; //返回H,这个对于删除第一个节点的学生会有使用,不应该返回空。
stup q;
q=H;
int i=0;
while(i<x)//遍历查找
{
q=q->next;
if(q==NULL)
{
return NULL;
}
i++;
}
return q;//返回找到的这个学生的节点
}
int length(stup H)//求链表长度函数
{
if(H==NULL) return -1;
stup q;
q=H;
int sum=0;
while(q)//遍历链表,获得长度
{
sum++;
q=q->next;
}
return sum;//返回长度值
}
int del(stup H)//删除学生信息函数
{
if(H==NULL) return -1;
if(H->next==NULL)//如果只有一个头节点
{
printf("H is empty\n");
printf("请输入命令=");
return -1;
}
stup q,p,r;
int x;
printf("请输入你要删除的学生的学号:");
scanf("%d",&x);
if((q=getnode(H,x-1))==NULL)//得到的是前一个的节点
{
printf("删除失败,没有对应学号的学生\n");
printf("请输入命令=");
return -1;
}
if(q->next==NULL)//前驱没有后继,代表这个节点是尾部节点,没有后继,那么删除后继显然不合理
{
printf("删除失败,没有对应学号的学生\n");
printf("请输入命令=");
return -1;
}
p=q->next;//暂时保存这个待删除的节点,你不保存,释放的时候,会找不到这个节点的
q->next=q->next->next;//重新指向后一个节点
free(p);//删除之后,释放。
p=NULL;//置空
printf("删除成功,输入T命令查或者输入F调整学生表\n");
printf("请输入命令=");
return 0;
}
int List(stup H)//打印学生信息函数
{
if(H==NULL) return -1;
printf("学生成绩如下:\n");
printf("学号\t语文\t数学\t外语\t平均分\n");
stup p;
p=H->next;//指向第一个元素,头节点不需要打印。
while(p)//遍历p,打印信息
{
printf("%d\t%.1f\t%.1f\t%.1f\t%.1f\n",p->num,p->Chinese,p->Math,p->English,p->Average);
p=p->next;
}
printf("请输入命令=");
return 0;
}
int listempty()//打印为空函数
{
printf("成绩表为空!请先输入命令T录入学生成绩\n");
printf("请输入命令=");
return 0;
}
int arrange(stup H)//整理学生信息函数
{
if(H==NULL) return -1;
stup q=H->next;//指向第一个节点
int i=1;
while(q)//遍历链表
{
q->num=i++; //重新给学号赋值
q=q->next;//指针移动
}
printf("整理学生表完成,你可以输入命令L查看\n");
printf("请输入命令=");
return 0;
}
int count(stup H)//计算平均分函数
{
if(H==NULL) return -1;
stup q;
q=H->next;//指向第一个节点
while(q)//遍历节点
{
q->Average=(q->Chinese+q->Math+q->English)/3.0;//求出对应的平均分
q=q->next;
}
printf("平均分已计算。请使用命令L查看。\n");
printf("请输入命令=");
return 0;
}
int sort(stup H)//排序函数,采用插入排序
{
if(H==NULL) return -1;
if(H->next->next==NULL)
{
printf("Only one,sort is unnecessary\n");
printf("请输入命令=");
return -1;
}
stup q,p,r,t;//建立四个节点
p=H->next->next;//指向第二个元素
q=r=H;//指向第一个元素
H->next->next=NULL;//断开链表。
while(p)//当待插入链表中还有节点
{
q=r=H;//每次插入一个后,节点回到初始状态。重新判断插入点
while(q && p->Average <= q->Average)//当待插入的节点小于该节点,那么我就往后走,因为要保证分数高的在前面,分数低的在后面。
{
r=q;//r表示q前驱,因为要插入q节点这个位置,所以保留前一个位置r
q=q->next;
}
t=p->next;//先让后续的链表用t指针保持起来。
p->next=r->next;//接下来两步实现插入
r->next=p;
p=t;//p重新指向下一个节点
}
printf("学生成绩已经拍好序,请输入指令L查看\n");
printf("请输入命令=");
return 0;
}
int search(stup H)//查找学生信息函数
{
if(H==NULL) return -1;
int e;
stup q;
printf("请输入要查询的学生的学号:");
scanf("%d",&e);
if((q=getnode(H,e))==NULL||q==H)//返回这个节点的函数为空,或者为头节点,都为非法数据
{
printf("查找失败,没有你要查找的学生\n");
printf("请输入命令=");
return -1;
}
printf("学号\t语文\t数学\t外语\t平均分\n");
printf("%d\t%.1f\t%.1f\t%.1f\t%.1f\n",q->num,q->Chinese,q->Math,q->English,q->Average);
printf("请输入命令=");
}
void clear()//清屏函数
{
system("clear");
printf("请输入命令=");
}
int modify(stup H)//修改学生信息函数
{
if(H==NULL) return -1;
stup p;
int a,n;
float b,c,d;
printf("请输入你要修改学生信息的学号:");
scanf("%d",&n);
if((p=getnode(H,n))==NULL||p==H)//返回这个节点为空或者为头节点都是非法数据
{
printf("没有你要修改信息的学生\n");
printf("请输入命令=");
return -1;
}
else
{
printf("该学生的信息如下所示:\n");//修改之前,先打印这个学生的信息。
printf("学号\t语文\t数学\t外语\t平均分\n");
printf("%d\t%.1f\t%.1f\t%.1f\t%.1f\n",p->num,p->Chinese,p->Math,p->English,p->Average);
printf("请输入你要修改的内容\n");
printf("学号\t语文\t数学\t外语\n");
scanf("%d%f%f%f",&a,&b,&c,&d);
//重新赋值,实现修改
p->num=a;
p->Chinese=b,
p->Math=c;
p->English=d;
p->Average=0;
printf("修改信息成功,输入命令L可查看\n");
printf("请输入命令=");
}
return 0;
}
stup freelist(stup H)//链表释放函数,返回值为NULL
{
if(H==NULL) return NULL;
stup p;
while(H)
{
p=H;//中间变量
free(p);//完成释放
H=H->next;//节点移动
}
p=NULL;
return NULL;//返回给我的main函数的H,因为在哪里它替我保存了这个链表。单独在这这里H=NULL,是不行的,因为这里是局部变量,外面也起不到作用。所以使用这种简单的方式。
}
test.c文件
#include<stdio.h>
#include"link.h"
#include <stdlib.h>
stup H,q;
//一块的测试函数,见名知意,我就不赘述了
void test_Type();
void test_del();
void test_count();
void test_List();
void test_search();
void test_arrange();
void test_sort();
void test_modify();
void test_clear();
void test_help();
int main()
{
char c;
help();
int d;
H=creat_list();//建立头节点,H指向这个节点
if(H==NULL) return -1;
while(1)//循环函数
{
scanf("%c",&c);
switch(c)
{
case 'T' : test_Type();//录入学生信息
break;
case 'X' : test_del();//删除学生信息
break;
case 'L' : test_List();//展示学生信息
break;
case 'S' : test_search();//查找学生
break;
case 'F' : test_arrange();//调整学生信息
break;
case 'A' : test_count(); //计算平均分
break;
case 'P' : test_sort(); //排序
break;
case 'M' : test_modify(); //修改学生信息
break;
case 'C' : test_clear();//清屏
break;
case 'H' : test_help();//查看帮助手册
break;
case 'Q' : printf("press anything quit\n");//退出
H=freelist(H);
q=NULL;
return -1;
default : printf("input error!once again\n请输入命令=");//其他
getchar();
break;
}
}
H=freelist(H);//调用释放函数。
q=NULL;
return 0;
}
//需要注意的是:对于每次输入都有垃圾字符会留在缓冲区,需要使用getchar()”吃掉“。
//调用这些删改查函数的时候,都应该先判断链表中节点是否为空,也就是判断有没有学生信息。
void test_Type()
{
Type(H);
getchar();
}
void test_del()
{
q=H;
if(q->next)
{
del(H);
getchar();
}
else
{
listempty();
getchar();
}
}
void test_count()
{
q=H;
if(q->next)
{
count(H);
getchar();
}
else
{
listempty();
getchar();
}
}
void test_List()
{
q=H;
if(q->next)
{
List(H);
getchar();
}
else
{
listempty();
getchar();
}
}
void test_search()
{
q=H;
if(q->next)
{
search(H);
getchar();
}
else
{
listempty();
getchar();
}
}
void test_arrange()
{
q=H;
if(q->next)
{
arrange(H);
getchar();
}
else
{
listempty();
getchar();
}
}
void test_sort()
{
q=H;
if(q->next)
{
sort(H);
getchar();
}
else
{
listempty();
getchar();
}
}
void test_modify()
{
q=H;
if(q->next)
{
modify(H);
getchar();
}
else
{
listempty();
getchar();
}
}
void test_clear()
{
clear();
getchar();
}
void test_help()
{
help();
getchar();
}
运行效果截图:

截图过多,我就不一一展示了。对于本个项目,基本的增删改查都实现了,使用简单的链表就可以实现这一功能。给大家说一下思路:
- 首先,我们需要一个头节点,来作为整个链表的开头,以便我能通过这个节点指针找到我的后续的数据(通过malloc函数去动态分配内存)。
- 建立好头节点之后,我可以先实现简单的录入功能,使用两个指针,一个用来遍历链表,一个用来得到我录入的信息,将我所输入的数据通过链表的方式进行”尾插“录入。(实现录入功能能)
- 先说getnode()函数,这个函数的作用是返回这个节点的地址,因为对于我实现删除或者查找甚至随机插入等的时候,我都需要先知道对应数据的前驱,也就是指向这个数据的指针,特别是对于删除功能而言,我们是应该先找到前一个结点,然后使用next指针,重新指向就可以实现这个功能。对于删除时输入的学号,也应该判断一下数据是否合法,非法打印错误信息。注:当你删除一个节点的时候,你应该使用free函数,将你删除的节点释放掉。
- 然后查找功能,你可以通过遍历链表的方式来获取,输入对应学号,当学号等于我的指针所指向的元素内容时,就可以将其打印(或者你可以使用getnode()函数,对于函数内部,我们可以经量少调用函数,虽然调用起来方便,但在一定程度上会影响效率)。遍历完还没找到,说明数据非法,那么当然会打印错误提示信息。
- 对于修改功能来说,首先也是对于数据非法的处理,非法就打印错误信息等。修改功能的实现,也很简单,通过遍历链表,找到了这个学号的节点,重新录入信息即可。
- 对于学生成绩排序这一功能,使用的是插入排序,也就是先断开节点。当我待插入的值,在合适位置的时候,通过改变指针指向完成插入。
以上是这个项目的简单流程,一些整理学生表的功能我就不赘述了,这个功能一般使用在删除了学生之后的时候,我可以使用这个函数整理一下我的学生表。
最后说说实现这个项目的一些经历和过程吧:
代码实现还是比较简单的,学习数据结构的话,应该多动手画画图,便于理解,在这个过程中,你会对指针的使用更上一步,当然也有很多地方有很多坑,比如指针总是指向一些未知区域,这是在指针移动的时候,你没有做好结束条件的判断,倘若在这之后你还在修改里面的内容的话,那么后果是无法想象的。总之也就是说:
- 在使用这个节点之前,先看是否定义好。
- 其次看指针是否指向正确。
- 节点是否是NULL。
- 对于循环处理的时候,应该先检查结束条件,就像写for()循环一样,应该写好初值,写好结束条件,写好改变值。不然你在后期使用这个节点的时候,也会出现段错误等错误。
- 在传入头节点的时候,都应该先判断这个节点是否开辟好了(每个都应该判断)。
- 因为这是动态分配的内存,我们应该malloc和free成对出现,一定要养成良好的习惯。
- 注意一下对于调用freelist()函数的时候,里面的H是个局部变量,你free之后置NULL其实也没什么作用,因为到了main里面,这个指针还是有指向,你后期还是可以操作到。这里应该使用最简单的传一个NULL给H,保证H置空。
- 最后,对于调试功能的话,可以使用gdb设置断点一步一个调试,实现一个功能测试一个功能,别以测正确数据为目的,我们更多应该要测试的是那些非法数据。这样才能更加提升我们的能力。
以上是小编初学链表的一些心得与体会。希望对你有所帮助。
更多推荐

所有评论(0)