【问题标题】:How to completely clear stdin and the \n before scanf()如何在 scanf() 之前完全清除 stdin 和 \n
【发布时间】:2022-01-19 14:11:19
【问题描述】:

环境:

  • Windows 7 企业版 SP1
  • gcc 版本 6.3.0 (MinGW.org GCC-6.3.0-1)

我是 C 语言的新手(不是编程新手)。 我正在将一个大程序迁移到 C 语言。除了这个,一切都很好。
我创建了这个小程序来重现问题:

int AAA ()
{
    const char *p = "Enter to proceed.. X to Exit.. : ";
    const char *ok = "OK" ;
    char proceed1 ;
    char proceed2 ;

    printf(p) ;
    fflush(stdin) ;
    scanf("%c",&proceed1);
    if (proceed1 == 'X'){
        return 0 ; }
    sleep(3) ;
    fflush(stdin) ;
    printf("\n%s",p) ;
    scanf("%c",&proceed2);
    if (proceed2 == 'X'){
        return 0 ; }
    printf("\n%s",ok) ;

    return 0 ;
}

一切都很好,但是如果用户(错误地)在proceed1上按了两次回车键,那么proceed2的scanf会自动读取缓冲区中保留的换行符'\n'...

我尝试了所有:rewind(stdin)fseek stdingetchar 在第二个提示绕过问题之前,但如果用户正确地按回车键一次,让scanf 挂起。
我再说一遍,我是 C 语言的新手。我知道这个问题可以通过编写一个不接受 \n 单独的 scanf 来绕过。

但在继续之前,我在这里问这个问题:
如何彻底清除stdin 缓冲区?

【问题讨论】:

  • 您曾经运行过冗长的控制台命令吗?它们在运行时确实接受并打印键盘输入。这不是质量差的标志。如果您使用 enter 进行确认 - 不要。请改用y 或输入yes 来确认用户的意图。
  • "如何彻底清除标准输入缓冲区?" --> 没有标准的方法来做到这一点。将stdin 视为在不同时间传入的数据,与stdout 或其他代码发生的情况不同步。也许只是不保存空白并使用scanf(" %c",&proceed2); - 注意添加的空间。
  • 这真的取决于你所说的“清除缓冲区”是什么意思。也许您的意思是“在读取块之前消耗所有存在的数据”,但也许您的意思是“读取所有数据直到下一个非空白字符”,或者您的意思可能是别的。也许您的意思是“丢弃(内部)缓冲区中的所有数据,但不再读取”。您指的是内部标准 IO 缓冲区还是终端缓冲区,还是其他一些缓存层?
  • question 12.26bC FAQ list 中有一些关于这个问题的讨论。

标签: c stdin


【解决方案1】:

仅仅因为这是一个足够常见的问题,尽管有其他类似问题和/或重复问题的链接,我认为这值得重复:

如果你想fflush(stdin),你几乎肯定做错了什么

输入缓冲区刷新说:“我不在乎你到目前为止输入了什么。我把它扔了,我们重新开始。”

这样的行为让程序的用户非常沮丧。用户期望他们的输入被识别并采取行动——即使这些输入是错误的!通过将其丢弃,您的程序会告诉用户他们的意愿没有得到尊重,让他们感觉自己对计算机的控制权少于

这是一个 XY 问题

想要清除输入的通常嫌疑人是scanf 失败。用户输入错误,输入被无效字符卡住,程序需要解决这个问题。

但是程序不需要比下一个换行更远,也就是说,它只需要丢弃在用户按下 Enter 之前输入的内容。这源于一个非常简单的原则:

用户将总是在每次提示输入后按 Enter 键

解决方案有三种形式:

  1. 崩溃和燃烧。这是一个强烈的信号,表明用户需要修正他的输入才能工作,实际上为他(或她)提供了一种更好的感觉控制:程序做它应该做的,接受它应该做的输入,并且什么都不做除非输入是正确的!

    这也让用户可以更好地控制他或她如何使用您的程序,因为他现在可以为程序提供准备好的输入,而无需照看键盘。

  2. 跳到 EOL 并要求用户重试。这假设一个人正在敲击键盘。但如果你要经历这种悲伤,那么:

  3. 将所有输入读取为字符串,一次一行,并努力处理该字符串(例如,使用sscanf)。这使您的输入和处理指针保持一致,您将永远不需要尝试与输入缓冲区重新同步。

显然,我的观点是 crash and burn 是您常用的控制台程序最正确的方法。

事实上,这是 Unix 核心设计的一部分。这种行为使各个实用程序能够组合起来执行有用的脚本任​​务。

TUI 程序例外。

text-based user interface 用于需要人工操作的程序*,并接管终端以不同的方式运行,而不需要用户按 Enter 来接收和处理输入。

游戏和文本编辑器就是这类程序的例子。

* 当然,自动化 TUI 仍然是可能的,但关键是与程序的预期交互不再是通过换行符分隔的流,而是通过直接的、单独的按键作为输入。

【讨论】:

    【解决方案2】:

    C 标准说fflush(stdin) 具有未定义的行为。即使在似乎具有预期效果的平台上,也不应该使用它来刷新待处理的输入。没有可移植的方法来刷新待处理的输入,即:在发出提示之前输入的所有输入。

    如果您只想使用用户输入的字符来解析以前使用scanf() 解析的条目,直到换行符,您可以使用这个简单的函数:

    #include <stdio.h>
    
    // Read and discard the rest of the current line of input.
    // Returns EOF at end of file, otherwise returns a newline character
    int flush_input(FILE *fp) {
        int c;
        while ((c = getc(fp)) != EOF && c != '\n')
            continue;
        return c;
    }
    

    如果你需要使用scanf(),你可以使用这两个调用:

    scanf("%*[^\n]");  // read the remaining characters on the line if any.
    scanf("%1[\n]");   // read the newline character if present.
    

    但请注意,您不应将这两个组合为对 scanf() 的单个调用,因为如果待处理字节是换行符,则第一次转换将失败,而第二次转换不会使用此换行符。

    【讨论】:

      猜你喜欢
      • 2013-08-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-13
      • 1970-01-01
      • 2019-06-14
      • 1970-01-01
      相关资源
      最近更新 更多