C-链表和文件操作实现学生信息管理系统

该管理系统可以实现学生信息的读取、录入、删除、修改、查询、打印、保存。更多的功能大家可以自己拓展(比如排序,按照分数段查询)

注:笔者的 C 语言水暂时位于大一的水平,代码免不了种种问题,望大牛轻喷+指正。

实现学生信息的读取、录入、删除、修改、查询、打印、保存。
读取:从文件中读取已有内容;
录入:录入初始的学生信息数据;
输出:将学生信息数据输出到屏幕;
删除:用户指定学生学号,将该数据删除;
修改:用户指定待修改数据的学生学号,录入修改后的数据;
保存:保存学生信息到文件。
学生信息包括:学号、姓名、性别、班级、5门课程的成绩。
文件的第一行记录学生的个数。
文件其他行,每一行为一个学生的信息。

在此时间如果不懂链表的可以看我之前的文章:      C-创建可增删查改的单向链表


#include "stdio.h"
#include "stdlib.h"
#include "string.h"

char filename[20];

typedef struct student{
	int num;
	char name[20];
	char sex[4];
	int classNo;
	float math;
	float English;
	float Virtue;	//思修
	float code;
	float other;
	struct student *next;
}STD;

STD *create(int *num);
void input(STD *head,int *num);
void add(STD *head,int *num);
void del(STD *head,int *num);
void change(STD *head);
void search(STD *head);
void print(STD *head,int *num);
void save(STD *head,int *num);

void menu();

int main()
{
	int choose = 0;
	int count = 0;
	STD *head;
        FILE *fp;
	printf("请输入文件所在位置:\n");
	scanf("%s",filename);
        fp = fopen(filename,"r");
	if (fp == NULL)
	{
		printf("文件打开失败,程序结束\n");
		return 0;
	}
	fclose(fp);
	head = create(&count);
	if (head == NULL)
	{
		return 0;
	}
	printf("当前有%d条学生信息.\n",count);
	while(count == 0)
	{
		printf("您需要输入学生信息后才能继续操作:\n");
		input(head,&count);
	}
	menu();
	scanf("%d",&choose);
	while(1){
		switch(choose){
			case 1:
				add(head,&count);
			break;
			case 2:
				del(head,&count);
			break;
			case 3:
				search(head);
			break;
			case 4:
				change(head);
			break;
			case 5:
				print(head,&count);
			break;
			case 6:
				save(head,&count);
			break;					
			case 0:
				save(head,&count);
				return 0;
			break;
			default:
			break;		
		}
		menu();
		scanf("%d",&choose);
	}
	return 0;
}

STD *create(int *num)
{
	STD *head,*p,*next,*tmp;	//tmp代表前一个
	FILE *fp = fopen(filename,"rb");
	head = (STD *)malloc(sizeof(STD));
	if (head == NULL)
	{
		printf("内存分配失败,程序结束\n");
		return NULL;
	}
	head->next = NULL;
	p = head;
	fread(num,sizeof(int),1,fp);
        if(*num == 0){
                return head;
        }
	while(!feof(fp)){
		next = (STD *)malloc(sizeof(STD));
		if (next == NULL)
		{
			printf("内存分配失败,程序结束\n");
			return NULL;
		}
		fread(next,sizeof(STD),1,fp);
		p->next = next;
		tmp = p;
		p = next;
	}
	free(p);
	tmp->next = NULL;
	return head;


}
void input(STD *head,int *num)
{
	//首次输入信息
	STD *p,*next;
	int times = 1;
	p = head;
	while(times){
		next = (STD *)malloc(sizeof(STD));
		if (next == NULL)
		{
			printf("内存分配失败,程序结束\n");
			return ;
		}
		printf("请输入学生的 学号 姓名 性别 班级 高数 英语 思修 程序设计 其他,用空格隔开(当学号为0时输入结束)\n");
		scanf("%d",&next->num);
		if (next->num != 0)
		{
			scanf("%s%s%d%f%f%f%f%f",next->name,next->sex,&next->classNo,&next->math,&next->English,&next->Virtue,&next->code,&next->other);
			(*num)++;
			p->next = next;
			p = next;
		}else{
			times = 0;
		}
	}
        p->next = NULL;
	if (*num != 0)
	{
		printf("录入成功,欢迎继续使用!\n");
	}

}

void add(STD *head,int *num)
{
	STD *p,*next;
	int i = 0;
	int position;
	p = head;
	printf("当前已经有%d条学生信息\n",*num);
	printf("请输入要增加到的位置: \n");
	scanf("%d",&position);
	while(p != NULL){
		if (position - 1 == i)
		{
			next = (STD *)malloc(sizeof(STD));
			if (next == NULL)
			{
				printf("内存分配失败,程序结束\n");
				return ;
			}
			printf("请输入要增加的学生的 学号 姓名 性别 班级 高数 英语 思修 程序设计 其他,用空格隔开(当学号为0时输入结束)\n");
			scanf("%d%s%s%d%f%f%f%f%f",&next->num,next->name,next->sex,&next->classNo,&next->math,&next->English,&next->Virtue,&next->code,&next->other);
			(*num)++;
			next->next = p->next;
			p->next = next;
			printf("增加成功\n");
			return ;
		}else{
			i++;
			p = p->next;
		}
	}
	printf("增加失败\n");
}
void del(STD *head,int *num)
{
	int s_num;
	char s[2];
	STD *p,*tmp;
	p = head;
	tmp = NULL;
	printf("请输入要删除学生的学号:\n");
	scanf("%d",&s_num);
	while(p != NULL){
		if (p->num == s_num)
		{
			printf("已经找到学号为%d %s 同学的信息,是否删除:(y/n)\n",p->num,p->name);
			scanf("%s",s);
			if (strcmp(s,"Y") == 0||strcmp(s,"y") == 0)
			{
				tmp->next = p->next;
				free(p);
				(*num)--;
				printf("删除成功\n");
				return ;
			}
		}else{
			tmp = p;
			p = p->next;
		}
	}
	printf("删除失败\n");

}
void change(STD *head)
{
	int s_num;
	char s[2];
	STD *p;
	p = head;
	printf("请输入要修改学生的学号: \n");
	scanf("%d",&s_num);
	while(p != NULL){
		if (p->num == s_num)
		{
			printf("已经找到学号为 %d|%s同学的信息,是否修改(y/n):\n",p->num,p->name);
			scanf("%s",s);
			if (strcmp(s,"Y") == 0||strcmp(s,"y") == 0)
			{
				printf("请输入该生新的 学号 姓名 性别 班级 高数 英语 思修 程序设计 其他,用空格隔开:\n");
				scanf("%d%s%s%d%f%f%f%f%f",&p->num,p->name,p->sex,&p->classNo,&p->math,&p->English,&p->Virtue,&p->code,&p->other);
				printf("修改成功\n");
				return ;
			}
		}else{
			p = p->next;
		}
	}
	printf("修改失败\n");
}
void search(STD *head)
{
	int s_num;
	STD *p;
	p = head;
	printf("请输入要查询的学生的学号: \n");
	scanf("%d",&s_num);
	while(p != NULL){
		if (p->num == s_num)
		{
			printf("已找到该生信息: \n");
			printf("学号: %d | 姓名: %s | 性别: %s | 班级: %d | 高数: %.2f | 英语: %.2f | 思修: %.2f | 程序设计: %.2f | 其他: %.2f |\n",p->num,p->name,p->sex,p->classNo,p->math,p->English,p->Virtue,p->code,p->other);;
			return ;
		}else{
			p = p->next;
		}
	}
	printf("未找到信息,请检查信息输入是否正确\n");
}
void print(STD *head,int *num)
{
	STD *p;
	p = head->next;
	printf("/*********************************/\n");
	printf("学号   姓名   性别   班级   高数   英语   思修   程序设计   其他\n");
	while(p != NULL){
		printf("%-10d%10s%4s%4d%6.2f%6.2f%6.2f%6.2f%6.2f\n",p->num,p->name,p->sex,p->classNo,p->math,p->English,p->Virtue,p->code,p->other);
		p = p->next;
	}
	printf("/*********************************/\n");
	printf("\t\t当前系统有 %d 位同学的信息\n",*num);
}
void save(STD *head,int *num)
{
	STD *p;
	FILE *fp;
	int i = 0;
	p = head->next;
	if ((fp = fopen(filename,"wb")) == 0)
	{
		printf("文件打开失败,请检查文件路径是否正确\n");
		printf("当前文件路径为:%s\n",filename);
		return ;
	}
	fwrite(num,sizeof(int),1,fp);
	while(p != NULL){
		if (fwrite(p,sizeof(STD),1,fp) != 0)
		{
			i++;
		}
		p = p->next;
	}
	if (i == *num)
	{
		printf("数据保存成功!\n");
	}else{
		printf("共有%d条数据,其中已保存%d条数据,%d条保存失败!\n",*num,i,*num-i);
	}
}
void menu()
{
	printf("||*欢迎使用本学生管理系统*||\n");
	printf("1.增加学生信息\n");
	printf("2.删除学生信息\n");
	printf("3.查找学生信息\n");
	printf("4.修改学生信息\n");
	printf("5.查看全部信息\n");
	printf("6.保存所有更改\n");
	printf("0.保存并退出\n");
	printf("||********************||\n");
	printf("请输入对应功能前的序号: \n");
}

在写代码的时候,笔者遇到了一个问题,看上述代码的朋友可能会注意到我在 create 函数中用到了一个结构体指针 tmp,可能有的朋友会不懂,这里 tmp 代表当前链节的上一个链节。

当文件内部的位置指针指向文件结束时,并不会立即设置FILE结构中的文件结束标识,只有再执行一次读文件操作,才会设置结束标志,此后调用feof()才会返回为真。

通俗一点说,在正常使用 feof 时往往会出现多读取一行的问题,(在第一版程序中 如果读取一个 有3个学生信息的二进制文件时,会显示4行学生信息,其中第四个同学的所有信息都是0),这样就会比较尴尬。

通过修改,在读取二进制文件后,当读取到文件结尾时,会创建一个链节,且内容位空,此时我们在上个循环中获取倒数第二个链节(即最后一个有意义的链节),让倒数第二个链节的 next = NULL,然后释放掉最后一个无意义的链节。

while(!feof(fp)){
	next = (STD *)malloc(sizeof(STD));
	if (next == NULL)
	{
		printf("内存分配失败,程序结束\n");
		return NULL;
	}
	fread(next,sizeof(STD),1,fp);
	p->next = next;
	tmp = p;
	p = next;
}
free(p);
tmp->next = NULL;