1. 问题描述
中国有一句俗语叫"三天打鱼,两天晒网"。某渔民从1990年1月1日起开始执行"三天打鱼,两天晒网"的计划。问:未来的某一天(从键盘输入),该渔民是在打鱼还是在晒网?
计划规则:
· 周期为5天:打鱼3天,晒网2天
· 周期循环:打鱼(3天) → 晒网(2天) → 打鱼(3天) → 晒网(2天) → ...
2. 学习目标
通过本练习,你将掌握:
· 日期间隔的计算方法
· 闰年的判断
· 年月日的处理技巧
· 周期问题的解决方法
· 输入数据的有效性验证
3. 算法思路
3.1 核心逻辑
要判断某天是打鱼还是晒网,需要:
-
计算从1990年1月1日到目标日期经过的总天数
-
用总天数对5取余(因为周期为5天)
-
根据余数判断:
· 余数为1、2、3:打鱼
· 余数为4、0:晒网
3.2 为什么这样判断?
假设1990年1月1日是周期的第1天(打鱼):
· 第1天:打鱼(余数1)
· 第2天:打鱼(余数2)
· 第3天:打鱼(余数3)
· 第4天:晒网(余数4)
· 第5天:晒网(余数0)
· 第6天:打鱼(余数1)
· 以此类推...
4. 代码实现
4.1 闰年判断函数
#include <stdio.h>
#include <stdbool.h>
// 判断是否为闰年
bool isLeapYear(int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
4.2 获取某月的天数
// 获取某年某月的天数
int getDaysInMonth(int year, int month) {
int daysInMonth[] = {31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31};
// 2月特殊处理
if (month == 2 && isLeapYear(year)) {
return 29;
}
return daysInMonth[month - 1];
}
4.3 计算从1990年1月1日到目标日期的总天数
// 计算总天数
int calculateTotalDays(int year, int month, int day) {
int totalDays = 0;
// 计算年份的天数
for (int y = 1990; y < year; y++) {
totalDays += isLeapYear(y) ? 366 : 365;
}
// 计算月份的天数
for (int m = 1; m < month; m++) {
totalDays += getDaysInMonth(year, m);
}
// 加上当月天数
totalDays += day;
return totalDays;
}
4.4 判断打鱼还是晒网
// 判断状态
const char* judgeFishingOrNetting(int totalDays) {
int remainder = totalDays % 5;
if (remainder >= 1 && remainder <= 3) {
return "打鱼";
} else {
return "晒网";
}
}
4.5 主函数
int main() {
int year, month, day;
printf("========================================\n");
printf(" 三天打鱼,两天晒网 判断程序\n");
printf("========================================\n");
printf("起始日期:1990年1月1日\n");
printf("请输入你要查询的日期(格式:年 月 日):\n");
// 输入日期
scanf("%d %d %d", &year, &month, &day);
// 输入验证
if (year < 1990) {
printf("错误:年份不能小于1990年!\n");
return 1;
}
if (month < 1 || month > 12) {
printf("错误:月份必须在1-12之间!\n");
return 1;
}
int maxDays = getDaysInMonth(year, month);
if (day < 1 || day > maxDays) {
printf("错误:日期必须在1-%d之间!\n", maxDays);
return 1;
}
// 计算总天数
int totalDays = calculateTotalDays(year, month, day);
// 输出结果
printf("----------------------------------------\n");
printf("从1990年1月1日到%d年%d月%d日\n", year, month, day);
printf("经过的总天数:%d天\n", totalDays);
printf("周期位置:第%d天(周期5天)\n", totalDays % 5 == 0 ? 5 : totalDays % 5);
printf("结果:这一天应该【%s】\n", judgeFishingOrNetting(totalDays));
printf("========================================\n");
return 0;
}
5. 完整代码(优化版)
#include <stdio.h>
#include <stdbool.h>
// 判断闰年
bool isLeapYear(int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
// 获取某月的天数
int getDaysInMonth(int year, int month) {
int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (month == 2 && isLeapYear(year)) {
return 29;
}
return days[month - 1];
}
// 验证日期是否有效
bool isValidDate(int year, int month, int day) {
if (year < 1990) return false;
if (month < 1 || month > 12) return false;
int maxDay = getDaysInMonth(year, month);
if (day < 1 || day > maxDay) return false;
return true;
}
// 计算从1990-01-01到目标日期的天数
int calculateDays(int year, int month, int day) {
int total = 0;
// 计算整年的天数
for (int y = 1990; y < year; y++) {
total += isLeapYear(y) ? 366 : 365;
}
// 计算整月的天数
for (int m = 1; m < month; m++) {
total += getDaysInMonth(year, m);
}
// 加上当月天数
total += day;
return total;
}
// 判断状态
void printResult(int days) {
printf("经过天数:%d天\n", days);
int remainder = days % 5;
printf("余数:%d(周期5天)\n", remainder);
if (remainder >= 1 && remainder <= 3) {
printf("结果:打鱼 💪\n");
} else {
printf("结果:晒网 😴\n");
}
}
int main() {
int year, month, day;
printf("+--------------------------------------+\n");
printf("| 三天打鱼,两天晒网 判断程序 |\n");
printf("+--------------------------------------+\n");
printf("| 起始日期:1990年1月1日 |\n");
printf("| 周期规律:打鱼3天 → 晒网2天 |\n");
printf("+--------------------------------------+\n\n");
// 输入日期
printf("请输入查询日期(年 月 日):");
scanf("%d %d %d", &year, &month, &day);
// 验证输入
if (!isValidDate(year, month, day)) {
printf("\n错误:无效的日期!\n");
printf("提示:年份>=1990,月份1-12,日期要符合当月天数\n");
return 1;
}
// 计算并输出结果
printf("\n=== 计算结果 ===\n");
int days = calculateDays(year, month, day);
printResult(days);
return 0;
}
6. 运行示例
+--------------------------------------+
| 三天打鱼,两天晒网 判断程序 |
+--------------------------------------+
| 起始日期:1990年1月1日 |
| 周期规律:打鱼3天 → 晒网2天 |
+--------------------------------------+
请输入查询日期(年 月 日):2024 3 18
=== 计算结果 ===
经过天数:12508天
余数:3(周期5天)
结果:打鱼 💪
7. 扩展练习
7.1 批量查询
void batchQuery() {
int year, month, startDay, endDay;
printf("请输入年份和月份,以及查询的日期范围:\n");
printf("年 月 起始日 结束日:");
scanf("%d %d %d %d", &year, &month, &startDay, &endDay);
printf("\n%s年%d月 %d日-%d日的状态:\n", year, month, startDay, endDay);
for (int day = startDay; day <= endDay; day++) {
if (isValidDate(year, month, day)) {
int days = calculateDays(year, month, day);
printf("%2d日:%s\n", day,
(days % 5 >= 1 && days % 5 <= 3) ? "打鱼" : "晒网");
}
}
}
7.2 统计功能
void statistics(int year, int month) {
int fishingDays = 0, nettingDays = 0;
int daysInMonth = getDaysInMonth(year, month);
for (int day = 1; day <= daysInMonth; day++) {
int totalDays = calculateDays(year, month, day);
if (totalDays % 5 >= 1 && totalDays % 5 <= 3) {
fishingDays++;
} else {
nettingDays++;
}
}
printf("%d年%d月:打鱼%d天,晒网%d天\n",
year, month, fishingDays, nettingDays);
}
7.3 自定义起始日期
// 修改程序,让用户可以自定义起始日期
int calculateDaysFromStart(int startYear, int startMonth, int startDay,
int targetYear, int targetMonth, int targetDay) {
// 计算从自定义起始日期到目标日期的天数
// 需要考虑起始日期可能不是1月1日
}
8. 常见问题解答
Q:为什么用余数1-3表示打鱼?
A:因为我们把起始日(1990年1月1日)当作周期的第1天,所以第1、2、3天打鱼,第4、5天晒网。总天数对5取余后,余数1、2、3对应打鱼,余数4和0对应晒网。
Q:如果起始日不是周期的第1天怎么办?
A:可以调整判断逻辑。例如如果起始日是晒网的第1天,那么余数1-2可能是晒网,3-5是打鱼,余数0是打鱼。关键是理解周期和起始点的关系。
Q:为什么要进行输入验证?
A:防止用户输入不合理的日期(如2月30日),确保程序正确运行。这是良好编程习惯的一部分。
Q:如何处理公元前或者未来日期?
A:本程序只处理1990年以后的日期。如果要处理更早的日期,需要考虑历法变化(如格里高利历改革)。未来日期理论上可以用同样的算法,只要年份不超过int范围。
9. 知识点总结
知识点 应用
闰年判断 正确处理2月天数
数组 存储每月天数
循环 计算累计天数
条件判断 输入验证、状态判断
取余运算 周期判断
函数封装 模块化代码
10. 课后作业
-
基础版:实现程序,可以查询任意1990年后的日期
-
进阶版:添加统计功能,计算某年某月的打鱼/晒网天数
-
挑战版:允许用户自定义起始日期和周期规则(如打鱼a天,晒网b天)
-
高难度:添加日期范围查询,并生成日历样式的输出
11. 思维拓展
这个程序的核心思想是周期问题,类似的应用有很多:
· 值班排班系统(如5人轮班)
· 交通信号灯控制
· 课程表安排
· 生产计划排程
理解并掌握这种周期问题的解决方法,对解决实际问题很有帮助!
记住:编程不仅仅是写代码,更重要的是理解问题背后的数学逻辑。通过这个练习,你不仅学会了日期计算,更掌握了周期问题的通用解决方法。