
备考 GESP C 三级的朋友看到真题解析里出现“日历制作”四个字第一反应往往是这能叫编程题打印一个日历表还不简单。真坐到电脑前动手写才发现到处都是细节——这个月有多少天、2 月到底多不多一天、1 号落在星期几、前面空几个格、一行到第 7 个日期要不要换行、最后一个日期后面还要不要补一个换行。每一个细节都能让人卡上十几分钟。这篇我把这道 2025 年 9 月认证的日历制作题从头到尾拆干净给出完整的 C 实现讲清楚三种推算星期几的思路、代码怎么分层、哪些测试数据值得提前跑以及考生最容易踩的坑。适合正在备考三级、想搞懂这类模拟输出题的朋友也适合考前拿来做快速自查。1. 题目到底在考什么先把题面拆干净1.1 题面通常长什么样这类日历题属于典型的“模拟输出”题题面一般非常简短输入一行两个整数比如2025 9分别代表年份和月份要求程序输出该月日历。第一行一般是星期日到星期六的星期表头日期按星期对齐成一行 7 列每个日期占固定宽度1 号之前的位置用空格补齐。不同考场的题面在细节上会有差异有的表头要求英文缩写Sun Mon Tue Wed Thu Fri Sat有的要求中文“日 一 二 三 四 五 六”有的要求每个日期占 4 个字符宽度且右对齐有的要求占 3 个字符宽度、日期之间空一格有的从周日开头有的从周一开始。这些细节看起来很琐碎但恰恰是判分时最容易丢分的地方。格式要求只要错一点哪怕日期全对也可能被扣掉不少分。1.2 四个隐藏考点这道题表面上没有考任何高深算法实际上把三级最常用的几项基本功全部串起来了。我把它拆成四个隐藏考点。考点常见陷阱闰年判断只判断y % 4 0漏掉世纪年必须能被 400 整除每月天数2 月固定按 28 天处理闰年忘记改成 29 天星期推算1 号到底排在第几列偏移量算错导致整行错位格式化输出对齐宽度不一致、换行位置不对、末尾缺换行先说闰年判断。规则是“四年一闰百年不闰四百年再闰”写成 C 逻辑表达式就是(y % 4 0 y % 100 ! 0) || (y % 400 0)。很多同学只写了前半部分结果一遇到 1900、2100 这种整百年就出错。这个点几乎是必考的边界因为题目给的年份范围往往故意包含 1900 和 2000。再说每月天数。平年的 12 个月天数可以用数组存下来{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}下标从 1 开始用0 号位置空着。这样做的好处是代码里不用写一堆if判断几月是 30 天、几月是 31 天只需要在 2 月时判断一次闰年即可。星期推算和格式化输出是这道题的核心也是我后面要重点展开的两块。整体来看这道题考的就是能不能把“小事”做细函数拆得清不清楚、边界想得全不全、输出格式控制得准不准。这也就是为什么它被放在三级。2. 星期推算的三条路线选对思路再写代码日历题的核心问题只有一个这个月的 1 号是星期几搞清楚 1 号的位置后面所有日期都能按顺序排下去。推算星期几常见有三条路线我一个个说。2.1 路线一基准日累加法推荐考场使用思路特别直白找一个已知星期几的日期作为基准比如1900-01-01是星期一。从这一天开始累加天数一直加到目标日期用总天数对 7 取模就能算出目标日期是星期几。假设我们定义一个规则0代表星期一1代表星期二依此类推6代表星期日。那么目标日期y年m月 1 日距离基准日经过的总天数为totaltotal % 7就是它对应的星期偏移。因为基准日1900-01-01是星期一所以余数为 0 时目标也是星期一余数为 1 时是星期二。计算total需要两部分从 1900 年到y-1年年底的天数加上y年从 1 月到m-1月的天数。第一部分用循环累加每年的天数闰年加 366平年加 365第二部分用前面说的monthDays(y, i)累加即可。这个方法的优点是完全不依赖任何公式逻辑从头到尾都能自己推出来考场上一旦思路卡住回到这个模型里重新算一遍就能恢复。缺点是年份跨度大时循环次数多一些但对三级的数据范围来说完全不是问题。2.2 路线二公式法代码短但容易记错第二种路线是套用现成的数学公式。最常见的基姆拉尔森公式可以写成下面这个函数int dayOfWeek(int y, int m, int d) { if (m 3) { m 12; y--; } return (d 2 * m 3 * (m 1) / 5 y y / 4 - y / 100 y / 400) % 7; }这个版本算出来的结果是0 表示星期一1 表示星期二依此类推6 表示星期日。注意这里对 1 月和 2 月做了特殊处理把它们看成上一年的 13 月和 14 月这是公式成立的前提。坦白说公式法并不适合在考场上现用。原因有两点第一公式版本非常多有的版本结果 0 代表星期日有的 0 代表星期一差一个1或-1就全错第二背公式容易记错对 1 月、2 月的特殊处理。如果你真的想用公式我强烈建议先用2025-09-01这种自己知道答案的日期验证一遍确认这个公式在你的编译器上返回的是你期望的值再往题目里套。2.3 三条路线怎么选我把三条路线放在一起对比一下。方案代码长度出错风险适用场景基准日累加法稍长但结构清晰低逻辑可控考场首选适合大多数学生基姆拉尔森公式很短高记错版本直接全错平时熟悉公式的人可以使用调用系统时间函数最短依赖环境可控性差不推荐在认证环境中使用我的建议很明确三级阶段老老实实用基准日累加法。原因很简单考试拼的不是代码最短而是确定性。公式法虽然写起来爽但一旦某个版本细节记错调试起来反而更浪费时间。基准日累加法的代码多几行但每一步都能用笔算验证心里踏实。3. 完整代码拆解照着写就能拿分的步骤下面直接给出一份完整的参考实现。我按“工具函数 主流程 格式化输出”三层来组织代码这样你在考场上拆步骤也方便。#include iostream #include iomanip using namespace std; // 判断闰年 bool isLeap(int y) { return (y % 4 0 y % 100 ! 0) || (y % 400 0); } // 返回 y 年 m 月的天数 int monthDays(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]; } // 从 1900-01-01星期一到 y 年 m 月 1 日经过的天数 int totalDays(int y, int m) { int sum 0; for (int i 1900; i y; i) { sum isLeap(i) ? 366 : 365; } for (int i 1; i m; i) { sum monthDays(y, i); } return sum; } int main() { int y, m; cin y m; // 0 表示星期一, 1 表示星期二 ... 6 表示星期日 int w totalDays(y, m) % 7; // 转成“周日开头”的下标: 星期日 0, 星期一 1 ... 星期六 6 int first (w 1) % 7; string week[7] {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; for (int i 0; i 7; i) { cout setw(4) week[i]; } cout endl; // 1 号之前补 first 个空格子, 每个格子宽度 4 for (int i 0; i first; i) { cout ; } int mday monthDays(y, m); for (int d 1; d mday; d) { cout setw(4) d; // 每输出 7 个日期换一行 if ((first d) % 7 0) { cout endl; } } cout endl; return 0; }3.1 工具函数闰年判断与每月天数代码最上面两个函数是整道题的基石。isLeap不用多说就是闰年判断的标准写法。monthDays里用了数组days[13]下标 0 不用所以才能做到“下标就是月份”。这样写比连续写十几个if要清晰得多也更容易检查错漏。这里有一个小细节值得注意2 月的处理必须放在数组初值的后面。也就是说先让数组给 2 月赋值为 28再单独判断如果闰年就返回 29。有些同学喜欢把闰年 29 直接塞进数组初始化结果数组里非 2 月的天数也被误改排错的时候很难发现。3.2 主流程从输入到整月输出totalDays是整个算法的发动机。它先用一个循环把从 1900 年到y-1年的整年天数加完再用第二个循环把y年前面几个月的天数加完。得到的sum就是从基准日到本月 1 号的总天数。int w totalDays(y, m) % 7;这行算出的是“基准日偏移视角”下的星期0 是星期一。但是国内常见日历排版习惯把星期日放在第一列所以要再转一次int first (w 1) % 7;。这个转换的原理是当 w 6星期日时first 应该等于 0当 w 0星期一时first 应该等于 1。用(w 1) % 7正好满足。如果题面要求从周一开始比如表头是“一 二 三 四 五 六 日”那first就直接等于w不需要加 1 再取模。这个映射关系建议你在脑子里过一遍不要死记公式理解了就永远忘不掉。3.3 格式化输出对齐和换行的坑格式化输出是日历题最容易被扣分的地方。代码里用了iomanip头的setw(4)它的作用是让下一个输出内容至少占 4 个字符宽度默认右对齐不足部分在左边补空格。这样每个日期都固定占 4 个格子列就对齐了。表头部分也是cout setw(4) week[i];这样Sun输出的时候左边会补 1 个空格整体占 4 个字符。日期数字 1 输出时左边会补 3 个空格。虽然视觉上表头文字和日期数字看起来不在同一列的正中间但它们在同一个右边界上对齐这是大多数标准输出题目的通用做法。补空格的逻辑是1 号前面有first个星期列每个列宽 4所以直接连续输出first次 4 个空格。这里最容易犯的错误是补first个空格而不是first个格子的宽度。换行条件是if ((first d) % 7 0)。这个式子第一次看有点绕我解释一下1 号所在列的下标是first所以 1 号在日历行的位置等于first (d - 1)。当这个位置等于 6也就是这一行的第 7 列时说明当前行已经满了应该换行。first (d - 1) 6等价于first d 7也就是(first d) % 7 0。这里不要自作聪明改成d % 7 0否则只要 1 号不在第一列换行就全是错的。整个主循环结束后我额外加了一个cout endl;。很多人觉得这个换行多余但它其实很有必要如果这个月的最后一天恰好落在某一行的末尾循环体内的换行已经输出过了但如果最后一天落在行中间比如只输出了 3 个日期那最后就需要一个换行来结束整行否则输出文件最后没有换行某些严格的判题系统会判格式错误。4. 测试与避坑把考场上的坑提前踩完代码写完不是终点用测试数据验证才是关键。这一节我给出三个值得用的测试用例再列一份高频错误速查表。4.1 三个验证用例跑通就放心第一个用例直接就用题目里的2025 9。2025 年 9 月 1 日恰好在题目对应的时间里是星期一9 月有 30 天。如果你的程序输出第一行是Sun Mon Tue Wed Thu Fri Sat第二行前面有 1 个空格位然后1出现在 Monday 这一列说明表头和星期推算基本正确。第二个用例用2024 2这是闰年测试的经典样本。2024 年 2 月有 29 天且 2 月 1 日是星期四。输出时第一行应该是Sun Mon Tue Wed Thu之后才开始放 1也就是前面补 4 个列。这样既能验证闰年 2 月天数又能验证跨列对齐。第三个用例用1900 1这是基准日所在月份。1900 年 1 月 1 日就是星期一所以 1 号应该在 Monday 列。如果你连这个用例都输出不对说明totalDays或first的映射逻辑有问题应该回头检查而不是继续往下调。我每次做这类题都会拿这三个用例当“三连测”基准边界、闰年边界、普通月份。三个都跑对了剩下的格式问题基本肉眼就能看出来。4.2 高频错误速查表我把平时答疑时遇到最多的错误整理成了表格你可以直接对照检查。症状可能原因处理方法2 月一直输出 28 天monthDays里忘记调用isLeap检查 2 月的分支是否返回 291 号顶格出现在第一列first映射错误用1900 1验证1 号应在 Mon 列每行数量错乱不是 7 个换行条件写成了d % 7 0改成(first d) % 7 0数字列不对齐setw宽度不统一所有列统一用setw(4)最后一行没有换行循环结束后缺endl在循环结束后补一个换行1900 年判断错误闰年判断漏了世纪条件把闰年判断写成完整表达式这些错误有一个共性单独看每一行代码都找不出毛病但组合起来就崩。所以我会建议你在写完代码后特意把第 1 行、第 29 行、最后一行都打印出来肉眼检查一遍比反复看逻辑高效得多。4.3 几种常见的变题玩法日历题在真实考试里不一定都是“输入年月输出月历”这种最简单的形式。我见过几种常见变题这里提前说下思路。第一种变题是输入一个年份输出全年 12 个月。这个只需要在外层加一个月份循环每个月输出结束后空一行。注意每个月都要重新计算 1 号的first不要共用上个月的结果。第二种变题是把表头改成从周一开始或者只输出“一二三四五六日”。这时候只要改week数组的内容以及把first (w 1) % 7换成first w其他代码几乎不用动。考场上遇到这种变题不用慌核心算法没变。第三种变题是输入一个日期输出它是星期几。这其实就是把totalDays再加当月的d-1天然后取模。相比日历输出少了格式对齐的部分反而更简单但考点完全一样。第四种变题比较刁钻要求把上个月的最后几天也显示在日历第一行用来补全空位。这就需要知道上个月的天数以及上个月 1 号的星期再往前推。思路还是基准日累加法只是多算一步但坑在于上一年 12 月或者跨年时的月份边界处理。无论变题怎么出底层都是“闰年 每月天数 星期偏移 格式控制”这四件套。把这四件套练熟变题就只是在外面包一层壳。5. 备考建议日历题背后的通用打法5.1 先把模拟题吃透再做真题我有一个很深的体会这类模拟输出题看懂了和会写了之间隔着一次完整的亲手实现。之前有个 A 同学平时看日历题解析觉得“就这”一到考场上自己写光是补空格就调了二十分钟。原因就是没亲手练过。我的建议是分三步练。第一步看完这篇文章后把代码抄一遍跑通样例。第二步合上文章自己在空白编辑器里重新写一遍写到卡住的地方标记下来重点复习。第三步给自己限时 15 分钟模拟考场环境把这道题完整写出来并跑通三个测试用例。三步走完这题才算真正吃透。日历题是一个特别好的“函数拆分训练题”。它的主流程很短难的是把isLeap、monthDays、totalDays这些小函数写对。你在备考三级的时候多练这种需要拆函数的模拟题对后面处理字符串、结构体相关的题目也有帮助。5.2 时间分配与自检步骤考场上做这道题我建议按下面这个节奏来。先花 2 分钟读题确认三个关键信息表头从哪天开始、每个日期占多宽、有没有特殊输出要求。然后花 5 分钟把isLeap和monthDays这两个工具函数写完这是保底分。接着花 10 到 15 分钟写主流程和格式化输出。最后留 3 分钟跑测试用例和检查格式。自检的时候我个人比较推荐一个土办法拿手机日历或系统日历对答案。随便挑一个你熟悉的月份比如当前月份把程序输出和真实日历一行一行对比。只要列位置能对上说明星期推算是对的再随便挑一个闰年 2 月比如 2024 年 2 月看看有没有 29 号。这个方法我在备考阶段一直在用考前也靠它快速找回手感。最后再分享一个细节如果你的程序输出2025 9时第一行空格和日期列肉眼看起来“有点歪”不要急着改对齐方式先用系统日历核对一下 1 号到底落在哪一列。很多时候代码逻辑是对的只是setw右对齐导致数字与文字给人的视觉感受不同。日期题最怕的不是不会做而是瞎调格式越调越乱。把逻辑核心稳住格式按照题面要求来这道题就能稳稳拿分。