用单链表优雅的写一个学生管理系统

用单链表优雅的写一个学生管理系统

在基本的数据结构:单链表(Singly Linked List)中我们了解了如何用动态内存申请创建单链表,如何对单链表进行一些简单的操作。为进一步了解单链表以及其结构具有的功能,那么现在我们就用单链表来搞点事情。(用单链表来做一个“增”,“删”,“改”,“查”系统)

菜单设计

学生信息管理系统,酒店管理系统,火车票管理系统等等无非涉及“增”,“删”,“改”,“查”这四个功能。所以我们了解这种系统运作的本质后都可以类比来写。

首先我们将系统的菜单设计出来。有了菜单我们在写程序时就有一个大纲,这是因为系统的菜单上显示了系统的功能 ,对应于不同的功能我们需要不同的模块去实现。我们用代码一个个去实现菜单上的功能。当菜单上所有的功能全部实现完后我们的系统也就基本做好了。当然在系统中有些功能是隐藏的,例如在本案例中我们默认了保存到文件的功能。这里保存到文件的功能被隐藏了,我们也需要将其实现。

void systemMenu()//菜单
{

	//菜单--功能
	//隐藏的功能:同步保存到文件

	printf("--------------【学生管理系统】--------------\n");
	printf("\t\t0.退出系统\n");

	//  主要的功能:  增 删 改 查
	printf("\t\t1.插入信息\n");//增
	printf("\t\t2.删除信息\n");//删
	printf("\t\t3.修改信息\n");//改
	printf("\t\t4.查找信息\n");//查

	printf("\t\t5.浏览信息\n");
	printf("---------------------------------------------\n");
	printf("请输入 (0-5):\n");
	//交互,提醒用户输入
}

按键交互

在设计完菜单后,对于菜单上的功能。我们如何进行选择去实现哪个功能呢?我们用户输入菜单中功能选项前的代码后,系统去实现其功能。这时候就用到我们的按键交互了。
这里的按键交互仅能够打印提示当前功能,具体的功能还需要我们后续用链表去实现

void keyDown()//按键交互
{
	int userKey;
	scanf("%d", &userKey);
	switch (userKey) 
	{
	case 0:
		printf("\t\t【退出系统】\n");
		
		break;
	case 1:
		printf("\t\t【插入信息】\n");
		
		break;
	case 2:
		printf("\t\t【删除信息】\n");
		
		break;
	case 3:
		printf("\t\t【修改信息】\n");
		
		break;
	case 4:
		printf("\t\t【查找信息】\n");
		
		break;
	case 5:
		printf("\t\t【浏览信息】\n");
		
		break;
	default:
		printf("输入错误,请重新输入.\n");
		
		break;
	}
}

入口函数

在完成上面两个步骤后我们来写一个入口函数进行测试一下,在最前面加上头文件:stdio.h 以及stdlib.h

int main(void)
{
	while (1)
	{
		systemMenu();
		keyDown(); 
		system("pause");
		system("cls");
	}
	return 0;
}

测试结果:
运行后结果显示菜单内容
用单链表优雅的写一个学生管理系统_第1张图片
交互按1回车后结果
用单链表优雅的写一个学生管理系统_第2张图片
从运行结果来看我们学生系统的菜单设计以及按键交互设计的符合预想。

设计数据

做学生信息管理系统即要求系统对学生的信息进行操作。那么我们需要确定管理学生的什么信息,以及用什么类型进行存储。比如录入学生姓名,学号,年龄,电话号码,家庭住址。
我们不难想到在这里需要建立一个结构体保存以上的所有项

struct student
{
	char name[20];
	char num[10];//学号
	int age;
	char tel[12];//电话号码
	char addr[20];//家庭住址
};

设计储存数据的结构——链表

在前面我们了解到链表就是结构体变量与结构体变量的连接,并且知道如何通过动态内存申请创建链表,并插入结点,删除结点,打印遍历链表(这里的插入结点和删除结点对应于学生管理系统中的“增”,“删”,打印遍历列表对应于学生管理系统中的浏览信息)。下面我们在以往链表的基础进行优化使其能够满足学生管理系统所需的功能。

  1. 我们将链表的创建与操作独立出来,新建.h文件,命名为singleLish.h。在菜单,按键交互以及入口函数所在的studentSystem.cpp文件中包含该singleLish.h文件。并将stdio.h 以及stdlib.h头文件移到singleLish.h中。
  2. 将结构体类型struct student 放在singleLish.h中,并在以往链表的基础上进行修改。这里我们注意要进行修改什么内容?我们来找不同点:之前结点的数据域类型是整形int,而在学生管理系统中结点的数据域类型为结构体类型struct student(刚接触结构体时我们可能会感觉有点不知所云。其实大可不必,我们要清楚一点:struct student是一种数据类型正如int为整形类型一样。同理struct student* 可以和int *进行类比)。在明白其区别仅仅是结点的数据域类型不同后我们就好办了。我们只需将数据域的类型由原来的int改为struct student就行了。当然在指定位置删除时,由于参与对比的数据类型的缘故,在删除结点时要用到strcmp(),添加头文件string.h。
#pragma once
#include
#include
#include

struct student
{
	char name[20];
	char num[10];//学号
	int age;
	char tel[12];//电话号码
	char addr[20];//家庭住址
};
struct Node
{
	struct student data;
	struct Node* next;
};

/*创建列表(表头)*/
struct Node* createList()
{
	struct Node* headNode = (struct Node*)malloc(sizeof(struct Node));
	headNode->next = NULL;
	return headNode;
}
/*创建结点*/
struct Node* createNode(struct student data)
{
	struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
	newNode->data = data;
	newNode->next = NULL;
	return newNode;
}

/*插入结点*/
//头插法  录入信息
void insertNodeByHead(struct Node* headNode, struct student data)
{
	struct Node* newNode = createNode(data);
	newNode->next = headNode->next;
	headNode->next = newNode;
}

/*删除结点*/
void deleteNodeByAppoinNum(struct Node* headNode, char* num)
{
	struct Node* posNode = headNode->next;
	struct Node* posFrontNode = headNode;
	if (posNode == NULL)
	{
		printf("无相关内容,无法删除.\n");
		return;
	}
	else
	{  
		while (strcmp(posNode->data.num,num))
		{
			posFrontNode = posNode;
			posNode = posNode->next;
			if (posNode == NULL)
			{
				printf("未找到指定内容,无法删除.\n");
				return;
			}
		}
		posFrontNode->next = posNode->next;//删除结点posNode
		free(posNode);
		printf("已删除.\n");
	}
}

/*打印遍历*/
void printList(struct Node* headList)
{
	struct Node* pMode = headList->next;
	if (pMode == NULL)
		printf("没有录入数据,无法浏览.\n");
	else
	{
		printf("姓名\t学号\t\t年龄\t电话\t\t住址\n");
		while (pMode)
		{
			printf("%s\t%s\t%d\t%s\t%s\n", pMode->data.name, pMode->data.num, pMode->data.age,
				pMode->data.tel, pMode->data.addr);
			pMode = pMode->next;
		}
		printf("\n");
	}
}

我们仔细比对上面代码和之前的链表代码,不难发现其框架相同,仅有的不同就是由数据域类型的不同引起的差别。
这就不难解释为什么说所有的“增”,“删”,“改”,“查”系统都可以参照这样的链表格式来写了。

3.在上一步骤中我们通过可以实现链表的“增”和“删”,下面我们来进行“改”和“查”。首先我们来看“查”:
我们可以以学号(学号不会有相同的情况)为依据进行查找。创建一个“可移动的指针”pMode,首先让其指向链表的第一个存有数据的结点。然后使其与指定的学号用strcmp()进行比对。比对结果相同则返回这个节点的地址。仅返回地址我们还不能浏览所查找学号的信息,故我们需要进一步将返回的结点进行打印。如下:

/*指定位置查找*/
struct Node* searchNodeByAppoinNum(struct Node* headList, char* num)
{
	struct Node* pMode = headList->next;
	if (pMode == NULL)
		return pMode;
	else
	{
		while (strcmp(pMode->data.num, num))
		{
			pMode = pMode->next;
			if (pMode == NULL)
				break;
		}
		return pMode;
	}
}

/*打印结点的数据*/
void printNode(struct Node* curNode)
{
	printf("姓名\t学号\t\t年龄\t电话\t\t住址\n");
	printf("%s\t%s\t%d\t%s\t%s\n", curNode->data.name, curNode->data.num, curNode->data.age,
		curNode->data.tel, curNode->data.addr);
}

4.接着我们来尝试修改学生的信息。同查找结点一样我们先根据学号找到要修改的结点。此时的查找过程我们调用searchNodeByAppoinNum()即可,查找到后将查找到的结点返回给curNode即可,我们通过对curNode的数据域的修改来进行修改学生的信息。

void changeNodeByAppoinNum(struct Node* headList, char* num)
{
	if (searchNodeByAppoinNum(headList, num) == NULL)
		printf("未找到相关信息,无法修改.\n");
	else
	{
		printf("请输入新的学生信息.\n");
		struct Node* curNode = searchNodeByAppoinNum(headList, num);
		scanf("%s%s%d%s%s", curNode->data.name, curNode->data.num, &curNode->data.age,
			curNode->data.tel, curNode->data.addr);
	}
}

进一步完善按键交互

前面提到最初的按键交互仅仅能打印提示当前功能,具体的功能还需要我们后续用链表去实现。那么在链表内容完善后,我们来进一步优化按键交互,使按键交互具有其实际的功能。

这里我们在studentSystem.cpp中创建了一个全局struct Node*类型指针变量list,并初始化为NULL,接着在main()中调用list = createList(),创建链表表头。
在keyDown()中定义了一个临时结构体变量tempData

void keyDown()//按键交互
{
	int userKey;
	struct student tempData;
	scanf("%d", &userKey);
	switch (userKey) 
	{
	case 0:
		printf("\t\t【退出系统】\n");
		system("pause");
		exit(0);
		break;
	case 1:
		printf("\t\t【插入信息】\n");
		printf("请输入学生姓名,学号,年龄,电话,住址:");
		scanf("%s%s%d%s%s", tempData.name, tempData.num, &tempData.age,
			tempData.tel, tempData.addr);
		insertNodeByHead(list, tempData);
		break;
	case 2:
		printf("\t\t【删除信息】\n");
		printf("请输入你要删除的学生学号\n");
		scanf("%s", tempData.num);
		deleteNodeByAppoinNum(list, tempData.num);
		break;
	case 3:
		printf("\t\t【修改信息】\n");
		printf("请输入要修改的学生学号.\n");
		scanf("%s", tempData.num);
		changeNodeByAppoinNum(list, tempData.num);
		break;
	case 4:
		printf("\t\t【查找信息】\n");
		printf("请输入需要查找的学号:");
		scanf("%s", tempData.num);
		if (searchNodeByAppoinNum(list, tempData.num) == NULL)
			printf("未找到相关信息!\n");
		else
			printNode(searchNodeByAppoinNum(list, tempData.num));
		break;
	case 5:
		printf("\t\t【浏览信息】\n");
		printList(list);
		break;
	default:
		printf("输入错误,请重新输入.\n");
		break;
	}
}

文件操作——将数据同步到文件

前面的工作可以说已经完成了一个学生管理系统。
下面我们为其新添加一个功能——同步到文件的功能,以便我们推出系统后,数据能同步保存下来

/*写在singleLish.h*/
/*文件操作*/
void readInfoFromFile(const char* fileName, struct Node* headList)
{
	FILE* fp = fopen(fileName, "r");
	if (fp == NULL)	
	{
		fp = fopen(fileName, "w");
	}
	struct student tempData;
	while (fscanf(fp,"%s\t%s\t%d\t%s\t%s\n", tempData.name, tempData.num, &tempData.age,
		tempData.tel, tempData.addr) != EOF)
	{
		insertNodeByHead(headList, tempData);
		memset(&tempData, 0, sizeof(tempData)); 
	}
	fclose(fp);
}
void saveInfoToFile(const char* fileName, struct Node* headList)
{
	FILE* fp = fopen(fileName, "w");
	struct Node* pMode = headList->next;
	while (pMode)
	{
		fprintf(fp,"%s\t%s\t%d\t%s\t%s\n", pMode->data.name, pMode->data.num, pMode->data.age,
			pMode->data.tel, pMode->data.addr);
		pMode = pMode->next;
	}
	fclose(fp);
}

另外需要在用户交互过程中,在插入,删除,修改数据后进行调用saveInfoToFile()进行保存,在入口函数循环前面调用readInfoFromFile(),打开文件。以便读取文件中存储的数据。

大家可以自己测试下文件操作

附上源码:

//studentSystem.cpp
#define _CRT_SECURE_NO_WARNINGS
#include"singleList.h"
struct Node* list = NULL;
void systemMenu()//菜单
{

	//菜单--功能
	//隐藏的功能:同步保存到文件

	printf("--------------【学生管理系统】--------------\n");
	printf("\t\t0.退出系统\n");

	//  主要的功能:  增 删 改 查
	printf("\t\t1.插入信息\n");//增
	printf("\t\t2.删除信息\n");//删
	printf("\t\t3.修改信息\n");//改
	printf("\t\t4.查找信息\n");//查

	printf("\t\t5.浏览信息\n");
	printf("---------------------------------------------\n");
	printf("请输入 (0-5):\n");
	//交互,提醒用户输入
}
void keyDown()//按键交互
{
	int userKey;
	struct student tempData;
	scanf("%d", &userKey);
	switch (userKey) 
	{
	case 0:
		printf("\t\t【退出系统】\n");
		system("pause");
		exit(0);
		break;
	case 1:
		printf("\t\t【插入信息】\n");
		printf("请输入学生姓名,学号,年龄,电话,住址:");
		scanf("%s%s%d%s%s", tempData.name, tempData.num, &tempData.age,
			tempData.tel, tempData.addr);
		insertNodeByHead(list, tempData);
		saveInfoToFile("student.txt", list);

		break;
	case 2:
		printf("\t\t【删除信息】\n");
		printf("请输入你要删除的学生学号\n");
		scanf("%s", tempData.num);
		deleteNodeByAppoinNum(list, tempData.num);
		saveInfoToFile("student.txt", list);
		break;
	case 3:
		printf("\t\t【修改信息】\n");
		printf("请输入要修改的学生学号.\n");
		scanf("%s", tempData.num);
		changeNodeByAppoinNum(list, tempData.num);
		saveInfoToFile("student.txt", list);
		break;
	case 4:
		printf("\t\t【查找信息】\n");
		printf("请输入需要查找的学号:");
		scanf("%s", tempData.num);
		if (searchNodeByAppoinNum(list, tempData.num) == NULL)
			printf("未找到相关信息!\n");
		else
			printNode(searchNodeByAppoinNum(list, tempData.num));
		break;
	case 5:
		printf("\t\t【浏览信息】\n");
		printList(list);
		break;
	default:
		printf("输入错误,请重新输入.\n");
		break;
	}
}
int main(void)
{
	list = createList();
	readInfoFromFile("student.txt", list);
	while (1)
	{
		systemMenu();
		keyDown(); 
		system("pause");
		system("cls");
	}
	return 0;
}
//singleLish.h

#pragma once
#include
#include
#include

struct student
{
	char name[20];
	char num[10];//学号
	int age;
	char tel[12];//电话号码
	char addr[20];//家庭住址
};

struct Node
{
	struct student data;
	struct Node* next;
};

//链表就是结构体变量与结构体变量连接在一起
//指针变为变量的第二种方法:动态内存申请
/*创建列表(表头)*/
struct Node* createList()
{
	 //有表头链表:第一个结点不存储数据
	 //无表头链表:第一个结点存储数据
	struct Node* headNode = (struct Node*)malloc(sizeof(struct Node));
	headNode->next = NULL;
	return headNode;
}
/*创建结点*/
struct Node* createNode(struct student data)
{
	struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
	newNode->data = data;
	newNode->next = NULL;
	return newNode;
}


/*插入结点*/
//头插法  录入信息
void insertNodeByHead(struct Node* headNode, struct student data)
{
	struct Node* newNode = createNode(data);
	newNode->next = headNode->next;
	headNode->next = newNode;
}

/*删除结点*/
void deleteNodeByAppoinNum(struct Node* headNode, char* num)
{
	struct Node* posNode = headNode->next;
	struct Node* posFrontNode = headNode;
	if (posNode == NULL)
	{
		printf("无相关内容,无法删除.\n");
		return;
	}
	else
	{  
		while (strcmp(posNode->data.num,num))
		{
			posFrontNode = posNode;
			posNode = posNode->next;
			if (posNode == NULL)
			{
				printf("未找到指定内容,无法删除.\n");
				return;
			}
		}
		posFrontNode->next = posNode->next;//删除结点posNode
		free(posNode);
		printf("已删除.\n");
	}
}



/*打印遍历*/
void printList(struct Node* headList)
{
	struct Node* pMode = headList->next;
	if (pMode == NULL)
		printf("没有录入数据,无法浏览.\n");
	else
	{
		printf("姓名\t学号\t\t年龄\t电话\t\t住址\n");
		while (pMode)
		{
			printf("%s\t%s\t%d\t%s\t%s\n", pMode->data.name, pMode->data.num, pMode->data.age,
				pMode->data.tel, pMode->data.addr);
			pMode = pMode->next;
		}
		printf("\n");
	}
}



/*指定位置查找*/
struct Node* searchNodeByAppoinNum(struct Node* headList, char* num)
{
	struct Node* pMode = headList->next;
	if (pMode == NULL)
		return pMode;
	else
	{
		while (strcmp(pMode->data.num, num))
		{
			pMode = pMode->next;
			if (pMode == NULL)
				break;
		}
		return pMode;
	}
}

/*打印结点的数据*/
void printNode(struct Node* curNode)
{
	printf("姓名\t学号\t\t年龄\t电话\t\t住址\n");
	printf("%s\t%s\t%d\t%s\t%s\n", curNode->data.name, curNode->data.num, curNode->data.age,
		curNode->data.tel, curNode->data.addr);
}

void changeNodeByAppoinNum(struct Node* headList, char* num)
{
	if (searchNodeByAppoinNum(headList, num) == NULL)
		printf("未找到相关信息,无法修改.\n");
	else
	{
		printf("请输入新的学生信息.\n");
		struct Node* curNode = searchNodeByAppoinNum(headList, num);
		scanf("%s%s%d%s%s", curNode->data.name, curNode->data.num, &curNode->data.age,
			curNode->data.tel, curNode->data.addr);
	}
}



/*文件操作*/
void readInfoFromFile(const char* fileName, struct Node* headList)
{
	FILE* fp = fopen(fileName, "r");
	if (fp == NULL)	
	{
		fp = fopen(fileName, "w");
	}
	struct student tempData;
	while (fscanf(fp,"%s\t%s\t%d\t%s\t%s\n", tempData.name, tempData.num, &tempData.age,
		tempData.tel, tempData.addr) != EOF)
	{
		insertNodeByHead(headList, tempData);
		memset(&tempData, 0, sizeof(tempData)); 
	}
	fclose(fp);
}
void saveInfoToFile(const char* fileName, struct Node* headList)
{
	FILE* fp = fopen(fileName, "w");
	struct Node* pMode = headList->next;
	while (pMode)
	{
		fprintf(fp,"%s\t%s\t%d\t%s\t%s\n", pMode->data.name, pMode->data.num, pMode->data.age,
			pMode->data.tel, pMode->data.addr);
		pMode = pMode->next;
	}
	fclose(fp);
}

你可能感兴趣的:(C)