【问题标题】:Flushing stdin after every input - which approach is not buggy?每次输入后刷新标准输入 - 哪种方法不是错误的?
【发布时间】:2023-03-08 06:26:01
【问题描述】:

Mark Lakata 指出我的问题中没有正确定义垃圾之后,我想出了这个。我会保持更新以避免混淆。


我正在尝试获取一个我可以在提示用户输入之前调用的函数,例如printf("Enter your choice:);,然后是scanf,并确保只有在提示之后输入的内容才会被scanf 扫描为有效输入。

据我所知,所需的功能是完全刷新标准输入。这就是我想要的。因此,出于此功能的目的,"garbage" 是用户输入中的所有内容,即在该用户提示之前的整个用户输入。


在 C 中使用 scanf() 时,总是存在输入缓冲区中存在额外输入的问题。所以我一直在寻找一个在每次 scanf 调用后调用的函数来解决这个问题。我使用thisthisthisthis 得到这些答案

//First approach
scanf("%*[^\n]\n");

//2ndapproach
scanf("%*[^\n]%*c");

//3rd approach
int c;
while((c = getchar()) != EOF) 
    if (c == '\n') 
        break;

所有这三个都在我所能找到的范围内工作,并通过参考文献。但在我的所有代码中使用其中任何一个之前,我想知道其中是否有任何错误?

编辑:

感谢Mark Lakata 在第三版中的一个错误。我在问题中更正了它。

EDIT2:

Jerry Coffin 回答后,我在代码中使用此程序测试了第 2 种方法:blocks IDE 12.11 using GNU GCC Compiler(编译器设置中未说明版本)。

#include<stdio.h>

int main()
{
    int x = 3; //Some arbitrary value
    //1st one
    scanf("%*[^\n]\n");
    scanf("%d", &x);
    printf("%d\n", x);

    x = 3;
    //2nd one
    scanf("%*[^\n]%*c");
    scanf("%d", &x);
    printf("%d", x);
}

我使用了以下 2 个输入

第一个测试输入(2个换行符,但垃圾输入中间没有空格)

abhabdjasxd


23
bbhvdahdbkajdnalkalkd



46

首先我通过printf 语句得到以下输出

23
46

即两个代码都能正常工作。

第二次测试输入:(垃圾输入中间有空格的2个换行符)

hahasjbas asasadlk


23
manbdjas sadjadja a


46

第二次我通过printf 语句得到以下输出

23
3

因此我发现第二个不会处理额外的垃圾输入空白。因此,它对垃圾输入并不是万无一失的。

我决定尝试第三个测试用例(垃圾包括非空白字符前后的换行符)

``
hahasjbas asasadlk


23

manbdjas sadjadja a


46

答案是

3
3

即在这个测试用例中都失败了。

【问题讨论】:

  • 为什么标有优化?
  • @0x90 因为我想知道它们是否是最优的,如果不可能优化。
  • @0x90 当我从文件中读取大量输入时,接受的答案很好,但是当我读取非常小的输入(如 1 或 2 个整数)时,链接问题中的方法不会因为以下原因而不太理想调用 malloc 然后将字符串解析为整数?
  • Aseem,我认为您处理这个问题的方法不正确。什么是“垃圾”输入?你想读什么?你只是想跳过所有不是数字的东西吗?

标签: c flush


【解决方案1】:

前两个略有不同:它们都读取并忽略所有字符,直到换行。然后第一个跳过所有连续的空格,因此在执行后,您读取的下一个字符将是非空格。

第二个读取并忽略字符,直到遇到换行符,然后再读取(并丢弃)一个字符。

如果您有(例如)双倍行距文本,则差异会显示出来,例如:

line 1

line 2

假设你读到了第 1 行中间的某个地方。如果你随后执行第一个,你读入的下一个字符将是第 2 行的 'l'。如果你执行第二个,你下一个字符读入将是第 1 行和第 2 行之间的换行符。

至于第三个,如果我真的要这样做,我会这样做:

int ch;
while ((ch=getchar()) != EOF && ch != '\n')
    ;

...是的,这确实工作正常——&amp;&amp; 强制一个序列点,所以它的左操作数首先被评估。然后是一个序列点。然后,当且仅当左操作数计算为 true 时,它才会计算其右操作数。

至于性能差异:由于您从一开始就处理 I/O,因此几乎没有合理的问题,即所有这些都将始终受 I/O 限制。尽管看起来很复杂,scanf(和公司)通常是经过多年使用和精心优化的代码。在这种情况下,手动循环可能会慢很多(例如,如果 getchar 的代码没有内联扩展)或者它可能是大致相同的速度。如果编写标准库的人不称职,那么它有可能显着加快速度的唯一方法。

就可维护性而言:IMO,任何声称了解 C 的人应该知道scanf 的扫描集转换。这既不是新的科学,也不是火箭科学。任何不知道它的人都不是一个称职的 C 程序员。

【讨论】:

  • 您的解释有帮助。在此之前我无法理解他们中的任何一个。我尝试了他们两个。但是由于某种原因,您的解释不成立,并且都在添加到问题中的测试用例 1 中起作用。是因为scanf 的内部工作吗?关于第三个测试用例的任何建议?
  • @AseemBansal:你的测试有点坏,因为%d 会跳过前导空格,所以有了它,是否已经跳过前导空格没有区别。之后尝试使用%c
  • @JerryCoffin 您可能需要重新考虑您的最后一段;您的前两段忽略了这样一个事实,即如果垃圾仅包含换行符,则 scanf("%*[^\n]%*c");scanf("%*[^\n]\n"); 在第一个说明符之后停止,因为它无法匹配任何内容。所以两者都解决不了问题。 (解决方法是将每个调用分成两个 scanf 调用)。
  • @MattMcNabb:在我看来,你只是在问问题没有问的东西(至少在当时——从那以后它经过了大量的编辑)。归根结底,这是他真正想要什么的问题。确实,一旦任何转换失败,scanf 就会停止扫描。即使换行符之前没有任何内容,他是否希望发生这种情况还是想将其分成两个调用来读取一个字符,这似乎还有待商榷。
【解决方案2】:

前两个示例使用了我什至不知道存在的 scanf 功能,而且我相信很多其他人都不知道。能够在未来支持一个特性是很重要的。即使它是一个众所周知的功能,它也会比您的第三个示例效率更低且更难读取格式字符串。

第三个例子看起来不错。

(编辑历史:我犯了一个错误,说 ANSI-C 不保证 && 的从左到右评估并提出更改。但是,ANSI-C 确实保证 && 的从左到右评估。我'我不确定 K&R C,但我找不到任何关于它的参考资料,也没有人使用它......)

【讨论】:

  • +1 表示您在第三次指出的错误。没看到那个。关于第一个和第二个,他们不使用任何额外的变量,而第三个确实有重复的函数调用。那么其中一个不应该更有效吗?
  • 看看scanf的内部结构,你会发现它的效率要低得多。另一方面,getchar()(取决于实现)非常简单,在某些实现中甚至可以内联在您的代码中,从而使其超级快。
  • 内联是什么意思?我不知道这个词。 this questionInline expansion wikipedia page 指的是同一个东西吗?
  • this 指的是同一个内联吗?这有更简单的解释。
  • inline 表示它是作为函数调用编写的,但它更像是一个#define 宏。调用函数没有开销,优化器还可以优化寄存器的使用。 C++ 有 inline 关键字来“强制”内联,但许多 C 编译器会自动进行大量优化。编程的第一条规则是首先获得一个工作的程序,然后再考虑优化。
【解决方案3】:

许多其他解决方案的问题是,当没有任何东西可以刷新时,它们会导致程序挂起并等待输入。等待EOF 是错误的,因为在用户完全关闭输入之前您不会得到它!

在 Linux 上,以下将执行 非阻塞刷新:

// flush any data from the internal buffers
fflush (stdin);

// read any data from the kernel buffers
char buffer[100];
while (-1 != recv (0, buffer, 100, MSG_DONTWAIT))
  {
  }

Linux 手册页说 stdin 上的 fflush 是非标准的,但“大多数其他实现的行为与 Linux 相同。”

MSG_DONTWAIT 标志也是非标准的(如果没有要传递的数据,它会导致 recv 立即返回)。

【讨论】:

  • 答案应该只使用 C 标准函数
【解决方案4】:

You should use getline/getchar:

#include <stdio.h>

int main()
{
  int bytes_read;
  int nbytes = 100;
  char *my_string;

  puts ("Please enter a line of text.");

  /* These 2 lines are the heart of the program. */
  my_string = (char *) malloc (nbytes + 1);
  bytes_read = getline (&my_string, &nbytes, stdin);

  if (bytes_read == -1)
    {
      puts ("ERROR!");
    }
  else
    {
      puts ("You typed:");
      puts (my_string);
    }

  return 0;

【讨论】:

  • getline() 是标准 C++ 而不是标准 C。getchar() 是标准 C。我有使用 getchar() 的方法之一,但不明白如何使用 getchar()读取 int 的浮点数等。
  • 您应该使用sscanf 读取本地缓冲区并使用sscanfstrtolstrtof
  • 代码或多或少地使用了 POSIX getline() 函数。 “或更少”是因为缓冲区大小变量 (nbytes) 的类型应为 size_t 而不是 int。通常,您也应该释放缓冲区。在这里,这无关紧要,但如果这是一个函数而不是 main() 程序,那就很重要了。
【解决方案5】:

我认为,如果您仔细查看此页面的右侧,您会看到许多与您的问题相似的问题。您可以在 windows 上使用 fflush()。

【讨论】:

  • 没必要。这取决于我在 Windows 上使用的编译器。我使用 Code:blocks IDE 和 gcc 编译器。它不允许使用fflush() 刷新标准输入。此外,这个问题并不特定于 Windows 上的 C,而是关于标准 C。
  • fflush 用于清除文件句柄缓冲区(即在驱动程序级别或 stdio 缓冲级别),而不是用于读取到文本行的末尾。
猜你喜欢
  • 1970-01-01
  • 2021-03-30
  • 2010-09-17
  • 2011-01-12
  • 2017-07-21
  • 2011-01-08
  • 1970-01-01
  • 2019-11-22
  • 1970-01-01
相关资源
最近更新 更多