工资系统第四版20221126

功能需求

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

包含文件的关系图

源程序(主函数在 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

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

黄铎彦

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

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

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

打赏作者

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

抵扣说明:

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

余额充值