一、基础知识
哈希链表
二、项目文件 & 代码
staff_manage_system目录下
1、Makefile文件
2、include-子目录
3、src-子目录
4、obj-子目录
5、bin-子目录
1、Makefile文件
ALL:
make -C ./src/
make -C ./obj/
.PHONY:CLEAN
CLEAN:
rm obj/*.o
rm bin/*
2、include-子目录
staff.h
源码
#ifndef STAFF_H
#define STAFF_H
//相关头文件
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//文件相关头文件
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define SIZE 50
//相关定义
typedef struct staff{
char name[20];
int id;
char position[20];
int salary;
char status[20];
}data_type;
typedef struct linkNode{
data_type data;
struct linkNode *pNext;
}Link;
typedef struct hash{
Link *pArr[SIZE];
int count;
int lastId;
}Hash;
typedef enum ret
{
/* member */
HASH_NULL = -4,
MALLOC_ERROR,
ERROR,
POS_ERROR,
OK
} ret_e;
//函数声明
int Menu(void);
Hash *createHash(void);
int hashFun(int key);
int insertHash(Hash *pHash,data_type item);
int showHash(Hash *pHash);
int deleteStaffInformation(Hash *pHash,int id);
int searchStaffInformation(Hash *pHash,int id,data_type *pData);
int modifyStaffInformation(Hash *pHash,data_type NewData);
int Load(Hash *PHash);
#endif
注释
这段代码是一个C语言的头文件(.h文件),主要用于定义与“员工”信息相关的数据结构以及声明一些函数。我会逐行为你解释:
c
#ifndef STAFF_H
这一行是预处理指令,用于检查是否已经定义了STAFF_H。如果没有定义,则编译接下来的代码;如果已经定义,则跳过接下来的代码,直到遇到#endif。这样做是为了防止头文件被多次包含,避免重复定义的问题。
c
#define STAFF_H
定义STAFF_H,这样,如果再次尝试包含这个头文件,由于STAFF_H已经被定义,所以不会再编译此头文件中的内容。
c
//相关头文件
这是一个注释,说明接下来的代码是引入相关的头文件。
c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
引入三个标准库头文件,分别为:
stdio.h:用于输入输出。
stdlib.h:包含一些常用函数,如内存分配函数。
string.h:用于字符串操作。
c
//文件相关头文件
注释说明接下来是引入与文件操作相关的头文件。
c
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
引入三个与文件操作和系统类型定义相关的头文件。
c
#define SIZE 50
定义一个宏SIZE,其值为50。
c
//相关定义
注释说明接下来是相关的数据结构和枚举定义。
c
typedef struct staff{
char name[20];
int id;
char position[20];
int salary;
char status[20];
}data_type;
定义了一个结构体staff,用于表示员工的信息。该结构体包含:
name:员工的名字,字符数组,长度为20。
id:员工的ID,整数类型。
position:员工的职位,字符数组,长度为20。
salary:员工的薪水,整数类型。
status:员工的状态,字符数组,长度为20。
然后,使用typedef为这个结构体定义了一个别名data_type。
c
typedef struct linkNode{
data_type data;
struct linkNode *pNext;
}Link;
定义了一个单链表节点结构体linkNode,包含一个data_type类型的数据和一个指向下一个节点的指针。然后,使用typedef为这个结构体定义了一个别名Link。
c
typedef struct hash{
Link *pArr[SIZE];
int count;
int lastId;
}Hash;
定义了一个哈希表结构体hash,包含一个Link指针数组(用于存储链表)、一个整数count(用于记录哈希表中的元素数量)和一个整数lastId(用于记录最后一个分配的ID)。然后,使用typedef为这个结构体定义了一个别名Hash。
c
typedef enum ret{
HASH_NULL = -4,
MALLOC_ERROR,
ERROR,
POS_ERROR,
OK
} ret_e;
定义了一个枚举类型ret,用于表示函数的返回值状态。这个枚举类型有一个别名ret_e。
c
//函数声明
注释说明接下来是函数的声明。
以下是一系列的函数声明,它们告诉编译器这些函数的存在,但具体的实现可能在其他.c文件中。
c
int Menu(void);
Hash *createHash(void);
int hashFun(int key);
int insertHash(Hash *pHash,data_type item);
int showHash(Hash *pHash);
int deleteStaffInformation(Hash *pHash,int id);
int searchStaffInformation(Hash *pHash,int id,data_type *pData);
int modifyStaffInformation(Hash *pHash,data_type NewData);
int Load(Hash *PHash);
这些函数大致的功能是:
Menu:可能是一个显示菜单的函数。
createHash:创建一个新的哈希表。
hashFun:哈希函数,可能用于计算员工的ID在哈希表中的位置。
insertHash:在哈希表中插入一个员工的信息。
showHash:显示哈希表中的所有员工信息。
deleteStaffInformation:根据ID删除一个员工
3、src-子目录
main.c
源码
#include"../include/staff.h"
int main(int argc, const char *argv[])
{
int op = 0;
int ret = 0;
data_type item;
int id = 0;
Hash *pHash = createHash();
data_type staffData;
while(1){
op = Menu();
if(-1 == op) break;
switch(op){
case 1:
printf("请输入新员工的姓名:\t");
scanf("%s",item.name);
item.id = pHash->lastId + 1;
pHash->lastId++;
printf("请输入新员工的职位:\t");
scanf("%s",item.position);
printf("请输入新员工的薪资:\t");
scanf("%d",&item.salary);
printf("请输入新员工的状态:\t");
scanf("%s",item.status);
ret = insertHash(pHash,item);
break;
case 2:
printf("请输入要开除员工的id号:\t");
scanf("%d",&id);
ret = deleteStaffInformation(pHash,id);;
break;
case 3:
printf("请输入所要查询员工的id号:\t");
scanf("%d",&id);
ret = searchStaffInformation(pHash,id,NULL);
break;
case 4:
printf("请输入要修改员工信息的id:\t");
scanf("%d",&id);
searchStaffInformation(pHash,id,&staffData);
printf("请输入调整后的岗位:\t");
scanf("%s",staffData.position);
printf("请定薪:\t");
scanf("%d",&staffData.salary);
ret = modifyStaffInformation(pHash,staffData);
break;
case 5:
ret = showHash(pHash);
break;
case 6:
ret = Save(pHash);
break;
case 7:
ret = Load(pHash);
break;
default:
puts("请输入正确的选项!!!");
break;
}
switch(ret){
case HASH_NULL:
puts("HASH_NULL");
break;
case MALLOC_ERROR:
puts("MALLOC_ERROR");
break;
case ERROR:
puts("ERROR");
break;
case POS_ERROR:
puts("POS_ERROR");
break;
}
}
return 0;
}
注释
以下是对于main函数的详细注释:
c
#include"../include/staff.h" // 引入员工信息相关的头文件
int main(int argc, const char *argv[]) // 主函数入口
{
int op = 0; // 操作选项变量,用于存储用户选择的操作
int ret = 0; // 函数返回值变量,用于存储函数执行后的状态
data_type item; // 定义一个员工信息结构体变量
int id = 0; // 员工ID变量
// 创建哈希表
Hash *pHash = createHash();
data_type staffData; // 用于临时存储员工信息的变量
// 循环显示菜单并等待用户输入操作选项
while(1){
op = Menu(); // 显示菜单并获取用户选择的操作选项
if(-1 == op) break; // 如果用户选择退出,则跳出循环
switch(op){ // 根据用户选择的操作选项执行相应的代码块
case 1: // 插入新员工信息
printf("请输入新员工的姓名:\t");
scanf("%s", item.name);
item.id = pHash->lastId + 1; // 新员工的ID为哈希表当前最后一个ID加1
pHash->lastId++; // 更新哈希表的最后一个ID
printf("请输入新员工的职位:\t");
scanf("%s", item.position);
printf("请输入新员工的薪资:\t");
scanf("%d", &item.salary);
printf("请输入新员工的状态:\t");
scanf("%s", item.status);
ret = insertHash(pHash, item); // 插入新员工信息到哈希表
break;
case 2: // 删除员工信息
printf("请输入要开除员工的id号:\t");
scanf("%d", &id);
ret = deleteStaffInformation(pHash, id); // 根据ID删除员工信息
break;
case 3: // 查询员工信息
printf("请输入所要查询员工的id号:\t");
scanf("%d", &id);
ret = searchStaffInformation(pHash, id, NULL); // 查询员工信息,不需要返回员工数据
break;
case 4: // 修改员工信息
printf("请输入要修改员工信息的id:\t");
scanf("%d", &id);
// 先查询员工信息
searchStaffInformation(pHash, id, &staffData);
printf("请输入调整后的岗位:\t");
scanf("%s", staffData.position);
printf("请定薪:\t");
scanf("%d", &staffData.salary);
ret = modifyStaffInformation(pHash, staffData); // 修改员工信息
break;
case 5: // 显示所有员工信息
ret = showHash(pHash); // 显示哈希表中的所有员工信息
break;
case 6: // 保存员工信息到文件
ret = Save(pHash); // 保存哈希表到文件
break;
case 7: // 从文件加载员工信息
ret = Load(pHash); // 从文件加载员工信息到哈希表
break;
default: // 非法操作选项
puts("请输入正确的选项!!!");
break;
}
// 根据函数返回值显示错误信息
switch(ret){
case HASH_NULL:
puts("HASH_NULL");
break;
case MALLOC_ERROR:
puts("MALLOC_ERROR");
break;
case ERROR:
puts("ERROR");
break;
case POS_ERROR:
puts("POS_ERROR");
break;
}
}
return 0; // 程序正常退出
}
这个main函数是程序的主入口点,它首先创建了一个哈希表来存储员工信息。然后进入一个无限循环,显示菜单并等待用户输入操作选项。根据用户的选择,程序执行相应的操作,如插入新员工、删除员工、查询员工信息、修改员工信息、显示所有员工信息、保存员工信息到文件以及从文件加载员工信息。每个操作都调用相应的函数,并根据函数的返回值来输出可能的错误信息。如果用户选择退出,则跳出循环并结束程序。
需要注意的是,这段代码假设已经有一个Menu函数用于显示菜单并获取用户输入的操作选项,以及一个Save函数用于保存哈希表到文件,这两个函数的具体实现在这段代码中并没有给出。另外,代码中的Save函数和Load函数应该是根据Hash结构体的具体
menu.c
源码
#include"../include/staff.h"
int Menu(void)
{
int op = 0;
puts("|--------------------员工管理系统--------------------|");
puts("| 1-->增加员工信息 |");
puts("| 2-->开除员工 |");
puts("| 3-->查询员工信息 |");
puts("| 4-->修改员工信息 |");
puts("| 5-->显示员工信息 |");
puts("| 6-->保存 |");
puts("| 7-->导入 |");
puts("|-1-->退出系统 |");
puts("|----------------------------------------------------|");
printf("请输入选项:");
scanf("%d",&op);
return op;
}
注释
以下是对于Menu函数的详细注释:
c
#include"../include/staff.h" //引入员工信息相关的头文件
// Menu函数用于显示菜单并获取用户输入的操作选项
int Menu(void)
{
int op = 0; // 定义一个整数变量用于存储用户选择的操作选项
// 输出菜单选项
puts("|--------------------员工管理系统--------------------|");
puts("| 1-->增加员工信息 |");
puts("| 2-->开除员工 |");
puts("| 3-->查询员工信息 |");
puts("| 4-->修改员工信息 |");
puts("| 5-->显示员工信息 |");
puts("| 6-->保存 |");
puts("| 7-->导入 |");
puts("|-1-->退出系统 |");
puts("|----------------------------------------------------|");
// 提示用户输入选项
printf("请输入选项:");
// 获取用户输入的操作选项
scanf("%d", &op);
// 返回用户选择的操作选项
return op;
}
Menu函数是一个独立的函数,它的主要功能是显示一个包含多个操作选项的菜单,并提示用户输入一个操作选项。用户输入的选项被存储在变量op中,并最终通过return语句返回这个选项。
具体功能如下:
使用puts函数输出菜单的每一行,以创建一个整齐美观的菜单界面。
使用printf函数提示用户输入一个操作选项。
使用scanf函数读取用户输入的整数,并将其存储在变量op中。
返回变量op的值,这个值代表了用户选择的操作选项,可以在调用Menu函数的代码处使用。
需要注意的是,这个函数假设用户会按照提示输入一个合法的整数选项。在实际应用中,可能需要添加一些错误处理机制来确保输入的有效性,比如检查用户是否输入了数字,以及输入的数字是否在菜单选项的范围内。此外,这个函数没有处理scanf可能遇到的输入错误,这在实际应用中也是需要考虑的。
create.c
源码
#include"../include/staff.h"
Hash *createHash(void){
Hash *pHash = NULL;
pHash = (Hash *)malloc(sizeof(Hash));
if(!pHash) return NULL;
memset(pHash,0,sizeof(Hash));
return pHash;
}
int hashFun(int key){
return key - 1;
}
注释
以下是对于createHash和hashFun函数的详细注释:
#include"../include/staff.h" // 引入包含Hash结构定义和相关宏/函数声明的头文件
// createHash函数用于创建一个新的Hash结构体并返回其指针
Hash *createHash(void) {
Hash *pHash = NULL; // 定义一个Hash类型的指针变量pHash,并初始化为NULL
// 为pHash动态分配内存,使其指向一个新的Hash结构体
pHash = (Hash *)malloc(sizeof(Hash));
// 检查内存分配是否成功
if (!pHash) return NULL; // 如果内存分配失败,则返回NULL
// 使用memset函数将新分配的Hash结构体内存区域初始化为0
memset(pHash, 0, sizeof(Hash));
// 返回新创建的Hash结构体的指针
return pHash;
}
// hashFun函数是一个简单的哈希函数,用于将键(key)转换为哈希表中的索引
int hashFun(int key) {
// 此哈希函数简单地将键减去1作为哈希值返回
// 注意:这种哈希函数可能不适合所有情况,因为它可能导致哈希冲突
// 在实际应用中,应该根据键的特点和哈希表的大小来选择合适的哈希函数
return key - 1;
}
createHash函数的功能是创建一个新的Hash结构体实例,并返回指向这个实例的指针。它首先尝试为Hash结构体分配内存,如果内存分配失败(即malloc返回NULL),则函数返回NULL。如果内存分配成功,它会使用memset函数将新分配的内存区域初始化为0,以确保结构体中的所有字段都被设置为默认值。最后,函数返回指向新创建的Hash结构体的指针。
hashFun函数是一个简单的哈希函数,它接受一个整数键key作为参数,并返回该键经过简单运算后的哈希值。在这个例子中,哈希函数仅仅是将键减去1。这种简单的哈希函数可能不适用于所有情况,因为它可能导致哈希冲突(即不同的键产生相同的哈希值)。在实际应用中,应该根据键的特点和哈希表的大小来选择合适的哈希函数,以减少哈希冲突的可能性。
insert.c
源码
#include"../include/staff.h"
int insertHash(Hash *pHash,data_type item){
if(NULL == pHash)
{
return HASH_NULL;
}
int index = hashFun(item.id);
Link *pNew = (Link *)malloc(sizeof(Link));
if(NULL == pNew)
{
return MALLOC_ERROR;
}
memset(pNew,0,sizeof(Link));
pNew->data = item;
pNew->pNext = pHash->pArr[index];
pHash->pArr[index] = pNew;
pHash->count++;
return OK;
}
注释
以下是insertHash函数的详细注释:
c
#include"../include/staff.h" // 引入员工管理系统的头文件,其中定义了Hash结构、data_type、Link结构、以及相关的宏和函数
// insertHash函数用于向哈希表中插入一个元素
int insertHash(Hash *pHash, data_type item) {
// 检查传入的哈希表指针是否为空
if (NULL == pHash) {
// 如果为空,则返回错误码HASH_NULL
return HASH_NULL;
}
// 计算元素的哈希值,确定元素在哈希表中的索引位置
int index = hashFun(item.id);
// 为新节点分配内存
Link *pNew = (Link *)malloc(sizeof(Link));
if (NULL == pNew) {
// 如果内存分配失败,则返回错误码MALLOC_ERROR
return MALLOC_ERROR;
}
// 将新分配的内存区域初始化为0
memset(pNew, 0, sizeof(Link));
// 将要插入的元素赋值给新节点的data字段
pNew->data = item;
// 将新节点插入到哈希表的指定位置,新节点指向原来该位置的头节点
pNew->pNext = pHash->pArr[index];
// 更新哈希表指定位置的头节点为新节点
pHash->pArr[index] = pNew;
// 哈希表中元素数量加1
pHash->count++;
// 插入成功,返回OK
return OK;
}
函数insertHash的主要功能是将一个data_type类型的元素item插入到哈希表pHash中。
首先,函数检查传入的哈希表指针pHash是否为空,如果为空则返回错误码HASH_NULL。
接着,使用hashFun函数计算item的哈希值,从而确定它在哈希表中的索引位置index。
然后,为新节点分配内存,如果内存分配失败则返回错误码MALLOC_ERROR。
使用memset函数将新分配的内存区域初始化为0,确保新节点是干净的。
将要插入的元素item赋值给新节点的data字段。
新节点插入到哈希表的指定位置index,新节点的pNext指针指向原来该位置的头节点,完成链表的头插操作。
更新哈希表在位置index的头节点为新节点。
哈希表中元素数量增加1,更新count字段。
最后,函数返回OK表示插入成功。
注意:
data_type、Hash、Link、HASH_NULL、MALLOC_ERROR、OK等应该在staff.h头文件中有所定义,它们可能是结构体、类型别名、宏或常量。
函数假设hashFun能够返回一个有效的索引值,并且这个索引值在哈希表的数组范围内。
函数没有处理哈希冲突,它只是简单地将新节点插入到哈希表的对应位置链表的头部。在实际应用中,可能需要更复杂的策略来处理哈希冲突,如链地址法、开放寻址法等。
函数没有进行边界检查,如检查index是否越界。在实际应用中,应该添加适当的边界检查来提高代码的健壮性。
show.c
源码
#include"../include/staff.h"
int showHash(Hash *pHash){
if(NULL == pHash)
{
return HASH_NULL;
}
int i;
Link *pTmp = NULL;
for(i = 0; i < pHash->lastId; i++){
pTmp = pHash->pArr[i];
while(NULL != pTmp){
printf("|----------员工信息----------|\n");
printf("|姓名:%s \n",pTmp->data.name);
printf("|工号:%d \n",pTmp->data.id);
printf("|职位:%s \n",pTmp->data.position);
printf("|薪资:%d \n",pTmp->data.salary);
printf("|状态:%s \n",pTmp->data.status);
printf("|^^^^^^^^^^^^^^^^^^^^^^^^^^^^^|\n");
pTmp = pTmp->pNext;
}
}
return OK;
}
注释
c
#include"../include/staff.h" // 引入员工管理系统的头文件,其中定义了Hash结构、Link结构、data_type类型以及相关的宏和函数
// showHash函数用于展示哈希表中所有员工的信息
int showHash(Hash *pHash) {
// 检查传入的哈希表指针是否为空
if (NULL == pHash) {
// 如果为空,则返回错误码HASH_NULL
return HASH_NULL;
}
// 遍历哈希表的每个位置
int i;
Link *pTmp = NULL;
for (i = 0; i < pHash->lastId; i++) {
// 获取当前位置的头节点
pTmp = pHash->pArr[i];
// 遍历当前位置的链表,展示每个员工的信息
while (NULL != pTmp) {
// 打印员工信息的分隔线
printf("|----------员工信息----------|\n");
// 打印员工的姓名
printf("|姓名:%s \n", pTmp->data.name);
// 打印员工的工号
printf("|工号:%d \n", pTmp->data.id);
// 打印员工的职位
printf("|职位:%s \n", pTmp->data.position);
// 打印员工的薪资
printf("|薪资:%d \n", pTmp->data.salary);
// 打印员工的状态
printf("|状态:%s \n", pTmp->data.status);
// 打印员工信息的分隔线
printf("|^^^^^^^^^^^^^^^^^^^^^^^^^^^^^|\n");
// 移动到下一个节点
pTmp = pTmp->pNext;
}
}
// 展示成功,返回OK
return OK;
}
函数showHash的主要功能是遍历哈希表,并打印出哈希表中每个员工的信息。
函数首先检查传入的哈希表指针pHash是否为空,如果为空则返回错误码HASH_NULL。
然后,使用循环遍历哈希表的每个位置,lastId是哈希表最后一个有效位置的索引加1,它表示哈希表的大小。
对于每个位置,函数获取该位置的头节点pTmp,并遍历该位置的链表。
在遍历链表的过程中,函数使用printf函数打印出每个员工的姓名、工号、职位、薪资和状态。
链表遍历完成后,函数继续遍历哈希表的下一个位置,直到遍历完所有的位置。
最后,函数返回OK,表示员工信息展示成功。
注意:
data_type应该在staff.h头文件中定义,它包含了员工的姓名、工号、职位、薪资和状态等字段。
Hash和Link结构也应在staff.h中定义,其中Hash结构包含了指向各个链表头节点的数组pArr以及表示哈希表大小的lastId等字段,Link结构则定义了链表节点的结构。
HASH_NULL和OK应该是预定义的宏或常量,用于表示不同的错误或成功状态。
函数假设哈希表已经被正确初始化,并且lastId的值反映了哈希表的实际大小。
函数没有处理任何错误情况,如哈希表损坏或链表节点损坏等。在实际应用中,应该添加适当的错误处理机制以提高代码的健壮性。
delete.c
源码
#include"../include/staff.h"
int deleteStaffInformation(Hash *pHash,int id){
if(!pHash) return HASH_NULL;
if(id < 0 || id > pHash->lastId) return POS_ERROR;
int pos = hashFun(id);
Link *pHead = pHash->pArr[pos];
if(NULL == pHead) return POS_ERROR;
while(pHash != NULL){
if(id == pHead->data.id){
pHash->count--;
strcpy(pHead->data.status,"已离职");
break;
}
pHead = pHead->pNext;
}
return OK;
}
注释
c
#include"../include/staff.h" // 引入员工管理系统的头文件,其中包含Hash、Link、data_type的定义,以及相关的函数和宏
// deleteStaffInformation函数用于删除哈希表中指定工号的员工信息,将员工状态标记为"已离职"
int deleteStaffInformation(Hash *pHash, int id) {
// 检查哈希表指针是否为空
if (!pHash) return HASH_NULL; // 如果为空,返回错误码HASH_NULL
// 检查员工工号是否在合法范围内
if (id < 0 || id > pHash->lastId) return POS_ERROR; // 如果不在合法范围内,返回错误码POS_ERROR
// 计算工号对应的哈希位置
int pos = hashFun(id);
// 获取哈希位置的头节点
Link *pHead = pHash->pArr[pos];
// 检查头节点是否为空
if (NULL == pHead) return POS_ERROR; // 如果为空,返回错误码POS_ERROR
// 遍历链表,查找指定工号的员工
while (pHead != NULL) { // 注意:原代码中的while条件判断有误,应该是判断pHead而非pHash
// 如果找到指定工号的员工
if (id == pHead->data.id) {
// 哈希表中员工数量减一
pHash->count--;
// 将员工状态修改为"已离职"
strcpy(pHead->data.status, "已离职");
// 找到并修改状态后,跳出循环
break;
}
// 移动到下一个节点
pHead = pHead->pNext;
}
// 如果没有找到指定工号的员工,但代码没有返回错误,这里可能需要添加检查逻辑
// (例如,检查pHead是否为NULL,若为NULL则表示未找到,应返回错误码)
// 删除成功,返回OK
return OK;
}
详细注释解释:
函数deleteStaffInformation接收一个指向Hash结构体的指针pHash和一个整数id作为参数,用于删除哈希表中指定工号的员工信息。
首先检查pHash是否为空,如果为空则返回错误码HASH_NULL。
接着检查工号id是否在合法范围内,即是否在0到pHash->lastId之间。如果不在范围内,则返回错误码POS_ERROR。
使用hashFun函数计算工号id对应的哈希位置pos。
获取哈希位置pos的头节点pHead。
检查头节点pHead是否为空,如果为空则返回错误码POS_ERROR。
使用while循环遍历链表,查找工号为id的员工。注意:原代码中的while循环条件判断错误,应该判断pHead是否为空,而不是pHash。
如果找到指定工号的员工,则将哈希表中的员工数量减一,并将该员工的状态字段status修改为"已离职"。
修改完状态后,跳出循环。
需要注意的是,如果未找到指定工号的员工,原代码没有返回错误码,可能需要在循环结束后添加检查逻辑,例如检查pHead是否为NULL,如果为NULL则表示未找到员工,应返回错误码。
最后,如果成功找到并修改了员工状态,则返回OK表示删除成功。
注意:在实际应用中,标记员工为"已离职"可能并不算是真正的"删除"操作,因为员工的信息仍然保留在哈希表中。如果需要完全删除员工信息,可能需要重新设计函数,考虑如何从哈希表和链表中移除节点。此外,对哈希表的修改操作应该考虑线程安全问题,避免并发访问时发生数据冲突。
search.c
源码
#include"../include/staff.h"
int searchStaffInformation(Hash *pHash,int id,data_type *pData){
if(!pHash) return HASH_NULL;
if(id < 0 || id > pHash->lastId) return POS_ERROR;
int pos = hashFun(id);
Link * pHead = pHash->pArr[pos];
if(!pHead) return POS_ERROR;
while(pHead != NULL){
if(id == pHead->data.id){
if(NULL == pData){
printf("|----------员工信息----------|\n");
printf("|姓名:%s \n",pHead->data.name);
printf("|工号:%d \n",pHead->data.id);
printf("|职位:%s \n",pHead->data.position);
printf("|薪资:%d \n",pHead->data.salary);
printf("|状态:%s \n",pHead->data.status);
printf("|^^^^^^^^^^^^^^^^^^^^^^^^^^^^^|\n");
}else{
*pData = pHead->data;
}
return OK;
}
pHead = pHead->pNext;
}
return OK;
}
注释
c
#include"../include/staff.h" // 引入员工管理系统的头文件
// searchStaffInformation函数用于在哈希表中搜索指定工号的员工信息
int searchStaffInformation(Hash *pHash, int id, data_type *pData) {
// 检查哈希表指针是否为空
if (!pHash) return HASH_NULL; // 如果为空,返回错误码HASH_NULL
// 检查工号是否在合法范围内
if (id < 0 || id > pHash->lastId) return POS_ERROR; // 如果不在合法范围内,返回错误码POS_ERROR
// 计算工号对应的哈希位置
int pos = hashFun(id);
// 获取哈希位置的头节点
Link *pHead = pHash->pArr[pos];
// 检查头节点是否为空
if (!pHead) return POS_ERROR; // 如果为空,返回错误码POS_ERROR
// 遍历链表,查找指定工号的员工
while (pHead != NULL) {
// 如果找到指定工号的员工
if (id == pHead->data.id) {
// 根据pData是否为NULL,决定是直接打印信息还是返回数据
if (NULL == pData) {
// 打印员工信息
printf("|----------员工信息----------|\n");
printf("|姓名:%s \n", pHead->data.name);
printf("|工号:%d \n", pHead->data.id);
printf("|职位:%s \n", pHead->data.position);
printf("|薪资:%d \n", pHead->data.salary);
printf("|状态:%s \n", pHead->data.status);
printf("|^^^^^^^^^^^^^^^^^^^^^^^^^^^^^|\n");
} else {
// 将员工数据赋值给pData指针指向的变量
*pData = pHead->data;
}
// 找到员工信息,返回OK
return OK;
}
// 移动到下一个节点
pHead = pHead->pNext;
}
// 如果未找到指定工号的员工,原代码错误地返回了OK
// 正确的做法应该是返回表示未找到的错误码,例如POS_ERROR
return POS_ERROR; // 未找到员工,返回错误码POS_ERROR
}
详细注释解释:
函数searchStaffInformation接收一个指向Hash结构体的指针pHash、一个整数id和一个指向data_type的指针pData作为参数。函数用于在哈希表中搜索指定工号的员工信息,并将信息打印或赋值给pData。
检查pHash是否为空,如果为空则返回错误码HASH_NULL。
检查工号id是否在合法范围内,即是否在0到pHash->lastId之间。如果不在范围内,返回错误码POS_ERROR。
使用hashFun函数计算id对应的哈希位置pos。
获取哈希位置pos的头节点pHead。
检查头节点pHead是否为空,如果为空则返回错误码POS_ERROR。
使用while循环遍历链表,查找工号为id的员工。
如果找到指定工号的员工,根据pData是否为NULL,决定是直接打印员工信息还是将信息赋值给pData指向的变量。
打印员工信息时,使用printf函数输出员工的姓名、工号、职位、薪资和状态。
将员工信息赋值给pData时,使用*pData = pHead->data。
找到员工信息后,返回OK。
注意:原代码中在循环结束后返回了OK,这是不正确的。如果循环结束仍未找到员工,应返回表示未找到的错误码,例如POS_ERROR。
修改了循环结束后的返回值,如果未找到员工,则返回POS_ERROR。
在实际应用中,还需要考虑pData指针的有效性,以避免可能的空指针解引用错误。此外,对于函数返回值的处理,应根据具体需求进行调整,以确保调用者能够正确处理各种情况。
modify.c
源码
#include"../include/staff.h"
int modifyStaffInformation(Hash *pHash,data_type NewData){
if(!pHash) return HASH_NULL;
int pos = hashFun(NewData.id);
Link *pHead = pHash->pArr[pos];
if(!pHead) return POS_ERROR;
while(pHead != NULL){
if(NewData.id == pHead->data.id){
pHead->data = NewData;
}
pHead = pHead->pNext;
}
return OK;
}
注释
c
#include"../include/staff.h" // 引入员工管理系统的头文件,包含Hash、Link、data_type的定义,以及相关的函数和宏
// modifyStaffInformation函数用于修改哈希表中指定工号的员工信息
int modifyStaffInformation(Hash *pHash, data_type NewData) {
// 检查哈希表指针是否为空
if (!pHash) return HASH_NULL; // 如果为空,返回错误码HASH_NULL
// 计算新数据对应的哈希位置
int pos = hashFun(NewData.id);
// 获取哈希位置的头节点
Link *pHead = pHash->pArr[pos];
// 检查头节点是否为空
if (!pHead) return POS_ERROR; // 如果为空,返回错误码POS_ERROR
// 遍历链表,查找指定工号的员工
while (pHead != NULL) {
// 如果找到指定工号的员工
if (NewData.id == pHead->data.id) {
// 用新数据覆盖旧数据
pHead->data = NewData;
// 修改成功,可以跳出循环
break;
}
// 移动到下一个节点
pHead = pHead->pNext;
}
// 注意:如果未找到指定工号的员工,函数仍然返回OK,这可能不是预期的行为
// 实际应用中,可能需要检查pHead是否为NULL,并在未找到员工时返回错误码
// 修改成功,返回OK
return OK;
}
详细注释解释:
函数modifyStaffInformation接收一个指向Hash结构体的指针pHash和一个data_type类型的变量NewData作为参数,用于修改哈希表中指定工号的员工信息。
首先检查pHash是否为空,如果为空则返回错误码HASH_NULL。
使用hashFun函数计算NewData中的工号id对应的哈希位置pos。
根据哈希位置pos,从哈希表的数组中获取对应的头节点pHead。
检查头节点pHead是否为空,如果为空则返回错误码POS_ERROR。
使用while循环遍历链表,查找工号为NewData.id的员工。
如果找到指定工号的员工,则使用新数据NewData覆盖旧数据pHead->data。
修改成功后,可以跳出循环。
需要注意的是,如果未找到指定工号的员工,原代码没有返回错误码,而是继续执行并返回OK。在实际应用中,可能需要检查pHead是否为NULL,并在未找到员工时返回错误码。
最后,如果成功修改了员工信息,则返回OK表示修改成功。
请注意,在实际应用中,修改员工信息可能涉及更多的逻辑,例如验证新数据的合法性、更新哈希表的计数等。此外,还需要考虑线程安全性和并发访问的问题。在设计和实现此类功能时,应仔细考虑这些方面以确保系统的正确性和稳定性。
save.c
源码
#include"../include/staff.h"
int Save(Hash *pHash){
int fw = open("staffInformation.txt",O_WRONLY | O_CREAT | O_TRUNC,0664);
if(fw < 0) perror("open error");
else{
int i;
for(i = 0; i < pHash->lastId; i++){
Link *pTmp = pHash->pArr[i];
while(NULL != pTmp){
int wr_count = write(fw,&pTmp->data,sizeof(data_type));
if(0 == wr_count){
puts("未成功写入!");
break;
}else if(wr_count < 0){
perror("写入失败");
break;
}
pTmp = pTmp->pNext;
}
}
close(fw);
}
}
注释
c
#include"../include/staff.h"
// Save函数用于将哈希表中的数据保存到文件"staffInformation.txt"中
int Save(Hash *pHash) {
// 使用open函数以只写、创建、截断方式打开文件,并设置权限为0664
int fw = open("staffInformation.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664);
// 检查文件是否成功打开
if (fw < 0) {
// 如果打开失败,打印错误信息
perror("open error");
return -1; // 返回错误码
} else {
int i;
// 遍历哈希表的每个位置
for (i = 0; i < pHash->lastId; i++) {
// 获取当前位置的链表头节点
Link *pTmp = pHash->pArr[i];
// 遍历链表
while (NULL != pTmp) {
// 写入链表节点的数据到文件
int wr_count = write(fw, &pTmp->data, sizeof(data_type));
// 检查写入结果
if (0 == wr_count) {
// 如果写入字节数为0,表示未成功写入
puts("未成功写入!");
break;
} else if (wr_count < 0) {
// 如果写入失败,打印错误信息
perror("写入失败");
break;
}
// 移动到下一个链表节点
pTmp = pTmp->pNext;
}
}
// 关闭文件
close(fw);
}
return 0; // 成功保存,返回0
}
详细注释解释:
Save函数用于将Hash结构体中的员工信息保存到名为staffInformation.txt的文件中。
使用open函数打开文件,并设置文件打开模式为O_WRONLY | O_CREAT | O_TRUNC,这意味着文件以只写方式打开,如果文件不存在则创建它,如果文件已存在则清空文件内容。同时设置文件权限为0664。
检查open函数的返回值fw,如果fw小于0,则表示文件打开失败,使用perror函数打印出系统提供的错误信息。
如果文件成功打开,则使用for循环遍历哈希表的每个位置(由pHash->lastId确定哈希表的大小)。
对于每个位置,获取该位置的链表头节点pTmp。
使用while循环遍历链表,直到链表末尾(pTmp为NULL)。
在循环内部,使用write函数将链表节点的数据(pTmp->data)写入文件。sizeof(data_type)确保写入的数据大小正确。
检查write函数的返回值wr_count。如果返回值为0,表示没有数据被写入,打印提示信息“未成功写入!”。如果返回值为负数,表示写入失败,使用perror函数打印错误信息。
如果写入成功,则将pTmp指针移动到链表的下一个节点,继续写入操作。
遍历完所有链表节点后,关闭文件。
如果整个保存过程成功完成,函数返回0。如果文件打开失败,则函数返回-1。
请注意,这段代码没有考虑多线程或并发访问文件的情况,也没有处理文件写入可能发生的边界情况(如磁盘空间不足)。在实际应用中,这些情况都应该被妥善处理。此外,代码没有包含错误处理逻辑来确保所有写入操作完成后才关闭文件,这在实际应用中也是非常重要的。
load.c
源码
#include"../include/staff.h"
int Load(Hash *pHash){
data_type messageData;
int fr = open("staffInformation.txt",O_RDONLY);
if(fr < 0) perror("open error");
else{
puts("打开员工信息系统成功!");
while(1){
int rd_count = read(fr,&messageData,sizeof(data_type));
if(0 == rd_count){
puts("导入信息完毕!");
break;
}else if(rd_count < 0){
perror("导入失败");
return ERROR;
}else{
insertHash(pHash,messageData);
pHash->lastId++;
}
}
close(fr);
}
}
注释
c
#include"../include/staff.h"
// Load函数用于从文件"staffInformation.txt"中加载员工信息到哈希表pHash中
int Load(Hash *pHash) {
data_type messageData; // 定义一个data_type类型的变量,用于存储从文件中读取的员工信息
// 使用open函数以只读方式打开文件
int fr = open("staffInformation.txt", O_RDONLY);
// 检查文件是否成功打开
if (fr < 0) {
// 如果打开失败,打印错误信息
perror("open error");
return ERROR; // 返回错误码
} else {
// 打印成功打开文件的提示信息
puts("打开员工信息系统成功!");
// 使用无限循环读取文件中的数据,直到文件结束或发生错误
while (1) {
// 从文件中读取数据到messageData变量中
int rd_count = read(fr, &messageData, sizeof(data_type));
// 检查读取结果
if (0 == rd_count) {
// 如果读取字节数为0,表示文件读取完毕
puts("导入信息完毕!");
break; // 跳出循环
} else if (rd_count < 0) {
// 如果读取失败,打印错误信息
perror("导入失败");
close(fr); // 关闭文件
return ERROR; // 返回错误码
} else {
// 如果读取成功,将读取到的员工信息插入到哈希表中
insertHash(pHash, messageData);
// 更新哈希表的lastId值,表示有新的员工信息被添加
pHash->lastId++;
}
}
// 关闭文件
close(fr);
}
return SUCCESS; // 导入成功,返回成功码
}
详细注释解释:
Load函数用于从文件staffInformation.txt中读取员工信息,并将这些信息加载到Hash结构体所表示的哈希表中。
定义了一个data_type类型的变量messageData,用于存储从文件中读取的每一条员工信息。
使用open函数以只读方式打开staffInformation.txt文件,并将文件描述符保存在变量fr中。
检查open函数的返回值fr,如果小于0,则表示文件打开失败,调用perror函数打印错误信息,并返回错误码ERROR。
如果文件成功打开,则打印成功打开文件的提示信息。
使用一个无限循环来不断读取文件中的数据。每次循环调用read函数从文件中读取sizeof(data_type)大小的数据到messageData变量中。
检查read函数的返回值rd_count。如果返回值为0,表示文件已经读取完毕,打印导入完成的提示信息,并跳出循环。
如果rd_count小于0,表示读取文件时发生错误,调用perror函数打印错误信息,关闭文件,并返回错误码ERROR。
如果rd_count大于0,表示成功读取到数据,调用insertHash函数将读取到的员工信息插入到哈希表中,并更新哈希表的lastId值。
循环结束后,关闭文件描述符fr。
如果整个加载过程成功完成,函数返回成功码SUCCESS。
注意:
在这个函数中,假设insertHash函数能够正确处理插入操作,并且Hash结构体已经正确初始化。
ERROR和SUCCESS是预定义的宏或者常量,用于表示操作成功或失败的状态。
文件读取和哈希表插入操作之间没有错误处理逻辑,例如检查insertHash函数的返回值。在实际应用中,应该对这些操作进行错误检查和处理,以确保程序的健壮性。
文件读取完毕后没有显式检查pHash->lastId是否超出了哈希表的实际容量,这在实际应用中也是需要考虑的。
Makefile
ALL:../obj/main.o ../obj/menu.o ../obj/create.o \
../obj/insert.o ../obj/show.o ../obj/delete.o \
../obj/search.o ../obj/modify.o ../obj/save.o \
../obj/load.o
../obj/main.o:main.c
gcc -c $< -o $@
../obj/menu.o:menu.c
gcc -c $< -o $@
../obj/create.o:create.c
gcc -c $< -o $@
../obj/insert.o:insert.c
gcc -c $< -o $@
../obj/show.o:show.c
gcc -c $< -o $@
../obj/delete.o:delete.c
gcc -c $< -o $@
../obj/search.o:search.c
gcc -c $< -o $@
../obj/modify.o:modify.c
gcc -c $< -o $@
../obj/save.o:save.c
gcc -c $< -o $@
../obj/load.o:load.c
gcc -c $< -o $@
4、obj-子目录
Makefile
ALL:
gcc *.o -o ../bin/App
5、bin-子目录
执行文件 App
三、项目代码待优化项:
1、modify.c文件
如果修改不存在的员工的信息,程序会段错误
2、待更新……