大部分初学计算机的同学完成的第一个比较完整的程序就是管理系统,不过细分之后可能还有图书管理系统,停车场管理系统。在小黄看来,一个人独立地完成这样一个系统对于自己代码能力的整体提升是非常大的,是把之前所学都融汇贯通的一个实现,同时也是对自己调试的能力的一个锻炼。
总而言之,如果有机会,大家可以自己试一试独立完成一个完成的系统。
本次代码已上传至gitee:职工管理系统(描述为 “ a system”)
目录

这是小黄选取的一个题目,对于完成类似的稍大型的题目,首先要做的第一件事情就是对每一个要求进行分析,确定需要哪些函数进行封装实现。可以发现,最开始小黄是准备以链表的形式存储每一个职工的数据,但是考虑到存在对职工的顺序进行排序的功能,因此使用数组会更加方便。
// 基本的菜单界面,包括 // 1.显示所有职工信息 2.按照工号或者姓名查询详细信息(姓名可能存在重复) // 3.查询基本工资低于1k的 4.打印工资条 // 5.修改记录 6.删除记录 // 7.按照基本工资或实发工资排序 // 8.统计各项工资平均值,存入新文件 // // 以“a”的形式,读取文件,从文件中读取已有的数据,并存入到链表之中 // 一个存入链表的函数 // // 一个显示函数,显示所有的信息 // // 一个查询函数,按照工号和姓名以及基本工资 查找 // // 打印工资条 // // 修改函数 // // 删除函数 // // *排序函数(出现排序的情况,尽量考虑使用结构体数组而不是链表) // // 统计函数
当确定好整个项目的框架之后就可以先写好需要哪些函数,函数的传参还有类型可以先全部写void类型,等之后根据具体的情况进行修改、补充、添加。下方是小黄已经完成之后头文件,头文件主要是函数的定义,一些声明等,以及库的引用声明,头文件使得代码的可读性大大提升,完成较大的项目时,大家可以多多尝试。
typedef struct person // sum 和 all 在其他数据录入的时候计算,避免使用时重复计算 { char id[20]; char name[20]; int basic; int wage; int extra; int hospital; int fund; int water; int sum; int all; }person; void menu(void); // 打印菜单,美化界面,优化用户体验 void save(person**); // 保存所有职工的信息,属于小黄个人单独添加的部分 void read(person**); // 从文件中读取信息,和save函数连用,使得项目在第二次打开后可以保留上次的信息 void show(person**); // 打印所有已存入的职工信息 person** search(int, person**, person**); // 辅助函数,依据不同方式查找职工(考虑到可能出现重名,但是职工号不可能相同的情况) void print(person**, person**); // 打印职工工资条 void add(person**); // 添加新的信息 void change(person**, person**); // 修改信息 void del(person**, person**); // 删除信息 void sort(person**); // 对员工信息排序(不同方式) void statistic(double*, person**); // 对整体数据进行统计 int cmp1(const void* a, const void* b); int cmp2(const void* a, const void* b); int cmp3(const void* a, const void* b); int cmp4(const void* a, const void* b); // 采用 qsort 进行排序,因此需要不同的 cmp 函数
完成此类程序,需要提前构建好一个主函数框架,想好一些测试案例,对于每一个函数写完就立马进行测试,防止连锁式错误。
例如小黄用的结构体指针数组,设置了三个成员直接加入,如此就方便进行一些测试,例如删除,打印所有信息等。然后设置一个循环,使得能多次操作。不过小黄个人建议,不要急着将菜单函数写入到主函数之中,然后再用 switch 语句分割,这样导致需要多次操作,是一件相对比较耽误时间的事情,甚至循环也不必要,先对每一个函数进行测试,测试完成之后就先屏蔽掉,等之后所有的都完成之后再去完成 while 和 switch 的排版等。
下方是小黄完成整个程序之后的主函数部分,单独成源文件,将函数的定义放到另一个源文件中,也是一种非常增加代码可读性的方式。
#include"function.h" extern int way,nouse; int main() { person* arr[1000] = { 0 }, * back[100] = { 0 }; int key = 0; person a = { "3", "萨达", 426, 1246, 398, 189, 235, 165 , 5930, 531 }; person b = { "2", "王小明", 486, 1246, 398, 189, 235, 165 , 5930, 5341 }; person c = { "1", "南新", 428, 1246, 398, 189, 235, 165 , 5920, 51 }; double sta[9] = { 0 }; arr[0] = &a, arr[1] = &b, arr[2] = &c; read(arr); while (1) { memset(back, 0, sizeof back); system("cls"); menu(); printf("请选择您要进行的操作:"); nouse = scanf("%d", &key); switch(key) { case 1: // 所有信息 show(arr); break; case 2: // 查询信息 printf("请选择您的查找方式:\n1.职工号\n2.姓名\n3.基本工资低于1000人员\n"); nouse = scanf("%d", &way); search(way, arr, back); if (back[0] == NULL) { system("cls"); printf("暂无相关人员信息!\n"); } else { show(back); } break; case 3: //打印工资条 print(arr, back); break; case 4: // 增加信息 add(arr); break; case 5: // 修改信息 change(arr, back); if (back[0] == NULL) { system("cls"); printf("暂无相关人员信息!\n"); } break; case 6: // 删除信息 del(arr, back); if (back[0] == NULL) { system("cls"); printf("暂无相关人员信息!\n"); } break; case 7: // 数据排序并输出 sort(arr); break; case 8: // 统计平均数 statistic(sta, arr); break; case 0: // 退出系统 save(arr); return 0; // 由于要跳出switch和while循环,且跳出之后没有操作,故直接return default: printf("wrong input!\n"); } system("pause"); } }
代码如下,简单的打印,不需要过多解释,空格只根据黑框大小自行调整的居中位置。
- void menu(void)
- {
- printf(" ################################################\n");
- printf(" ############### ###############\n");
- printf(" ############### 职工管理系统 ###############\n");
- 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(" 0.退出系统 \n");
先清空屏幕,打印一个表头,循环首先只能在1000的范围内,其次,打印遇到NULL的时候就可以退出循环,因为数据已经全部打印完;同时需要注意要是NULL指针的话,访问会出错,即注意判断的顺序。
- void show(person** arr)
- {
- system("cls");
- printf("%4s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s\n","序号", "职工号", "姓名", "基本工资", "职务工资", "津贴", "医疗保险", "公积金", "水电费", "应发工资", "实发工资");
- for (int i = 0; i < 1000; i++)
- {
- if (arr[i] == NULL)
- {
- printf("\n");
- break;
- }
- printf("%4d %10s %10s %10d %10d %10d %10d %10d %10d %10d %10d\n", i + 1, arr[i]->id, arr[i]->name, arr[i]->basic, arr[i]->wage, arr[i]->extra, arr[i]->hospital, arr[i]->fund, arr[i]->water, arr[i]->sum, arr[i]->all);
- }
- }
查询到信息之后需要保存下来,同时还要决定是以什么来进行查找,也就是总共的参数有三个,查询方式 way,原数组 arr,记录返回的数组 back,之所以返回也使用数组储存是因为可能出现重名的情况,还有多个基本工资低于 1000 的职工,因此需要保存下来。同时优化体验,若没有找到符合条件的职工时,则输出 “ 暂无相关人员信息! ”;否则,则输出所有符合条件的职工的信息。
注意!查询的时候需要对 arr [ i ] 进行判断是否为空,然后判断内容是否符合筛选条件,先后顺序不能乱!因为若先判断是否符合条件的时候,若为空,那么则会出现访问异常的情况。
- person** search(int way, person** arr, person** back)
- {
- int n = 0;
- char check[20] = { 0 };
- switch (way)
- {
- case 1:
- {
- int key = 0;
- printf("请输入所查询职工号:");
- nouse = scanf("%s", check);
- for (int i = 0; i < 1000; i++)
- if (arr[i] != NULL && strcmp(arr[i]->id, check) == 0)
- {
- back[0] = arr[i];
- break;
- }
- break;
- }
- case 2:
- {
- printf("请输入所查询姓名:");
- nouse = scanf("%s", check);
- for (int i = 0; i < 1000; i++)
- if (arr[i] != NULL && strcmp(arr[i]->name, check) == 0)
- back[n++] = arr[i];
- break;
- }
- case 3:
- {
- for (int i = 0; i < 1000; i++)
- if (arr[i] != NULL && arr[i]->basic < 1000)
- {
- back[n++] = arr[i];
- }
- break;
- }
- }
- return back;
- }
修改信息第一步就是先找到被修改人,那么调用 search 函数先找到所有符合要求的人,由于可能出现重名等情况,因此优化体验,使用户可以根据序号选择具体需要修改哪一个职工的信息,然后依次对每一条信息进行修改。
- void change(person** arr, person** back)
- {
- printf("请选择您的查找方式:\n1.职工号\n2.姓名\n3.基本工资低于1000人员\n");
- nouse = scanf("%d", &way);
- search(way, arr, back);
- if (back[0] != NULL)
- {
- show(back);
- int target = 1;
- if (back[1] != NULL)
- {
- printf("请选择您要修改的序号:");
- nouse = scanf("%d", &target);
-
- }
- printf("请依次输入:\n");
- printf(" 职工号: ");
- nouse = scanf("%s", (back[target - 1])->id);
- printf(" 姓名: ");
- nouse = scanf("%s", back[target - 1]->name);
- printf("基本工资:");
- nouse = scanf("%d", &(back[target - 1]->basic));
- printf("职务工资:");
- nouse = scanf("%d", &(back[target - 1]->wage));
- printf(" 津贴: ");
- nouse = scanf("%d", &(back[target - 1]->extra));
- printf("医疗保险:");
- nouse = scanf("%d", &(back[target - 1]->hospital));
- printf(" 公积金: ");
- nouse = scanf("%d", &(back[target - 1]->fund));
- printf(" 水电费: ");
- nouse = scanf("%d", &(back[target - 1]->water));
- back[target - 1]->sum = back[target - 1]->basic + back[target - 1]->wage + back[target - 1]->extra;
- back[target - 1]->all = back[target - 1]->sum - back[target - 1]->hospital - back[target - 1]->fund - back[target - 1]->water;
- printf("修改成功!\n");
- }
- }
由于 id 也就是职工号是必定不相同的,因此通过 search 找到需要删除的职工之后即可通过遍历 arr 数组并比较 id 来判断是否是需要删除的职工。
注意!注释掉的一行free需要加上!此处注释掉的原因是给出的三个测试用案例不是用 malloc 开辟的,所以无法 free 掉;同时由于删除必定会减少一个,为了防止越界,故循环只到 arr[ 998 ] ,而有可能出现数组恰好填满信息的情况,因此最后需要给 arr[ 999 ] 置空。
- void del(person** arr, person** back)
- {
- printf("请选择您的查找方式:\n1.职工号\n2.姓名\n3.基本工资低于1000人员\n");
- nouse = scanf("%d", &way);
- search(way, arr, back);
- if (back[0] != NULL)
- {
- int target = 1;
- if (back[1] != NULL)
- {
- show(back);
- printf("请选择您要删除的序号:");
- nouse = scanf("%d", &target);
- }
- memset(back[target - 1], 0, sizeof(struct person));
- if (back[target - 1] != 0)
- {
- for (int i = 0; i < 1000; i++)
- {
- if (!strcmp(arr[i]->id, back[target - 1]->id))
- {
- //free(arr[i]);
- for (int x = i + 1; x < 1000; x++)
- {
- arr[x - 1] = arr[x];
- }
- arr[999] = NULL;
- break;
- }
- }
- printf("删除成功!\n");
- }
- else
- printf("删除失败!\n");
- }
- }
为了快捷,故使用了系统自带的 qsort 函数,只需要修改 cmp 函数即可,此时采用函数指针和 switch 函数构成的交换表进行,可自行修改 return 内两个指针的顺序来调整升序或降序。
- void sort(person** arr)
- {
- int (*cmp)(const void*, const void*) = &cmp1;
- printf("请选择您的排序方式:\n");
- printf("1.根据职工号排序:\n");
- printf("2.根据姓名排序:\n");
- printf("3.根据基本工资排序:\n");
- printf("4.根据实发工资排序:\n");
- int num = 0, nums = 0, i = 0;
- for (i = 0; i < 1000; i++)
- if (arr[i] == NULL || i == 1000)
- break;
- nouse = scanf("%d", &num);
- switch(num)
- {
- case 1:
- cmp = cmp1;
- break;
- case 2:
- cmp = cmp2;
- break;
- case 3:
- cmp = cmp3;
- break;
- case 4:
- cmp = cmp4;
- break;
- default:
- printf("wrong input!\n");
- return;
- }
- qsort(arr, i, sizeof(arr[0]), cmp);
- }
-
- int cmp1(const void* a, const void* b)
- {
- return strcmp((*(struct person**)a)->id, (*(person**)b)->id);
- }
-
- int cmp2(const void* a, const void* b)
- {
- return strcmp((*(person**)a)->name, (*(person**)b)->name);
- }
-
- int cmp3(const void* a, const void* b)
- {
- return (*(person**)a)->basic - (*(person**)b)->basic;
- }
-
- int cmp4(const void* a, const void* b)
- {
- return (*(person**)a)->all - (*(person**)b)->all;
- }
因为每次统计函数需要用户执行,且每次需要更新,因此以 “ w ” 的形式打开,计算方式是累加在平均。
- void statistic(double* sta, person** arr)
- {
- FILE* p = NULL;
- p = fopen("statistic.csv", "w");
- if (p == NULL)
- exit(0);
- for (int i = 0; i < 1000 && arr[i] != NULL; i++)
- {
- sta[0]++;
- sta[1] += arr[i]->basic;
- sta[2] += arr[i]->wage;
- sta[3] += arr[i]->extra;
- sta[4] += arr[i]->hospital;
- sta[5] += arr[i]->fund;
- sta[6] += arr[i]->water;
- sta[7] += arr[i]->sum;
- sta[8] += arr[i]->all;
- }
- sta[1] /= sta[0];
- sta[2] /= sta[0];
- sta[3] /= sta[0];
- sta[4] /= sta[0];
- sta[5] /= sta[0];
- sta[6] /= sta[0];
- sta[7] /= sta[0];
- sta[8] /= sta[0];
- fprintf(p,"%s,%s,%s,%s,%s,%s,%s,%s,%s\n", "总人数", "平均基本工资", "平均职务工资", "平均津贴", "平均医疗保险", "平均公积金", "平均水电费", "平均应发工资", "平均实发工资");
- fprintf(p,"%0lf,%.2lf,%.02lf,%.2lf,%.2lf,%.2lf,%.2lf,%.2lf,%.2lf", sta[0], sta[1], sta[2], sta[3], sta[4], sta[5], sta[6], sta[7], sta[8]);
- system("cls");
- printf("平均统计:\n");
- printf("%10s %10s %10s %10s %10s %10s %10s %10s %10s\n", "总人数", "基本工资", "职务工资", "津贴", "医疗保险", "公积金", "水电费", "应发工资", "实发工资");
- printf("%10.0lf %10.2lf %10.02lf %10.2lf %10.2lf %10.2lf %10.2lf %10.2lf %10.2lf\n\n", sta[0], sta[1], sta[2], sta[3], sta[4], sta[5], sta[6], sta[7], sta[8]);
- fclose(p);
- }
保存时需要注意为空的情况和信息的最后一个,这两种情况都不需要添加换行符,这么做的原因和读取数据的函数有关,具体由下方解释。
读取信息的时候,需要先用一个字符串读取掉表头的文字,注意 csv 文件读取出来时,不同的单元格之间是用逗号分隔,因此会被 %s 读取到,故用一个字符串即可,然后用 fgetc 读取一个字符并记录下来,若为 \n 则表示后续还有信息,若为 \0 则表示已经到达文件结尾,退出循环。同时读取的时候由于 csv 文件读取方式的原因,采用了正则表达式, %[^,] 表示读取字符串直到遇到 " , " 停止。
将保存函数放到主函数的循环之外,即每次退出程序的时候自动保存,然后读取文件则是项目开始运行的时候自动读取,起到了优化体验的话。
- void save(person** arr)
- {
- FILE* p = NULL;
- p = fopen("data.csv", "w");
- if (p == NULL)
- exit(0);
- int i = 0;
- fprintf(p, "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s", "职工号", "姓名", "基本工资", "职务工资", "津贴", "医疗保险", "公积金", "水电费", "应发工资", "实发工资");
- if (arr[i] != NULL)
- fprintf(p, "\n");
- while (arr[i + 1] != NULL)
- {
- fprintf(p, "%s,%s,%d,%d,%d,%d,%d,%d,%d,%d\n", arr[i]->id, arr[i]->name, arr[i]->basic, arr[i]->wage, arr[i]->extra, arr[i]->hospital, arr[i]->fund, arr[i]->water, arr[i]->sum, arr[i]->all);
- i++;
- }
- if (arr[i] != NULL)
- fprintf(p, "%s,%s,%d,%d,%d,%d,%d,%d,%d,%d", arr[i]->id, arr[i]->name, arr[i]->basic, arr[i]->wage, arr[i]->extra, arr[i]->hospital, arr[i]->fund, arr[i]->water, arr[i]->sum, arr[i]->all);
- }
-
- void read(person** arr)
- {
- FILE* p = NULL;
- int i = 0, judge = 1;;
- char k = 0;
- char nouse[100] = { 0 };
- p = fopen("data.csv", "r");
- if (p == NULL)
- return;
- judge = fscanf(p, "%s", nouse);
- k = fgetc(p);
- while (k != EOF)
- {
- person* node = (person*)malloc(sizeof person);
- arr[i] = node;
- judge = fscanf(p, "%[^,],%[^,],%d,%d,%d,%d,%d,%d,%d,%d", arr[i]->id, arr[i]->name, &arr[i]->basic, &arr[i]->wage, &arr[i]->extra, &arr[i]->hospital, &arr[i]->fund, &arr[i]->water, &arr[i]->sum, &arr[i]->all);
- k = fgetc(p);
- i++;
- }
- }
1. 考虑到快捷,本项目采用了静态表,但是可以进行优化,例如当数组满时,采用 realloc 开辟更大的空间。
2. 统计函数一块,可以添加大数运算的函数,防止人员过多时,可能出现的溢出现象。
3. 以上建议仅供参考,可以尝试改进,但是仅作为课程设计项目来说的话不太有必要,若使用中发现错误或不理解的地方欢迎私信小黄,也可私信QQ482999194。
