功能需求
1)输入、输出工资信息(包括:员工号、姓名、性别、出生日期、岗位工资、补贴总额、代扣总额);--可考虑增加“身份证编号”并进行检验 2)输出应发工资总额,应发工资(总额)的最高、最低和平均值,男、女性工资平均值等; 3)按员工号、姓名、年龄、应发工资等排序后输出信息; 4)按员工号查找并输出其信息,也可删除查到的员工。
特点
较第二版增加了身份证号码的简单核验功能;使用指针数组(程序中体现为指向指针的指针)。
这是我看完《C Primer Plus》第17章的链表部分之后对程序结构做了一些调整。之前是按照功能来划分文件的。现在我参照课本,在定义类型的同时就赋予一些基本的操作接口,然后进行层层的封装。当然程序还不算完美,请各位网友多多指教!
测试数据
正确的测试数据
1010 伍惺委 男 1990.10.20 231222199010208854 3220 1500 300
1011 张艳亲 女 1989-12-9 360200198912094855 3530 1800 400
1012 沈民涌 男 1978-11-22 500114197811223173 2540 1300 250
1013 颜衍圈 m 1965.10.31 361027196510313171 3400 1200 180
1014 王恳呛 男 1979-5-4 421224197905046728 4780 2000 450
1006 林指愤 f 1991-1-2 130623199101028238 3009 1500 300
1007 潘介馏 男 1964-2-28 632322196402280748 3520 1800 400
1008 曾院借 男 1973-3-27 45060319730327439x 2300 1300 250
1009 陈渝 男 1967-5-28 152921196705282830 3340 1200 180
1001 杨玲 F 1966-7-7 35032219660707608X 3755 2000 450
不正确的测试数据
1010 伍惺委 a 1990.10.20 231222199010208854 3220 1500 300
1010 伍惺委 男 1990.10.20 231222199110208851 3220 1500 300
1011 张艳亲 女 1989-12-9 36020019891209485x 3530 1800 400
1012 沈民涌 男 1978-11-22 5001141978112231731 2540 1300 250
1013 颜衍圈 m 1965.10.31 36102719 3400 1200 180
1014 王恳呛 男 1979-5-4 4212241!?\\0504672 4780 2000 450
1006 林指愤 f 1991-1-2 1???23199101028238 3009 1500 300
1007 潘 男 1964-2-28 632322196402280748 3520 1800 400
1007 潘介馏介馏 男 1964-2-28 632322196402280748 3520 1800 400
008 曾院借 男 1973-3-27 45060319730327439x 2300 1300 250
a008 曾院借 男 1973-3-27 45060319730327439x 2300 1300 250
11008 曾院借 男 1973-3-27 45060319730327439x 2300 1300 250
1008 曾院借 男 1973.3-27 45060319730327439x 2300 1300 250
1008 曾院借 男 1973-3.27 45060319730327439x 2300 1300 250
1008 曾院借 男 1973_3-27 45060319730327439x 2300 1300 250
1008 曾院借 男 1973-3_27 45060319730327439x 2300 1300 250
1008 曾院借 男 1973-3-27 45060319730327439x -2300 13000 250
1008 曾院借 男 1973-3-27 45060319730327439x 2300 -1300 250
1008 曾院借 男 1973-3-27 45060319730327439x 2300 1300 -250
1008 曾院借 男 1973-3-27 45060319730327439x 23 13 250
1009 陈渝 男 67-5-28 152921196705282830 3340 1200 180
1001 杨玲 F 1966-7-32 35032219660707608X 3755 2000 450
100a 杨玲 F 1966-7-32 35032219660707608X 3755 2000 450
1001 杨玲 F 1966-7-7 35032219660707608X 3755 2000 450
1001 杨玲 F 1966-7-7 35032219660707608X 3755 2000 450
1016 吕袥疫 男 1955-9-4 35032219550904387X 3060 1500 300
1016 吕袥疫 男 1955-0-4 35032219550904387X 3060 1500 300
1016 吕袥疫 男 1955-13-4 35032219550904387X 3060 1500 300
包含文件的关系图![](https://i-blog.csdnimg.cn/blog_migrate/5d11ad02845757ba58950dce8d498302.png)
源程序(主函数在 entry.c 中)
// cmp.h
#pragma once
#ifndef _INC_CMP
#define _INC_CMP
#define CMP_ARGS const void* arg1, const void* arg2
int id_asc_cmp(CMP_ARGS);
int id_dsc_cmp(CMP_ARGS);
int name_asc_cmp(CMP_ARGS);
int name_dsc_cmp(CMP_ARGS);
int sex_asc_cmp(CMP_ARGS);
int age_asc_cmp(CMP_ARGS);
int age_dsc_cmp(CMP_ARGS);
int slr_asc_cmp(CMP_ARGS);
int slr_dsc_cmp(CMP_ARGS);
int ic_asc_cmp(CMP_ARGS);
int ic_dsc_cmp(CMP_ARGS);
#undef CMP_ARGS
#endif // !_INC_CMP
// employee.h
#pragma once
#ifndef _INC_EMPLOYEE
#define _INC_EMPLOYEE
#pragma region 宏定义
#define ID_LEN 4
#define MIN_HANZI 2
#define MAX_HANZI 4
#define NAME_MAX_LEN ((MAX_HANZI) * 2)
#define NAME_MIN_LEN ((MIN_HANZI) * 2)
#define MIN_AGE 18
#define MAX_AGE 60
#define MAX_MONEY 99999
#define ID_CARD_LEN 18
#define ID_SIZE ((ID_LEN) + 1)
#define NAME_SIZE ((NAME_MAX_LEN) + 1)
#define ID_CARD_SIZE ((ID_CARD_LEN) + 1)
#define SLR_TOT_ITEM 8
#define DATA_FILE "info.dat"
#pragma endregion
#pragma region 类型
typedef char Work_id[ID_SIZE]; // 员工号,定义时用静态,输出时直接按字符串输出
typedef char Name[NAME_SIZE]; // 姓名,定义时用静态,输出时直接按字符串输出
typedef struct // 日期
{
int year, month, day;
} Date;
typedef char Id_card[ID_CARD_SIZE]; // 身份证号,输出时直接按字符串输出
typedef struct // 员工信息
{
Work_id id;
Name name;
int is_male;
Date birth_day;
Id_card id_card;
double pstwg; //岗位工资
double sbsd; //补贴总额
double wthhld; //代扣总额
double slr; //应发工资
}Employee;
#pragma endregion
#pragma region 输入函数
/// <summary>
/// 从键盘读入员工信息
/// </summary>
/// <param name="e">指向员工信息的地址</param>
/// <returns>当且仅当读入正确时返回 0,读入结束时返回 EOF。</returns>
int get_employee_info(Employee* e);
/// <summary>
/// 追加员工信息到文件
/// </summary>
/// <param name="e">指向员工信息的地址</param>
void append_to_file(Employee* e);
/// <summary>
/// gets的安全版本
/// </summary>
/// <param name="str">字符串首地址</param>
/// <param name="buf_len">最大字符串的大小</param>
/// <returns>如果遇到 EOF,则返回空;否则返回字符串首地址</returns>
char* s_gets(char* str, int buf_len);
#pragma endregion
#pragma region 输出函数
/// <summary>
/// 显示员工信息
/// </summary>
/// <param name="e">指向员工信息的地址</param>
void show_employee_info(Employee* e);
#pragma endregion
#pragma region 判断函数
/// <summary>
/// 根据出生日期确定年龄,判断年龄有没有超过上限
/// </summary>
/// <param name="date">生日结构体的地址</param>
/// <returns>如果没有超过上限,返回1;否则返回0。</returns>
int age_in_up_range(const Date* const date);
#pragma endregion
#endif // !_INC_EMPLOYEE
// employee_list.h
#pragma once
#ifndef _INC_EMPLOYEE_LIST
#define _INC_EMPLOYEE_LIST
#include "employee.h"
#include <stdio.h>
#pragma region 类型
typedef struct // 员工信息列表
{
Employee** e_list;
long tot_rec;
}Employee_list;
typedef enum // 排序方式
{
ID_ASC, /* 按工号升序 */
ID_DSC, /* 按工号降序 */
NAME_ASC, /* 按姓名升序 */
NAME_DSC, /* 按姓名降序 */
SEX, /* 按性别 */
AGE_ASC, /* 按年龄升序 */
AGE_DSC, /* 按年龄降序 */
SLR_ASC, /* 按工资升序 */
SLR_DSC, /* 按工资降序 */
IC_ASC, /* 按身份证号升序 */
IC_DSC /* 按身份证号降序 */
}Sort_way;
#pragma endregion
#pragma region 函数
/// <summary>
/// 从数据文件中加载员工信息列表
/// <para>注意:使用完毕后务必用 free_list 来释放!</para>
/// </summary>
/// <param name="el">指向列表的指针</param>
/// <returns>当且仅当加载成功时返回 0</returns>
int load_list(Employee_list* el);
/// <summary>
/// 按指定的方式对员工列表排序
/// </summary>
/// <param name="el">指向列表的指针</param>
/// <param name="sw">排序方式</param>
/// <returns>当且仅当排序成功时返回 0</returns>
int sort_list(Employee_list* el, Sort_way sw);
/// <summary>
/// 批量显示员工信息(包含表头)
/// </summary>
/// <param name="el">指向列表的指针</param>
void traverse_show(Employee_list* el);
/// <summary>
/// 按员工号查找指定员工信息
/// </summary>
/// <param name="el">指向列表的指针</param>
/// <param name="target_id">目标员工号</param>
/// <returns>如果找到,返回在员工列表中的下标;否则返回 -1。</returns>
long search_employee(Employee_list* el, Work_id target_id);
/// <summary>
/// 按员工号删除指定员工信息(结果还在内存,没有保存)
/// </summary>
/// <param name="el">指向列表的指针</param>
/// <param name="target_id">目标员工号</param>
/// <returns>当且仅当删除成功时返回 0</returns>
int del_employee(Employee_list* el, Work_id target_id);
/// <summary>
/// 删除退休员工信息(结果还在内存,没有保存)
/// <para>前提:列表已按照年龄降序排列</para>
/// </summary>
/// <param name="el">指向列表的指针</param>
/// <returns>当且仅当删除成功或没有已退休员工时返回 0</returns>
int remove_retired(Employee_list* el);
/// <summary>
/// 将员工列表的信息写入文件
/// </summary>
/// <param name="el">指向列表的指针</param>
void flush_data_file(Employee_list* el);
/// <summary>
/// 释放员工列表
/// </summary>
/// <param name="el">指向列表的指针</param>
void free_list(Employee_list* el);
/// <summary>
/// 打印表头
/// </summary>
/// <param name="">无参数</param>
inline void pri_table_head(void)
{
printf("%4s %8s %4s %14s %18s %8s %8s %8s %8s\n",
"编号", "姓名", "性别", "出生日期", "身份证号",
"岗位工资", "补贴总额", "代扣总额", "应发工资");
}
#pragma endregion
#endif // !_INC_EMPLOYEE_LIST
// entry_needed.h
#pragma once
#ifndef _INC_ENTRY
#define _INC_ENTRY
#include "employee_list.h"
#pragma region 函数
/// <summary>
/// 在主函数中获取用户的选择。如果选择有误,则提醒用户重新输入
/// </summary>
/// <returns>
/// 一个大写字母,分别对应下面的选择:
/// <para>I 输入员工工资信息</para>
/// <para>T 输出员工工资统计信息</para>
/// <para>S 排序后输出员工工资信息</para>
/// <para>F 按员工号查找员工并输出其工资信息</para>
/// <para>D 按员工号查找员工并删除其工资信息</para>
/// <para>Q 退出</para>
/// </returns>
char get_choice_in_main(void);
/// <summary>
/// 输入员工工资信息
/// </summary>
void input_salary_info(void);
/// <summary>
/// 输出员工工资统计信息
/// </summary>
void output_statistics(void);
/// <summary>
/// 排序后输出员工工资信息
/// </summary>
void output_with_order(void);
/// <summary>
/// 在主函数中按员工号查找员工
/// </summary>
void search_in_main(void);
/// <summary>
/// 在主函数中按员工号删除员工
/// </summary>
void del_employee_in_main(void);
/// <summary>
/// 在主函数中删除退休员工
/// </summary>
void rm_retired_in_main(void);
#pragma endregion
#endif // !_INC_ENTRY
// cmp.c
#include "employee_list.h"
#include <string.h>
int id_asc_cmp(const void* arg1, const void* arg2)
{
return strcmp((*(Employee**)arg1)->id, (*(Employee**)arg2)->id);
}
int id_dsc_cmp(const void* arg1, const void* arg2)
{
return strcmp((*(Employee**)arg2)->id, (*(Employee**)arg1)->id);
}
int name_asc_cmp(const void* arg1, const void* arg2)
{
return strcmp((*(Employee**)arg1)->name, (*(Employee**)arg2)->name);
}
int name_dsc_cmp(const void* arg1, const void* arg2)
{
return strcmp((*(Employee**)arg2)->name, (*(Employee**)arg1)->name);
}
int sex_asc_cmp(const void* arg1, const void* arg2)
{
return (*(Employee**)arg1)->is_male - (*(Employee**)arg2)->is_male;
}
int age_asc_cmp(const void* arg1, const void* arg2)
{
Date* d1 = &(*(Employee**)arg1)->birth_day,
* d2 = &(*(Employee**)arg2)->birth_day;
if (d1->year > d2->year)
{
return -1;
}
else if (d1->year < d2->year)
{
return 1;
}
else
{
if (d1->month > d2->month)
{
return -1;
}
else if (d1->month < d2->month)
{
return 1;
}
else
{
return d2->day - d1->day;
}
}
}
int age_dsc_cmp(const void* arg1, const void* arg2)
{
Date* d1 = &(*(Employee**)arg1)->birth_day,
* d2 = &(*(Employee**)arg2)->birth_day;
if (d1->year > d2->year)
{
return 1;
}
else if (d1->year < d2->year)
{
return -1;
}
else
{
if (d1->month > d2->month)
{
return 1;
}
else if (d1->month < d2->month)
{
return -1;
}
else
{
return d1->day - d2->day;
}
}
}
int slr_asc_cmp(const void* arg1, const void* arg2)
{
if ((*(Employee**)arg1)->slr > (*(Employee**)arg2)->slr)
return 1;
else if ((*(Employee**)arg1)->slr < (*(Employee**)arg2)->slr)
return -1;
else return 0;
}
int slr_dsc_cmp(const void* arg1, const void* arg2)
{
if ((*(Employee**)arg1)->slr > (*(Employee**)arg2)->slr)
return -1;
else if ((*(Employee**)arg1)->slr < (*(Employee**)arg2)->slr)
return 1;
else return 0;
}
int ic_asc_cmp(const void* arg1, const void* arg2)
{
return strcmp((*(Employee**)arg1)->id_card, (*(Employee**)arg2)->id_card);
}
int ic_dsc_cmp(const void* arg1, const void* arg2)
{
return strcmp((*(Employee**)arg2)->id_card, (*(Employee**)arg1)->id_card);
}
// employee.c
#define _CRT_SECURE_NO_WARNINGS
#define IN_DATE_LEN 11
#define MAX_INPT_WIDTH 256
#include "employee.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#pragma region 内部函数原型
static inline int id_ok(const Work_id id);
static inline int name_ok(const Name name);
static inline int sex_ok(const char* const sex, int* is_male);
static inline int birthday_ok(const char* const day_str, Date* day);
static inline int is_leap(const int year);
static inline int is_valid_date(const Date* const date);
static inline int get_max_day(const int month, const int year);
static inline int age_in_range(const Date* const date);
static inline int money_in_range(const Employee* const e);
static inline int have_repeat_record(const Work_id id);
static inline int id_card_ok
(Id_card id_card_no, const Date* const birth);
static inline int id_card_len_ok(const Id_card id_card_no);
static inline int id_card_birth_ok
(const Id_card id_card_no, const Date* const birth);
static inline int id_card_char_ok(Id_card id_card_no);
static int age_in_down_range(const Date* const date);
#pragma endregion
int get_employee_info(Employee* e)
{
static char inpt[MAX_INPT_WIDTH];
if (s_gets(inpt, MAX_INPT_WIDTH))
{
static char in_id[MAX_INPT_WIDTH], in_name[MAX_INPT_WIDTH],
in_sex[MAX_INPT_WIDTH],
in_date[MAX_INPT_WIDTH], in_id_card[MAX_INPT_WIDTH];
if (sscanf(inpt, "%s%s%s%s%s%lf%lf%lf", in_id, in_name, in_sex,
in_date, in_id_card, &e->pstwg, &e->sbsd, &e->wthhld) == SLR_TOT_ITEM)
{
e->slr = e->pstwg + e->sbsd - e->wthhld;
if (id_ok(in_id) && name_ok(in_name) && sex_ok(in_sex, &e->is_male)
&& birthday_ok(in_date, &e->birth_day) &&
id_card_ok(in_id_card, &e->birth_day) &&
money_in_range(e) && !have_repeat_record(in_id))
{
strncpy(e->id, in_id, ID_SIZE);
strncpy(e->name, in_name, NAME_SIZE);
strncpy(e->id_card, in_id_card, ID_CARD_SIZE);
return 0;
}
}
return 1;
}
return EOF;
}
void append_to_file(Employee* e)
{
FILE* fp = fopen(DATA_FILE, "ab");
fwrite(e, sizeof(Employee), 1, fp);
fclose(fp);
}
void show_employee_info(Employee* e)
{
printf("%-4s %8s %4s %d年%2d月%2d日 %18s %8g %8g %8g %8g\n",
e->id, e->name, e->is_male ? "男" : "女",
e->birth_day.year, e->birth_day.month,
e->birth_day.day, e->id_card,
e->pstwg, e->sbsd, e->wthhld, e->slr);
}
int age_in_up_range(const Date* const date)
{
time_t now = time(NULL);
struct tm* today = localtime(&now);
Date now_date = { .year = today->tm_year + 1900,
.month = today->tm_mon + 1,.day = today->tm_mday };
if ((now_date.year > date->year + MAX_AGE) ||
(now_date.year == date->year + MAX_AGE &&
(now_date.month > date->month ||
(now_date.month == date->month && now_date.day > date->day))))
{
return 0;
}
return 1;
}
char* s_gets(char* str, int buf_len)
{
char* ret_ptr = fgets(str, buf_len, stdin);
if (ret_ptr)
{
char* enter = strchr(str, '\n');
if (enter)
{
*enter = 0;
}
else while (getchar() != '\n');
}
return ret_ptr;
}
#pragma region 内部函数定义
/// <summary>
/// 员工号是否符合要求
/// </summary>
/// <param name="id">存储员工号的字符串</param>
/// <returns>符合要求返回1,否则返回0。</returns>
inline int id_ok(const Work_id id)
{
if (strlen(id) > ID_LEN)
{
printf("\a员工号的长度超过 %d 位!\n", ID_LEN);
return 0;
}
if (!strchr("123456789", id[0]))
{
puts("\a员工号的第一位必须是 1~9 之间的数字!");
return 0;
}
for (int i = 0; id[i]; i++)
{
if (!strchr("0123456789", id[i]))
{
puts("\a员工号从第2位开始必须是 0~9 之间的数字!");
return 0;
}
}
return 1;
}
/// <summary>
/// 姓名是否符合要求
/// </summary>
/// <param name="name">存储姓名的字符串</param>
/// <returns>符合要求返回1,否则返回0。</returns>
inline int name_ok(const Name name)
{
size_t len = strlen(name);
if (len > NAME_MAX_LEN)
{
printf("\a名字的长度超过了 %d 个汉字!\n", MAX_HANZI);
return 0;
}
if (len < NAME_MIN_LEN)
{
printf("\a名字的长度不足 %d 个汉字!\n", MIN_HANZI);
return 0;
}
return 1;
}
/// <summary>
/// 性别是否正确
/// </summary>
/// <param name="sex">存储性别的字符串</param>
/// <param name="is_male">参数输出:是不是男生</param>
/// <returns>正确返回1,否则返回0。</returns>
inline int sex_ok(const char* const sex, int* is_male)
{
#define LETTER sex[0]
if (toupper(LETTER) == 'M' || !strcmp(sex, "男"))
{
*is_male = 1;
return 1;
}
if (toupper(LETTER) == 'F' || !strcmp(sex, "女"))
{
*is_male = 0;
return 1;
}
puts("\a性别指定不正确!");
return 0;
#undef LETTER
}
/// <summary>
/// 出生日期是否合法并且使年龄在规定范围内
/// </summary>
/// <param name="day_str">待读取的存储出生日期的字符串</param>
/// <param name="day">参数输出:出生日期</param>
/// <returns>如果出生日期是否合法并且使年龄在规定范围内,
/// 返回1,否则返回0。</returns>
inline int birthday_ok(const char* const day_str, Date* day)
{
if (sscanf(day_str, "%d.%d.%d", &day->year, &day->month, &day->day) != 3
&& sscanf(day_str, "%d-%d-%d", &day->year, &day->month, &day->day) != 3)
{
puts("\a日期的格式不正确!");
return 0;
}
if (is_valid_date(day) && age_in_range(day))
{
return 1;
}
return 0;
}
/// <summary>
/// 判断给定的年份是不是闰年
/// </summary>
/// <param name="year">给定的年份</param>
/// <returns>该年份是否为闰年</returns>
inline int is_leap(const int year)
{
return (year % 4 == 0 && year % 100 || year % 400 == 0);
}
/// <summary>
/// 判断日期是否合法
/// </summary>
/// <param name="date">保存日期的地址</param>
/// <returns>如果合法返回1,否则返回0。</returns>
inline int is_valid_date(const Date* const date)
{
if (date->month < 1 || date->month > 12)
{
puts("\a月份必须在 1~12 之间!");
return 0;
}
if (date->day < 1)
{
puts("\a日期必须大于零!");
return 0;
}
int mday = get_max_day(date->month, date->year);
if (date->day > mday)
{
printf("\a%d 年 %d 月只有 %d 天!\n", date->year, date->month, mday);
return 0;
}
return 1;
}
/// <summary>
/// 求给定年份的某个月份的最大天数
/// </summary>
/// <param name="month">月份</param>
/// <param name="year">年份</param>
/// <returns>最大天数</returns>
inline int get_max_day(const int month, const int year)
{
switch (month)
{
case 1: case 3: case 5: case 7: case 8: case 10: case 12: return 31;
case 4: case 6: case 9: case 11: return 30;
case 2: if (is_leap(year)) return 29;
else return 28;
default: puts("出现了错误。"); return -1;
}
}
/// <summary>
/// 根据出生日期确定年龄,判断年龄是否在范围内
/// </summary>
/// <param name="date">指向出生日期结构体的指针</param>
/// <returns>如果年龄在 [MIN_AGE, MAX_AGE] 内,返回1,否则返回0。</returns>
inline int age_in_range(const Date* const date)
{
if (!age_in_up_range(date))
{
printf("\a年龄超过了 %d!\n", MAX_AGE);
return 0;
}
if (!age_in_down_range(date))
{
printf("\a年龄不足 %d!\n", MIN_AGE);
return 0;
}
return 1;
}
/// <summary>
/// 判断员工的岗位工资、补贴总额、代扣总额和应发工资是否在范围内
/// </summary>
/// <param name="e">指向员工结构体的指针</param>
/// <returns>如果岗位工资、补贴总额、代扣总额和应发工资
/// 都在 [0, MAX_MONEY] 范围内,返回1;否则返回0。</returns>
inline int money_in_range(const Employee* const e)
{
#define MONEY_IN_RANGE(money) ((money) >= 0 && (money) <= MAX_MONEY)
if (!MONEY_IN_RANGE(e->pstwg))
{
printf("\a岗位工资不在 [0, %d] 的范围内!\n", MAX_MONEY);
return 0;
}
if (!MONEY_IN_RANGE(e->sbsd))
{
printf("\a补贴总额不在 [0, %d] 的范围内!\n", MAX_MONEY);
return 0;
}
if (!MONEY_IN_RANGE(e->wthhld))
{
printf("\a代扣总额不在 [0, %d] 的范围内!\n", MAX_MONEY);
return 0;
}
if (!MONEY_IN_RANGE(e->slr))
{
printf("\a应发工资不在 [0, %d] 的范围内!\n", MAX_MONEY);
return 0;
}
return 1;
#undef MONEY_IN_RANGE
}
/// <summary>
/// 检查文件里面是否已存在工号相同的记录
/// </summary>
/// <param name="e">输入的工号</param>
/// <returns>如果文件不存在或者文件存在且没有相同工号的记录,
/// 返回0;否则返回1。</returns>
inline int have_repeat_record(const Work_id id)
{
FILE* fp = fopen(DATA_FILE, "rb");
int same = 0;
if (fp)
{
while (!same && !feof(fp))
{
Employee tmp;
fread(&tmp, sizeof(Employee), 1, fp);
same = !strcmp(id, tmp.id);
}
if (same)
{
puts("\a文件里已经发现了员工号相同的记录!");
}
fclose(fp);
}
return same;
}
/// <summary>
/// 确定身份证号是否合法。
/// <para>合法的标准是:</para>
/// <para>1.长度必须是 ID_CARD_LEN 位。</para>
/// <para>2.出生日期部分必须和先前输入的出生日期一致。</para>
/// <para>3.校验码要正确。</para>
/// </summary>
/// <param name="id_card_no">存储身份证号的字符串</param>
/// <param name="birth">出生日期结构体的地址</param>
/// <returns>如果合法返回1,否则返回0。</returns>
inline int id_card_ok(Id_card id_card_no, const Date* const birth)
{
return id_card_len_ok(id_card_no) &&
id_card_birth_ok(id_card_no, birth) && id_card_char_ok(id_card_no);
}
inline int id_card_len_ok(const Id_card id_card_no)
{
if (strlen(id_card_no) != ID_CARD_LEN)
{
printf("\a身份证号码长度必须是 %d 位!\n", ID_CARD_LEN);
return 0;
}
return 1;
}
inline int id_card_birth_ok(const Id_card id_card_no, const Date* const birth)
{
#define BEFORE_BIRTH_WIDTH 6
int y = 0, m = 0, d = 0;
sscanf(id_card_no + BEFORE_BIRTH_WIDTH, "%4d%2d%2d", &y, &m, &d);
if (y != birth->year || m != birth->month || d != birth->day)
{
puts("\a身份证号中的出生日期与前面输入的出生日期不匹配!");
return 0;
}
return 1;
#undef BEFORE_BIRTH_WIDTH
}
inline int id_card_char_ok(Id_card id_card_no)
{
#define VERIFY_DIVISOR 11
int v = 0;
const static int coefficient[ID_CARD_LEN - 1] =
{ 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 };
for (int i = 0; i < ID_CARD_LEN - 1; i++)
{
v += (id_card_no[i] - '0') * coefficient[i];
if (!isdigit(id_card_no[i]))
{
printf("\a身份证号的前 %d 位必须是 0~9 的数字!\n", ID_CARD_LEN - 1);
return 0;
}
}
v %= VERIFY_DIVISOR;
id_card_no[ID_CARD_LEN - 1] = toupper(id_card_no[ID_CARD_LEN - 1]);
const static int veri_code[VERIFY_DIVISOR] =
{ '1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2' };
if (id_card_no[ID_CARD_LEN - 1] != veri_code[v])
{
printf("\a第 %d 位的校验码有误!\n", ID_CARD_LEN);
return 0;
}
return 1;
#undef VERIFY_DIVISOR
}
/// <summary>
/// 根据出生日期确定年龄,判断年龄有没有低于下限
/// </summary>
/// <param name="date">生日结构体的地址</param>
/// <returns>如果没有低于下限,返回1;否则返回0。</returns>
int age_in_down_range(const Date* const date)
{
time_t now = time(NULL);
struct tm* today = localtime(&now);
Date now_date = { .year = today->tm_year + 1900,
.month = today->tm_mon + 1,.day = today->tm_mday };
if ((now_date.year < date->year + MIN_AGE) ||
(now_date.year == date->year + MIN_AGE &&
(now_date.month < date->month ||
(now_date.month == date->month && now_date.day < date->day))))
{
return 0;
}
return 1;
}
#pragma endregion
// employee_list.c
#define _CRT_SECURE_NO_WARNINGS
#include "employee_list.h"
#include "cmp.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
static inline int trim_list(Employee_list* el);
int load_list(Employee_list* el)
{
el->e_list = NULL;
el->tot_rec = 0;
FILE* fp = fopen(DATA_FILE, "rb");
if (!fp) return 1;
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
rewind(fp);
if (size)
{
el->tot_rec = size / sizeof(Employee);
el->e_list = malloc(el->tot_rec * sizeof(Employee*));
if (el->e_list == NULL) return 2;
for (long i = 0; i < el->tot_rec; i++)
{
el->e_list[i] = malloc(sizeof(Employee));
if (el->e_list[i] == NULL) return 3;
fread(el->e_list[i], sizeof(Employee), 1, fp);
}
}
fclose(fp);
return 0;
}
int sort_list(Employee_list* el, Sort_way sw)
{
int(*cmp)(const void* arg1, const void* arg2);
switch (sw)
{
case ID_ASC: cmp = id_asc_cmp; break;
case ID_DSC: cmp = id_dsc_cmp; break;
case NAME_ASC: cmp = name_asc_cmp; break;
case NAME_DSC: cmp = name_dsc_cmp; break;
case SEX: cmp = sex_asc_cmp; break;
case AGE_ASC: cmp = age_asc_cmp; break;
case AGE_DSC: cmp = age_dsc_cmp; break;
case SLR_ASC: cmp = slr_asc_cmp; break;
case SLR_DSC: cmp = slr_dsc_cmp; break;
case IC_ASC: cmp = ic_asc_cmp; break;
case IC_DSC: cmp = ic_dsc_cmp; break;
default: puts("出现了错误"); return 1;
}
qsort(el->e_list, el->tot_rec, sizeof(Employee*), cmp);
return 0;
}
void traverse_show(Employee_list* el)
{
pri_table_head();
for (long i = 0; i < el->tot_rec; i++)
{
show_employee_info(el->e_list[i]);
}
printf("一共有 %d 名员工。\n", el->tot_rec);
}
long search_employee(Employee_list* el, Work_id target_id)
{
int found = 0;
for (long i = 0; i < el->tot_rec && !found; i++)
{
if (!strcmp(el->e_list[i]->id, target_id)) return i;
}
return -1;
}
int del_employee(Employee_list* el, Work_id target_id)
{
long index = search_employee(el, target_id);
if (index == -1) return -1;
pri_table_head();
show_employee_info(el->e_list[index]);
printf("\a确定吗?确定 = Y 或 y。");
int choice = toupper(getchar());
while (getchar() != '\n');
if (choice == 'Y')
{
free(el->e_list[index]);
memcpy(el->e_list + index, el->e_list + index + 1,
(el->tot_rec - index - 1) * sizeof(Employee*));
el->tot_rec--;
int err = trim_list(el);
if (err)
{
fprintf(stderr, "调整员工列表的大小失败!");
return 1;
}
}
return 0;
}
int remove_retired(Employee_list* el)
{
long cnt = 0;
puts("正在查找已退休员工...\n");
pri_table_head();
for (long i = 0; i < el->tot_rec; i++)
{
if (!age_in_up_range(&el->e_list[i]->birth_day))
{
show_employee_info(el->e_list[i]);
free(el->e_list[i]);
cnt++;
}
}
if (cnt)
{
el->tot_rec -= cnt;
memcpy(el->e_list, el->e_list + cnt,
el->tot_rec * sizeof(Employee*));
int err = trim_list(el);
if (err)
{
fprintf(stderr, "调整员工列表的大小失败!");
return 1;
}
}
else puts("还没有已退休的员工!");
return 0;
}
void flush_data_file(Employee_list* el)
{
FILE* fp = fopen(DATA_FILE, "wb");
for (long i = 0; i < el->tot_rec; i++)
{
fwrite(el->e_list[i], sizeof(Employee), 1, fp);
}
fclose(fp);
}
void free_list(Employee_list* el)
{
for (int i = 0; i < el->tot_rec; i++)
{
free(el->e_list[i]);
}
free(el->e_list);
el->e_list = NULL;
el->tot_rec = 0;
}
#pragma region 内部函数定义
/// <summary>
/// 调整员工列表的大小
/// <para>前提:列表中的员工个数已经更新,并且
/// 要保留的员工已经放到列表的最前端,不留空位</para>
/// </summary>
/// <param name="el">列表的地址</param>
/// <returns>当且仅当调整成功返回 0</returns>
inline int trim_list(Employee_list* el)
{
if (el->tot_rec)
{
Employee** tmp = realloc(el->e_list, el->tot_rec * sizeof(Employee*));
if (tmp != NULL)
{
el->e_list = tmp;
return 0;
}
return 1;
}
else // 必须单独处理零个员工的情况!
{
el->e_list = NULL;
return 0;
}
}
#pragma endregion
// entry.c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include "entry_needed.h"
int main(void)
{
char choice = get_choice_in_main();
while (choice != 'Q')
{
switch (choice)
{
case 'I':input_salary_info(); break;
case 'S':output_with_order(); break;
case 'T':output_statistics(); break;
case 'F':search_in_main(); break;
case 'D':del_employee_in_main();break;
case 'R':rm_retired_in_main(); break;
default:
puts("出现了错误。"); break;
}
choice = get_choice_in_main();
}
return 0;
}
// entry_needed.c
#define _CRT_SECURE_NO_WARNINGS
#define MAX_ID_INPT_LEN 7
#include "entry_needed.h"
#include <stdio.h>
#include <ctype.h>
#include <string.h>
static inline void eat_line(void);
static inline void pri_hint(void);
static inline char get_sort_choice(void);
char get_choice_in_main(void)
{
#define MENU \
"\n选择一个选项:\n"\
"I 输入员工工资信息\n"\
"T 输出员工工资统计信息\n"\
"S 排序后输出员工工资信息\n"\
"F 按员工号查找员工并输出其工资信息\n"\
"D 按员工号查找员工并删除其工资信息\n"\
"R 删除已退休员工的信息\n"\
"Q 退出\n\n"\
"输入你的选择:_\b"
while (1)
{
printf(MENU);
char ch;
scanf(" %c", &ch);
ch = toupper(ch);
eat_line();
if (ch == 'I' || ch == 'T' || ch == 'S' || ch == 'R'
|| ch == 'F' || ch == 'D' || ch == 'Q')
{
return ch;
}
puts("\a选择有误,请重新输入!\n");
}
#undef MENU
}
void input_salary_info(void)
{
pri_hint();
Employee e;
int cnt = 0, err;
while ((err = get_employee_info(&e)) != EOF)
{
if (err) puts("刚刚的输入有误,请检查!\n");
else
{
append_to_file(&e);
cnt++;
}
}
puts("输入结束\n");
if (cnt)
{
puts("开始按年龄降序排序...");
Employee_list el;
if (load_list(&el) == 0)
{
sort_list(&el, AGE_DSC);
flush_data_file(&el);
free_list(&el);
puts("排序完成!");
}
else fprintf(stderr, "加载员工列表失败,无法排序!\n");
}
}
void output_statistics(void)
{
Employee_list el;
double max_ = -1, min_ = MAX_MONEY + 1,
tot_s = 0, tot_m = 0, tot_f = 0;
long m = 0, f = 0;
if (load_list(&el) == 0)
{
for (long i = 0; i < el.tot_rec; i++)
{
tot_s += el.e_list[i]->slr;
if (el.e_list[i]->slr > max_) max_ = el.e_list[i]->slr;
if (el.e_list[i]->slr < min_) min_ = el.e_list[i]->slr;
if (el.e_list[i]->is_male)
{
m++;
tot_m += el.e_list[i]->slr;
}
else
{
f++;
tot_f += el.e_list[i]->slr;
}
}
if (el.tot_rec)
{
printf("应发工资最高为 %g,最低为 %g。"
"所有员工应发工资总额为 %g,平均为"
" %g。\n", max_, min_, tot_s, tot_s / el.tot_rec);
printf("男性员工共 %d 人", m);
if (m) printf(",应发工资平均为 %g", tot_m / m);
printf(";女性员工共 %d 人", f);
if (f) printf(",应发工资平均为 %g", tot_f / f);
puts("。");
}
else puts("一个员工也没有!");
free_list(&el);
}
else fprintf(stderr, "加载员工列表失败,无法统计!\n");
}
void output_with_order(void)
{
Employee_list el;
if (load_list(&el) != 0)
{
fprintf(stderr, "加载员工列表失败,无法输出!\n");
return;
}
Sort_way sw;
switch (get_sort_choice())
{
case '1':sw = ID_ASC; break;
case '2':sw = ID_DSC; break;
case '3':sw = NAME_ASC; break;
case '4':sw = NAME_DSC; break;
case '5':sw = SEX; break;
case '6':sw = AGE_ASC; break;
case '7':sw = AGE_DSC; break;
case '8':sw = SLR_ASC; break;
case '9':sw = SLR_DSC; break;
case 'A':sw = IC_ASC; break;
case 'B':sw = IC_DSC; break;
case 'Q':putchar('\n'); return;
default: puts("出现了错误"); free_list(&el); return;
}
sort_list(&el, sw);
traverse_show(&el);
putchar('\n');
free_list(&el);
}
void search_in_main(void)
{
static Work_id wid;
printf("\n请输入员工编号(只接受前 %d 位):____\b\b\b\b", ID_LEN);
s_gets(wid, ID_SIZE);
Employee_list el;
if (load_list(&el) != 0)
{
fprintf(stderr, "加载员工列表失败,无法查找!\n");
return;
}
long pos = search_employee(&el, wid);
if (pos == -1) puts("没找到这名员工。");
else
{
pri_table_head();
show_employee_info(el.e_list[pos]);
}
free_list(&el);
}
void del_employee_in_main(void)
{
static Work_id wid;
printf("\n请输入员工编号(只接受前 %d 位):____\b\b\b\b", ID_LEN);
s_gets(wid, ID_SIZE);
Employee_list el;
if (load_list(&el) != 0)
{
fprintf(stderr, "加载员工列表失败,无法删除!\n");
return;
}
if (del_employee(&el, wid) == 0)
{
flush_data_file(&el);
puts("操作成功完成!\n");
}
else puts("出错了!");
free_list(&el);
}
void rm_retired_in_main(void)
{
Employee_list el;
if (load_list(&el) != 0)
{
fprintf(stderr, "加载员工列表失败,无法删除退休员工!\n");
return;
}
if (remove_retired(&el) == 0)
{
flush_data_file(&el);
puts("操作成功完成!\n");
}
else puts("出错了!");
free_list(&el);
}
#pragma region 内部函数定义
/// <summary>
/// 丢弃一行中多余的字符
/// </summary>
/// <param name="">无参数</param>
inline void eat_line(void)
{
while (getchar() != '\n');
}
/// <summary>
/// 对输入的提示和要求
/// </summary>
inline void pri_hint(void)
{
puts("\n员工工资信息录入\n");
puts("对输入的要求如下:");
printf("1.员工号。员工号是员工信息的唯一标识,不允许重复。"
"长度 <= %d。\n员工号格式:第 1 位为 1~9 之间的数字,"
"后面的字符可以是任意数字。\n", ID_LEN);
printf("2.姓名。姓名必须是 %d 个以上的汉字,"
"最多 %d 个汉字。\n", MIN_HANZI, MAX_HANZI);
puts("3.性别。性别只能是 男(M)或 女(F)。字母"
"大小写均可。");
printf("4.出生日期。分隔符必须为 -(减号)或 . (下脚点)。"
"\n由该出生日期得到的年龄必须在 %d~%d 之间。我们不考虑"
"男女退休年龄的区别。\n", MIN_AGE, MAX_AGE);
printf("5.身份证号。长度必须为 %d 位。我们会检查它的校验码是否正确。\n"
"校验码中的 X 会自动转换成大写。\n", ID_CARD_LEN);
printf("6.岗位工资、补贴总额、代扣总额。它们都是范围在 "
"[0, %d] 之间的实数。\n而 应发工资 = 岗位工资 + 补贴"
"总额 - 代扣总额,算出来的结果也应该在这个范围内。\n\n", MAX_MONEY);
puts("各个输入项之间只需用一个空格隔开。"
"空格太多会导致后面的项目无法读入。");
puts("按这样的格式输入:员工号 姓名 性别 "
"出生日期 身份证号 岗位工资 补贴总额 代扣总额");
puts("例如 :1010 伍惺委 男 "
"1966-7-7 35032219660707608X 3220 1500 300");
puts("或者 :1010 伍惺委 m "
"1966.7.7 35032219660707608x 3220 1500 300\n");
puts("要结束输入,请在行首按 Ctrl + Z,然后按回车。");
}
inline char get_sort_choice(void)
{
while (1)
{
puts("\n选择一种排序方式\n");
puts("1 按员工号从小到大排序");
puts("2 按员工号从大到小排序");
puts("3 按员工姓名升序排序");
puts("4 按员工姓名降序排序");
puts("5 按员工性别排序");
puts("6 按员工年龄从小到大排序");
puts("7 按员工年龄从大到小排序");
puts("8 按员工应发工资从小到大排序");
puts("9 按员工应发工资从大到小排序");
puts("A 按员工身份证号从小到大排序");
puts("B 按员工身份证号从大到小排序");
puts("Q 不排序退出");
printf("\n输入选择:_\b");
char c;
scanf(" %c", &c);
c = toupper(c);
eat_line();
if (strchr("123456789ABQ", c)) return c;
puts("\a选择有误,请重新输入!");
}
}
#pragma endregion