文章目录
一、任务要求
应市场需求,某工程师现设计了一款新上下班打卡机,打卡机具有以下功能:
(1)上班打卡,员工具有编号(首位为 1 的六位编号),输入编号后,再输入校验码,校验码生成规则:员工编号除首位反序,再与员工编号求和,如:员工编号,110086,校验码为 178087。校验码错误即打卡失败。记录打卡时间 。
(2)下班打卡,只需输入员工编号即可。记录打卡时间,显示该人员今天上班时长,如果上班时长不够,显示早退 xx 分钟。可以更新下班打卡时间。无下班打卡显示缺卡。
(3)可以设置规定上班时长,如 9 小时
(4)测试需要可以规定 6 秒=实际 1 小时,每次测试,输入指令后,开启打卡机,打卡机开启模拟时间为:周一早上七点。程序运行结束为周五晚 12 点。
(5)实行弹性打卡制,如前一天上班时长超过规定时长 3 小时以上,第二天迟到 2 小时以内不算迟到。
(6)打卡机运行结束之前,每周该打卡机会生成每周考勤周报,显示周平均上班时长,周迟到,早退,缺卡次数等。
二、思路
1.整个程序主要分为以下几个部分
1.1主函数
1.2非阻塞性函数
1.3待机函数
1.4判断打卡机是否开始工作函数
1.5打卡机工作函数
1.6显示时间函数
1.7上午打卡函数
1.8下午打卡函数
2.1头文件
1.1主函数
由于这是模拟打卡机,测试的时候会出现时间跨度大的情况如上午下午缺卡,也会出现时间跨度小的情况如早退迟到。所以要灵活选择测试时间比例。
主函数代码如下:
/*
* @file 打卡机
* @version 1.0
* @author cyw
* @date 2020/9/28
* 1.测试以6s为1h, 打卡机设定从0点开始工作,show函数显示的是系统时间,也就是09时代表09时,主要调试早退迟到等情况
* 2.测试以60s为1h,打卡机设定从0点开始工作,show函数显示的是系统时间,也就是09分代表09时,主要调试缺卡这种时间跨度大的状况
*/
#include<stdio.h>
#include<time.h>
#include"punch_card.h"
int main(int argc,char *argv[])
{
unsigned int N = 0,code = 0;
unsigned char j = 0,n = 0,week = 0;
unsigned char morning_card_shortage = 0,night_card_shortage = 0,timelate = 0,leave_early = 0;
float total_time = 0,average_time = 0;
time_t timep;
time (&timep);
p = gmtime(&timep);
week = p->tm_wday;
member.week= week;
N = member.code;
while(week < 4)//方便调试,星期数自加
{
clockin_machine_start();//判断打卡机是否开始工作
work_start();
week++;
member.week= week;
}
N = member.code;
for(j = 1;j < 6;j++)//汇总一星期各项数据
{
timelate += member.number[N].work_data[j].timelate;
leave_early += member.number[N].work_data[j].leave_early;
morning_card_shortage += member.number[N].work_data[j].morning_card_shortage;
night_card_shortage += member.number[N].work_data[j].night_card_shortage;
total_time += member.number[N].work_data[j].total_time;
}
week = week - 3;
average_time = (total_time / week);
printf("打卡周报:\r\n");
printf("周迟到数 %d次\r\n",timelate);
printf("周早退次数:%d次\r\n",leave_early);
printf("周上午缺卡次数:%d次\r\n",morning_card_shortage);
printf("周下午缺卡次数:%d次\r\n",night_card_shortage);
printf("周上班平均时长:%.2f小时\r\n",average_time);
return 0;
}
1.2非阻塞性输入函数
c语言的scanf函数是阻塞性输入函数,即如果没有输入,程序则会停在当前位置。c++中有非阻塞性输入函数但是c语言无法引用,所以提供给读者一个c语言版本的非阻塞性输入函数,读者可以直接引用,注意其宏定义。
非阻塞性输入函数宏定义如下:
#define TTY_PATH “/dev/tty”
#define STTY_US "stty raw -echo -F "
#define STTY_DEF "stty -raw echo -F "
非阻塞性输入函数代码如下:
int _my_getchar(void)//非阻塞输入函数
{
fd_set rfds;
struct timeval tv;
int ch = 0;
system(STTY_US TTY_PATH);
FD_ZERO(&rfds);
FD_SET(0, &rfds);
tv.tv_sec = 0;
tv.tv_usec = 6000000; //设置等待超时时间..6s
if (select(1, &rfds, NULL, NULL, &tv) > 0)//检测键盘是否有输入
{
ch = getchar();
}
return ch;
}
1.3待机函数
通过非阻塞性输入函数我们可以制作一个待机函数,当非阻塞性输入函数检测到员工编号输入时,会逐个显示输入的数字,当输入6个数字则会进入上午打卡函数。未满6个数字则会被上午打卡函数重复调用直至满6个数字。如果非阻塞性输入函数没有检测到有数字输入会待机6秒(可设置),6秒后会调用show函数输出当前时间,然后也会被上午打卡函数重复调用,这样就完成待机界面。
待机函数代码如下:
int employee_info_get(int *number)
{
char temp = 0;
static char num_string[6] = {0};
static unsigned char i = 0;
char count = 0;
temp = _my_getchar();
if(0 != temp)
{
if('q' == temp)//q表示结束程序
{
system(STTY_DEF TTY_PATH);
return 2;
}
printf("%c\r\n",temp);
if(i < 5)
{
num_string[i] = temp;
i++;
return 0;
}
else
{
num_string[5] = temp;
i = 0;
//把字符数组的内容转换成整型
//"123456" -> 123456
*number = atoi(num_string);
//当一个数组重复使用时候,在用完的时候把数组清空
memset(num_string,0,sizeof(num_string));
return 1;
}
}
else
{
printf("当前时间为:");//如果12s内没有输入就进入待机状态,显示当前时间
show();
count = member.count;
count++;
member.count = count;
}
return 0;
}
1.4判断打卡机是否开始工作函数
通过时间显示函数获取当前时间,如果是双休日打卡机则不工作并发出提示后退出,注意0代表星期天。
判断打卡机是否开始工作函数代码如下:
int clockin_machine_start(void)//判断打卡机是否开始工作函数
{
unsigned char hour = 0,week = 0;
time_t timep;
time (&timep);
p = gmtime(&timep);
week = p->tm_wday;
member.week= week;
hour = 8 + p->tm_hour;
if(week == 0 || week == 6)//如果是星期六或星期日打卡机不工作
{
printf("今天是星期%d请于工作时间打卡\r\n",week);exit(0);
}
return 0;
}
1.5打卡机工作函数
打卡机工作函数代码如下:
int work_start(void)//打卡机工作函数
{
unsigned int N = 0,code = 0;
unsigned char count = 0,n = 0,week = 0;
unsigned char morning_card_shortage = 0,night_card_shortage = 0,flag_morning =0,flag_night = 0;
clock_in_morning(); //上午打卡函数
N = member.code;
week = member.week;
count = 0;
member.count = count;
clock_in_night(); //下午打卡函数
getchar();
flag_morning = member.number[N].work_data[week].flag_morning;
flag_night = member.number[N].work_data[week].flag_night;
if(flag_morning == 0)//flag_morning==0代表上午没有打卡,缺卡次数+1
{
member.number[N].work_data[week].morning_card_shortage++;
}
else
{
}
if(flag_night == 0)//flag_night==0代表下午没有打卡,缺卡次数+1
{
member.number[N].work_data[week].night_card_shortage++;
}
else
{
}
count = 0;
return 0;
}
1.6显示时间函数
通过地址访问获取系统时间
显示时间函数代码如下:
void show(void)//显示时间函数
{
unsigned char week = 0;
time_t timep;
time (&timep);
p = gmtime(&timep);
week = member.week;
//显示年月日时分秒以及星期
printf("%d年 ",1900 + p->tm_year);
printf("%d月",1 + p->tm_mon);
printf("%d日",p->tm_mday);
printf("%d时",8 + p->tm_hour);
printf("%d分",p->tm_min);
printf("%d秒",p->tm_sec);
printf("---------- 星期%d\r\n",week);
}
1.7上午打卡函数
通过result判断进入哪一阶段,result = 1则进入上午打卡函数后续阶段,result = 2则测试结束退出程序,result为其他比如为0则重新循环,在重复循环过程中就会重复调用待机函数。而后续阶段主要包含验证员工编号、判断是否迟到、判断是否上午缺卡、判断是否满足弹性打卡制度。
上午打卡函数代码如下:
int clock_in_morning(void)//上午打卡函数
{
unsigned int tmp = 0,N = 0,code = 0,number = 0,number1 = 0,check_number = 0,check_code_right = 0,first_time = 0,last_time = 0;
unsigned char min = 0,hour = 0,week = 0,result = 0,count = 0,flag_morning = 0,card_time = 0;
unsigned char morning_card_shortage = 0,timelate = 0,late_hour = 0;
float total_time = 0;
time_t timep;
time (&timep);
p = gmtime(&timep);
while(1)//当检测到有完整6位的员工编号输入时进入后续阶段,否则进入待机状态(显示当前时间)
{
count = member.count;
result = employee_info_get(&number);
min = p->tm_min;
if(12 == count)//如果12小时内没有输入,判定上午缺卡并结束
{
card_time = 12;
member.card_time = card_time;
return 0;
}
if(2 == result)
{
printf("sys over\r\n");
return 0;
}
else if(1 == result)//后续阶段
{
N = number;
member.code = number;
week = member.week;
member.number[N].work_data[week].flag_morning++;//上午打卡标志
number1 = number - 100000;
while(number1 != 0)//反序
{
tmp = tmp * 10 + number1 % 10;
number1 = number1 / 10;
}
check_code_right = number + tmp;
printf("请输入验证码:");
scanf("%d",&check_number);
printf("%d\r\n",check_number);
L1:if(check_number == check_code_right)
{
printf("当前时间为:");
show();//显示当前时间函数
first_time = p->tm_sec;//将分钟换算成秒钟,方便测试
min = p->tm_min;
first_time = min * 60 + first_time;
member.number[N].work_data[week].first_time = first_time;
}
else
{
printf("验证码错误,请重新输入:\r\n");
scanf("%d",&check_number);
printf("%d\r\n",check_number);
goto L1;
}
hour=8 + p->tm_hour;
card_time = p->tm_hour;//min
member.card_time = card_time;
if(hour > 8 && hour <= 9)//8-9点打卡视为打卡成功if(hour > 8 && hour <= 9)
{
printf("上班打卡成功\r\n");
break;
}
else if(hour > 9 && hour <= 10)//9-10点打卡视为迟到if(hour > 9 && hour <= 10)
{
printf("打卡成功不过您迟到了!\r\n");
late_hour = hour - 9;//计算迟到时间(如果迟到的话),没迟到是负值也没事,这个值只保存一天会被后一天替代
member.number[N].work_data[week].late_hour = late_hour;
member.number[N].work_data[week].timelate++;
if(member.number[N].work_data[week].late_hour <= 2 && (member.number[N].work_data[week-1].total_time-9) >= 3)//前一天加班时长超过3小时及以上,第二天迟到2小时以内不算迟到!
{
printf("不过,鉴于您昨天加班超过规定上班时间3小时以上以及今日迟到时间在2小时以内,今日不算迟到!\r\n");
member.number[N].work_data[week].timelate--;//避免重复计算
}
break;
}
else if(hour > 10)//当天10点后打卡都视为上午缺卡
{
printf("上午缺卡\r\n");
member.number[N].work_data[week].morning_card_shortage++;
break;
}
}
}
getchar();
return 0;
}
1.8下午打卡函数
下午打卡函数思路与上午打卡函数类似
下午打卡函数代码如下:
int clock_in_night(void)//下午打卡函数
{
unsigned int tmp = 0,N = 0,code = 0,T = 0,number = 0,number1 = 0,check_number = 0,check_code_right = 0,first_time = 0,last_time = 0;
unsigned char min = 0,hour = 0,week = 0,result = 0,count = 0,flag_night = 0,card_time = 0;
unsigned char night_card_shortage = 0,timelate = 0,late_hour = 0,leave_early = 0;
float total_time = 0;
time_t timep;
time (&timep);
p = gmtime(&timep);
while(1)//当检测到有完整6位的员工编号输入时进入后续阶段,否则进入待机状态(显示当前时间)
{
L2:result = employee_info_get(&number);
card_time = member.card_time;
count = member.count;
if((22 - card_time) == count)//如果通过上午打卡函数,会得到card_time,card_time为上午打卡时间
{ //如果没有通过上午打卡函数,说明上午0-12点都没打卡,card_time为固定12.在22点前没有进行下午打卡,判定下午缺卡
return 0;
}
if(2 == result)
{
printf("sys over\r\n");
return 0;
}
else if(1 == result)//后续阶段
{
N = number;
member.code = number;
week = member.week;
member.number[N].work_data[week].flag_night++;//下午打卡标志
number1 = number - 100000;
while(number1 != 0)//反序
{
tmp = tmp * 10 + number1 % 10;
number1 = number1 / 10;
}
check_code_right = number + tmp;
printf("请输入验证码:");
scanf("%d",&check_number);
printf("%d\r\n",check_number);
L3:if(check_number == check_code_right)
{
printf("当前时间为:");
show();
last_time = p->tm_sec;
min = p->tm_min;
last_time = min * 60 + last_time;
member.number[N].work_data[week].last_time = last_time;
}
else
{
printf("验证码错误,请重新输入:\r\n");
scanf("%d",&check_number);
printf("%d\r\n",check_number);
goto L3;
}
total_time = (member.number[N].work_data[week].last_time - member.number[N].work_data[week].first_time) / 6; //60
member.number[N].work_data[week].total_time = total_time;
hour = 8 + p->tm_hour;
min = p->tm_min;
if(total_time < 9)//公司规定时长为9小时
{
printf("离公司规定时长还差%.2f小时\r\n",9 - total_time);
}
else
{
printf("已经完成规定时长,超额%.2f小时\r\n",total_time - 9);
}
if(hour < 18)//18点前打卡下班视为早退if(hour < 18)
{
printf("您早退下班了!");
printf("今天上班时长:%.2f小时\r\n",total_time);
member.number[N].work_data[week].leave_early++;
printf("是否取消下班打卡,取消请输入1并回车确定,确认请输入0并回车确定\r\n");//可以取消下午打卡,重新计算下班时间和工作时长
scanf("%d",&T);
if(T == 1) //取消下午打卡,重新计算下班时间和工作时长
{
member.number[N].work_data[week].total_time = 0;
member.number[N].work_data[week].leave_early--;//避免重复计算
tmp=0;
getchar();
goto L2;
}
else if(T == 0)//选择下班
{
printf("好的,不选择重新打卡\r\n");
printf("今天上班时长:%.2f小时\r\n",total_time);
return 0;
}
}
else//18点后下班成功
{
printf("今天上班时间:%.2f小时\r\n",total_time);
printf("下班打卡成功\r\n");
return 0;
}
}
}
return 0;
}
2.1头文件
头文件代码如下:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<time.h>
#include<memory.h>
#include<unistd.h>
#define TTY_PATH "/dev/tty"
#define STTY_US "stty raw -echo -F "
#define STTY_DEF "stty -raw echo -F "
#ifndef punch_card_H
#define punch_card_H
struct MEMBER{
int code; //存储员工编号
char week; //存储当前星期
char count;
char card_time;
struct NUMBER
{
struct WORK_DATA
{
int first_time; //当天早上打卡时间,以秒为单位
int last_time; //当天下午打卡时间,以秒为单位
char late_hour; //当天迟到时间(如果迟到的话)
char morning_card_shortage; //当天早上缺卡
char night_card_shortage; //当天下午缺卡
char timelate; //当天迟到
char leave_early; //当天早退
char flag_morning; //上午打卡标志
char flag_night; //下午打卡标志
double total_time; //当天工作总时间
}work_data[7]; //存储该员工整个星期工作信息
}number[199999]; //存储公司全部员工信息
}member;
struct tm *p; //存储时间信息
void show(void); //显示时间函数
int _my_getchar(void); //非阻塞输入函数
int employee_info_get(int *number); //非阻塞输入函数
int clock_in_morning(void); //上午打卡函数
int clock_in_night(void); //下午打卡函数
int clockin_machine_start(void); //判断打卡机是否开始工作函数
int work_start(void); //打卡机开始工作函数
#endif
三、总结
这个打卡机程序是之前写的,还有很多不足和可以改进的地方,将来会找时间加以改进,希望这篇文章能够对大家有所帮助。