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