C语言实现员工管理系统

员工管理系统

1. 题目要求

设计一个计算机程序,能够实现简单的员工管理功能。

  1. 每个员工的信息包括:编号、姓名、性别、出生年月、学历、职务、电话、住址等。
  2. 系统的功能包括:
    1. 文件操作:将数据输出到文件中以及从文件中加载数据
    2. 查询:按特定条件查找员工。
    3. 修改:按编号对某个员工的某项信息进行修改。
    4. 插入:加入新员工的信息。
    5. 删除:按编号删除已离职员工的信息。
    6. 排序:按特定条件对所有员工的信息进行排序。

2. 结构定义

2.1 员工结构定义

// 生日结构
typedef struct birthday
{
	int year;
	int month;
	int day;
}birthday;

// 员工结构
typedef struct employee
{
	int id;
	char name[50];
	char gender[10];
	birthday birthday;
	char qualification[20];
	char job[30];
	char teleNum[15];
	char location[50];
}employee;

2.2 存储结构定义

typedef struct employee employee;
// 存储结构定义
typedef employee SLDataType;
typedef struct SeqList_dynamic
{
	SLDataType* SeqList;//指向可以修改大小的数据空间
	int size;//有效数据个数
	int capacity;//数据空间的总大小
}SL;

//动态顺序表的实现
//初始化和销毁
void SLInit(SL* ps);
void SLDestroy(SL* ps);
//顺序表的扩容 
void SLCheckCapacity(SL* ps);
//顺序表的头部插⼊删除 / 尾部插⼊删除 
void SLPushBack(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
void SLPushFront(SL* ps, SLDataType x);
void SLPopFront(SL* ps);
//顺序表指定位置之前插⼊/删除数据 
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);

3. 项目文件

在本项目中,一共有五个文件

  1. SeqList.h:存放动态顺序表功能声明
  2. SeqList.c:存放动态顺序表功能定义
  3. employee.c:存放员工管理系统功能声明
  4. employee.h:存放员工管理系统功能定义
  5. test.c:测试文件

4. 项目功能

本项目主要实现下面的功能:

  1. 加载上次程序结束时保存到文件的员工信息
  2. 从控制台输入员工信息
  3. 查找指定信息的员工:仅包括id和姓名查找
  4. 修改员工的信息
  5. 插入一个新员工
  6. 删除指定员工
  7. 对员工信息按照指定信息排序:仅包括id、姓名和生日
  8. 打印所有员工信息
  9. 向文件中写入员工信息数据,便于下次读取

程序主菜单

void menu()
{
	printf("*****************************\n");
	printf("******员工信息管理系统*******\n");
	printf("*1. 输入员工信息\n");
	printf("*2. 查找员工\n");
	printf("*3. 修改员工\n");
	printf("*4. 插入员工\n");
	printf("*5. 删除员工\n");
	printf("*6. 排序输出员工信息\n");
	printf("*7. 查看所有员工信息\n");
	printf("*8. 退出系统\n");
	printf("*****************************\n");
}

程序功能对应函数

// 程序菜单
void menu();

// 导入数据
void LoadEmployee(SL* employees);

// 初始化——输入
void employeeInfoGet(SL* employees);

// 查找员工
// 查找方式菜单
void menuForFind();
// 查找员工——返回下标
int findEmployee(SL* employees);
// 查找员工——显示对应员工信息
void findEmployee_print(SL* employees);

// 修改菜单
void menuForModify();
// 修改员工信息
void modifyEmployee(SL* employees);

// 插入员工信息
void insertEmployee(SL* employees);

// 删除员工信息
void deleteEmployee(SL* employees);

// 排序菜单
void menuForSort();
// 按照指定内容排序
void sortEmployees(SL* employees);
// 打印排序后的内容
void printSortedEmp(employee* tmp, int size);

// 向文件中写入数据
void writeIntoFile(SL* employees);

// 销毁系统数据
void destroyEmployee(SL* employees);

// 打印所有员工信息
void printEmployees(SL* employees);

程序主函数

#define _CRT_SECURE_NO_WARNINGS 1

#include "SeqList.h"
#include "employee.h"

int main()
{
	SL employees;
	SLInit(&employees);
	char ans = 0;
	printf("是否需要导入上次的数据:y/n");
	scanf(" %c", &ans);
	if (ans == 'y')
	{
		LoadEmployee(&employees);
	}
	menu();
	printf("请输入选项:");
	int choice = 0;
	while (scanf("%d", &choice))
	{
		switch (choice)
		{
		case 1:
			employeeInfoGet(&employees);
			break;
		case 2:
			findEmployee_print(&employees);
			break;		
		case 3:
			modifyEmployee(&employees);
			break;
		case 4:
			insertEmployee(&employees);
			break;
		case 5:
			deleteEmployee(&employees);
			break;
		case 6:
			sortEmployees(&employees);
			break;
		case 7:
			printEmployees(&employees);
			break;
		case 8:
			destroyEmployee(&employees);
			printf("谢谢使用");
			return 1;
		default:
			printf("请按照菜单重新输入\n");
			break;
		}
		menu();
		printf("请输入选项:");
	}
}

5. 功能实现

5.1 加载数据

在加载数据函数中,使用fopen函数和fread函数进行文件操作,当fread文件读到一组数据时,向顺序表中插入一组数据,再循环读取直到读到文件结尾

// 导入数据
// 加载上一次的数据
void LoadEmployee(SL* employees) 
{
	FILE* pf = fopen("employees.txt", "rb"); 
	if (pf == NULL) 
	{
		perror("fopen error!\n"); 
		return;
	}
	employee tmp;
	while (fread(&tmp, sizeof(employee), 1, pf))
	{
		SLPushBack(employees, tmp);
	}
	fclose(pf);
	printf("成功导入历史数据\n");
}

5.2 输入数据

首先通过num控制输入的员工个数

int num = 0;
printf("请输入员工个数:");
scanf("%d", &num);

接着通过for循环根据num的值控制数据的输入,对于生日的输入来说,为了保证生日日期的合法性,使用if语句和goto语句处理不正确的日期,输入完一组数据后,调用顺序表的尾插函数向顺序表中插入数据

for (int i = 0; i < num; i++)
{
	employee tmp;
	printf("请输入第%d个员工\n", i + 1);
	printf("请输入员工id:");
	scanf("%d", &(tmp.id));
	printf("请输入姓名:");
	scanf("%s", tmp.name);
	printf("请输入员工性别:");
	scanf("%s", tmp.gender);
	printf("请按照年月日的顺序以空格间隔输入员工生日:");
setBirth:
	scanf("%d%*c%d%*c%d", &(tmp.birthday.year),
						&(tmp.birthday.month),
						&(tmp.birthday.day));
	if (tmp.birthday.month < 1 || tmp.birthday.month > 12 ||
		(tmp.birthday.day < 1 ||
		tmp.birthday.day > GetMonthDays(tmp.birthday.year, tmp.birthday.month)))
	{
		printf("请重新输入生日\n");
		goto setBirth;
	}
	printf("请输入员工学历:");
	scanf("%s", tmp.qualification);
	printf("请输入员工职业:");
	scanf("%s", tmp.job);
	printf("请输入员工电话:");
	scanf("%s", tmp.teleNum);
	printf("请输入员工的住址:");
	scanf("%s", tmp.location);
	SLPushBack(employees, tmp);
}

完整模块代码

// 获取日期函数
int GetMonthDays(int year, int month)
{
	int monthDays[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	if (month == 2 && (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)))
	{
		return monthDays[month] + 1;
	}
	return monthDays[month];
}

// 获取输入
void employeeInfoGet(SL* employees)
{
	int num = 0;
	printf("请输入员工个数:");
	scanf("%d", &num);

	for (int i = 0; i < num; i++)
	{
		employee tmp;
		printf("请输入第%d个员工\n", i + 1);
		printf("请输入员工id:");
		scanf("%d", &(tmp.id));
		printf("请输入姓名:");
		scanf("%s", tmp.name);
		printf("请输入员工性别:");
		scanf("%s", tmp.gender);
		printf("请按照年月日的顺序以空格间隔输入员工生日:");
	setBirth:
		scanf("%d%*c%d%*c%d", &(tmp.birthday.year),
							&(tmp.birthday.month),
							&(tmp.birthday.day));
		if (tmp.birthday.month < 1 || tmp.birthday.month > 12 ||
			(tmp.birthday.day < 1 ||
			tmp.birthday.day > GetMonthDays(tmp.birthday.year, tmp.birthday.month)))
		{
			printf("请重新输入生日\n");
			goto setBirth;
		}
		printf("请输入员工学历:");
		scanf("%s", tmp.qualification);
		printf("请输入员工职业:");
		scanf("%s", tmp.job);
		printf("请输入员工电话:");
		scanf("%s", tmp.teleNum);
		printf("请输入员工的住址:");
		scanf("%s", tmp.location);
		SLPushBack(employees, tmp);
	}
}

5.3 查找员工信息

在设计查找模块时,一共实现了两种查找方式:

  1. 查找返回下标
  2. 查找打印员工信息
5.3.1 查找返回下标

调用返回下标函数便于删除函数调用,一共实现了两种查找方式

  1. 按照名字查找
// 按照名字查找
int findBy_name(SL* employees, char* target)
{
	for (int i = 0; i < employees->size; i++)
	{
		if (strcmp(employees->SeqList[i].name, target) == 0)
		{
			return i;// 返回对应位置下标
		}
	}
	return -1;
}
  1. 按照id查找
// 按照id查找
int findBy_id(SL* employees, int target)
{
	for (int i = 0; i < employees->size; i++)
	{
		if (employees->SeqList[i].id == target)
		{
			return i;// 返回对应位置下标
		}
	}
	return -1;
}

调用两种函数的主调函数

// 查找员工——返回下标
int findEmployee(SL* employees)
{
	int choice = 0;
	int pos = 0;
	menuForFind();
	printf("请选择查找方式:");
	while (scanf("%d", &choice))
	{
		switch (choice)
		{
		case 1:
		{
			int target = 0;
			printf("请输入需要查找的员工id:");
			scanf("%d", &target);
			pos = findBy_id(employees, target);
			return pos;
		}
		break;
		case 2:
		{
			char name[50] = { 0 };
			printf("请输入需要查找的员工姓名:");
			scanf("%s", name);
			pos = findBy_name(employees, name);
			return pos;
		}
		break;
		case 3:
			return -1;
		default:
			menuForFind();
			printf("请重新按照菜单输入:");
			break;
		}
		menuForFind();
		printf("请选择查找方式:");
	}
	return -1;
}
5.3.2 查找打印员工信息

调用返回下标的函数,根据返回的pos下标打印对应的员工信息

// 查找并打印员工信息
void findEmployee_print(SL* employees)
{
	// 调用findEmployee函数
	int pos = findEmployee(employees);
	if (pos != -1)
	{
		printf("\t员工id:%d", employees->SeqList[pos].id);
		printf("\t姓名:%s", employees->SeqList[pos].name);
		printf("\t性别:%s", employees->SeqList[pos].gender);
		printf("\t生日:%d-%02d-%02d\n", employees->SeqList[pos].birthday.year,
			employees->SeqList[pos].birthday.month,
			employees->SeqList[pos].birthday.day);
		printf("\t学历:%s", employees->SeqList[pos].qualification);
		printf("\t职业:%s", employees->SeqList[pos].job);
		printf("\t电话:%s", employees->SeqList[pos].teleNum);
		printf("\t地址:%s\n", employees->SeqList[pos].location);
	}
	else
	{
		printf("查无此人\n");
	}
}

5.4 修改员工信息

实现修改员工信息函数可以选择修改8种信息:

  1. 编号
  2. 姓名
  3. 性别
  4. 出生日期
  5. 学历
  6. 职业
  7. 电话
  8. 地址

对于生日日期的输入来说,存在合法日期判断,与输入处理方式类似,结合if语句和goto语句进行处理

[!IMPORTANT]
在下面的代码中,对于生日信息的修改使用了整体修改和判断而不是针对年、月或者日进行单独修改,原因如下:
以年为例,如果用户的生日所在年份是闰年,那么对应的2月有29天,但是当用户需要修改年为非闰年时,那么2月的日期也需要修改,此时需要对年、月和日都写判断条件,所以看似是修改一个年份,实际上个别情况需要两个变量都需要修改,所以为了使得修改方便,考虑整体修改再最后整体判断
// 修改员工
void modifyEmployee(SL* employees)
{
	// 调用查找函数找出需要修改的员工
	int pos = findEmployee(employees);
	if (pos == -1)
	{
		printf("查无此人\n");
		return;
	}
	int choice = 0;
	menuForModify();
	printf("请选择需要修改的内容:");
	while (scanf(" %d", &choice))
	{
		switch (choice)
		{
		case 1:
			printf("请输入需要修改的id:");
			scanf("%d", &(employees->SeqList[pos].id));
			printf("修改完成\n");
			break;
		case 2:
			printf("请输入需要修改的姓名:");
			scanf("%s", employees->SeqList[pos].name);
			printf("修改完成\n");
			break;
		case 3:
			printf("请输入需要修改的性别:");
			scanf("%s", employees->SeqList[pos].gender);
			printf("修改完成\n");
			break;
		case 4:
		{
			modifyBirth:
			printf("请输入修改后的年月日,以空格间隔:");
			scanf("%d%*c%d%*c%d", &(employees->SeqList[pos].birthday.year),
				&(employees->SeqList[pos].birthday.month),
				&(employees->SeqList[pos].birthday.day));
			if (employees->SeqList[pos].birthday.month < 1 || 
				employees->SeqList[pos].birthday.month > 12 ||
				(employees->SeqList[pos].birthday.day < 1 ||
					employees->SeqList[pos].birthday.day > 
					GetMonthDays(employees->SeqList[pos].birthday.year, 
						employees->SeqList[pos].birthday.month)))
			{
				printf("日期不合法,请重新输入生日\n");
				goto modifyBirth;
			}
		}
			printf("修改完成\n");
			break;
		case 5:
			printf("请输入需要修改的学历:");
			scanf("%s", employees->SeqList[pos].qualification);
			printf("修改完成\n");
			break;
		case 6:
			printf("请输入需要修改的职业:");
			scanf("%s", employees->SeqList[pos].job);
			printf("修改完成\n");
			break;
		case 7:
			printf("请输入需要修改的电话:");
			scanf("%s", employees->SeqList[pos].teleNum);
			printf("修改完成\n");
			break;
		case 8:
			printf("请输入需要修改的地址:");
			scanf("%s", employees->SeqList[pos].location);
			printf("修改完成\n");
			break;
		case 9:
			return;
		default:
			printf("请重新选择\n");
			break;
		}
		menuForModify();
		printf("请选择需要修改的内容:");
	}
}

5.5 插入员工信息

在插入员工信息函数中,因为与输入员工信息函数基本思路相同,所以考虑直接复用输入员工信息函数

// 插入数据
void insertEmployee(SL* employees)
{
	// 复用输入函数
	employeeInfoGet(employees);
}

5.6 删除员工信息

删除员工信息首先需要调用查找到指定员工,再执行删除,如果没找到,则提示“查无此人,删除失败”,否则“删除成功”,因为删除是删除顺序表中的元素,所以直接调用顺序表的删除函数

// 删除员工
void deleteEmployee(SL* employees)
{
	// 调用查找函数
	int pos = findEmployee(employees);
	if (pos < 0)
	{
		printf("查无此人,删除失败");
	}
	SLErase(employees, pos);
	printf("删除完成\n");
}

5.7 排序员工信息

实现员工的排序信息一共有三种实现方式:

  1. 按照员工id排序
// 按照id排序
int cmp_id(const void* p1, const void* p2)
{
	return ((employee*)p1)->id - ((employee*)p2)->id;
}
  1. 按照员工姓名排序
// 名字比较
int cmp_name(const void* p1, const void* p2)
{
	return strcmp(((employee*)p1)->name, ((employee*)p2)->name);
}
  1. 按照员工生日日期排序
// 日期比较
int birthdayCmp(const void* p1, const void* p2)
{
	//如果年大就直接返回1
	if (((employee*)p1)->birthday.year > ((employee*)p2)->birthday.year)
	{
		return 1;
	}
	else if (((employee*)p1)->birthday.year == ((employee*)p2)->birthday.year &&
		((employee*)p1)->birthday.month > ((employee*)p2)->birthday.month)//年相等时比较月份,月份大就直接返回true
	{
		return 1;
	}
	else if (((employee*)p1)->birthday.year == ((employee*)p2)->birthday.year &&
		((employee*)p1)->birthday.month == ((employee*)p2)->birthday.month && ((employee*)p1)->birthday.day > ((employee*)p2)->birthday.day)//年相等,月份相等时,天大就直接返回true
	{
		return 1;
	}
	else//其他情况均返回-1
	{
		return -1;
	}
}

在排序主调函数中,使用C语言库中的qsort函数结合函数指针调用上述三种函数完成对应的排序功能

调用三种函数的主调函数:

直接调用函数

// 排序主体
void sortEmployees(SL* employees)
{
	int choice = 0;
	menuForSort();
	printf("请选择需要排序的字段:");
	while (scanf("%d", &choice))
	{
		switch (choice)
		{
		case 1:
			qsort(employees->SeqList, employees->size, sizeof(employee), cmp_id);
			printSortedEmp(employees->SeqList, employees->size);
			break;
		case 2:
			qsort(employees->SeqList, employees->size, sizeof(employee), cmp_name);
			printSortedEmp(employees->SeqList, employees->size);
			break;
		case 3:
			qsort(employees->SeqList, employees->size, sizeof(employee), birthdayCmp);
			printSortedEmp(employees->SeqList, employees->size);
			break;
		case 4:
			return;
		default:
			menuForSort();
			printf("请重新按照菜单输入:");
			break;
		}
		menuForSort();
		printf("请选择需要排序的字段:");
	}
}

转移表优化

// 转移表优化
void sortEmployees(SL* employees)
{
	int choice = 0;
	menuForSort();
	printf("请选择需要排序的字段:");
	// 定义函数指针数组
	int (*cmp[4])(const void*, const void*) = {0, cmp_id, cmp_name, birthdayCmp};
	//cmp arr[4] = {0, cmp_id, cmp_name, birthdayCmp};
	while (scanf("%d", &choice))
	{
		if (choice >= 1 && choice <= 3)
		{
			qsort(employees->SeqList, employees->size, sizeof(employee), cmp[choice]);
			printSortedEmp(employees->SeqList, employees->size);
		}
		else if (choice == 4)
		{
			break;
		}
		else
		{
			menuForSort();
			printf("请重新输入:");
		}
		menuForSort();
		printf("请选择需要排序的字段:");
	}
}

为了可以更好看到排序后的结果,在每一次排序结束后,将自动执行一次排序结果打印函数

// 打印排序后的内容
void printSortedEmp(employee* tmp, int size)
{
	for (int i = 0; i < size; i++)
	{
		printf("第%d名员工:\n", i + 1);
		printf("\t员工id:%d", tmp[i].id);
		printf("\t姓名:%s", tmp[i].name);
		printf("\t性别:%s", tmp[i].gender);
		printf("\t生日:%d-%02d-%02d\n", tmp[i].birthday.year,
			tmp[i].birthday.month,
			tmp[i].birthday.day);
		printf("\t学历:%s", tmp[i].qualification);
		printf("\t职业:%s", tmp[i].job);
		printf("\t电话:%s", tmp[i].teleNum);
		printf("\t地址:%s\n", tmp[i].location);
	}
}

5.8 写入数据与顺序表空间释放

为了更好地保存数据,在程序结束时考虑将内存中的数据使用fopenfwrite函数写入硬盘中

// 向文件中写入数据
void writeIntoFile(SL* employees)
{
	FILE* pf = fopen("employees.txt", "wb"); 
	if (pf == NULL) 
	{
		perror("fopen error!\n"); 
		return;
	}
	//将通讯录数据写⼊⽂件
	for (int i = 0; i < employees->size; i++)
	{
		fwrite(employees->SeqList + i, sizeof(employee), 1, pf);
	}
	fclose(pf);
	printf("数据保存成功!\n");
}

在程序结束前,防止内存泄漏问题,需要对顺序表在堆上的空间进行释放,考虑设计空间销毁函数,因为需要写数据到文件中,所以写入文件过程可以放入空间释放函数中,通过询问用户是否需要保存数据实现更好的交互性

void destroyEmployee(SL* employees)
{
	char ans = 0;
	printf("是否需要保存数据:y/n");
	scanf(" %c", &ans);
	if (ans == 'y')
	{
		writeIntoFile(employees);
	}
	SLDestroy(employees);
}

5.9 打印所有员工信息

为了更好让用户看到已经保存到内存缓冲区的内容,考虑使用打印函数将缓冲区内容打印到控制台

// 打印所有员工信息
void printEmployees(SL* employees)
{
	// 没有员工直接返回
	if (employees->size == 0)
	{
		printf("暂无数据\n");
		return;
	}
	for (int i = 0; i < employees->size; i++)
	{
		printf("第%d名员工:\n", i + 1);
		printf("\t员工id:%d", employees->SeqList[i].id);
		printf("\t姓名:%s", employees->SeqList[i].name);
		printf("\t性别:%s", employees->SeqList[i].gender);
		printf("\t生日:%d-%02d-%02d\n", employees->SeqList[i].birthday.year,
			employees->SeqList[i].birthday.month,
			employees->SeqList[i].birthday.day);
		printf("\t学历:%s", employees->SeqList[i].qualification);
		printf("\t职业:%s", employees->SeqList[i].job);
		printf("\t电话:%s", employees->SeqList[i].teleNum);
		printf("\t地址:%s\n", employees->SeqList[i].location);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

怡晗★

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值