【问题标题】:C: Clearing STDINC:清除标准输入
【发布时间】:2014-09-23 14:49:26
【问题描述】:

基本上在每个 printf 之前的 windows 代码块中我都有“fflush(stdin);”哪个有效。当我将代码复制到 Linux 时,它不起作用,“fflush(stdin);”的任何替代方案也不起作用我发现。无论我似乎采用哪种方式,输入似乎都没有在缓冲区中清除,或者我的代码中的某些内容不正确。

#include <stdio.h>
#include <math.h>
#include <limits.h>
#include <ctype.h>

int main()
{
   char pbuffer[10], qbuffer[10], kbuffer[10];
   int p=0, q=0, k=0;
   int r, i, Q, count, sum;
   char a[3];
   a[0]='y';
   while(a[0]=='y' || a[0]=='Y')
   {
      printf("Enter a p value: \n");
      fgets(pbuffer, sizeof(pbuffer), stdin);
      p = strtol(pbuffer, (char **)NULL, 10);

      printf("Enter a q value: \n");
      fgets(qbuffer, sizeof(qbuffer), stdin);
      q = strtol(qbuffer, (char **)NULL, 10);

      printf("Enter a k value: \n");
      fgets(kbuffer, sizeof(kbuffer), stdin);
      k = strtol(kbuffer, (char **)NULL, 10);

      while(p<q+1)
      {
         Q=p;
         sum=0;
         count=0;
         while(Q>0)
         {
            count++;
            r = Q%10;
            sum = sum + pow(r,k);
            Q = Q/10;
         }

         if ( p == sum && i>1 && count==k )
         {
            printf("%d\n",p);

         }
         p++;
         a[0]='z';
      }
      while((a[0]!='y') && (a[0]='Y') && (a[0]!='n') && (a[0]!='N'))
      {
         printf("Would you like to run again? (y/n) ");
         fgets(a, sizeof(a), stdin);
      }
   }
   return 0;
}

【问题讨论】:

  • @PaulR,你是我相信错了。符合这些功能是非标准的,不可移植。 fpurge() 函数是在 4.4BSD 中引入的,在 Linux 下不可用。函数 __fpurge() 是在 Solaris 中引入的,并且存在于 glibc 2.1.95 及更高版本中。 linux.die.net/man/3/fpurge
  • a 定义为char 您的输入是错误 (scanf(" %s", &amp;a);):它将尝试在内存中至少写入'\0' 字符a 所在位置之后的位置。那个记忆位置不属于你。
  • @Satya:你可能是对的 - 我会删除我的评论。
  • 请注意fflush 的这种行为并没有得到广泛支持,并且与“flush”的通常含义不兼容。真的没有标准的、可移植的方法来清除输入流而不读取它。
  • 顺便说一句:pow(r,k); 可能是个问题。很多关于那个的帖子。使用类似unsigned powuu(unsigned x, unsigned y) { unsigned z = 1u; unsigned base = x; while (y) { if (y &amp; 1u) { z *= base; } y &gt;&gt;= 1u; base *= base; } return z; }

标签: c input buffer stdin fflush


【解决方案1】:

调用fflush(stdin) 不是标准的,因此行为未定义(有关详细信息,请参阅this answer)。

您可以调用scanf,而不是在stdin 上调用fflush,传递一个格式字符串,指示函数读取直到并包括换行符'\n' 字符在内的所有内容,如下所示:

scanf("%*[^\n]%1*[\n]");

星号告诉scanf 忽略结果。

另一个问题是调用scanf 以使用格式说明符" %s" 将字符读入变量a:当用户输入非空字符串时,空终止符会导致缓冲区溢出,从而导致未定义的行为(@987654332 @ 是一个字符的缓冲区;字符串 "y" 有两个字符 - {'y', '\0'},第二个字符写在缓冲区末尾之后)。您应该将a 更改为具有多个字符的缓冲区,并将该限制传递给scanf

char a[2];
do {
    printf("Would you like to run again? (y/n) \n")
    scanf("%1s", a);
} while(a[0] !='y' && a[0] !='Y' && a[0]!='n' && a[0]!='N' );
}

【讨论】:

  • 当我添加那个 scanf 函数时,它变得有点古怪。看来我输入的字符越多,它就成为未来输入的输入(q和k值)。
  • @AndrewRicci " %s"%s 前面的空格看起来也很可疑。
  • 或者他可以将a 保留为单个char 并使用" %c"
  • scanf("%*[^\n]\n"); 将扫描非'\n',如果找到一个将扫描任意数量的空白并且将返回直到输入以下非空白。最好使用scanf("%*[^\n]%1*[\n]");
  • @chux 感谢您的评论!
【解决方案2】:

我认为你要做的事情比看起来更困难。

我对您尝试执行的操作的解释是禁用提前输入,这样如果用户在您的程序处理其他内容时输入了某些字符,它们就不会出现在提示符处。这实际上很难做到,因为它是操作系统级别的功能。

您可以在打印提示之前在设备上进行非阻塞读取,直到您在 errno 中获得 EWOULDBLOCK。或者tcsetattr function family 可能会有所帮助。看起来有一种方法可以在其中排出文件描述符的输入,但它可能与 fgets/fscanf 交互不良

一个更好的主意是完全不用担心它。 Unix 用户习惯于提前输入,而您想要的是他们意想不到的行为。

【讨论】:

    【解决方案3】:

    不再需要刷新输入缓冲区。

    OP 使用fgets() 而不是scanf() 作为输入的方法是正确的,OP 应该继续这种方法:

    char a;
    while(a !='y' && a !='Y' && a!='n' && a!='N' ) {
    
      printf("Would you like to run again? (y/n) \n");
      if (fgets(kbuffer, sizeof(kbuffer), stdin) == NULL) 
        Handle_EOForIOerror();
    
      int cnt = sscanf(kbuffer, " %c", &a); // Use %c, not %s
      if (cnt == 0) 
        continue; // Only white-space entered
      }
    

    最好不要使用scanf(),因为它会尝试一次性处理用户 IO 和解析,但都做得不好。


    当前 OP 的某些问题源于fgets()scanf(" %s", &amp;a); 之后(应该是scanf(" %c", &amp;a); 的UB。将scanf()fgets() 混合通常会出现scanf(" %c", &amp;a); 离开 的问题在输入缓冲区中输入'\n',要求代码在下一个fgets() 之前刷新输入缓冲区。否则fgets() 会得到陈旧的'\n',而不是新的一行信息。

    使用fgets()作为用户IO,不需要刷新。


    示例fgets() 包装器

    char *prompt_fgets(const char *prompt, char dest, long size) {
      fputs(prompt, stdout);
      char *retval = fgets(dest, size, stdin);
      if (retval != NULL) {
        size_t len = strlen(dest);
        if (len > 1 && dest[len-1] == '\n') { // Consume trailing \n
          dest[--len] = '\0';
        }
        else if (len + 1 == dest) {  // Consume extra char
          int ch;
          do {
            ch == fgetc(stdin);
          } while (ch != '\n' && ch != EOF);
        }
      return retval;
      }  
    

    【讨论】:

    • 你是对的,我不知道为什么我即使混合它......无论如何,当我修复它时,我的问题仍然存在,在第一次运行并选择再次运行后,我在其正下方等待提示时提示“输入 p 值”和“输入 q 值:”。
    • 怀疑你正在使用类似printf("Would you like to run again? (y/n) \n"); if (fgets(buffer, 2, stdin) ... 的东西,而buffer 太小了。 (至少需要 3 个。)否则我将不得不查看您更正的代码。查看缓冲区是否太小,此提示的 '\n' 仍留在输入缓冲区中,下一个 fgets() 无需等待另一行输入即可将其接收 - 因此出现“输入 p 值”的 2 个提示和"输入一个 q 值:",
    • 我刚刚更新了我的代码。它仍然表现得很奇怪。通常,当我输入一组较大的字符时,它会跳过提示并再次循环。我可能只是重新开始,我想知道您是否可以为我指出编码/处理输入的安全方式的方向。
    • 1) 更改您的帖子以使答案看起来像是在解决错误的问题是不好的。如果回答了这个问题,最好附加您的更改,甚至发布一个新问题。 2)建议通过这个编辑过的答案中的帮助函数获取您的用户输入。可能比你需要的多。这个想法是将用户提示/输入封装在一个通用功能中。每次提示后,应使用最多 '\n' 的用户输入