C语言选择结构实验题全解析:if、switch与OJ常见坑
第一次上机实验交顺序结构的时候我周围很多同学的心态是printf 打几行字符串代码能跑就行本质上是照着课本抄。到了实验二选择结构画风立刻不一样了。题组叫“实验二选择结构1-8”很多高校的OJ平台上都有这种格式的作业一套八道题考的就是 if、else、switch 和条件表达式这几种分支写法。写顺序结构时程序只有一条路可走写选择结构时程序开始分道扬镳同一个输入满足不同条件走不同分支这一步迈不过去后面的循环、函数、数组都会很别扭。这套题适合三类人正在上程序设计基础课的大一学生、第一次带实验课的助教、想确认自己分支结构到底学没学扎实的自学者。我的建议是先别急着打开编译器敲代码先把这套题的出题逻辑和常见坑捋清楚再动手写效率会高很多。1. 这套实验题在考什么1.1 为什么选择结构是第一个真正的分水岭顺序结构的代码是“一条直线走到底”输入、计算、输出三步写完交差几乎没有思考负担。但选择结构不一样它要求程序根据条件产生不同走向。说白了就是让程序开始“做决定”。这背后涉及的不仅仅是 if 的语法还有逻辑判断、条件组合、边界处理甚至是对用户输入的防御性思考。我见过很多同学在这套题上卡住不是因为不会写 if而是因为脑子里没有一个“判断树”的概念。比如给你一个年份它是不是闰年你要先想清楚闰年的规则是什么再把规则翻译成逻辑表达式最后才轮到写 if。整个过程里语法只占三成剩下的七成在“想清楚规则”。这也是为什么我把这套题称为分水岭能独立做完前四题的人说明已经开始用程序的思维方式看问题了。1.2 从第1题到第8题题目在按什么顺序递进这类题组的设计通常不是随机堆八道题而是遵循“单分支→双分支→多分支→嵌套→多条件聚合→switch 应用”的递进路径。我见过多个学校的版本具体题号不完全一样但8道题的类型基本逃不开下面这张表题号范围常见题型核心考点前1-2题判断奇偶、判断正负、取绝对值单条件 if、关系表达式中间3-4题分段函数、闰年判断多分支 else if、逻辑与或中间5-6题两个数/三个数比较大小嵌套 if、临时变量交换后续7-8题成绩等级转换、三角形类型、字符分类多条件组合、switch 用法从这个顺序能看出一个规律每往后一道题条件就多一层。前两题只需要单个 if到第三四题就开始出现“并且”“或者”这种复合逻辑第五六题需要嵌套判断最后两题往往要处理离散值的等值匹配这时候用 switch 会比写一长串 else if 更合适。如果你做题时感觉越做越吃力大概率不是语法问题而是前几题的逻辑训练没跟上。2. 动笔前必须掌握的语法底线2.1 if 的三种基本形态if 语句其实就三种形态单分支、双分支、多分支。单分支只有一个 if条件成立就执行不成立就跳过。双分支加一个 else条件成立走一边不成立走另一边。多分支就是多个 if 嵌套或者用 else if 串起来。新手最常见的错误是把 else if 拆成多个独立的 if导致条件之间互相干扰。// 单分支 if (n 0) { printf(正数\n); } // 双分支 if (n 0) { printf(正数\n); } else { printf(非正数\n); } // 多分支else if 链 if (n 0) { printf(正数\n); } else if (n 0) { printf(负数\n); } else { printf(零\n); }这里有个我在实验课上反复强调的细节即使 if 后面只有一条语句也建议写花括号。理由很实际你以后维护代码时如果要在 if 分支里加一行输出忘了补花括号那新增的语句就变成“无条件执行”了这种 bug 很难用肉眼发现。另一个常见坑是 if 后面不小心加了分号比如 if (n 0); 这样写if 的条件判断和一个空语句绑定后面的代码无论如何都会执行结果就是逻辑全乱。2.2 关系表达式和逻辑表达式的四个坑关系表达式用的符号不多但新手踩坑率极高。第一个坑是把数学里的连续比较直接搬过来比如 a b c。这在 C 语言里不会报错但语义完全不同它会先算 a b 得到 0 或 1再拿这个结果去跟 c 比较。正确的写法是拆成两个关系表达式用逻辑与连接a b b c。第二个坑是赋值号和等号混淆。if (n 1) 在 C 语言里是合法的它把 1 赋给 n然后判断 n 是否为真非零。这会导致条件永远成立。我见过不少同学写了半天找不到问题最后发现就是这个所以调试时看到 if 条件里出现单等号要立刻警惕。第三个坑是浮点数比较。如果你写 if (x 0.1)大概率不会得到你想要的结果因为浮点数在内存里是近似存储的0.1 其实是一个无限循环二进制小数。判断浮点数等于某个值时应该用差的绝对值小于一个极小量比如 if (fabs(x - 0.1) 1e-6)。这套题里如果有分段函数题目通常输入是整数或浮点数输出要保留小数这时候尤其要注意不要用 去比较浮点结果。第四个坑是逻辑表达式的短路求值。C 语言里 和 || 的求值有一个规则只要左边能决定整个表达式的真假右边就不会执行。比如经典写法 if (p ! NULL p-value 0)如果 p 是 NULL右边根本不会去访问避免了解引用空指针。这套题虽然不涉及指针但理解短路求值能帮你判断复杂条件时少走弯路至少要知道逻辑与和逻辑或的运算优先级低于关系运算符。2.3 switch 什么时候用怎么用switch 的适用场景很明确对整型或字符型变量做等值判断而且分支比较多的时候。比如成绩等级转换分数除以 10 之后得到一个 0 到 10 的整数再对每个整数用 switch 判断比写一串 else if 更加清晰。但要注意switch 不能直接判断区间比如 x 90这种必须用 if。很多新手硬用 switch 做区间判断最后只能写出又长又难看的代码。用 switch 时最容易忘的是 break。如果没有 break执行完匹配的 case 之后程序会继续往下穿透执行下一个 case直到遇到 break 或者 switch 结束。这是 switch 的设计特性不是 bug但新手写十个 case 九个漏 break就会产生莫名其妙的输出。另外多个 case 可以共用一组语句比如 case 10: case 9: 都输出 A只需要在最后一个 case 后面写执行体加 break。int score, level; scanf(%d, score); level score / 10; switch (level) { 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); }这段代码里score 是 90 时level 等于 9输出 Ascore 是 100 时level 等于 10也会落入 case 10 和 case 9 的共用分支。default 是可选的但建议写上处理那些预期之外的值。2.4 OJ 输入输出规则要提前确认这套题是在 OJ 上判题不是本地运行看结果所以输入输出格式必须严格匹配。我在实验课上看到的最大一类 WAWrong Answer就是本地运行结果看起来完全正确但提交后不对原因通常出在输出格式上。比如题目要求输出 “a is bigger” 后面跟换行你少打了空格OJ 就会判定错误。读入数据时也要分情况。有些题目只有一组测试数据有些会连续输入多组直到文件结束。如果你不确定可以用 while (scanf(...) ! EOF) 的方式处理多组输入这样单组数据也能跑多组数据也能跑。另一个细节是 getchar 读取字符时前面如果用了 scanf 读整数换行符还留在缓冲区如果直接用 getchar 会读到一个多余的回车。解决办法是在 getchar 之前先把换行吃掉或者用 scanf 的格式串处理空白字符。3. 八道题的逐题拆解与易错点3.1 前两题单条件判断先学会“走一条分岔路”前两道题一般是最简单的比如判断一个整数是奇数还是偶数或者判断一个数是否为正数。这类题只要把条件写对基本就过关了。以判断奇偶为例核心表达式是 n % 2 0表示 n 能被 2 整除。这里有个小细节n 如果是负数C 语言里取模结果的符号和被除数一致所以 -3 % 2 结果是 -1不是 1。如果你写 if (n % 2 1) 来判断奇数负数就会漏掉。稳妥的写法是判断不等于 0if (n % 2 ! 0)。取绝对值也是高频题。有人喜欢用 if 判断也可以直接用条件表达式 x 0 ? x : -x但要注意如果 x 是负数-x 才是正数。这个条件表达式本质上是 if 的简写但它只能返回一个值不能在中间执行多条语句用的时候要分清楚。3.2 中间三道分段函数、闰年判断、比较大小分段函数题很经典比如根据 x 的范围计算 y。这种题要求你用 else if 把区间划分清楚而且区间的边界最重要。假设规则是 x 小于 1 时 y xx 大于等于 1 且小于 10 时 y 2x-1x 大于等于 10 时 y 3x-1。写代码时条件的顺序和边界的等于号必须严格对应。double x, y; scanf(%lf, x); if (x 1) { y x; } else if (x 10) { // 这里隐含了 x 1 y 2 * x - 1; } else { y 3 * x - 1; }注意第二个分支没有写成 x 1 x 10也没有问题因为第一个 if 已经排除了 x 1 的情况程序走到第二个 else if 时说明 x 必然大于等于 1。这种省略写法简洁但新手容易看不懂如果你自己思路不够清晰就老老实实把完整条件写出来不容易出错。闰年判断是这套题里最容易出逻辑错误的题目之一。判断规则是能被4整除但不能被100整除或者能被400整除。翻译成表达式就是 year % 4 0 year % 100 ! 0 || year % 400 0。这里要注意 的优先级高于 ||所以括号可以不加但为了可读性我建议加上。很多同学只写了 year % 4 0没排除整百年份结果 1900 被判成闰年但 1900 其实不是。这种边界情况只有测试数据里藏了才会暴露所以写完代码之后一定要自己试几组边界年份。两个数或者三个数比大小题目核心思路是引入临时变量。比如找三个数中的最大值可以先把第一个数放进 max然后依次和第二个、第三个比较谁大就把谁赋给 max。这种方法是“打擂台”思想不需要写井字格一样的嵌套判断。3.3 后面三道成绩等级、三角形类型、字符分类成绩等级转换题是 switch 的经典应用我在前面已经给过代码。这道题的重点有两个一是把连续分数区间变成离散等级整数除以 10 的技巧二是对 100 分这个边界的处理100 分除以 10 是 10需要和 9 合并。如果你写出 if (score 90) 的版本也没问题但 switch 版本更直观也更贴合本套实验“选择结构”的训练目标。三角形类型的判断题考的是等腰、等边、普通三角形以及能不能构成三角形的判定顺序。很多人一上来就判断等边、等腰忘记先判断三边能不能组成三角形像 1 2 4 这种输入按等边等腰判断会出错输出还是不构成三角形。正确的做法是先用两边之和大于第三边三组不等式判断是否构成三角形构成之后再进一步判断等边、等腰。这里还有一个技巧判断等腰用 a b || b c || a c 而不是 a b b c后者是等边条件。字符分类题比如输入一个字符判断它是字母、数字、空格还是其他字符。字母的判断条件要涵盖大写和小写c a c z || c A c Z。这个题容易忽略的是换行符本身也是字符如果你用 getchar 读取可能把上一次输入留下的回车当成判断对象所以输入前处理缓冲区很重要。另外有些版本会考 switch 实现简单计算器两个数和一个运算符用 switch 对加号、减号、乘号、除号分别处理除号分支还要判断除数为 0 的情况这也是选择结构综合应用的好题目。3.4 一个可以直接抄的 OJ 提交骨架不管做哪道题OJ 提交的代码结构都是固定的准备好这个模板可以节省很多时间#include stdio.h int main() { // 读入数据 // 逻辑处理 // 输出结果 return 0; }头文件只写你用到的东西这套题基本只需要 stdio.h。main 函数的返回类型一定要是 int最后记得 return 0。OJ 判题时只关心程序输出和退出结果如果你不写 return 0很多编译器会帮你补一个但最好还是养成习惯。注释不要写得太多太密OJ 对注释没要求关键是代码本身逻辑清晰。4. 本地能跑、OJ 不通过的排查套路4.1 输出格式OJ 最不讲情面的一关我统计过实验课的数据第一次提交就通过的人不到三分之一其中失败原因里输出格式错误占了大头。题目要求输出 “max10”结果你只输出了 10要求输出结果后换行你忘了加 \n要求输出保留两位小数你直接输出整数。这些在本地看都不明显但在 OJ 的严格比对下就是错。所以写完代码后先把题目要求读三遍逐字对空格、换行、大小写。建议写完代码之前就把输出部分写好而不是逻辑做完再补输出这样不容易漏。输出浮点数时注意 double 对应的是 %lf 输入%.2lf 输出float 对应 %f混用在小数精度要求高的题目里会出问题。4.2 边界情况与逻辑覆盖OJ 的测试数据里一定有边界值。判断奇偶的题目会放进负数闰年题目会放进 1900 和 2000成绩等级题目会放进 59、60、69、70、100。你的代码要在这些点上都输出正确结果。很多人靠“猜”写条件比如成绩等级题直接用 if (score 90 score 100)漏掉了 89 分以下应该输出 B 的情况这就是条件覆盖不全。把自己当成测试员给每道题准备一组边界数据跑一遍。比如分段函数题x 是 0、1、9、10 这些边界三角形题输入 1 2 3 这种两边之和等于第三边的极端情况应该输出不能构成三角形计算器题输入除数为 0。这些数据自己测过了心里才有底。4.3 常见编译错误与低级失误速查表现象原因解决办法error: expected ;语句结尾漏分号看报错行号往前找error: stray \241用了中文标点换成英文半角符号if 分支不按预期执行if 后多打分号删掉 if (条件) 后面的分号输出全是同样内容赋值号写成等号检查 是否写成 switch 穿透执行丢失 break每个非空 case 补 break读入字符串被跳过缓冲区残留换行用 getchar 吃掉多余回车这张表是我从实验课答疑里整理出来的高频问题。最后一个缓冲区问题尤其隐蔽比如先读一个整数再用 getchar 读字符输入 “5 a” 时getchar 可能读到的是空格而不是 a正确的读取方式是 scanf( %c, ch)格式串里加一个空格让 scanf 自动跳过空白字符。5. 给新手的三条实操建议5.1 先画流程图再写代码很多同学拿起键盘就开写if 写到一半发现条件反了然后删掉重写。更高效的做法是先在纸上画一个流程图菱形是判断矩形是处理箭头是走向。画完流程图再翻译成代码你会发现自己对每一层分支都心里有数。老实说这套题里最难的第7道题三角形判断如果直接在纸上画三分钟就能理清先判构成、再判等边等腰的顺序对着屏幕反而会卡住。5.2 学会用边界数据“轰”自己的代码每次写完程序不要急着提交先想这组输入里最刁钻的值是什么然后喂给程序。比如判断闰年输入 2000 和 1900分段函数题输入 0、1、9、10字符分类题输入大写 Z、小写 a、数字 0、空格。边界数据能帮你发现条件里漏掉的等号或小于号。这套题的数据范围一般不大但边界值一定有你在本地跑过就知道 OJ 里那些隐藏测试点大概长什么样。5.3 如果可能别一个人硬憋实验课的意义在于讨论。当你盯着一段代码半小时看不出问题时旁边同学一句“你这个 else 是不是匹配错了”可能比你自己死磕效率高得多。当然这不是让你直接抄答案正确的打开方式是先自己写卡住了可以和同学对一下思路甚至互相给对方出边界测试数据。我在后面带实验课的时候最喜欢看到的是两个人一起盯着一个错误输出讨论条件覆盖范围这比默默写十道题更有价值。最后再说一个小技巧这套题做完以后把每道题目的完整逻辑用文字描述一遍比如“先判断三边是否能构成三角形若不能则输出不构成若能再按等边、等腰、普通依次判断”。写出来的过程会逼你发现自己逻辑里模糊的部分。如果你现在正在为这套选择题结构抓狂别着急按我上面说的先把语法底线的四个坑避开再逐题拆边界条件你今晚就能把这八道题全部拿下。