xtu oj 1055这道题,是我在湘潭大学OJ(Online Judge在线评测系统)上刷C语言基础题时印象比较深的一道switch语句练习题。代码量不大,但对switch的几个关键细节——case穿透、break位置、default兜底逻辑——要求得很细,理解不到位的话,交上去大概率要吃几个WA(Wrong Answer)。如果你是刚开始用OJ刷题的新手,或者对switch语句只停留在"见过没用过"的阶段,这篇博文从题目思路到完整代码都给你拆明白,顺便把OJ提交的各种坑也一并排掉。
1. 先把switch语句的核心机制搞透,再做题才不会懵
1.1 switch和if-else到底差在哪
很多同学一开始都会问:既然有了if-else,为什么还要学switch?这个问题问得很关键,因为它直接决定了你什么时候该用switch。
if-else的底层逻辑是“逐个判断”,就像安检口排队,一个人一个人地查证件,一个一个来。如果条件分支很多,代码会长得吓人,可读性也会直线下降。switch则完全不一样,它的逻辑是“直接定位”——系统先取出switch后面的表达式的值,拿这个值和各个case后面的常量做匹配,一旦找到相等的那个入口,就直接跳进去执行。有点像你去火车站,拿着票直接在对应的检票口进去,不用挨个问每个窗口能不能走。
我用一道简单的题目举例子:输入1到5,输出对应的中文星期。用if写就是五六个else if,每个分支重复结构相似,代码项很长;用switch写,整个逻辑变成一张干净的“映射表”,哪一种情况对应什么输出,一眼看明白。这正是switch的核心优势:适合处理离散的、有限取值的情况。
1.2 语法结构:表达式、case标签、break和default
一个标准的switch语句长下面这个样子:
switch (表达式) { case 常量表达式1: 语句1; break; case 常量表达式2: 语句2; break; default: 默认语句; }这里有四个容易踩坑的点,分开说。
第一,switch后面括号里的表达式必须是整型或者字符型。C语言里char本质上也是整数(字符对应ASCII码值),所以能放进去。浮点数、字符串类型的值都不能直接用,这是编译阶段就会报错的硬规则。
第二,case后面跟的必须是常量表达式。什么叫常量?就是值在编译时已经确定的量,比如数字1、字符'A'。如果你写成case x:,编译器直接不认识,因为x是变量,值要到运行的时候才知道,这是典型的编译错误。我见过不少同学把变量写进case里,然后抱着代码一脸疑惑,其实语法规则就明明白白摆在那里。
第三,break是跳出口,它决定执行完当前case后会不会继续往下“溜”。这一块是新手最容易翻车的地方,下面单独展开说。
第四,default不是必须的,但强烈建议写上。default负责处理所有case都没匹配上的情况,起一个兜底作用。没有它的话,当表达式的值不在任何case范围内时,整个switch语句就什么都不执行,静默结束。在OJ题目里,如果题目没有保证输入一定在合法范围内,default能在逻辑上给你加一层保护。
1.3 最容易出事的case穿透,一次讲明白
case穿透这个词听起来有点抽象,我用一个经典错误代码来解释。假设题目要求输入1输出"one",输入2输出"two",输入3输出"three"。这段代码编译没问题,运行结果却很奇怪:
switch (n) { case 1: printf("one\n"); case 2: printf("two\n"); case 3: printf("three\n"); }如果我输入1,输出是:
one two threewhy?因为switch只负责“跳到匹配的case入口”,它不负责决定什么时候停下来。当你输入1,程序从case 1:进去,一路往下执行,没有break拦着它,它就一直执行到switch语句结束,把后面所有case里的语句全部跑了一遍。这个现象就叫“穿透”。
穿透是个长期困扰初学者的点,但换个角度看,它反而是switch一个很灵活的特性。多个case共享同一段代码就是利用穿透实现的,比如:
switch (score / 10) { case 10: case 9: printf("A\n"); break; case 8: printf("B\n"); break; }这里case 10:和case 9:都没有自己的执行体,它们的共同点是会穿透到下一段可执行代码。因为case 10没有break,程序自然滑到case 9,case 9又没有break,再滑到后面的printf,最后统一输出A。这个写法在成绩等级转换类题目里特别实用。
2. xtu oj 1055的题目类型与输入输出细节
2.1 这类OJ题面通常长什么样
我没有拿到1055的原题截图,从它排在xtu oj题库前段位置、题目编号和分类来看,这必然是一道语法入门级的选择结构题,配上"switch"这个关键词,考法基本离不开“输入一个整数或者字符,输出对应的结果”。最常见的有三类:
- 数字转星期:输入1输出Monday,输入2输出Tuesday,以此类推;
- 成绩等级转换:输入百分制分数或等级符号A/B/C/D,输出对应的评语;
- 月份天数判断:输入月份,输出该月有多少天。
这些题型的共同点是:输入值是一个有限的离散集合,输出值也相对固定,非常契合switch的“查表”特性。说句实在话,这类题用if-else也能做,但switch在代码结构和可读性上明显更优。
2.2 多组测试数据的处理是OJ题的分水岭
xtu oj和大多数OJ平台的评判机制一样,一份代码提交上去,后台会用多组测试数据去跑你的程序,你的程序必须从头到尾处理完所有数据,并且所有输出都正确,才能拿到AC(Accepted)。这个机制理解不到位,很多同学就会卡在“为什么我本地运行没错,提交却是WA”。
核心代码模式是:
int n; while (scanf("%d", &n) != EOF) { // 处理逻辑 }这里scanf的返回值就是读入成功的数据个数,当读到文件末尾时,它返回EOF。在本地控制台里,你需要在输入完数据后按Ctrl+Z(Windows)或Ctrl+D(Linux/Mac)来模拟文件结束。在OJ评测时,这个结束标志是评测系统自动给出的,你不需要操心。
有一个中学生经常犯的错:在代码里单独给一组数据的输入写了处理逻辑,提交后评测系统喂了多组数据,程序只处理了第一组就结束了,后面几组数据根本没被读取,直接WA。所以记住一句话:OJ题只要没明确说单组数据,一律按多组写。
2.3 输出格式的隐藏要求
OJ对输出格式的要求严格得近乎苛刻。常见的错误有两种。
第一种是画蛇添足。有些同学在代码里写了printf("请输入一个数字:\n")这种提示语,本地测试确实挺人性化,但提交到OJ上,评测系统会把提示语的输出和标准答案比对,多一个字符都是错,直接判WA。
第二种是忘了换行。每一组输出之间要以换行分隔,有些题目还要求最后一行也有换行。最稳妥的做法是printf输出完内容后,结尾统一带\n,例如printf("Monday\n");。
对比一下:
| 错误写法 | 正确写法 |
|---|---|
| printf("Monday"); | printf("Monday\n"); |
| printf("请输入:"); printf("Monday"); | printf("Monday\n"); |
本地跑起来看起来差不多,在OJ眼里一个天一个地。
3. 一份可直接提交的参考代码与逐行拆解
3.1 基础版:数字转星期的标准解答
以最常见的“输入整数1到7,输出对应星期的英文单词”为例,完整代码如下:
#include <stdio.h> int main(void) { int n; while (scanf("%d", &n) != EOF) { switch (n) { case 1: printf("Monday\n"); break; case 2: printf("Tuesday\n"); break; case 3: printf("Wednesday\n"); break; case 4: printf("Thursday\n"); break; case 5: printf("Friday\n"); break; case 6: printf("Saturday\n"); break; case 7: printf("Sunday\n"); break; default: printf("Input error\n"); } } return 0; }这道题代码量不大,但覆盖了switch语句的全部核心点:多分支、break、default。你拿这份代码去跑各种OJ平台的同类题,基本都能过。
3.2 逐行拆解关键位置的设计意图
int main(void)。有的教材喜欢写void main(),这在很多OJ的C编译器下也能编译通过,但不符合C语言标准,主流OJ平台普遍使用的GCC编译器在默认情况下允许,但严格模式下会报警告。养成写int main(void)并且最后return 0;的习惯,是最稳妥的,这也是很多OJ的编译指令默认开启的规范检查项。
while (scanf("%d", &n) != EOF)。这一行是整份代码的“主循环”,负责读入多组数据。有的同学写while (1)然后里面判断scanf返回值,逻辑上等价,但不如直接写在循环条件里简洁。这里的EOF就是-1,代表文件结束符。
case 1:前的缩进和每个case分支缩进一行。这是纯代码风格问题,不缩进也能过编译,但缩进能让结构一目了然。OJ题将来你会经常回看自己的代码,一份清晰的代码能帮你省大量时间。
每个case里的break。前面讲过穿透,这里就是标准防穿透写法。特别注意default分支不用加break,因为它是最后一个分支,执行完就自然退出switch了,加了也不影响,但没必要。
printf("Input error\n")。这就是default的兜底作用。虽然题目一般会保证输入在1到7之间,但default的存在让代码更加健壮,万一评测数据里冒出一个非法的输入,程序至少有明确的输出而不是静默不做任何事。
3.3 换场景:如果1055改成成绩等级转换
“输入一个整数成绩,输出等级”是另一个高频考法,把switch和整型除法的技巧一结合,代码非常精炼:
#include <stdio.h> int main(void) { int score; while (scanf("%d", &score) != EOF) { if (score < 0 || score > 100) { printf("Invalid\n"); continue; } switch (score / 10) { case 10: case 9: printf("A\n"); break; case 8: printf("B\n"); break; case 7: printf("C\n"); break; case 6: printf("D\n"); break; default: printf("E\n"); } } return 0; }这里面的核心技巧是score / 10。整型除法会丢掉小数部分,所以90到99除以10都等于9,80到89除以10都等于8。于是,每个区间正好映射到一个case,用穿透把10和9合并到同一个A等级。这个技巧在很多题目里都能用到,属于那种“知道一次就会一直用”的套路。
如果你拿到的1055题面是输出星期而不是等级,代码结构完全一样,只是case分支里的输出文本换一换。这也就是我强调的:刷OJ,刷的不是某个题的答案,是一类题的通用解法。
3.4 一个容易被忽略的头文件问题
printf和scanf的声明在stdio.h里,所以#include <stdio.h>必须出现在代码顶部,一行都不能少。有些同学在一个OJ题上见过别人用#include <bits/stdc++.h>这种万能头文件,但那是C++的写法,按C语言提交时不一定支持,老老实实用stdio.h最稳。
4. 实战中我踩过的坑:编译错误、逻辑错误与OJ状态速查
4.1 三个高频编译错误,几乎每题都会遇到
错误一:case后跟变量
int target = 1; switch (n) { case target: // 编译错误 break; }编译器报错信息类似于case label does not reduce to an integer constant。解决方法是把变量换算成常量:要么直接用if语句,要么把变量的可能值枚举成多个case。
错误二:忘写break导致逻辑结果错乱
这个在OJ上特别隐蔽。程序能编译,运行也不崩溃,就是输出结果不对。如果评测数据里连续有几组数恰好是“刚好踩中穿透路径”的值,那错误就暴露得非常彻底。排查方法很简单:对照代码检查每个case分支末尾有没有break。
错误三:字符switch直接写给数字
有的同学输入的是字符,但case里写着数字。字符'2'和数字2在C语言里是两个完全不同的东西,前者ASCII码值是50,后者是2。如果要判断字符,应该写成:
char ch; scanf("%c", &ch); switch (ch) { case '2': // 处理字符'2' break; }4.2 OJ评测状态速查表
新手第一次提交时看到一堆英文缩写容易懵。我整理了一个速查表:
| 状态 | 含义 | 应对思路 |
|---|---|---|
| AC | Accepted,通过 | 没问题 |
| WA | Wrong Answer,答案错误 | 逻辑有问题,重点检查分支条件、边界值、输出格式 |
| CE | Compile Error,编译错误 | 看编译报错信息,逐行修 |
| PE | Presentation Error,格式错误 | 多半是多了空格、少了换行 |
| RE | Runtime Error,运行时崩溃 | 多为数组越界、除零、空指针 |
| TLE | Time Limit Exceeded,超时 | 代码效率太低,多半是循环或递归写得不好 |
| MLE | Memory Limit Exceeded,超内存 | 一般是数组开太大或递归栈溢出 |
对1055这种入门题,最容易遇到的是WA和CE,PE偶尔也会冒出来。WA的话优先检查case里是不是忘写break,CE优先检查case后是不是跟了变量。
4.3 本地调试:printf大法和边界值测试
没有调试器时,printf大法永远是最直接的调试手段。在switch前后加临时的printf输出,看变量n到底是多少,看有没有进switch,看走了哪个case分支。比如:
printf("debug: n = %d\n", n); switch (n) { case 1: printf("debug: in case 1\n"); printf("Monday\n"); break; }当然,提交前必须把这些调试输出全删掉,否则它们会成为多余输出被评测系统判WA。这个坑我自己踩过,一次忘记删调试信息,白白多交了好几次。
边界值测试是我强烈建议养成的好习惯。数字转星期的题,边界值至少要测四组:1(最小值)、7(最大值)、0(非法值)、8(非法值)。如果这四组输出都对,这道题的正确率就有保障了。
4.4 别在OJ里玩嵌套过深的switch
有同学为了展示技术,会写出switch套switch的代码。1055这种入门题完全用不上,而且嵌套一多,break的作用域就变得不好判断,常常出现内层break只跳出了内层switch、外层逻辑也跟着乱的情况。实际上,C语言的break只能跳出最近的一层循环或switch,多层嵌套时容易产生混乱。代码写出来是给人看的,简洁清晰永远优先于“花哨”。
5. 从1055延伸出去的switch进阶思路
5.1 枚举类型和switch是天然搭档
如果在代码里定义了一个枚举类型,switch配合枚举写起来非常优雅:
enum Color { RED, GREEN, BLUE }; enum Color c = GREEN; switch (c) { case RED: printf("Red\n"); break; case GREEN: printf("Green\n"); break; case BLUE: printf("Blue\n"); break; }枚举值本质上是整型常量,而且有名字、可读性好。平时用OJ做题可能用不上,但在以后写工程项目时,这种组合会让代码清晰很多。入门阶段知道这个方向就行,没必要在基础题上强行用。
5.2 用switch写菜单程序
如果你在本地想写点小工具练手,用switch实现一个命令行菜单是特别好的实战素材。比如写一个简单的计算器,接收用户输入的运算符和两个操作数:
char op; int a, b; while (scanf("%d %c %d", &a, &op, &b) != EOF) { switch (op) { case '+': printf("%d\n", a + b); break; case '-': printf("%d\n", a - b); break; case '*': printf("%d\n", a * b); break; case '/': if (b == 0) { printf("Error\n"); } else { printf("%d\n", a / b); } break; default: printf("Invalid operator\n"); } }这一小段代码把switch、字符读取、除零保护全练到了,难度比1055略高一点,适合拿来检验自己掌握程度。
5.3 字符输入时的缓冲区残留问题
当你用scanf读完数字再去读字符时,经常会遇到一个奇怪的bug:程序不按预期等用户输入字符,直接跳过了。问题不在switch,而在输入缓冲区里残留的换行符。scanf("%d")会跳过空白字符,但它不会把用户按回车产生的换行符消费掉,后面的scanf("%c")会直接读到这个换行符。
解决方案是在读字符之前先用getchar()把换行符吃掉,或者用scanf(" %c", &ch)在格式串里加一个空格,让scanf先跳过空白字符。
这道1055如果输入的是数字,你暂时不用太担心这个细节。但此顺序在OJ刷题里迟早遇到,提前知道能少一个纠结点。
5.4 不同编译环境的小差异
xtu oj的C语言评测基本以GCC为主。但本地练习时你可能会用Dev C++(自带旧版MinGW GCC)、Visual Studio Code配GCC、或者老式的VC6.0。这些环境都支持标准的switch语法,但在“是否允许在case里定义变量”这类细节上行为可能不同。
比如在case分支里直接int x = 10;,一些老编译器会报错,因为跨case的初始化容易导致跳转问题。按C语言规范,case分支里定义变量需要额外的花括号包裹:
switch (n) { case 1: { int x = 10; printf("%d\n", x); break; } default: break; }基础OJ题基本碰不到这种写法,属于进阶知识,先记住结论就行。
我个人刷xtu oj这类入门题时,最大的感悟是:不要把switch当成语法知识背,把它当成一张“查表工具”来理解。拿到题目先问自己三个问题——输入值是不是离散的、输出结果是不是有限的、能不能用一张表把映射关系列出来。如果能,就用switch;如果不能,老老实实用if。这套判断逻辑一旦建立,1055以及后续一大批类似的OJ题对你来说就是同一道题,换个数据、换个输出文本而已。最后再分享一个小习惯:每AC一道题,把代码存到一个文件夹里,文件名标注题目号和考点,比如“1055_switch”。刷过一百道题以后回看这些代码,你会非常直观地看到自己的C语言是怎么一步步变熟练的,那种成就感比单纯刷题数量有用得多。