C语言 人事管理系统练习

C语言 人事管理系统练习

一、简述

    简单的一个人事管理系统程序。使用链表进行数据的操作(增删改查、排序),最后将数据保存在文件中。

    (例子中的数据录入不严谨,结构体设计也是有待改进)

二、效果


三、工程结构


四、源文件

head.h文件

#define FILENAME "employee.Liang" 
#define HEADER "工号--------姓名------年龄----性别------出生年月------地址------电话----Email\n"
#define FORMATSTR " %4s\t%8s\t%4s\t%4s\t%4s\t\t%4s  \t%4s  \t%4s\n"
#define FORMAT_PNT(p)  (p)->num,(p)->name,(p)->age,(p)->sex,(p)->birth,(p)->addr,(p)->tel,(p)->email

typedef struct employee	//员工结构体 定义 
{
	char num[4];// 工号
	char name[8];// 姓名
	char age[4];//年龄
	char sex[4];//性别
	char birth[4];//出生年月
	char addr[4];//地址
	char tel[4];//电话
	char email[4];//email
	struct employee *next;
}DATA;

void About();//关于 
void MainMenu();//主菜单函数 
void Add();	//添加函数 
void AddTail(DATA * newDATA);//尾部添加函数
void AddHead(DATA * newDATA);//头部添加函数
void Show();//显示全部数据 
void Save();//保存到文件 
void Read();//读取文件 
void Clear();//清除文件内容 
void MyFree();//释放链表 
void FindByNum();//根据工号查询 
void Find();//查询子菜单 
void Delete();//删除子菜单 
void DeleteByNum();//根据工号删除 
void Modify();//根据工号修改 
void Sort();//排序 
void SortByNum();//根据工号排序

main.c文件

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

DATA* head = NULL;//全局结构体指针,作为链表的头指针 

int main()
{
	system("color 1f");//设置背景颜色 
	int choice = 10;//记录用户选择 ,初始化为10 
	Read();//加载文件数据 
	
	while(1)//主循环 
	{
		MainMenu();
		fflush(stdin);//清空输入缓冲区,防止scanf获取到上次存留输入 
		scanf("%1d",&choice);//指定接收长度(因为只提供0~8功能选项)
		 
		switch(choice)//根据用户选择执行相应功能 
		{
			case 1: Add();//添加 
					break;
			case 2: Show();//展示 
					break;
			case 3: Find();//查找 
					break;
			case 4: Delete();//删除 
					break;
			case 5: Modify();//修改 
					break;
			case 6: Sort();//排序 
					break;
			case 9: break;
			case 8: About();//关于 
					break;
			case 7: Clear();//清空文件数据 
					break;
			case 0: exit(0);//退出程序 
					break;
			default:break;
		}
		
	}
	
	return 0;
}

void MainMenu()//主菜单界面 
{
	system("cls");//清屏 
	printf("  \t┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n");
	printf("  \t┃**********************************************************┃\n");
	printf("  \t┃***┏━━━━━━━━━━━━━━━━━━━━━━━━┓***┃\n");
	printf("  \t┃***┃************************************************┃***┃\n");
	printf("  \t┃***┃***                                         ****┃***┃\n");
	printf("  \t┃***┃***         欢迎使用职工信息管理系统        ****┃***┃\n");
	printf("  \t┃***┃***                                         ****┃***┃\n");
	printf("  \t┃***┃***                 【1】录入               ****┃***┃\n");
	printf("  \t┃***┃***                 【2】浏览               ****┃***┃\n");
	printf("  \t┃***┃***                 【3】查询               ****┃***┃\n");
	printf("  \t┃***┃***                 【4】删除               ****┃***┃\n");
	printf("  \t┃***┃***                 【5】修改               ****┃***┃\n");
	printf("  \t┃***┃***                 【6】排序               ****┃***┃\n");
	printf("  \t┃***┃***                 【7】清空               ****┃***┃\n");
	printf("  \t┃***┃***                 【8】关于               ****┃***┃\n");
	printf("  \t┃***┃***                 【0】退出               ****┃***┃\n");
	printf("  \t┃***┃***                                         ****┃***┃\n");
	printf("  \t┃***┃************************************************┃***┃\n");
	printf("  \t┃***┗━━━━━━━━━━━━━━━━━━━━━━━━┛***┃\n");
	printf("  \t┃**********************************************************┃\n");
	printf("  \t┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n");
	printf("  \t ****************【请输入您的选择(0~8)】:");
}

void Add()//添加函数 
{
	Read();//将文件数据读取到全局链表
	DATA* p = NULL;//用来遍历链表,看看新添加的是否已存在 
	char isContinue = 'n';//控制是否继续添加 ,控制是否已经存在 
	char cAdd = 'T';//控制是尾部添加还是头部添加 ,或者是返回菜单 
	
	system("cls");//清屏 
	printf("\t\t====添加子菜单=====\n"); 
	printf("\t\t【1】尾部添加\t\n");
	printf("\t\t【2】头部添加\t\n");
	printf("\t\t【0】返回主菜单\t\n");
	printf("\t\t请选择【0~2】:");
	
	fflush(stdin);//清空缓冲区,防止接收到上次存留输入 
	cAdd = getchar();//接收用户选择 
	if(cAdd == '0') return;//返回主菜单 
	DATA *newDATA = NULL;//声明结构体指针,用来指向新的结构体 
	do
	{
		newDATA =(DATA*)malloc(sizeof(DATA));//申请空间 
		if(newDATA == NULL)//申请失败 
		{
			printf("%d:malloc error!\n",__LINE__);//__LINE__是宏定义,表示当前行号 
			return;//返回 
		}
		memset(newDATA,0,sizeof(DATA));//将新申请的空间清零 
		newDATA->next = NULL;//将结构体指针域置空
		 
	 	//接收用户输入,记得指定长度,并且接收前清空输入缓冲区 ,防止接收到非法数据 
		printf("【请输入员工的   工资号】:");fflush(stdin);//接收前清空缓冲区,防止接收到上次存留输入 
		scanf("%4s",&(newDATA->num));
		//数据不为空 
	 	p = head;//p指向头指针,用p遍历链表,头指针不动 
	 	while(p!=NULL)//节点不为空 
	 	{
		 	if(strncmp(p->num,newDATA->num,4)==0)//指定长度比较工号 ,如果相等 ,p指向的节点就是所查找的 
		 	{
		 		isContinue = 'e' ;//标志工号已经存在 
		 		break;//直接结束循环 
		 	}
		 	p = p->next;//不相等,指向下一个节点 
	 	}
		if(isContinue !='e')//工号没有重复才可以继续添加 
		{
			printf("【请输入员工的     姓名】:");fflush(stdin);//接收前清空缓冲区,防止接收到上次存留输入 
			scanf("%8s",&(newDATA->name));
			printf("【请输入员工的     年龄】:");fflush(stdin);//接收前清空缓冲区,防止接收到上次存留输入 
			scanf("%4s",&(newDATA->age));
			printf("【请输入员工的     性别】:");fflush(stdin);//接收前清空缓冲区,防止接收到上次存留输入 
			scanf("%4s",&(newDATA->sex));
			printf("【请输入员工的 出生年月】:");fflush(stdin);//接收前清空缓冲区,防止接收到上次存留输入 
			scanf("%4s",&(newDATA->birth));
			printf("【请输入员工的     地址】:");fflush(stdin);//接收前清空缓冲区,防止接收到上次存留输入 
			scanf("%4s",&(newDATA->addr));
			printf("【请输入员工的     电话】:");fflush(stdin);//接收前清空缓冲区,防止接收到上次存留输入 
			scanf("%4s",&(newDATA->tel));
			printf("【请输入员工的   E-mail】:");fflush(stdin);//接收前清空缓冲区,防止接收到上次存留输入 
			scanf("%4s",&(newDATA->email));
			if(cAdd == '1')
				AddTail(newDATA);//尾部添加 
			else
				AddHead(newDATA);//头部添加 
		}
		else//工号重复 
		{
			printf("工号已存在!\n");
			free(newDATA);//释放申请的空间 
		} 
		
		printf("【是否继续录入(y/n)】:");
		fflush(stdin);//接收前清空缓冲区,防止接收到上次存留输入 
		isContinue=getchar();//接收用户输入 
		
	}while(isContinue=='y' || isContinue=='Y');
 	
 	Save(); //将数据保存到文件 
 
    
}

void AddTail(DATA * newDATA)//尾部添加 
{
	if(head == NULL)//如果链表为空 
	{
		head = newDATA;//直接让头指针指向第一个数据 
		return;
	}
	
	//头指针不为空 
	DATA *p = head;
	while(p->next !=NULL)//循环直到p指向最后一个 
	{
		p = p->next;//p指向下一个 
	}
	p->next = newDATA;//将新的数据添加到尾部 
	
}

void Show()//显示全部 
{
	system("cls"); //清屏 
	Read();//从文件中读取数据到全局链表中 
	int count = 0;//记录数据数目 
	if(head==NULL)//如果头指针为空,说明为空数据 
	{
		printf("空数据!\n");
		system("pause");//暂停,等待 
		return;	
	}
	//数据不为空 
	DATA* p = head;//用p来逐个指向数据节点,头指针不动 
	
	printf(HEADER);//打印标题 
	while(p!=NULL)//如果当前节点不为空 
	{
		count++;//数目加1 
		printf(FORMATSTR,FORMAT_PNT(p));//打印格式化数据 
		p = p->next;//p指向下一个节点 
	}
	printf("【总共有 %d 条记录】\n",count);//打印数据条数 
	system("pause");//暂停,等待
	
}

void Save()//保存到文件 
{
	
	FILE* pf = fopen(FILENAME, "w");//以写方式操作文件 
	if (!pf)//如果文件指针为空,即打开文件失败 
	{
		puts("保存文件时失败\n");
		return;//返回 
	}
	
	DATA* p = head;//头指针不动,用p来遍历链表 
	while (p!=NULL)//当前节点不为空,继续循环 
	{
		//fwrite (const void* p, size_t a, size_t b, FILE* pf);从p地址读取a次数据到pf指向文件,每次读取b字节 
		fwrite(p, 1, sizeof(DATA)-sizeof(struct employee *), pf);//sizeof(DATA)-sizeof(struct employee *)只保存数据域,不保存结构体中的指针域 
		p = p->next;//p指向下一个节点 
	}
	fclose(pf);//关闭文件 
	//puts("保存成功!\n"); 

}
void Read()//读取数据文件 
{
	MyFree();//释放链表空间 
	
	FILE* pf = fopen(FILENAME, "r");//以读打开文件方式 
	if(pf==NULL || fgetc(pf) == EOF )//如果文件打开失败,或者文件不存在 
	{
		return;//返回 
	} 
	int flag = 0;//循环控制,读取文件数据成功 ,置1,循环继续 
	rewind (pf);//将文件指针指向文件开头,(因为使用这个函数:fgetc(pf),文件指针已经移动了,不再指向开头) 
	DATA *newDATA = NULL;//声明一个结构体指针,用来指向新的结构体 
	do
	{
		flag = 0;
		newDATA =(DATA*)malloc(sizeof(DATA));//申请空间,用来存放新数据 
		if(newDATA == NULL)//申请空间失败 
		{
			printf("%d:malloc error!\n",__LINE__);//__LINE__是宏定义,表示当前行号 
			break;//结束循环 
		}
		memset(newDATA,0,sizeof(DATA));//将申请到的空间清0 
		newDATA->next = NULL;//将新结构体指针域置空 
		
	    //fread (void* p, size_t a, size_t b, FILE* pf);从pf指向文件中每次读取b个字节,读a次 放到p 指向空间 
	    //记得减去结构体里面的指针域空间 struct employee * 所占空间其实就是4字节(如果是32位地址线) 
	    if(fread(newDATA,1,sizeof(DATA)-sizeof(struct employee *), pf)== sizeof(DATA)-sizeof(struct employee *))
	    {
	    	flag = 1;//置1,继续循环 
	    	AddTail(newDATA);//将从文件中读取到的数据尾部添加到全局链表 
		}
		
		
	}while(flag);//能读取到数据,继续向下读取 
	
	free(newDATA);//因为无论文件有无数据,读取到最后时,最后申请的空间肯定没有用上,所以要释放 
	//newDATA = NULL;//养成良好习惯,释放后置空,可以不用 
	fclose(pf);//关闭文件 
	
} 

void Clear()//清空文件数据 
{
	char c='n';//确认是否清空数据 
	
	printf("【确认清空数据】(y/n):");
	fflush(stdin);//接收前清空缓冲区,防止接收到上次存留输入 
	c=getchar();//接收用户输入 
	if(c=='y' || c=='Y')
	{
		
        FILE *fp = fopen(FILENAME,"w");//以写方式打开文件,之前的内容会被清空 
	    fclose(fp);//关闭文件 
		printf("【清空数据成功!】\n");
		MyFree();//释放链表空间	
		system("pause");//暂停,等待 
	}
}

void MyFree()//释放链表空间 
{
	DATA  *p = head,*p1 = NULL;
	while (p!=NULL)//p1,p指向 相邻的两个节点,将p1释放,然后将p1,p同时移动 
	{
		p1 = p;
		p = p->next;
		free(p1);//释放p1指向空间 
	}
	head = NULL;//因为head是全局变量,且还会用到 ,所以释放指定空间后置空,防止误操作; 
}

void FindByNum()//按照工号查找 
{
	char num[4] = {0};//声明一个字符数组,并初始化 
	printf("请输要查找的入员工工号:");
	
	fflush(stdin);//接收输入前清空输入缓冲区,防止接收到你上次存留数据 
	scanf("%4s",num);//指定长度接收字符 
	
	Read();//从文件中读取数据到全局链表 
	if(head==NULL)//如果头指针为空,即数据为空 
	{
		system("cls"); //清屏 
		printf("空数据\n");
		system("pause");
		return;//直接返回 
	}
	//数据不为空 
 	DATA* p = head;//p指向头指针,用p遍历链表,头指针不动 
 	while(p!=NULL)//节点不为空 
 	{
	 	if(strncmp(p->num,num,4)==0)//指定长度比较工号 ,如果相等 ,p指向的节点就是所查找的 
	 	{
	 		break;//直接结束循环 
	 	}
	 	p = p->next;//不相等,指向下一个节点 
 	}
 	if(p!=NULL)//如果p指向不为空,说明找到了(p遍历链表,找不到的话会指向空) 
 	{
 		system("cls");//清屏 
 		printf(" \t===【已经找到工号为:%4s 的员工】===\n",p->num);
	 	printf(HEADER);//打印头部 
	 	printf(FORMATSTR,FORMAT_PNT(p));//格式化输出数据 
 	}
 	else//p为空,说明找不到 
 	{
 		printf("\t====查无此人====\n");
 	}
  	system("pause");//暂停一下 
} 	
	

void Find()//查找菜单 
{
	char choice = 'n';//控制是否继续查找 
	while(1)
	{
		system("cls");//清屏 
		printf("\t\t====查询子菜单=====\n"); 
		printf("\t\t【1】按工号查询\t\n");
		printf("\t\t【2】按姓名查询\t\n");
		printf("\t\t【3】按邮箱查询\t\n");
		printf("\t\t【0】返回主菜单\t\n");
		printf("\t\t请选择【0~3】:");
		fflush(stdin);//清空缓冲区 
		choice = getchar();//获取用户选择 
		switch(choice)//根据选择执行相应的功能函数 ,这里只是实现了按工号查询 
		{
			case '1':
			case '2':
			case '3': FindByNum();
			          break;
			case '0':return;
			default :break;
		}
	}	
} 

void DeleteByNum()//按照工号删除 
{  
	char num[4] = {0};//申明字符数组用来接收要删除的工号 
	char choice = 'n';//用来确认是否删除 
	printf("请输入要删除的员工工号:");
	
	fflush(stdin);//清空缓冲区 
	scanf("%4s",num);//接收指定长度的输入 
	
	Read();//从文件从读取数据到全局链表 
	if(head==NULL)//如果头指针为空,即数据为空 
	{
		system("cls"); //清屏 
		printf("空数据\n");
		system("pause");
		return;//返回 
	}
	//数据不为空 
 	DATA* p = head;
 	DATA* p1 = head;
 	while(p!=NULL)//p1指向p,p遍历节点 
 	{
 		
	 	if(strncmp(p->num,num,4)==0)//指定长度比较,如果相等,说明找到要删除的节点 ,p指向要删除的节点 
	 	{
	 		break;//退出循环 
	 	}
	 	
	 	p1 = p; //p1跟p指向同一节点,下面一句p指向下一节点,变为p1指向p 
	 	p = p->next;
 	}
 	if(p!=NULL)//p不为空,说明找到要删除的节点 
 	{
 		printf("\t====已经找到学号为:%4s 员工====\n",p->num);
	 	printf(HEADER);//打印头部 
	 	printf(FORMATSTR,FORMAT_PNT(p));//格式化打印 
	 	
	 	printf("\t\t****确认删除?[y/n]:"); 
	 	fflush(stdin);//清空输入缓冲区 
	 	choice = getchar();//接收输入 
	 	if(choice == 'y' || choice == 'Y')
	 	{
	 		if(p == head)//如果找到的节点是头节点 
	 		{
			 	head = p->next;//第二个节点成为头部节点 
		 	}	
 			else
			{
				p1->next = p->next;//p1指向p的下一节点,p指向找到的节点,p1指向找到节点的前一节点 
			}
	 		free(p);//释放p指向空间 
	 		p = NULL;//释放后置空,习惯 
	 		Save();//将数据保存到文件 
	 		printf("\t\t****删除成功!\n");
	 	}
 	}
 	else//p为空 
 	{
 		printf("\t====查无此人====\n");
 	}
  	system("pause");
} 

void Delete()//删除 
{
	char choice = 'n';//接收用户选择 
	while(1)
	{
		system("cls");//清屏 
		printf("\t\t====删除子菜单=====\n"); 
		printf("\t\t【1】按工号删除\t\n");
		printf("\t\t【2】按姓名删除\t\n");
		printf("\t\t【3】按邮箱删除\t\n");
		printf("\t\t【0】返回主菜单\t\n");
		printf("\t\t请选择【0~3】:");
		fflush(stdin);//清空缓冲区 
		choice = getchar();//接收用户输入 
		switch(choice)
		{
			case '1':
			case '2':
			case '3': DeleteByNum();
			          break;
			case '0':return;
			default :break;
		}
	}	
}

void Modify()//按照工号修改 
{
	char num[4] = {0};//工号 
	char tel[4] = {0};//电话 
	char addr[4] = {0};//地址 
	char email[4] = {0};//邮箱 
	printf("请输入要修改的员工工号:");
	
	fflush(stdin);//清空缓冲区 
	scanf("%4s",num); 
	
	Read();//从文件读取数据到全局链表 
	if(head==NULL)//头指针为空,即数据为空 
	{
		system("cls"); 
		printf("空数据\n");
		system("pause");
		return;
	}
	//数据不为空 
 	DATA* p = head;
 	while(p!=NULL)//查找 
 	{
	 	if(strncmp(p->num,num,4)==0)//找到 
	 	{
	 		break;//退出循环 
	 	}
	 	p = p->next;//指向下一节点 
 	}
 	if(p!=NULL)//p不为空,说明找到了 
 	{
 		printf("\t====已经找到学号为:%4s 员工====\n",p->num);
	 	printf(HEADER);//打印头部 
	 	printf(FORMATSTR,FORMAT_PNT(p));//格式化输出 
	 	 
	 	printf("【将addr 改为:】");
	 	fflush(stdin);//清空输入缓冲区
		scanf("%4s",addr);
		strncpy(p->addr,addr,4);//字符串指定长度复制,给节点更新数据 
		
	 	printf("【将 tel 改为:】");
	 	fflush(stdin);//清空输入缓冲区
		scanf("%4s",tel);
		strncpy(p->tel,tel,4);//字符串指定长度复制,给节点更新数据 
		
	 	printf("【将email改为:】");
	 	fflush(stdin);//清空输入缓冲区
		scanf("%4s",email);
		strncpy(p->email,email,4);//字符串指定长度复制,给节点更新数据 
		
		Save();//将链表数据保存到文件 
		printf("\t\t==修改成功!\n");
 	}
 	else//p为空,找不到数据 
 	{
 		printf("\t====查无此人====\n");
 	}
  	system("pause");
}

void SortByNum()//按工号排序 
{
	if(head==NULL)//如果头指针为空(数据为空) 
	{
		system("cls"); //清屏 
		printf("空数据\n");
		system("pause");
		return;//返回 
	}
	//数据不为空 
	DATA* p = head;//p指向头部 
	DATA* q = NULL;
	DATA* m = NULL;
	//从小到大排序,每比较一次,找到比较中的最小值。比如q指向开始比较位置 ,m,q进行比较,m指向较小的节点;
	//如果m不是同q指向同一节点了,m,q节点交换  
	while (p->next!=NULL)//循环比较,p遍历链表 ,m,q进行比较,m指向较小的节点 
	{
		m = p;
		q = m->next;//m,q指向相邻两个节点,m在前,q在后 
		
		while (q!=NULL)//q从p指向节点的下一节点开始遍历比较,将最小值放到p指向节点 
		{
			if (strncmp(m->num,q->num,4)>0)//m节点的大于q节点的 
				m = q;//m指向较小的 
			q = q->next;//q指向下一节点 
		}

		if (m != p)//m不同p指向,交换m,p节点值,使p指向本轮比较中的最小值 
		{
			DATA t = *p;
			*p = *m;
			p->next = t.next;
			t.next = m->next; 
			*m = t;
		}

		p = p->next;//p指向下一节点 
		if(p==NULL) break;//p指向空,链表比较结束,退出循环 
	}
	
 	p = head;//p重新指向链表头部 
	printf(HEADER);//打印头部 
	while(p!=NULL)//循环遍历 
	{
		printf(FORMATSTR,FORMAT_PNT(p));//格式化输出 
		p = p->next;//p指向下一节点 
	}

	system("pause");//暂停 
}

void Sort()//排序 
{
	char choice = 'n';//功能选择 
	while(1)
	{
		system("cls");
		printf("\t\t====排序子菜单=====\n"); 
		printf("\t\t【1】按工号排序\t\n");
		printf("\t\t【2】按姓名排序\t\n");
		printf("\t\t【3】按邮箱排序\t\n");
		printf("\t\t【0】返回主菜单\t\n");
		printf("\t\t请选择【0~3】:");
		fflush(stdin);//清空缓冲区 
		choice = getchar();//接收用户选择 
		switch(choice)
		{
			case '1':
			case '2':
			case '3': SortByNum();
			          break;
			case '0':return;
			default :break;
		}
	}	
}

void AddHead(DATA * newDATA)//头部添加 
{
	newDATA->next = head;//新节点指向头节点 
	head = newDATA; //新节点成为头节点 
} 
void About()
{
	system("cls");//清屏 
	printf("\t\t\t\t作者:Genven_Liang\n");
	printf("\t\t\t\t版本:v1.2\n");
	printf("\t\t\t\t日期:2018.06.25(改进)\n");
	printf("\t\t\t\t邮箱:\n");
	printf("\t\t\t\t自学路上共勉之!\n");  
	system("pause");//程序暂停,等待按键按下 
}



五、总结

1、链表操作需要注意空间的释放,防止内存泄漏。不再使用的空间需要进行及时的释放。

2、数据输入合法性检测有待增强。注意结构体个成员的存储长度,程序中没有进行严谨的设计,数据输入也是随便的输入

猜你喜欢

转载自blog.csdn.net/nanfeibuyi/article/details/80810691