C 语言中输入缓冲区的坑

3 年前(已编辑)
/ ,
73
这篇文章上次修改于 1 年前,可能部分内容已经不适用,如有疑问可询问作者。

最近在学 C,跟着《C Primer Plus》做案例的时候碰到了一个问题,导致程序陷入了死循环,代码如下:

这个函数的功能是,打印一个菜单供用户选择,将用户选择的选项结果返回。程序需要对错误的输入进行处理:

  • 用户输入的不是数字
  • 用户输入的数字不在 1-5 的范围内

如果是上面两种情况之一,则进入 while 循环,提示用户输入正确的数字。

针对第二种错误,上面的代码可以正常运行:

Kapture 2023-06-29 at 14.59.32

Kapture 2023-06-29 at 14.59.32

但是,当我们输入一个字母后,问题出现了:

Kapture 2023-06-29 at 15.01.56

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

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

Kapture 2023-06-29 at 21.22.38

此外,开发终端应用时,应充分考虑用户的错误输入并妥善地处理。

  • Loading...
  • Loading...
  • Loading...
  • Loading...