用单向链表实现学生管理系统

        首先创建三个文件stu.c、stu.h、main.c

        因为我们接下来会用到字符串比较、malloc函数以及自定义函数,故需要四个头文件

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

首先在开始写代码之前,需要明确,我们需要哪些功能,实现这些功能有需要哪些模块。

        首先要实现基本的增、删、改、查、以及遍历,我们就需要 “尾插函数”、“删除节点函数”、“查询函数”、”遍历函数“。在插入函数中,我们需要申请节点,故需要“节点申请函数”、“头节点创建函数”;在删除函数中,我们要判断链表是否为空,所以还需要”判空函数“。当我们进行查询时,需要找到前一个节点,故寻址函数也是必需品……

最后的最后,我们也不能忘记将链表销毁

所以我们需要在头文件stu.h中申明以下函数

#ifndef  __STU__
#define  __STU__

//创建头节点
Linklist *create();

//申请子节点
Linklist *node_add( STU student);

//判空
int empty(Linklist *S);

//遍历
void list_show(Linklist *L); 

//增加学生信息(默认尾插,你别无选择)
int list_intsrt_tail(Linklist *S, STU student);

//寻找前一个位置地址
Linklist *find(Linklist *S, Linklist *p);

//任意位置删除
void delete_everypos(Linklist *S, Linklist *p);

//基于姓名删除
int delete_by_name(Linklist *S, char n[]);

//基于学号删除
int delete_by_id(Linklist *S, int n);

//基于姓名修改
int updata_name(Linklist *S, char arr[]);

//基于学号修改
int updata_id(Linklist *S, int id);

//基于姓名查找
int select_by_name(Linklist *S, char n[]);

//基于学号查找
int select_by_id(Linklist *S, int n);

//基于成绩查找
int select_by_score(Linklist *S, float n);

//学号升序
void sort(Linklist *S);

//释放链表
void free_list(Linklist* S);

#endif

然后是创建链表的结构体

我采用的是结构体嵌套共用体,在共用体里用结构体指针指向另一个结构体的方法来创建变量

typedef struct STU
{
	char name[10];  //姓名
	int id;        //学号
	float score;  //成绩
}STU;

typedef struct Node
{
	union
	{
		STU *info;      //学生信息
		int len;        //链表长度
	};	
    //指针域
	struct Node *next;
}Linklist;

        用在共用体内定义结构体指针的方法,相比与直接在共用体内定义结构体的方法,虽然以后申请节点要手动申请一次STU结构体的空间,但是这样做相对的会更加节省空间(指头节点省空间)

        思路梳理完毕,接下来是各个功能函数的实现

        在主函数中,我采取经典的用while(1)实现死循环的手法建立菜单框架。但是因为在增删改查中,有部分模块有多个分支方向,比如,查找中,可以选择通过学号查找,也可以通过姓名查找。于是我经过考虑,最终选择在switc结构里嵌套switch

以下是主函数代码总览

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


int main(int argc, const char *argv[])
{
	//创建链表
	Linklist *S = create();
	while(1)
	{
		printf("欢迎使用学生管理系统\n");
		printf("请选择您要使用的功能\n");
		printf("1:增加学生信息\n");
		printf("2:删除学生信息\n");
		printf("3:修改学生信息\n");
		printf("4:查找学生信息\n");
		printf("5:查看全部学生信息\n");
		printf("6:学号升序\n");
		printf("0:退出\n");
		printf("请输入编号>>>");
		int n;
		scanf("%d",&n);
		switch(n)
		{
			case 1:    //增
				{
					for(int i=0;; i++)        //在栈区申请变量,录入成绩
					{
						STU student;
						printf("请输入姓名>>>");
						scanf("%s",student.name);
						//当输入#时,录入结束
						if(strcmp( student.name, "#" ) ==0 )
						{
							break;
						}
						printf("请输入学号>>>");
						scanf("%d",&student.id);
						printf("请输入成绩>>>");
						scanf("%f",&student.score);
						printf("\n");

						//在堆区申请空间,把栈区变量的值录入
						list_intsrt_tail(S, student);       //调用增函数
					}
					printf("信息录入完成\n\n");
				}break;
			case 2:    //删
				{
					printf("1:基于姓名删除\n");
					printf("2:基于学号删除\n");
					printf("请选择您要使用的功能分支 >>> ");
					int n2;
					scanf("%d",&n2);
					switch(n2)
					{
						case 1:  //基于姓名删除
							{
								char n[10];
								printf("\n请输入学生姓名:");
								scanf("%s",n);
								delete_by_name(S, n);
							}break;
						case 2:  //基于学号删除
							{
								int n;
								printf("\n请输入学生学号:");
								scanf("%d",&n);
								delete_by_id(S, n);
							}break;
						default :printf("选项不存在\n");break;
					}
				}break;
			case 3:    //改
				{
					printf("1:基于姓名修改\n");
					printf("2:基于学号修改\n");	
					printf("请选择您要使用的功能分支 >>> ");
					int n3;
					scanf("%d",&n3);
					switch(n3)
					{
						case 1:  //基于姓名修改
							{
								char arr[10];
								printf("请输入姓名>>>");
								scanf("%s",arr);
								updata_name(S, arr);
								
							}break;
						case 2:  //基于学号修改
							{
								int id;
								printf("请输入学号>>>");
								scanf("%d",&id);
								updata_id(S, id);
							}break;
						default :printf("选项不存在\n");break;
					}
				}break;
				
			case 4:    //查
				{
					printf("基于姓名查找\n");
					printf("基于学号查找\n");
					printf("基于成绩查找\n");
					printf("请选择您要使用的功能分支 >>> ");
					int n4;
					scanf("%d",&n4);
					switch(n4)
					{
						case 1:  //基于姓名查找
							{
								char arr[10];
								printf("请输入姓名>>>");
								scanf("%s",arr);
								select_by_name(S, arr);
							}break;
						case 2:  //基于学号查找
							{
								int n;
								printf("请输入学号>>>");
								scanf("%d",&n);
							}break;
						case 3:  //基于成绩查找
							{
								float n;
								printf("请输入成绩>>>");
								scanf("%f",&n);
							}break;
						default :printf("选项不存在\n");break;
					}
				}break;
			case 5:               //遍历链表,查看学生信息
				{
					list_show(S);
				}break;
			case 6:               //学号顺序排列
				{
					sort(S);
				}break;
			case 0:
				{
					exit (0);
				}break;
			default :printf("编号输入有误,请重新输入\n");break;
		}
	}
	return 0;
}

        接下来是其中用到的函数

        首先是最早的创建节点函数,与一般情况从的操作并无不同。

//创建头节点
Linklist *create()
{
	Linklist *S = (Linklist*)malloc(sizeof(Linklist));
	if( NULL==S )
	{
		printf("头节点创建失败\n");
		return NULL;
	}

	//初始化
	S->next = NULL;
	S->len = 0;
	printf("创建成功\n");
	return S;
}

        但是在申请子节点空间时就要注意了,在Linklist结构体中的共用体内的结构体指针,在使用malloc函数时,并不会自动帮你申请空间。打个比方,这个指针相当于一张小纸片,上面写着你公司所在的地址,但是在malloc函数里,这张纸所占用的空间,也只是一张纸大小罢了。所以,你需要手动调用malloc函数去申请它指向的这个结构体所占用的空间。

//申请子节点
Linklist *node_add( STU student)
{
	Linklist *p = (Linklist*)malloc(sizeof(Linklist));
	p->info = (STU*)malloc(sizeof(STU)); //手动申请共用体内结构体指针指向的结构体所占用的空间
	if(NULL == p || NULL == p->info)
	{
		printf("子节点申请失败\n");
	}
	
	//保存数据
	*(p->info) = student;
	p->next = NULL;
	//printf("节点申请成功\n");
	return p;

        判空函数最简单,只需要一个三目运算符构成的语句即可

//判空
int empty(Linklist *S)
{
	return S->next == NULL ? 1:0;
}

        因为学生信息表,一般信息直接添加在最后,故处于方便考虑,我只提供尾插法来执行增加信息功能

//增加学生信息(默认尾插,你别无选择)
int list_intsrt_tail( Linklist *S, STU student )
{
	if(NULL == S)
	{
		printf("所给链表不合法\n");
		return -1;
	}
	
	Linklist *p = node_add(student);
	Linklist *q = S;
	while(q->next != NULL)
	{
		q = q->next;
	}
	q->next = p;
	//表的变化
	S->len++;
}

        当插入学生信息后,因为之后的代码会越来越长,我们需要边写代码遍检查代码的正确性,于是,我们需要一个遍历函数,以便时刻监视链表内的数据变化是否符合预期

//遍历
void list_show(Linklist *L) 
{
	//判断逻辑
	if(NULL==L || empty(L))  //判断链表是否存在以及是否为空
	{
		printf("遍历失败\n");
		return ;
	}
	//遍历 
	printf("链表元素分别是:\n");
	Linklist *q = L->next;  //从第一个元素开始
	while(q != NULL)          //到最后一个元素结束
	{
		printf("%s\t%d\t%.1f\t\n",q->info->name, q->info->id, q->info->score);
		q=q->next;
	}
	printf("\n");              //处于美观性考虑,我空了一行出来
}

        至此,我们完成了增删改查四大模块中的“增”。下一步是“删”

        删除功能依据判定的变量不同,有两个方向,基于姓名和基于学号,但是总体来说,两个函数极为相似,只是基于的变量不同罢了。它们的函数发挥作用,还需要两个辅助模块:寻找前一个节点地址以及任意位置删除函数,这两个模块组合在一起,形成了删除功能的核心模块

//寻找前一个位置地址,寻址函数
Linklist *find(Linklist *S, Linklist *p)
{
	Linklist *q = S;        //让指针q指向头节点
	while(q->next != p)      //让指针指到目标节点的前一个节点处停下
	{
		q = q->next;
	}
	return q;                //返回其地址
}

//任意位置删除
void delete_everypos(Linklist *S, Linklist *p)
{
	Linklist *q = find(S, p);  //调用寻址函数,找到p指针所在节点的前一个节点
	q->next = p->next;          //让p的前一个节点的指针绕过p,指向p的后一个节点
	p->next = NULL;             //使p指向的节点断开与链表的连接
	free(p);           //释放p指向节点的空间,即删除了节点p
	p = NULL;           //让p指向NULL,防止其变成野指针
	//表的变化
	S->len--;           
}

        有了这两个函数组成的模块,删除就变得很简单了,我们要做的,只是遍历链表,一个一个节点比对,找出我们所需的节点,进行操作即可,随后进行的改、查函数也脱胎于此

//基于姓名删除
int delete_by_name(Linklist *S, char n[])
{
    //逻辑判断
	if(NULL == S || empty(S))
	{
		printf ("表非法,或表为空\n");
		return -1;
	}
	Linklist *p = S->next;
	//判定条件:没找到匹配的姓名,或者p的下一位不为空
	for(int i=0; strcmp(p->info->name, n) != 0 && p->next != NULL; i++)
	{
		p = p->next;
	}
    //判断离开循环是因为找到了节点,还是跑完了链表
	if(strcmp(p->info->name , n) == 0)
	{
		delete_everypos(S, p);        //找到节点即调用删除模块删掉节点
		printf("删除成功\n\n");
	}else
	{
		printf("未找到该学生\n");
	}
}
/********************************************/
//基于学号删除
int delete_by_id(Linklist *S, int n)
{
    //逻辑判断
	if(NULL == S || empty(S))
	{
		printf ("表非法,或表为空\n");
		return -1;
	}
	Linklist *p = S->next;
	//判定条件:没找到匹配的学号,或者p的下一位不为空
	for(int i=0; p->info->id != n && p->next != NULL; i++)
	{
		p = p->next;
	}
    //判断离开循环是因为找到了节点,还是跑完了链表
	if(p->info->id == n)
	{
		delete_everypos(S, p);       //找到节点即调用删除模块删掉节点
		printf("删除成功\n\n");
	}else
	{
		printf("未找到学生\n");
	}
}

        改查的函数与删除函数的区别不大,在修改函数中,为了方便起见,我采用了结构体对结构体赋值的方式,可以减少操作步骤。在输入结构体内信息时,我们可以直接定义一个变量,在栈区申请空间,可以减少手动申请空间释放空间的繁琐操作。具体程序原理与删除函数大差不差,我在此就不过多赘述。

//基于姓名修改
int updata_name(Linklist *S, char arr[])
{
	//逻辑判断
	if(NULL == S || empty(S))
	{
		printf("表非法或表空,无法修改\n");
		return -1;
	}
	Linklist *p = S->next;
	//寻找
	//判定条件:没找到匹配的姓名,或者p的下一位不为空
	for(int i=0; strcmp(p->info->name, arr) != 0 && p->next != NULL; i++)
	{
		p = p->next;
	}
	if(strcmp(p->info->name , arr) == 0)
	{
		STU student;
		printf("请输入姓名>>>");
		scanf("%s",student.name);
		printf("请输入学号>>>");
		scanf("%d",&student.id);
		printf("请输入成绩>>>");
		scanf("%f",&student.score);
		//直接结构体赋值
		*(p->info) = student;
		printf("\n修改完成\n\n");
	}else
	{
		printf("未找到学生信息\n\n");
	}
}

/**************************************************/

//基于学号修改
int updata_id(Linklist *S, int id)
{
	//逻辑判断
	if(NULL == S || empty(S))
	{
		printf("表非法或表空,无法修改\n");
		return -1;
	}
	Linklist *p = S->next;
	for(int i=0; p->info->id != id && p->next != NULL; i++)
	{
		p = p->next;
	}
	if(p->info->id == id)
	{
		STU student;
		printf("请输入姓名>>>");
		scanf("%s",student.name);
		printf("请输入学号>>>");
		scanf("%d",&student.id);
		printf("请输入成绩>>>");
		scanf("%f",&student.score);
		//直接结构体赋值
		*(p->info) = student;
		printf("\n修改完成\n\n");
	}
	else
	{
		printf("未找到学生信息\n\n");
	}
}

查找函数也没什么好说的,逻辑与其他两函数完全一致,本质上来说,删、改、查,都是要找到需要操作的节点,然后决定是将其抹除、将其修改、还是将其输出。这导致,它们只会在操作节点部分会有差别,主体函数则是基本一致。

以下是查找模块,不再赘述。

//基于姓名查找
int select_by_name(Linklist *S, char n[])
{
	if(NULL == S || empty(S))
	{
		printf ("表非法,或表为空\n");
		return -1;
	}
	Linklist *p = S->next;
	//判定条件:没找到匹配的姓名,或者p的下一位不为空
	for(int i=0; strcmp(p->info->name, n) != 0 && p->next != NULL; i++)
	{
		p = p->next;
	}
	if(strcmp(p->info->name , n) == 0)
	{
		printf("%s,学号为:%d,成绩为:%.1f",p->info->name, p->info->id, p->info->score);
	}else
	{
		printf("未找到该学生\n");
	}
}

/************************************************************/

//基于学号查找
int select_by_id(Linklist *S, int n)
{
	if(NULL == S || empty(S))
	{
		printf ("表非法,或表为空\n");
		return -1;
	}
	Linklist *p = S->next;
	//判定条件:没找到匹配的姓名,或者p的下一位不为空
	for(int i=0; p->info->id != n && p->next != NULL; i++)
	{
		p = p->next;
	}
	if(p->info->id == n)
	{
		printf("%s,学号为:%d,成绩为:%.1f",p->info->name, p->info->id, p->info->score);
	}else
	{
		printf("未找到该学生\n");
	}
}

然后是排序模块,其实按照我本来的构想,这个模块不是一个独立存在的功能,而是一个辅助函数,它将在增删改查四大功能的结束后,自动调用。这样可以保证链表内元素的有序。但是最后经过多方面的考虑,因为时间等因素,我放弃了这个方案,将其独立了出来。

以下是基于学号的升序排列,本制上不过是基础的冒泡排序法

//学号升序
void sort(Linklist *S)        //双循环冒泡排序法罢了
{
	Linklist *p = S->next;
	for(int i=1; p->next == NULL; i++)
	{
		for(int j=0; p->next->next == NULL; j++)
		{
			if(p->info->id > p->next->info->id)
			{                                       //用异或的方式交互值
				p->info->id ^= p->next->info->id;   // a ^= b;
				p->next->info->id ^= p->info->id;   // b ^= a;
				p->info->id ^= p->next->info->id;   // a ^= b; 
			}
			p = p->next;
		}
	}
	printf("排序完成\n");
}

最后我们还需要销毁链表,防止内存泄漏

总的来说,是靠循环使用头删法,最终释放头节点的空间来实现。

//释放链表
void free_list(Linklist* S)
{
	Linklist* q = S->next;           //定义指针指向第一个元素
	Linklist* p = S;                 //定义指针指向头节点(也可以直接拿头节点操作)
	for (int i = 0; p->next != NULL; i++)  //循环使用头删法,直到链表为空
	{
		p->next = q->next;  //孤立
		q->next = NULL;     //断开连接
		free(q);            //释放空间
		q = p->next;        //移动到下一个元素
	}
	free(S);       //释放头节点空间,并让头节点指针指向NULL,防止变成野指针
    S = NULL;
}

至此,一个完整的学生管理系统(丐版)就实现了

  • 2
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老K殿下

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值