上一篇中的课后练习大家普遍反映太简单了。其实,麻雀虽小,五脏俱全。今天我们来看看它的解法。
1. 题目
请编程实现一个功能,输入任意一个日期,计算出那一天是星期几。这道题没有任何限制,你可以设计任何自己喜欢的交互形式,可以使用任何自己能想到的算法。
2. 分析
这道题的题干并不是很具体,它其实是一道从功能开始的设计题。首先要考虑的是交互方法的设计。程序的输入时一个日期,这个日期采用什么形式,怎么输入都是不确定的。
为了Debug方便,我依然用了之前的程序中习惯的方法,从文件读取测试用例。于是,这个题目变成了这样。
给出一个文件input.txt
,内容如下:
5
1900-01-01 1
2017-5-24 3
2017-1-1 0
2017-3-6 1
2017-4-20 4
第一行给出的是日期的个数,之后每一行给出两个元素:日期和星期。请编程实现判断日期是星期几,之后用后面的正确答案来判断程序是否执行正确。
这个题目看起来比较奇怪,居然给出了正确答案。其实这是软件开发中常用的一种方法叫做单元测试。我们可以通过这种方法来测试程序实现的功能是否正确。
3. 程序主干
我们首先来实现一个main函数,代码如下:
int main()
{
int i, cnt;
freopen("input.txt", "r", stdin);
scanf("%d", &cnt);
for (i = 0; i < cnt; i++)
{
printf("#%d : ", i + 1);
if (Process() == 1)
{
printf("Right\n");
}
else
{
printf("Wrong\n");
}
}
return 1;
}
是不是很熟悉,这段程序首先从文件中读取测试用例的个数,保存在cnt
变量中。之后循环来判断核心的算法是否正确,正确在屏幕上打印Right,错误打印Wrong。
判断测试核心算法的函数是Process
,接下来我们就实现它。
4. 测试函数
Process函数源码如下:
#define MAX 15
char g_arr[MAX];
int Process()
{
int ret, answer, i;
int year, month, day;
int week;
char* pStr = g_arr;
scanf("%s", pStr);
scanf("%d", &answer);
year = GetNextNum(&pStr);
month = GetNextNum(&pStr);
day = GetNextNum(&pStr);
week = GetWeek(year, month, day);
if (week == answer)
{
return 1;
}
else
{
return 0;
}
}
这个函数有三部分功能:
4.1 读取数据
这部分很简单,通过scanf
从文件中读取数据,这个前面的文章中已经讲过了。这里需要说明的是我们拿到一个类似“2017-01-01”这样的字符串,如何得到它对应的年月日对应的int
值呢。这里调用了一个GetNextNum
函数,它的实现如下:
// 得到字符串中的第一组数字
int GetNextNum(char** ppStr)
{
int ret = 0;
int num;
char* pStr = *ppStr;
while (1)
{
if (*pStr < '0' || *pStr > '9')
{
break;
}
num = *pStr - '0';
ret = ret * 10 + num;
pStr++;
}
pStr++;
*ppStr = pStr;
return ret;
}
这个方法应该是教科书的练习题,相信大家都能看懂。需要说明的是,参数使用了char** ppStr
这个二维指针,原因在于我们希望每次调用这个函数时,都能够从上一次处理之后的位置开始。如果写成char* pStr
,会出现什么样的情况,请大家自己试验。
4.2 判断星期
判断星期时,调用的就是我们要测试的核心算法函数GetWeek
,这个函数的功能是输入年月日三个参数,返回一个星期数,0~6表示星期日到星期六。
4.3 判断是否正确
把GetWeek
函数的结果与文件中的正确结果比较,正确返回1,错误返回0。
5. 判断星期
之前的文章21天C语言代码训练营(第六天)里曾经定义过相关的接口,我们把需要的几个拿过来。
// 判断闰年,是闰年返回1,是平年返回0
int IsLeapYear(int year)
{
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
return 1;
else
return 0;
}
// 返回输入年份的1月1日是周几
int GetFirstWeek(int year)
{
return (35 + year + year / 4 - year / 100 + year / 400) % 7;
}
int g_days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
// 返回输入的年份中输入的月份天数
int GetDays(int year, int month)
{
if (month == 2 && IsLeapYear(year))
{
return g_days[month - 1] + 1;
}
else
{
return g_days[month - 1];
}
}
这三个函数的功能都很简单,相信一看就能明白。之前有很多人问GetFirstWeek
这个函数为什么这样实现,这个是网上找的算法,具体的推导过程我没有留意,只是验证了一下发现能用而已。
有了这几个接口,我们就能很轻松地写出今天的核心函数了:
int GetWeek(int year, int month, int day)
{
int i;
int week = GetFirstWeek(year);
for (i = 1; i < month; i++)
{
week += GetDays(year, i);
}
week--;
week += day;
week = week % 7;
return week;
}
这个函数的功能是输入年月日三个参数,返回当天是星期几。
好了,现在运行一下程序,看看执行结果。
这个题目虽然简单,但这种通过一段程序测试另一段程序的思想希望大家仔细体会。
6. 课后练习
用你学过的C语言知识,设计一个番茄钟程序。要求满足如下功能:
- 可以设置倒计时时间
- 拥有倒计时效果
- 倒计时结束后有提醒
- 使用记录保存在文件中
我是天花板,让我们一起在软件开发中自我迭代。
如有任何问题,欢迎与我联系。