[图文详解]C语言实现学生成绩管理系统,采用链表结构

目录

一、前言(学习请从头开始看)

二、要求和思路

2.1 要求

2.2 数据结构的定义

2.3 基本函数的申明

2.3.1 链表初始化

2.3.2 尾部增加节点

2.3.3 删除节点

2.3.4 修改节点 

2.3.5 搜索节点 

三、具体构建

3.1 函数申明 

3.2 读取文件

3.3 写入文件

3.4 增加学生

3.5 删除学生

3.6 修改学生

3.7 查找学生

3.8 显示学生

3.9 主菜单

3.10 主函数

四、完整代码(搬运请看这)

五、界面展示

5.1 显示信息

5.2 删除学生 

5.3 修改学生 

六、代码交流


一、前言

相信很多大一的同学在做c语言程序设计期末大作业的时候,都会被这些题目所困扰:学生成绩管理系统、学生信息管理系统、图书借阅管理系统、通讯录、选课系统......上网百度到别人的代码,有的人用C++写,有的人用类C写,有的人用C写,有的人用链表写,有的人用顺序表(结构体数组)写,搬运完别人的代码,还得自己看懂并且修改,以防和别的同学雷同。但不同的学校采用不同的c语言教材,不同的老师又有不同的理念:有的老师要求学生使用vb(Visual Basic)来进行编码,有的老师要求学生使用vs(Visual Studio)来进行编码......

vb的缺点是比较死板,规范陈旧,如变量申明必须在函数一开始的时候,否则会报错;vs体量大不说,要求用scanf_s来代替scanf,用fopen_s来代替fopen,并且scanf_s在接收字符串的时候用法和传统的scanf不一样,fopen_s的用法也和fopen稍有不同。个人认为如果不做大型项目,大学4年使用Dev-Cpp足以应付基础的C语言学习和C++学习,Dev-Cpp精巧强大,兼容性较好,不像vb和vs一样纠结于细枝末节,令初学者对C语言的学习望而生畏。一般的Dev-Cpp安装好后默认关闭了调试模式,需要手动打开,这里附上教程

说完IDE,接下来说说语言:受严蔚敏《数据结构(C语言版)》的影响,很多人在学习数据结构的时候逐渐将C语言代码转变为了类C代码,因为默认使用.cpp文件,类C代码(其实就是C语言代码中混入了少量使代码能够更加简洁的C++的语法)在部分IDE中也能运行。本文所使用的也是类C,部分函数为方便采用了C++的引用符号&(C语言中&是取地址符号,C++中既可以用做取地址也可以用作引用),目的是和传统的数据结构教程相对应,有利于读者后续对数据结构的学习。

通过之前顺序表的学习,相信很多同学都对管理系统的代码结构有了一定的了解,本文用链式结构实现学生成绩管理系统,用到了一些数据结构的思想和理念。

这里简单介绍一些顺序表和链表的区别:顺序表中每个元素在内存中的分布是连续的,当删除中间的某个元素的时候,需要把其后面的所有元素向前移动,比如删除数组a中的第6个元素,那么原先的第7个元素需要移动到现在第6个的位置,原先的第8个元素需要移动到现在第7个的位置......最后再修改顺序表的长度即可;而链表中每个元素(链表中习惯称作节点)在内存中的分布是离散的,当删除中间的某个节点的时候,只需要把该节点的前一个节点和后一个节点进行链接,再释放该节点的空间。

首先定义一个数据结构,再申明操作该数据结构的基本函数,最后实现这些基本函数。

二、要求和思路

2.1 要求

对学生信息(学号、姓名、成绩)进行统一管理,实现增、删、改、查、显,且需要用到文件读写的知识对学生信息进行读取和保存。

2.2 数据结构的定义

数据结构的思想就是基于现有的数据结构(本文的数据结构是链表)和相关函数来实现需求。

链表中的每一个节点都有数据域和指针域,数据域用来存放该节点所存储的信息,指针域用来获取下一个节点在内存中的位置。如下是非常经典的链表节点的定义方式:

typedef struct LNode{//定义节点 
	ElemType data;   //数据域 
	LNode *next;     //指针域 
}LNode, *LinkList;

typedef 可以理解为是一个取名字的符号,他将struct LNode这个结构体取了一个简单的代号叫LNode。通常我们定义一个结构体变量,需要这样写struct LNode p,当我们取完代号之后,可以直接写作LNode p。这里举一个一般的例子说明一下

#include<stdio.h>
struct a{
	int b;
};
typedef struct a a;
int main(){
	a b;
	b.b = 2;
	printf("%d", b.b);
}

这段代码首先声明结构体a,接着将结构体a取代号为a,即省略了struct。然后在主函数中直接使用a来声明一个结构体变量b,再对他进行赋值和输出。

这段代码和链表节点的定义方式的区别在于,节点的定义方式取代号和申明结构体同时进行,然后用代号在节点结构体中申明一个指针变量,可以说是一举三得。而这段代码就比较中规中矩,便于理解了。肯定有人要问*LinkList是怎么回事,*LinkList和他左边的LNode一样,也是代号,只不过这个代号的类型是指向LNode的指针。可以这样理解,*表示这个代号的类型是LNode类型的指针,LinkList表示这个代号的具体名字。为什么这里要申明一个LNode类型的指针呢?当然是为了编程方便。后面出现的LinkList其实都可以用LNode *来替换。 

2.3 基本函数的申明

链表的基本函数有很多,但最最最基础的只有几个,初始化链表、销毁链表、增加节点、删除节点、查找节点、修改节点、打印节点。这里先罗列链表的基本函数:(本文省略了销毁链表)

void CreateList(LinkList &L);                    //创建链表 
void NodeAppend(LinkList &L, ElemType e);		 //增加节点 
int NodeDelete(LinkList &L, int num);			 //删除节点 
int NodeModify(LinkList &L, int num, ElemType e);//修改节点 
int NodeSearch(LinkList L, int num, ElemType &e);//查找节点 

这里CreateList和NodeAppend的函数类型是void,意思是没有返回值,而其余三个函数的返回值是int,通过返回值来判断删除、修改、查找操作是否成功。当然,创建链表和增加节点的操作也可能不成功,即没有内存空间可以申请,遇到这种情况通常是直接用exit(1)来退出整个程序,但一般来说,这两个操作都是能正常运行的,所以我也就没写异常退出的情况了。

2.3.1 链表初始化

//创建链表(含头节点) 
void CreateList(LinkList &L){
	L = (LinkList)malloc(sizeof(LNode));
	L -> next = NULL;
}

朴实无华的创建一个链表。

2.3.2 尾部增加节点

//尾部增加节点 
void NodeAppend(LinkList &L, ElemType e){
	LNode *q = (LinkList)malloc(sizeof(LNode));
	q -> data = e;
	q -> next = NULL;
	LNode *p = L;
	while(p -> next) p = p -> next;
	p -> next = q;
}

一般插入都采用的是头插法,即在链表头部插入,这里为了逻辑更清晰一些,采用了更费时的尾部插入法。首先函数传入的参数是L和e,L是链表,e是新增节点的数据域。要插入一个节点,首先需要申明一个节点空间用于存储e。这里LNode *q = (LinkList)malloc(sizeof(LNode))的作用是申明一个LNode类型的指针q,申明一段LNode大小的空间,并让q指向这段空间。当然也可以写作LNode *q = (LNode *)malloc(sizeof(LNode)),因为LNode * 和 LinkList是等价的。正常来说,q是一个指针,想要对q指向的空间进行赋值,应该使用解地址符*,考虑到q是指针类型,c\c++对结构体的指针类型的数据提供了一个更便捷的符号 ->,用以直接访问该指针指向的结构体的内部成员。当然你也可以使用*来实现,如q -> data = e可以改写为 (*q).data = e

接着申明一个指针变量p指向链表的头部,通过while循环将p逐步更新,直到p -> next为空,此时p指向链表尾部的节点,用q来更新p -> next实现节点的尾部插入。

2.3.3 删除节点

//根据编号删除节点 
int NodeDelete(LinkList &L, int num){
	LNode *p,*q;
	p = L;
	while(p -> next){
		q = p -> next;
		if(num == (q -> data).num){
			p -> next = q -> next;
			free(q);
			return 0;
		} 
		p = p -> next;
	}
	return 1;
}

返回0表示删除成功,返回1表示未找到节点,删除失败。

2.3.4 修改节点 

//根据编号修改节点 
int NodeModify(LinkList &L, int num, ElemType e){
	LNode *p,*q;
	p = L;
	while(p -> next){
		q = p -> next;
		if(num == (q -> data).num){
			q -> data = e;
			return 0;
		} 
		p = p -> next;
	}
	return 1;
}

 返回0表示修改成功,返回1表示未找到节点,修改失败。

2.3.5 搜索节点 

//根据编号搜索节点,若找到则将节点信息存储至e并返回 
int NodeSearch(LinkList L, int num, ElemType &e){
	LNode *p, *q;
	p = L;
	while(p -> next){
		q = p -> next;
		if(num == (q -> data).num){
			e = q -> data;
			return 0;
		} 
		p = p -> next;
	}
	return 1;
}

返回0表示查找成功,返回1表示未找到节点,查找失败。

三、具体构建

实现了基本的数据结构(数据结构和基本操作),现在只要在其基础上增加交互性就能实现成绩管理系统。

首先要定义学生信息结构体,并将其取代号为ElemType来和链表的数据域相统一。

//student结构体存储学生信息 
typedef struct student{
   int num;//学号
   char name[10];//姓名
   int score;//分数 
}student;
typedef student ElemType;

这里先将struct student取代号为student,再将student取代号为ElemType。当然也可以一步到位。

//student结构体存储学生信息 
typedef struct student{
   int num;//学号
   char name[10];//姓名
   int score;//分数 
}ElemType;

 这里再提供一种方法,请读者自行理解。

//student结构体存储学生信息 
typedef struct{
   int num;//学号
   char name[10];//姓名
   int score;//分数 
}student;
typedef student ElemType;

3.1 函数申明 

void ReadFile(LinkList &L);						 //读取文件 
void WriteFile(LinkList L); 					 //写入文件 
void AddStudent(LinkList &L);					 //增加学生 
void DeleteStudent(LinkList &L);				 //删除学生 
void ModifyStudent(LinkList &L);				 //修改学生 
void SearchStudent(LinkList L);					 //查找学生 
void DisplayStudent(LinkList L);				 //显示学生 
void Menu();                                     //主菜单 

3.2 读取文件

//读取文件中的数据到链表 
void ReadFile(LinkList &L){
	FILE *fp = NULL;
	fp = fopen("student.txt", "r");
	if(fp == NULL){
        printf("Can not open the file\n");
        exit(1);
    }
    CreateList(L);
    ElemType temp;
	while(fscanf(fp, "%d %s %d", &temp.num, temp.name, &temp.score) != EOF){
		NodeAppend(L, temp);
	}
	fclose(fp); 
}

3.3 写入文件

//将链表中的数据用覆盖的方式写入文件中 
void WriteFile(LinkList L){
	FILE *fp = NULL;
	fp = fopen("student.txt", "w");
	if(fp == NULL){
        printf("Can not open the file\n");
        exit(1);
    }
    LNode *p = L;
    ElemType temp;
    while(p -> next){
    	temp = p -> next -> data;
    	fprintf(fp, "%d %s %d\n", temp.num, temp.name, temp.score);
    	p = p -> next;
	} 
	fclose(fp);
}

3.4 增加学生

//录入学生信息 
void AddStudent(LinkList &L){
	ElemType e;
	printf("请输入要录入的学生信息:\n");
	printf("学号\t姓名\t分数\n");
	scanf("%d %s %d", &e.num, e.name, &e.score);
	NodeAppend(L, e);
	system("pause");
}

3.5 删除学生

//删除学生信息 
void DeleteStudent(LinkList &L){
	int num;
	printf("请输入要删除的学生学号:");
	scanf("%d", &num);
	if(NodeDelete(L,num) == 1) 
		printf("未查找到该学生!\n");
	else 
		printf("删除成功!\n");
	system("pause");
}

3.6 修改学生

//修改学生信息 
void ModifyStudent(LinkList &L){
	ElemType e;
	int num; 
	printf("请输入要修改的学生的当前学号:");
	scanf("%d", &num);
	printf("请输入修改后的信息:学号 姓名 分数\n");
	scanf("%d %s %d", &e.num, e.name, &e.score);
	if(NodeModify(L, num, e) == 1) 
		printf("未查找到该学生!\n");
	else 
		printf("修改成功!\n");
	system("pause");
}

3.7 查找学生

//查找学生信息 
void SearchStudent(LinkList L){
	ElemType e;
	int num; 
	printf("请输入要查找的学生学号:\n");
	scanf("%d", &num);
	if(NodeSearch(L, num, e) == 1) 
		printf("未查找到改该学生!\n");
	else 
		printf("学号\t姓名\t分数\n%d %s %d\n", e.num, e.name, e.score);
	system("pause");
}

3.8 显示学生

//显示学生信息 
void DisplayStudent(LinkList L){  
	printf("学号\t姓名\t分数\n");
	LNode *p = L;
	ElemType e;
	while(p -> next){
		e = (p -> next) -> data;
		printf("%d %s %d\n", e.num, e.name, e.score);
		p = p -> next;
	}
	system("pause");
}

3.9 主菜单

//主菜单 
void Menu(){
	system("cls"); 
	printf("------------------------------------------\n");
	printf("             学生管理系统                 \n");
	printf("                                          \n"); 
	printf("          作者微信:qczsbwjzjn            \n");
	printf("                                          \n");
	printf("             欢迎交流咨询~                \n");
	printf("                                          \n");
	printf("    1.增加学生信息    2.删除学生信息      \n");
	printf("                                          \n");
	printf("    3.修改学生信息    4.查找学生信息      \n"); 
	printf("                                          \n"); 
	printf("    5.显示所有信息    0.退出管理系统      \n");
	printf("                                          \n");
	printf("------------------------------------------\n");
	printf("                                          \n");
	printf("    请输入:");
}

3.10 主函数

//主函数 
int main(){
	LinkList L;
	CreateList(L);
	ReadFile(L);
	int choice = 1;
	while(choice){
		Menu();
		scanf("%d", &choice);
		switch(choice){
			case 1: AddStudent(L); break;
			case 2: DeleteStudent(L); break;
			case 3: ModifyStudent(L); break;
			case 4: SearchStudent(L); break;
			case 5: DisplayStudent(L); break;
			case 0: WriteFile(L); break;
		}
	}
	return 0;
}

四、完整代码

#include<stdio.h>
#include <stdlib.h>

//student结构体存储学生信息 
typedef struct student{
   int num;//学号
   char name[10];//姓名
   int score;//分数 
}ElemType;

typedef struct LNode{//定义节点 
	ElemType data;   //数据域 
	LNode *next;     //指针域 
}LNode, *LinkList;
 
void CreateList(LinkList &L);                    //创建链表 
void NodeAppend(LinkList &L, ElemType e);		 //增加节点 
int NodeDelete(LinkList &L, int num);			 //删除节点 
int NodeModify(LinkList &L, int num, ElemType e);//修改节点 
int NodeSearch(LinkList L, int num, ElemType &e);//查找节点 
void ReadFile(LinkList &L);						 //读取文件 
void WriteFile(LinkList L); 					 //写入文件 
void AddStudent(LinkList &L);					 //增加学生 
void DeleteStudent(LinkList &L);				 //删除学生 
void ModifyStudent(LinkList &L);				 //修改学生 
void SearchStudent(LinkList L);					 //查找学生 
void DisplayStudent(LinkList L);				 //显示学生 
void Menu();                                     //主菜单 

//创建链表(含头节点) 
void CreateList(LinkList &L){
	L = (LinkList)malloc(sizeof(LNode));
	L -> next = NULL;
}

//尾部增加节点 
void NodeAppend(LinkList &L, ElemType e){
	LNode *q = (LinkList)malloc(sizeof(LNode));
	q -> data = e;
	q -> next = NULL;
	LNode *p = L;
	while(p -> next) p = p -> next;
	p -> next = q;
}

//根据编号删除节点 
int NodeDelete(LinkList &L, int num){
	LNode *p,*q;
	p = L;
	while(p -> next){
		q = p -> next;
		if(num == (q -> data).num){
			p -> next = q -> next;
			free(q);
			return 0;
		} 
		p = p -> next;
	}
	return 1;
}

//根据编号修改节点 
int NodeModify(LinkList &L, int num, ElemType e){
	LNode *p,*q;
	p = L;
	while(p -> next){
		q = p -> next;
		if(num == (q -> data).num){
			q -> data = e;
			return 0;
		} 
		p = p -> next;
	}
	return 1;
}

//根据编号搜索节点,若找到则将节点信息存储至e并返回 
int NodeSearch(LinkList L, int num, ElemType &e){
	LNode *p, *q;
	p = L;
	while(p -> next){
		q = p -> next;
		if(num == (q -> data).num){
			e = q -> data;
			return 0;
		} 
		p = p -> next;
	}
	return 1;
}

//读取文件中的数据到链表 
void ReadFile(LinkList &L){
	FILE *fp = NULL;
	fp = fopen("student.txt", "r");
	if(fp == NULL){
        printf("Can not open the file\n");
        exit(1);
    }
    CreateList(L);
    ElemType temp;
	while(fscanf(fp, "%d %s %d", &temp.num, temp.name, &temp.score) != EOF){
		NodeAppend(L, temp);
	}
	fclose(fp); 
}

//将链表中的数据用覆盖的方式写入文件中 
void WriteFile(LinkList L){
	FILE *fp = NULL;
	fp = fopen("student.txt", "w");
	if(fp == NULL){
        printf("Can not open the file\n");
        exit(1);
    }
    LNode *p = L;
    ElemType temp;
    while(p -> next){
    	temp = p -> next -> data;
    	fprintf(fp, "%d %s %d\n", temp.num, temp.name, temp.score);
    	p = p -> next;
	} 
	fclose(fp);
}

//录入学生信息 
void AddStudent(LinkList &L){
	ElemType e;
	printf("请输入要录入的学生信息:\n");
	printf("学号\t姓名\t分数\n");
	scanf("%d %s %d", &e.num, e.name, &e.score);
	NodeAppend(L, e);
	system("pause");
}

//删除学生信息 
void DeleteStudent(LinkList &L){
	int num;
	printf("请输入要删除的学生学号:");
	scanf("%d", &num);
	if(NodeDelete(L,num) == 1) 
		printf("未查找到该学生!\n");
	else 
		printf("删除成功!\n");
	system("pause");
}

//修改学生信息 
void ModifyStudent(LinkList &L){
	ElemType e;
	int num; 
	printf("请输入要修改的学生的当前学号:");
	scanf("%d", &num);
	printf("请输入修改后的信息:学号 姓名 分数\n");
	scanf("%d %s %d", &e.num, e.name, &e.score);
	if(NodeModify(L, num, e) == 1) 
		printf("未查找到该学生!\n");
	else 
		printf("修改成功!\n");
	system("pause");
}

//查找学生信息 
void SearchStudent(LinkList L){
	ElemType e;
	int num; 
	printf("请输入要查找的学生学号:\n");
	scanf("%d", &num);
	if(NodeSearch(L, num, e) == 1) 
		printf("未查找到改该学生!\n");
	else 
		printf("学号\t姓名\t分数\n%d %s %d\n", e.num, e.name, e.score);
	system("pause");
}

//显示学生信息 
void DisplayStudent(LinkList L){  
	printf("学号\t姓名\t分数\n");
	LNode *p = L;
	ElemType e;
	while(p -> next){
		e = (p -> next) -> data;
		printf("%d %s %d\n", e.num, e.name, e.score);
		p = p -> next;
	}
	system("pause");
}

//主菜单 
void Menu(){
	system("cls"); 
	printf("------------------------------------------\n");
	printf("             学生管理系统                 \n");
	printf("                                          \n"); 
	printf("          作者微信:qczsbwjzjn            \n");
	printf("                                          \n");
    printf("             可以帮忙代写,                \n");
	printf("             欢迎交流咨询~                \n");
	printf("                                          \n");
	printf("    1.增加学生信息    2.删除学生信息      \n");
	printf("                                          \n");
	printf("    3.修改学生信息    4.查找学生信息      \n"); 
	printf("                                          \n"); 
	printf("    5.显示所有信息    0.退出管理系统      \n");
	printf("                                          \n");
	printf("------------------------------------------\n");
	printf("                                          \n");
	printf("    请输入:");
}

//主函数 
int main(){
	LinkList L;
	CreateList(L);
	ReadFile(L);
	int choice = 1;
	while(choice){
		Menu();
		scanf("%d", &choice);
		switch(choice){
			case 1: AddStudent(L); break;
			case 2: DeleteStudent(L); break;
			case 3: ModifyStudent(L); break;
			case 4: SearchStudent(L); break;
			case 5: DisplayStudent(L); break;
			case 0: WriteFile(L); break;
		}
	}
	return 0;
}

五、界面展示

5.1 显示信息

5.2 删除学生 

5.3 修改学生 

下面附上下载链接(含测试文件)

六、代码交流

代码交流群:877518298

猜你喜欢

转载自blog.csdn.net/qq_42276781/article/details/112466816