C 语言中输入缓冲区的坑
最近在学 C,跟着《C Primer Plus》做案例的时候碰到了一个问题,导致程序陷入了死循环,代码如下:
这个函数的功能是,打印一个菜单供用户选择,将用户选择的选项结果返回。程序需要对错误的输入进行处理:
- 用户输入的不是数字
- 用户输入的数字不在 1-5 的范围内
如果是上面两种情况之一,则进入 while 循环,提示用户输入正确的数字。
针对第二种错误,上面的代码可以正常运行:

Kapture 2023-06-29 at 14.59.32
但是,当我们输入一个字母后,问题出现了:

Kapture 2023-06-29 at 15.01.56
出现这个 bug 的时候,我记得之前好像在哪里也碰到过,就把《C Primer Plus》的目录往前翻,翻到 4.4(printf() 和 scanf()) 那一章,找到了答案:
如果遇到一个非数字字符,scanf() 会将该字符放回输入,不会把值赋给指定变量。程序在下一次读取输入时,首先读到的就是刚刚放回的字符。
所以,while 循环条件表达式中的
scanf("%d", &code)
每次从输入中读到的都是字符 'a',条件永远为 true,程序崩溃。
二、输入缓冲区
上面这段话有几个加粗的地方:输入。
这里的 “输入” 是啥?有什么用?其实,“输入” 指的就是输入缓冲区,这里我们只讨论行缓冲(通过换行符刷新缓冲区)。

Kapture 2023-06-29 at 20.01.40
首先,我们粗略地分析这张 gif 图片展示的输入输出的流程:
输入设备依次输入字符(键盘) -> 字符进入缓冲区 -> 按下回车 -> 输出设备依次输入字符(显示器)
如果没有缓冲区,每次按下键盘,程序会立即将刚刚输入的字符输出到显示器上,就像这样:
hheelllloo,,wwoorrlldd!!
是不是觉得非常难受?而且,由于输入的字符立即被输出,当我们不小心输错字符的时候,无法通过退格键进行修改,输入缓冲区帮助我们解决了这些问题。每输入一个字符,程序会将该字符暂时存放在某一块内存中,按下回车键,程序将这些字符作为一个块进行依次读取。
读取缓冲区字符的方式
1. scanf()
假设缓冲区中有这些字符:123abcd
scanf()
函数从缓冲区中读取字符(每读取一个,缓冲区中的字符就会少一个),发现第一个字符是 '1',可以转成 int,于是继续往后读取,直到遇见 'a'。然后,通过转换说明 "%d" 将 "123" 转换为 int 类型写入内存,返回成功操作的个数 1,退出 while 循环。此时,缓冲区剩下的字符是 abcd
,供后续的函数读取,比如 scanf("%*s")
、getchar()
等。
但是,如果缓冲区中的字符一开始就是 abcd
呢?程序会陷入死循环,因为缓冲区的字符没人读了,一直卡在那里,while 循环的条件一直成立。
所以,解决方案呼之欲出,只要我们在循环内部将这些垃圾字符读走就 ok 了。
我们在循环内部加了一行 scanf("%*s");
,修饰符 '*' 的作用是跳过赋值操作,这行语句可以帮助我们将没用的字符读走,直到下一个空格。
其实,这种做法还有一个问题,如果缓冲区中是
abcd efg
,中间有个空格的话,循环会多跑一遍,打印的提示语句会重复。
2. getchar()
除了上面的做法,更常用、更优秀的方案是使用 getchar()
函数,该函数用于读取一个字符,并将结果返回:
上面代码的效果有点类似于 echo
,读取缓冲区中的字符并将其输出。
所以,我们可以修改一下之前的代码:
这一次,我们在循环内部加了一行代码 while(getchar() != '\n') continue;
(这里的 continue
写不写无所谓),将缓冲区中的垃圾字符全部读走。
注意这里的条件 getchar() != '\n'
,为什么要判断?
如果不加这个条件,程序将陷入无限循环,会一直“吞掉”用户的输入,后续的代码永远无法运行,尽管用户按了回车键(换行符 '\n' 也会存入缓冲区)。
当用户按下回车,意味着程序需要开启一段新的逻辑,所以,如果缓冲区中出现了换行符,应该退出循环,将后续的字符交给其他函数处理。
三、总结
当 scanf()
函数的赋值操作失败时,会将读取的字符放回输入缓冲区,我们需要及时地将这些无效的字符处理掉(使用 getchar()
函数)。

Kapture 2023-06-29 at 21.22.38
此外,开发终端应用时,应充分考虑用户的错误输入并妥善地处理。