资讯动态

C语言经典100例第31题:字符输入、缓冲区与switch判断的完整拆解

发布时间:2026/10/8 16:41:34 来源:尧图企业网站定制
C语言经典100例刷到第31题画风会突然变一下前面那些题大多靠循环、数组、格式化输出就能过关这道题却开始考验你如何跟用户的字符输入打交道尤其是输入缓冲区这个老熟人。题目本身很短就是大家常刷的菜鸟教程C经典100例里那道输入星期几的第一个字母来判断一下是星期几如果第一个字母一样则继续判断第二个字母。我第一次看以为是个switch小练习真正敲完才发现char输入远比int输入娇气scanf怎么就把换行符吃进去了为什么输完T以后第二个字母总是读个寂寞这些都不是玄学而是C标准输入流的基本功。这篇我把完整思路、参考代码、踩坑过程和几个改造方向都过一遍适合刚学完分支语句、准备用经典题练手的朋友。1. 练习31到底在考什么先把思路拆明白1.1 把英文星期表对齐才知道判断逻辑长什么样这道题的原型来自英文星期单词Monday、Tuesday、Wednesday、Thursday、Friday、Saturday、Sunday分别对应星期一到星期日。只看首字母情况是这样首字母对应星期是否需要再看第二个字母MMonday星期一不需要TTuesday星期二/ Thursday星期四需要WWednesday星期三不需要FFriday星期五不需要SSaturday星期六/ Sunday星期日需要看到没有真正会产生歧义的只有两组T开头的 Tuesday 和 ThursdayS开头的 Saturday 和 Sunday。题目里说的“如果第一个字母一样则继续判断第二个字母”指的就是这两个分组。M、W、F这三个首字母在七个单词里是唯一的读到就可以直接输出结果不用再等第二个字符。所以这道题表面上是个日期判断实际上是在练一个很常见的小算法先根据某个特征做粗分类分类完如果还分不清再引入第二个特征做细分类。这种“两级判断”的写法后面很多地方都会用到比如做登录校验、多级筛选菜单本质上都是这个套路。1.2 为什么用 switch 而不是一长串 if看到分支判断第一反应可能是写一堆 if-else。用 if 当然也能做只是这道题的分支是按单个字符来的用 switch 会让代码结构清晰得多。C语言里的 char 本质上是整数类型switch的表达式要求就是整型所以switch(first)完全合法case M这种写法也是标准做法。有人会担心 switch 不支持像case M || m这种写法这个担心是对的C语言不允许这样合并 case。如果你想兼容大小写要么在判断前用tolower()统一转成小写要么写两个 case 叠一起case M: case m: printf(星期一\n); break;这个语法很多人第一次见会懵其实它利用的就是 switch 的“贯穿”特性如果没有 break执行到下一个 case 时不会做任何判断直接沿用前一个 case 的执行结果。这个特性用对了是技巧用错了就是大坑后面排查部分我会专门讲。2. 参考代码与关键实现2.1 主循环骨架while scanf 怎么搭下面这份代码稍微做了一点完善让它能连续多次判断而不是跑一次就退出。你用记事本也好、用VS Code也好先把这个骨架跑通再说#include stdio.h int main() { char first, second; printf(请输入星期几的第一个字母\n); while (scanf(%c, first) ! EOF) { getchar(); // 吃掉首字母后遗留的换行符 switch (first) { case M: printf(星期一\n); break; case W: printf(星期三\n); break; case F: printf(星期五\n); break; case T: printf(请输入第二个字母\n); scanf(%c, second); getchar(); // 清掉第二个字母后的换行符 if (second u) printf(星期二\n); else if (second h) printf(星期四\n); else printf(第二个字母不正确\n); break; case S: printf(请输入第二个字母\n); scanf(%c, second); getchar(); // 清掉第二个字母后的换行符 if (second a) printf(星期六\n); else if (second u) printf(星期日\n); else printf(第二个字母不正确\n); break; default: printf(输入错误请重新输入\n); } } return 0; }先看最外层这个while (scanf(%c, first) ! EOF)。老手可能更熟悉while (scanf(...) 1)意思是“只要成功读到一个字符就继续”。这里的! EOF也能用但要注意scanf如果遇到输入结束符会返回 EOF循环就退出了。在 Linux 上按 CtrlD 表示结束输入Windows 控制台一般按 CtrlZ 后再回车。循环体里第一件事就是getchar()。为什么要多读一个字符因为当你输入M并按下回车输入缓冲区里实际存的是两个字符M和\n。scanf(%c, first)只会先取走M那个换行符还赖在缓冲区里。如果不处理下一次scanf(%c, ...)读到的就会是\n程序就开始胡言乱语。这里的getchar()就是专门把换行符“顺手带走”的。2.2 switch 分支和二次判断怎么写代码里的 switch 分了五类。M、W、F 三个 case 是直接命中打印完 break 就结束。真正需要细看的是 T 和 Scase T: printf(请输入第二个字母\n); scanf(%c, second); getchar(); if (second u) printf(星期二\n); else if (second h) printf(星期四\n); else printf(第二个字母不正确\n); break;T 开头可能对应 Tuesday第二个字母是 u也可能对应 Thursday第二个字母是 h。所以这里不能直接给结论必须先向用户再要一个字符。S 开头同理Saturday 的第二个字母是 aSunday 的第二个字母是 u。这个设计看起来简单细节都在输入上。你会发现我不仅在第一个字符读完后加了getchar()在第二个字符读完后也加了一个getchar()。如果只清理第一个第二次输入的换行符会残留到下一次循环导致下一次scanf(%c, first)读到一个换行然后getchar()又去等后续输入程序表现就是“好像卡住了其实在等一个永远不来的字符”。这个坑我后面会单独细说。还有一个容易被忽略的地方second这个变量在 M、W、F 分支里根本没被赋值。但因为那些分支不会用到它所以这样写不会出问题。如果你心里不踏实可以在定义时直接char first, second \0;初始化一下更严谨。3. 字符输入与缓冲区练习31真正的隐藏考点3.1 scanf(%c) 不会跳过空白这是所有问题的根源很多新手对scanf的理解是“从键盘读点东西”但没细想它到底怎么读。事实是scanf的很多格式说明符会自动跳过前面的空白字符比如空格、制表符、换行符但%c不在其列。scanf(%d)读整数时就算你前面多按了几个回车也能直接读到数字scanf(%c)可不管这些缓冲区里第一个字符是什么它就原封不动读走什么。打个比方输入缓冲区就像食堂打饭的窗口%d这种格式串会先把餐盘上掉落的饭粒扒拉一边专门挑肉夹%c则像一个不挑食的人窗口给什么就吃什么。按回车留下的那个\n在%c眼里和字母A没有任何区别都是一样会被吃掉的字符。所以练习31里最常见的报错就是程序提示“请输入第二个字母”但还没等你输入它就已经把换行当成第二个字母拿去判断了。如果你用printf把读到的字符以%d形式打出来会看到那个值是 10正好是换行符的 ASCII 码。3.2 getchar 的正确与错误用法getchar()的作用就是读一个字符。在练习31里它最常见的用法是吸收缓冲区里残留的换行。这个用法本身没问题但很多朋友把它当成万能清屏工具每个字符后面都无脑加一个getchar()结果越改越乱。为什么因为getchar()只吃一个字符不是“把这一行剩下的内容全部清掉”。假设用户第一轮输入的是Tu然后回车这时缓冲区里是T、u、\n。scanf(%c, first)读走T后getchar()会把u吃掉而不是吃掉\n。到了二次判断时缓冲区剩下的只有\n程序自然拿不到正确的第二个字母。所以说getchar()在这里其实是有前提的它假设用户每次都只输入一个字母就回车。如果你想让程序更健壮要么用循环把这一行剩余字符全读掉要么干脆用fgets整行读取。后者的优势我在第五部分会展开。3.3 EOF、CtrlD/CtrlZ 与循环退出再补充一个和输入缓冲区有关的零碎知识点EOF。控制台输入和文件输入不一样文件读到末尾会自然遇到 EOF而键盘输入需要用户主动通知系统“我这边没有更多输入了”。在 Linux 终端这个信号是 CtrlD在 Windows 的 CMD 或 PowerShell 里通常是先按 CtrlZ再按一次回车scanf才会收到 EOF。很多新手试 CtrlZ 发现没反应就是因为少了最后一下回车。这个现象不是 C 语言本身的问题而是控制台终端的行缓冲机制决定的。如果你写的程序用了while (scanf(...) ! EOF)一定要给使用者解释清楚怎么退出循环否则别人双击运行后按什么都退不出来只能直接关窗口。4. 常见问题与排查技巧实录4.1 明明没输入第二个字母程序就往下走这是练习31出现频率最高的 Bug。表现是输入T回车程序打印“请输入第二个字母”还没等你敲键盘它就已经判断完甚至输出了一个错误结果。什么原因就是T后面的换行符被第二次scanf(%c, second)读走了。排查方法也很直接在关键位置加一行打印把读到的字符以 ASCII 码的形式打出来printf(debug: first%d second%d\n, first, second);如果second打出 10那就说明读到的不是字母是换行。这时候就需要在第二次scanf之后把换行清掉或者改用前面代码里的getchar()。4.2 每次循环都会多打印一次“输入错误”这个问题的根源和上面类似但看起来更隐蔽。如果你在第二个字符读完后没有清换行那么第二次循环开始时scanf(%c, first)读到的还是一个换行符getchar()又去读下一个字符并把它吞掉整个程序的输入节奏全被打乱。典型现象是输完正确的M回车打印完“星期一”马上又自动打印一次“输入错误”。如果你遇到这个不要怀疑 switch 写错了优先检查循环体里面是不是残留了没清干净的换行。4.3 break 漏写造成的“晴天霹雳”有朋友在 T 分支里写完后忘了加 break结果运行到T时程序会接着执行 S 分支的代码又提示一次“请输入第二个字母”整个逻辑乱成一团。这本质上是 switch 的贯穿特性在起作用C 语言里case分支默认是不自动停止的必须靠 break 跳出去。我的建议是每写完一个 case立刻检查后面有没有break;。这个习惯一开始会觉得很机械但刷完一百例之后你会感谢它。因为这种低级错误最烧时间IDE 不会给你报错运行时现象又五花八门。4.4 输入多个字符后程序开始“乱跳”如果你在运行程序时手一滑输入了ab然后回车只靠一个getchar()是完全招架不住的因为getchar()只吃一个字符剩下的b和\n还会留在缓冲区里作妖。更稳妥的清行方式是用一个小循环void clear_line() { int c; while ((c getchar()) ! \n c ! EOF) ; }这个函数会把从当前字符开始一直到换行符为止的所有字符全部读掉。把前面代码里的getchar()换成clear_line()程序对“输入不老实”的用户会宽容很多。顺便说一句有些教材会让你用fflush(stdin)清输入缓冲区这个做法在标准 C 里其实是未定义行为不同编译器表现差异很大不建议作为通用方案。这个练习里还有一个常见问题用户输入了小写字母m程序没反应。解决办法很简单在判断前统一转成小写#include ctype.h // ... scanf( %c, first); first tolower(first);第二个字母也做同样的转换这样u、U、h、H都能正确识别。需要注意的是tolower()的返回值是 int赋给 char 没问题但使用时最好强转一下避免某些编译器告警。5. 练完这一题还可以往这三个方向改5.1 把判断逻辑封装成函数主函数立刻清爽练习31作为一道入门题写成一长串 switch 完全没问题。但刷题不能只停在“能跑”我建议你试着把判断逻辑抽出来封装成一个函数。比如这样#include stdio.h #include ctype.h int week_day(char first, char second) { switch (first) { case m: return 1; case w: return 3; case f: return 5; case t: if (second u) return 2; if (second h) return 4; return -1; case s: if (second a) return 6; if (second u) return 7; return -1; default: return -1; } } int main() { char first, second \0; char *week_names[] {, 一, 二, 三, 四, 五, 六, 日}; printf(请输入星期几的第一个字母\n); while (scanf( %c, first) ! EOF) { if (first T || first S) { printf(请输入第二个字母\n); scanf( %c, second); } int day week_day(tolower(first), tolower(second)); if (day -1) printf(输入错误请重新输入\n); else printf(星期%s\n, week_names[day]); } return 0; }这里有两个小改动值得你注意。一是scanf( %c, first)里%c前面加了一个空格这个空格会先跳过所有空白字符相当于把之前手动getchar()清换行的工作交给了格式串自己。二是返回数字而不是直接打印主函数再用一个字符串数组翻译成中文这样以后再想改成输出英文名或者输出周几只改数组内容和调用处就行。5.2 换成交互方式用 fgets 整行读取前两个字母如果你已经对 scanf 和缓冲区感到心累有一个更“暴力”但更稳的方案干脆不去跟单个字符较劲直接用fgets把一整行读进来。原题虽然要求“先输入第一个字母如果相同再输入第二个字母”但作为练习你可以把交互方式改成一次输入前两个字母比如Tu、Th、Sa、Su。#include stdio.h #include ctype.h int main() { char line[16]; printf(请输入星期几的前两个字母如 Tu、Th、Sa、Su\n); while (fgets(line, sizeof(line), stdin) ! NULL) { char first tolower(line[0]); char second tolower(line[1]); switch (first) { case m: printf(星期一\n); break; case w: printf(星期三\n); break; case f: printf(星期五\n); break; case t: if (second u) printf(星期二\n); else if (second h) printf(星期四\n); else printf(输入有误\n); break; case s: if (second a) printf(星期六\n); else if (second u) printf(星期日\n); else printf(输入有误\n); break; default: printf(输入有误\n); } } return 0; }fgets会连换行符一起读到字符串里所以Tu加回车这一行实际存的是{T,u,\n,\0}。我们关心的前两个字符就是line[0]和line[1]缓冲区换行残留之类的问题在这一版里基本不存在了。缺点是交互方式跟原题不完全一致但作为改写训练非常值得做一遍。5.3 从 stdin 缓冲区延伸到文件读写练习31里反复出现的“换行残留”“缓冲区清理”本质上都是 C 语言标准输入输出流的行为。这个知识点不会因为你刷完这一题就结束后面学到文件读写时会再次遇到只是从键盘换成了文件流。熟悉stdin的字符读取机制后再学fread、fwrite、fseek、setvbuf这些函数理解速度会快很多。我个人刷题习惯是先把参考代码跑通再故意删掉一个getchar()或者 break观察程序怎么“发疯”最后再把逻辑封装成函数。这个过程比单纯背代码有用得多因为调试时你记住的每一个异常现象都是后面排查真实程序 Bug 的线索。练习31看起来只考星期几判断实际上它把 C 语言输入输出的底层理解又往前推了一步你在这里踩过的坑之后都会变成读懂别人代码时的经验。

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

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

免费获取报价 →
↑