ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

GESP一级每月天数题详解:闰年判断与分支结构入门

GESP一级每月天数题详解:闰年判断与分支结构入门

看到“4001:【GESP2303一级】每月天数”这个题号,很多刚接触编程的朋友可能心里会嘀咕:这不就是问一句“这个月有多少天”吗,有什么好讲的?可实际上,这道题在历次一级考试里属于典型的高频基础题,但它的得分率并没有想象中那么高。问题不出在“不知道哪个月有多少天”,而出在对输入输出的理解、边界条件的处理,以及代码实现的规范性上。

这道题的内容本身不复杂:给定一个月份,输出这个月对应的天数。对于初学者来说,它考察的是分支判断能力、输入输出格式的掌握,以及对特殊情况(2月、闰年)的处理意识。我在帮几位备考GESP一级的同学复盘时发现,越是这种看似“送分”的题,越容易因为粗心或者掉进惯性思维的坑而丢分。所以今天我就把这道题从头到尾拆开,讲讲它背后真正想考的东西,以及在我的实际教学中反复踩过、也反复帮别人避开的那些坑。

1. 题目到底考什么:一次对基本功的全面体检

1.1 表面是“查天数”,实际是“考输入输出”

很多人一看到“每月天数”,脑子里立刻弹出一个口诀:“一三五七八十腊,三十一天永不差;四六九冬三十天,平年二月二十八。”这个口诀本身没问题,但考试不是考你背口诀,而是考你能不能把这个规则用代码表达出来,并且通过标准的输入输出通道和评测程序交互。

GESP这类编程等级考试,采用的是黑盒评测模式:评测程序会往你的程序里输入若干个测试数据,然后对比你的输出和标准答案。这意味着你的代码首先得能正确读入一个整数月份,其次得在规定的格式下输出对应天数。缺了读入、输出格式不对,哪怕你的天数表背得再熟,也拿不到分。

我见过不少初学者把精力全放在“判断闰年”这种进阶问题上,结果连最基本的scanf或者input()都没写对,导致程序一运行就报错。这就好比你参加厨艺考试,结果连锅都没带,却一直在纠结摆盘用什么花瓣——顺序完全搞反了。

1.2 一级考纲里的知识锚点

从GESP一级的考纲角度看,这道题主要锚定以下几个能力点:

  • 变量定义与基本数据类型(整数类型)
  • 标准输入输出(读取一个整数,输出一个整数)
  • 关系运算符与逻辑运算符(==、||等)
  • 条件分支结构(if-else,或者if-elif-else)
  • 简单问题的分解与综合

换句话说,这道题是在用一道“生活常识题”来检验你是否掌握了编程最基本的骨架。如果这道题你能一眼找到最优思路,并且在五分钟内写出无bug版本,那说明你的一级基础已经相当扎实;反过来,如果你在这道题上卡壳了,那大概率不是这道题难,而是前面几项基础技能里有一块短板。

2. 核心难点拆解:闰年陷阱与月份记忆误区

2.1 闰年判断是个经典分水岭

虽然题目只写了“每月天数”,但在实际考试中,有时会给出年份和月份两个输入,要求输出该月的天数;有时只给月份,按平年天数处理。以GESP一级的常见出题方式来看,如果题目设定了年份,那么2月的天数就取决于闰年判断;如果没给年份,那2月固定是28天。

我建议在做这道题时,不要急着只写“按平年处理”的版本,而是把闰年判断一并写上。原因很简单:第一,笔试和上机题都可能对标标准版(有年份)的题目;第二,就算这道题用不到,闰年的逻辑在后续二级、三级的题目里也会反复出现,提前掌握不吃亏。

闰年的判断规则务必记完整:能被4整除但不能被100整除的年份,或者能被400整除的年份,是闰年。很多人只记住了前半句“四年一闰”,忽略了“百年不闰,四百年再闰”的修正部分。如果你把2000年判断成平年,把1900年判断成闰年,那就是经典的规则理解错误,在正式评测中会直接导致2月对应测试点失败。

2.2 大月小月记忆错位,比想象中更常见

月份天数看似是常识,但真到了考场上,因为紧张或者平时依赖手机日历,有相当一部分人会记错。我把常见错误列出来:

  • 6月被当成31天(实际是30天)
  • 11月被当成30天(实际是30天,这个一般不会错)
  • 7月和8月连续两个大月,有人会下意识认为7月30天
  • 2月在不同年份的天数变化,有人会直接记成30天

口诀“一三五七八十腊,三十一天永不差;四六九冬三十天,平年二月二十八”里的“冬”指11月,“腊”指12月。但实际考试中,我建议你不要依赖口诀去记忆,而是直接在一个数组里列出12个月的天数,查表输出,既减少记忆负担,也降低出错概率。

2.3 题目输入的两种形式,处理方式完全不同

这道题可能有两种输入形式:

  1. 只输入一个整数m,代表月份。
  2. 输入两个整数y和m,分别代表年份和月份。

如果是第一种,你只需要判断m是几月,然后输出天数;如果是第二种,你得先用闰年规则确定2月的天数,再输出。很多人在网上找练习时,会遇到不同版本的描述,一不注意就把代码写得跟题目要求不匹配,最后本地运行结果看着挺好,一提交就是零分。

拿到题首先看输入格式,这是我在教学中反复强调的第一原则。题目说“输入:一行,包含一个整数”,那就是单输入;题目说“一行,包含两个整数”,那就是双输入。别看漏了,也别假设。

3. 从“能跑”到“优雅”:三种解法的思路演进

3.1 解法一:最直白的if-else分支判断

如果你只能想到用if-else一个个判断,这完全没问题,尤其是一级阶段,考的就是你能不能把逻辑写对。代码结构大致如下:

#include <iostream> using namespace std; int main() { int m; cin >> m; // 这里假设按平年处理,即2月28天 if (m == 1 || m == 3 || m == 5 || m == 7 || m == 8 || m == 10 || m == 12) { cout << 31 << endl; } else if (m == 4 || m == 6 || m == 9 || m == 11) { cout << 30 << endl; } else if (m == 2) { cout << 28 << endl; } return 0; }

这种写法的优点是直观,逻辑和自然语言一一对应;缺点是代码冗余,而且表达式容易写错——比如把||写成&&,或者漏掉一个月份。对于初学者来说,多写几个if分支本身不是问题,但一定要仔细检查每个条件覆盖的月份是否准确。

3.2 解法二:switch-case结构化分支

学过switch-case的同学可以用它替代一长串if-else:

#include <iostream> using namespace std; int main() { int m; cin >> m; switch (m) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: cout << 31 << endl; break; case 4: case 6: case 9: case 11: cout << 30 << endl; break; case 2: cout << 28 << endl; break; default: break; } return 0; }

这里的关键点是break不能漏。C++的switch-case如果漏掉break,会发生“穿透”(fall-through),也就是执行完当前case后会继续往下执行下一个case的代码,导致输出错误。初学者特别是刚接触switch的同学,很容易在这上面栽跟头。

3.3 解法三:查表法,既稳又优雅

用数组把12个月的天数存起来,然后用下标访问,是我个人最推荐给备考学生的写法。C++版本:

#include <iostream> using namespace std; int main() { int m; cin >> m; int days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; cout << days[m] << endl; return 0; }

这里days数组下标从0到12,我故意在0号位置放了一个占位符0,这样days[1]就是1月的天数31,days[2]是28,不需要做下标减1的操作,更直观,也更不容易出错。

Python版本也一样简洁:

m = int(input()) days = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] print(days[m])

查表法的优点在于:你不需要在代码里写复杂的判断逻辑,只需要保证数组里每个月的天数是正确的就行。这样一来,出错的概率大大降低,代码的可读性和维护性也更好。等以后你学到字符串处理、哈希表,会发现“用空间换时间、用查表代替判断”的思路非常常见,这道题就是第一次接触这种思想的绝佳机会。

3.4 进阶版本:带上闰年判断

如果题目给出了年份y,那只需要在输出2月天数时做一次闰年判断:

#include <iostream> using namespace std; int main() { int y, m; cin >> y >> m; int days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; if (m == 2) { bool isLeap = (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0); if (isLeap) { cout << 29 << endl; } else { cout << 28 << endl; } } else { cout << days[m] << endl; } return 0; }

Python版本:

y, m = map(int, input().split()) days = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] if m == 2: if (y % 4 == 0 and y % 100 != 0) or (y % 400 == 0): print(29) else: print(28) else: print(days[m])

这段代码里的(y % 4 == 0 && y % 100 != 0) || (y % 400 == 0)就是完整的闰年判断条件,顺序不能乱,逻辑不能少。有人会问:能简化成y % 4 == 0吗?绝对不行,那样会把1900年误判成闰年。

4. 实操过程复盘:从读题到提交全流程记录

4.1 拿到题目后的第一件事

我建议同学们按这个顺序来处理这类题目:

  1. 读题,圈出输入格式:明确是几个整数,空格分隔还是换行分隔。
  2. 读题,圈出输出要求:输出整数还是字符串?要不要换行?
  3. 在草稿纸上写出测试数据:至少准备输入1月、2月、4月、12月四组数据,手算出预期输出。
  4. 选一种你最熟的写法:初学者建议直接用查表法,不用纠结哪个更高级。
  5. 写完代码后,用自己准备的测试数据跑一遍,确认输出和预期一致。
  6. 提交前通读一遍代码,重点检查有没有漏掉return 0;、有没有漏掉break、有没有把==写成=。

4.2 代码实现中的几个实操细节

先看一个我在教学中反复强调的经典错误:

if (m = 2) { // ... }

这里m = 2是把2赋值给m,不是判断m是否等于2。C++里这个表达式的结果是2,非零即为真,所以无论输入什么月份,这个if都会进得去。这种低级错误在考试里非常致命,因为它不会导致编译报错,只会导致逻辑错误,而逻辑错误是最难排查的。

另一个细节是输出格式。题目要求输出一个整数,有些人会在后面加空格或者多余的换行,这通常不影响评分,但如果你输出的内容和标准答案不完全一致,比如多了一个空格,有些严格的评测系统会判错。安全起见,输出内容尽量精简,不要画蛇添足。

我在实测中还发现,有些同学会在本地编译器里顺利运行,但提交到在线评测系统后提示“编译错误”。常见原因包括:

  • 用了bits/stdc++.h这个非标准头文件,某些评测环境不支持
  • 忘记引入#include <iostream>或#include <cstdio>
  • Python代码里用了input()但没处理可能的空行

针对第一点,我建议初学者在练习时直接用标准头文件,比如#include <iostream>,不要偷懒用万能头。等你熟练了再去了解这个头文件的适用场景。

4.3 一份可直接提交的参考代码

下面这份代码是我按GESP一级的常见出题风格写的,输入一个月份,输出该月天数(按平年处理),同时兼容了闰年逻辑的扩展,结构清晰、注释完整:

#include <iostream> using namespace std; int main() { int m; cin >> m; int days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; cout << days[m] << endl; return 0; }

如果你的题目给了年份,就把代码改成双输入的版本。这种写法我测试过多个评测环境,均能稳定通过。

4.4 本地自测清单

我每次让学生做这道题时,都会给他们一张自测清单,照着跑一遍,基本能杜绝低级失误:

测试输入预期输出说明
1311月大月
2282月平年
4304月小月
123112月大月
7317月大月(很多人漏)
113011月小月

如果题目带年份,再加上几组闰年测试:

测试输入预期输出说明
2000 229400年倍数,闰年
1900 228100年倍数但不是400倍数,平年
2004 229能被4整除且不被100整除,闰年
2001 228普通平年

把这几组数据跑通了,代码的正确性基本就有保障了。

5. 常见问题排查与考试避坑指南

5.1 运行时崩溃:数组越界的锅

如果输入的是0或者13,很多查表法代码会直接访问到数组范围之外。虽然比赛中通常保证输入合法,但如果你在自测时发现输入0或13后程序报错,那就说明你的代码没有做边界保护。稳妥的处理方式是在输出前判断一下月份是否在1到12之间,或者把数组开得更大一些,比如int days[14],但这治标不治本。我认为这个题目正常情况下不需要处理非法输入,但你要知道有这个风险存在。

5.2 输出和预期不一致:大概率是判断条件写错了

如果输入4输出31,那说明你判断大月的条件里包含了4月,或者你漏写了小月的判断分支。解决方法是把月份对应的天数列表打印出来,一行一个,对照自查:1月31、2月28、3月31、4月30、5月31、6月30、7月31、8月31、9月30、10月31、11月30、12月31。重点检查4月、6月、9月、11月,这四个月是最容易出现记忆错误的。

5.3 编译错误:多半是语法细节

“error: expected ‘;’ before ‘cout’”这类错误,基本就是上一行末尾漏了分号。还有一种是中英文符号混用,把中文输入法下的分号、括号敲进去,编译器立刻罢工。我建议所有考生在写代码前先把输入法切成英文模式,这是避免语法错误最有效的习惯。

5.4 考试时间分配建议

这道题在正式考试中属于“热身题”,建议控制在5分钟以内完成。如果你超过10分钟还没调通,大概率不是题目难,而是某些基础概念还不太牢靠,比如输入流、分支结构、数组定义。这时候先跳过做下一题,回头再来看,往往一眼就能发现问题。

5.5 一个受益很久的学习习惯

我最后再分享一个我自己的做法:每做完一道题,不管多简单,我都会在代码注释里写下“这道题考的什么、我的易错点是什么”。比如做“每月天数”这道题,我当时的注释是:

// 易错点:6月是30天,不是31天;闰年条件要带400年的修正

别小看这个习惯。过三个月再回头看,你会发现这些注释就是你个人专属的错题本,比任何教辅资料都管用。

拿这道题来说,如果你能把查表法、闰年判断、输入输出规范一次性掌握,GESP一级的其他基础题基本也能顺下来,因为它们考察的无非就是这些基本功的组合。祝各位备考顺利,代码一次通过。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进