资讯动态

GESP三级真题解析:日历制作中的闰年判断与星期推算

发布时间:2026/10/19 4:24:50 来源:尧图企业网站定制
最近好几个刷GESP三级真题的同学都来问我“日历制作”这道题题库里的编号是4142。题目本身不复杂——输入一个年份和一个月份输出这个月的完整日历表但真正动起手来很多人卡在同一个地方我知道2025年1月1日是星期三可怎么算出别的月份1号是星期几这篇文章就把整道题从算法到代码再到各种翻车点完整过一遍适合正在备考GESP三级或者想彻底搞懂日期计算类基础算法的同学。1. 这道日历题到底在考什么GESP三级的出题逻辑1.1 题目还原一个看起来很小、其实很综合的程序按照GESP三级常见的出题风格这道“日历制作”的题意大致是输入两个整数第一个是年份y第二个是月份m然后输出该月的日历。输出格式一般要求第一行是星期表头接下来每一行排7个日期1号之前用空格填满。有的题目要求星期天开头有的要求星期一开头具体看样例格式我后面给的代码按“Sun Mon Tue Wed Thu Fri Sat”也就是星期天到星期六排列并告诉你改成星期一起始版本要动哪里。你说这题难吗真不难。它没有复杂的数据结构没考递归甚至连排序都用不上。但它被选进三级的真题恰恰是因为它把很多基础知识点揉在了一起闰年判断、月份天数表、星期推算、格式化输出。任何一个环节出错最终打出来的日历就会错位、多空、少换行。很多同学一看到这类题就说“直接打印不就行了吗”结果一写就露馅先不说闰年光是搞清楚某一天到底是星期几就已经能让一半人卡住。1.2 考点拆解看似考日期实际上考的是这些底层能力我把这道题的考点拆成四块逐个说清楚。第一闰年判断。这是日期类题目的地基判断错了2月天数全错后面一切白搭。第二月份天数表。大月31天、小月30天、2月28或29天这东西你得有一种可靠的方式体现在代码里而不是写一大堆if分支。第三星期推算。日历的本质是一个无限循环的7天周期你需要从一个“确定知道星期几”的日期出发算出目标日期相对于它的偏移量。第四输出格式控制。日历题的判分对格式很敏感多一个空格、少一个换行都可能被当成错误答案。这四块能力合起来其实就是三级考试反复强调的“把现实问题转化成程序逻辑”的能力。日期和星期属于生活常识人人都懂但把它变成精确、可验证的代码就需要你同时具备数学建模和工程细节两方面的素养。2. 日历算法的地基闰年规则与日期天数累加2.1 闰年判断为什么是四个条件而不是一个日历题里闰年是绕不开的坎。这里有一个很多新手都会写错的条件我先把它完整写出来bool isLeap(int y) { return ((y % 4 0) (y % 100 ! 0)) || (y % 400 0); }一句话概括就是能被4整除但不能被100整除或者能被400整除。为什么“能被4整除”这个条件不够因为公历的置闰规则是为了修正回归年和历法年之间的误差。每四年加一天会加多所以整百年份不加闰除非它能被400整除。你说这规则反直觉吗确实有点反直觉但它是现实世界的规则程序必须遵守它。给你两组记住这条规则的测试数据1900年不是闰年2000年是闰年。我见过不少同学写闰年判断时只写y % 4 0测试数据给的是2000结果碰巧能过但输入1900就输出2月有29天直接翻车。所以我在给学生讲题时反复强调日期类题目只要涉及闰年一律单独抽一个isLeap函数出来方便单独测试也方便后面累加天数时反复调用。2.2 从“已知星期推算未知星期”的通用套路假设你已知2025年1月1日是星期三想算出2025年9月1日是星期几怎么做你只需要算两点之间的天数差然后对7取模。从1月1日到9月1日之间经过了1月的31天、2月的28天、3月的31天、4月的30天、5月的31天、6月的30天、7月的31天、8月的31天合计243天。243除以7余5星期三往后推5天就是星期一。写成公式就是目标星期 (起始星期 天数差 % 7) % 7这里面有个很重要的约定问题如果你的星期映射是0表示星期日、1表示星期一一直排到6表示星期六那这个公式天然兼容。只要起始星期也用同样的映射编号写清楚模7得到的余数就是偏移量。很多翻车案例都是因为有人用1到7表示星期结果星期天到底是7还是0没有统一算出来的日历整体错位。你可以把这个过程想象成一条无限长的传送带上面每隔7个格子循环标着“日一二三四五六”你要做的事情就是找到目标日期在传送带上的格子。天数差模7就是向后走的格子数。3. 两种主流解法纯模拟和公式直算的取舍3.1 方案A从参考日期逐日累加天数代码长但逻辑直白最直观的思路就是从参考日期开始一天一天或者一个月一个月地数到目标月份1号。对GESP三级的数据范围来说年份基本在一两百年内逐月累加完全够用。复杂度是O(y m)就算让你从公元1年累加到2100年也就几万次循环瞬时跑完。模拟的好处是每一步都符合直觉。你先累加参考年到目标年之间的完整年份天数再累加目标年份内从1月到m-1月的天数得到的数就是两个日期之间的天数差。代码哪里写错一眼就能看出来排错成本极低。我在考场上会优先选这种写法因为你不必背任何公式三级考试拼的不是“代码最短”而是“在有限时间里写对、写稳”。3.2 方案B直接套用基姆拉尔森公式优雅但容易背错如果你想少写循环可以直接用基姆拉尔森公式计算某一天是星期几。它的形式是W (d 2m 3(m1)/5 y y/4 - y/100 y/400 1) % 7其中d是日期m是月份y是年份。有一个关键前提如果月份是1月或2月要把年份减1月份变成13或14。W为0表示星期日1表示星期一以此类推。这个公式确实很优雅一行就能算出任意日期是星期几。但我对它的态度比较保守除非你把它背得滚瓜烂熟并且在考场上已经验证过否则谨慎使用。它的问题在于分母、取整符号很容易写错而且1月2月要做特殊处理这件事至少有一半人第一次写会漏。漏了之后你会得到一张2月到12月全对、1月和2月全错的日历这种bug非常隐蔽很难查。3.3 我在考场上会选哪种方案我给学生讲这块时一般是这样建议的如果题目没说只能用某种方法默认用方案A也就是“累加天数差模7”。理由很简单它和你对日历的理解是同一个模型不容易记错。公式类解法适合你在草稿纸上手算验证或者你明确知道自己记得很扎实。考试时间有限用五天可以保证的方法绝不用五五开的方法。这是我带学生参加各类编程等级考试多年总结出来的原则尤其适用于三级这种基础题量比较大的场次。4. 完整代码实现与逐段拆解4.1 第一版基于2025年1月1日星期三做参考点下面先给出一份基于参考日期的完整C实现。我设定2025年1月1日是星期三这个日期本身就是GESP2509这个考试时间附近的真实日期拿它当基准点非常自然。#include iostream #include iomanip using namespace std; bool isLeap(int y) { return ((y % 4 0) (y % 100 ! 0)) || (y % 400 0); } int daysInMonth(int y, int m) { int days[13] {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; if (m 2 isLeap(y)) return 29; return days[m]; } long long diffFrom2025Jan1(int y, int m) { long long sum 0; for (int i 2025; i y; i) { sum isLeap(i) ? 366 : 365; } for (int i 1; i m; i) { sum daysInMonth(y, i); } return sum; } int main() { int y, m; cin y m; long long totalDays diffFrom2025Jan1(y, m); int firstDay (3 totalDays % 7) % 7; // 参考点2025-01-01是周三对应3 cout Sun Mon Tue Wed Thu Fri Sat endl; for (int i 0; i firstDay; i) { cout ; } int dim daysInMonth(y, m); for (int d 1; d dim; d) { cout setw(3) d ; if ((firstDay d - 1) % 7 6) { cout endl; } } if ((firstDay dim - 1) % 7 ! 6) { cout endl; } return 0; }4.2 这段代码里的关键细节这段代码里有几个点必须讲透。第一星期映射。我用0表示星期日、1表示星期一一直到6表示星期六。参考点2025年1月1日是星期三所以它对应的数字是3。(3 totalDays % 7) % 7的含义是先把天数差按7取余得到偏移量然后从3这个位置开始偏移。第二换行条件。firstDay d - 1表示当前日期在整周中的位置取值范围是从0到6。当这个值等于6也就是“星期六”的位置时说明这一行已经排满7个日期需要换行。这种写法比“写一个计数器每加到7就换行”更贴近日历网格的本质也更好理解。第三循环结束后的补换行。如果这个月的最后一天不是星期六循环内的换行就不会触发最后一行会没有换行符。很多同学在这里漏掉最后的endl导致输出内容的最后一行直接粘在命令行提示符后面提交判题时也因为输出格式不完整被扣分。这个坑非常经典我几乎每次讲这道题都要强调一遍。4.3 通用版本从公元1年1月1日开始累加上面这个版本有一个明显的局限性如果输入年份早于2025年循环一次都不会执行天数差直接算成0日历就全错了。虽然GESP2509这套题大概率不会让你输入2025年以前的年份但你自己练习时最好用更通用的版本。通用的做法是选定公元1年1月1日为星期一作为基准然后从第1年开始累加到y-1年的完整年份天数。这样无论输入什么年份都不怕代码改动量很小我直接把完整代码挂在这里平时练习和提交都建议用这个版本。#include iostream #include iomanip using namespace std; bool isLeap(int y) { return ((y % 4 0) (y % 100 ! 0)) || (y % 400 0); } int daysInMonth(int y, int m) { int days[13] {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; if (m 2 isLeap(y)) return 29; return days[m]; } long long totalDaysBefore(int y, int m) { long long sum 0; for (int i 1; i y; i) { sum isLeap(i) ? 366 : 365; } for (int i 1; i m; i) { sum daysInMonth(y, i); } return sum; } int main() { int y, m; while (cin y m) { long long days totalDaysBefore(y, m); int firstDay (1 days % 7) % 7; // 公元1年1月1日是星期一所以在星期映射里对应1 cout Sun Mon Tue Wed Thu Fri Sat endl; for (int i 0; i firstDay; i) { cout ; } int dim daysInMonth(y, m); for (int d 1; d dim; d) { cout setw(3) d ; if ((firstDay d - 1) % 7 6) { cout endl; } } if ((firstDay dim - 1) % 7 ! 6) { cout endl; } } return 0; }这套代码的好处是支持多组输入直到文件结束这也是GESP里常见的输入方式。while (cin y m)会把所有测试数据都跑完而不是只算一组就退出。如果你遇到的是单组输入把这个循环去掉改成直接读取一次就行。5. 实测中最容易翻车的四个坑5.1 星期编号对不齐0和7的哲学问题日历题最常见的翻车点就是星期编号不统一。有人习惯把星期日写成7还有人把星期一写成0这些都不是不行但你必须自洽。我的建议是统一用0到60表示星期日。这样跟取模运算天然匹配也跟很多现成公式的约定一致。如果你非要用1到7那么计算firstDay时还得特判“模7等于0就变成7”徒增麻烦。而且这种1到7的编号在查错时特别容易让人头晕一排日历出来看着哪里都对又哪里都不对。5.2 闰年条件写错优先级也能坑人闰年条件写错的方式不止“只写y%40”这一种。有些人知道需要加上y%100和y%400但括号加错了位置。C里的优先级高于||所以(y % 4 0 y % 100 ! 0) || (y % 400 0)和y % 4 0 (y % 100 ! 0 || y % 400 0)完全是两个东西。后者实际上等价于y % 4 0因为y%400为0时y%4一定也为0这个条件并不能排除1900年。我见过不少代码就这么写的测试普通年份全对一到1900就错。所以建议写闰年判断时把括号加得明明白白不要依赖优先级让读代码的人和自己都省心。5.3 输出格式过不了样例空格和换行的细节日历题的样例输出对格式要求很严格。每个日期占几个字符、第一行表头中间是几个空格、日期之间的空格数量都必须和样例完全一致。我见过代码逻辑完全正确最后因为多了个空格、少了个换行被判错的情况。这里分享一个实测技巧在本地跑完程序后把输出重定向到文本文件然后用支持“显示空格和制表符”的编辑器逐行检查对齐。比如我的代码里每个日期区域占4个字符setw(3) d 正好让数字占3位靠右对齐再跟一个空格所以表头每个单词后面也跟一个空格前导空格一次输出4个。这样整张表才能列对齐。如果你把setw(3)换成setw(2)前导空格的数量也要改成3个这个联动关系忘了输出就会歪。5.4 基准日期选错导致全局偏移很多同学喜欢拿题目样例里的日期当参考点这本身没问题但有一个隐患如果你把某个日期是星期几记错了整个日历会全部错位而且错得非常整齐单看某一行很难发现。真正稳妥的做法是选一个你百分百确定的日期当基准。对我来说2025年1月1日是星期三2025年9月1日是星期一这两个日期我反复验证过可以直接当测试用例。代码写完后先输入这两个月份检查如果输出和真实日历一致再输入其他数据这样能把基准错误的概率降到最低。6. 考场实战策略10分钟写出满分代码的检查清单6.1 先手算再写代码我给学生强调过一个习惯写日期类题目动手敲键盘之前先在草稿纸上把目标月份的日历手算一遍。举个例子输入2025 9。你已知2025年9月1日是星期一9月有30天。那么9月日历的第一行就从星期一的位置开始排到星期六共6个日期第二行从星期日开始完整排7天一共排4行最后第5行排2天。把这个“标准答案”写在草稿纸上代码写完后输入同样的数据去比对。如果手算和程序输出完全一致那其他输入大概率也没问题。这个习惯在考场上特别有用。单测通过带来的信心能让你敢继续往下做别的题而不是卡在这一题反复怀疑自己。6.2 我常用的三组测试数据这里给你我平时讲这道题必用的三组测试数据它们覆盖了平月、闰月、普通月份和跨年累加第一组2025 1。2025年1月1日是星期三31天第一行应该有3个前导空位。第二组2025 9。2025年9月1日是星期一30天。第三组2024 2。2024年是闰年2月有29天而且2024年2月1日是星期四。这三组数据如果都通过这道题基本就能拿满分了。尤其是2024 2这一组它同时检验了年份累加、闰年和2月天数三个关键点比随便拿个月份测一下有效得多。6.3 做题顺序与时间分配建议GESP三级考试的题量并不少日历制作属于典型的“中等耗时题”不会简单到送分也不至于卡死人。我的建议是把它放在考试中段做先把更稳的、纯模拟类的题拿下来再回头啃这种需要点推理的题目。如果你在调试过程中发现代码对不上样例千万不要急着加特判。比如“如果年份等于2025就输出什么”这种写法只能让你过眼前的样例过不了隐藏测试点而且这种代码一眼就能被发现扣分很重。正确做法是回到算法本身用一个最简单的月份反复推演找到那个让星期错位的逻辑漏洞。我个人的体会是这种日历题最拉分的地方不是在算法层面上而是在细节上。闰年写没写全、换行补没补、对齐对不对、取模边界管没管每一个小细节都可能是一个测试点。只要把上面说的四个坑都堵上10分钟内写完并一次跑对完全做得到。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑