【问题标题】:fgets() doesn't work as expected in Cfgets() 在 C 中不能按预期工作
【发布时间】:2017-07-20 05:07:43
【问题描述】:

给定以下代码:

#include <stdio.h>
int main()
{
 int testcase;
 char arr[30];
 int f,F,m;
 scanf("%d",&testcase);
 while(testcase--)
 {
  printf("Enter the string\n"); 
  fgets(arr,20,stdin);
  printf("Enter a character\n");
  F=getchar();
  while((f=getchar())!=EOF && f!='\n')
    ;
  putchar(F);
  printf("\n");
  printf("Enter a number\n");
  scanf("%d",&m);
 }
 return 0;
}

我希望用户输入一个字符串、一个字符和一个数字,直到测试用例变为零。

我的疑惑/问题:

1.用户无法输入字符串。似乎fgets 不起作用。为什么?

2.如果我使用scanf 而不是fgets,则getchar 无法正常工作,即我在其中输入的任何字符都只是putchar 作为新行。为什么?

感谢您的帮助。

【问题讨论】:

  • scanf("%d",&amp;testcase); --> scanf("%d%*c", &amp;testcase); 跳过数字后的换行符。
  • fgets 从 FILE 中读取,直到遇到新行。但是您对scanf 的第一次使用是在输入流中留下'\n' 字符,然后fgets 使用它并认为输入已完成。
  • 为什么我们需要跳过数字后的新行?
  • fgets 收到换行符。
  • 最好不要将scanf()fgets() 混用以避免这些麻烦;尝试将fgets()sscanf() 一起使用,而不是单独使用scanf()

标签: c


【解决方案1】:

fgets()scanf()getchar()这样的混合函数很容易出错。 scanf() 函数通常会在输入流中留下一个 \n 字符,而 fgets() 通常不会,这意味着对 I/O 函数的下一次调用可能需要也可能不需要处理前一次调用的内容留在输入流中。

更好的解决方案是对所有用户输入使用一种风格的 I/O 函数。 fgets()sscanf() 一起使用可以很好地解决这个问题。应该检查函数的返回值,如果发生错误,fgets() 返回一个空指针; sscanf() 返回成功分配的次数,可用于验证输入是否符合预期。

这是已发布代码的修改版本。 fgets() 将输入存储在慷慨分配的缓冲区中;请注意,如果有足够的空间,此函数将输入最多并包括 \n 字符。如果输入字符串不包含空格,可以使用sscanf()提取字符串,不用担心换行符;同样,使用sscanf() 提取字符或数字输入可以减轻代码进一步处理\n 的负担。

#include <stdio.h>

int main(void)
{
    int testcase;
    char arr[30];
    char F;
    int m;
    char buffer[1000];

    do {
        puts("Enter number of test cases:");
        if (fgets(buffer, sizeof buffer, stdin) == NULL) {
            /* handle error */
        }
    } while (sscanf(buffer, "%d", &testcase) != 1 || testcase < 0);

    while(testcase--)
    {
        puts("Enter the string");
        /* if string should not contain spaces... */
        if (fgets(buffer, sizeof buffer, stdin) == NULL) {
            /* handle error */
        }
        sscanf(buffer, "%29s", arr);
        printf("You entered: %s\n", arr);
        putchar('\n');

        puts("Enter a character");
        if (fgets(buffer, sizeof buffer, stdin) == NULL) {
            /* handle error */
        }
        sscanf(buffer, "%c", &F);
        printf("You entered: %c\n", F);
        putchar('\n');

        do {
            puts("Enter a number");
            if (fgets(buffer, sizeof buffer, stdin) == NULL) {
                /* handle error */
            }
        } while (sscanf(buffer, "%d", &m) != 1);

        printf("You entered: %d\n", m);
        putchar('\n');
    }

    return 0;
}

另一方面,如果输入字符串可能包含空格,fgets() 可以将输入直接读入arr,但是存储的字符串将包含一个\n 字符,它应该可能会被删除。一种方法是使用strcspn() 函数查找\n 的索引:

#include <string.h>  // for strcspn()
/* ... */
        puts("Enter the string");
        /* or, if string may contain spaces */
        if (fgets(arr, sizeof arr, stdin) == NULL) {
            /* handle error */
        }
        /* replace newline */
        arr[strcspn(arr, "\r\n")] = '\0';

        printf("You entered: %s\n", arr);
        putchar('\n');
/* ... */

请注意,在将%sscanf() 函数一起使用时,应始终指定最大宽度以避免缓冲区溢出。这里,读入arr 时是%29s,因为arr 可以容纳30 个chars,并且必须为空终止符(\0)保留空间。检查来自sscanf() 的返回值以查看用户输入是否无效,在这种情况下会再次要求输入。如果测试用例数小于0,则必须重新输入。

【讨论】:

    【解决方案2】:

    终于找到了解决方案,如何安全地一起使用scanf和fgets。

    #include <stdio.h>
    int main()
    {
     int testcase,f,F,m;
     char arr[30];
     scanf("%d",&testcase);
     while((f=getchar())!=EOF && f!='\n')
      ;
     while(testcase--)
     {
      printf("Enter the string\n");
      fgets(arr,30,stdin);  
      printf("Enter a character\n");
      F=getchar();
      while((f=getchar())!=EOF && f!='\n')
        ;
      putchar(F);
      printf("\n");
      printf("Enter a number\n");
      scanf("%d",&m);
      while((f=getchar())!=EOF && f!='\n')
      ;
     }
    }
    

    我们需要确保在 fgets 读取任何内容之前,使用简单的 while 循环刷新缓冲区。

    感谢大家的帮助。

    【讨论】:

    • 这种循环样式是清除输入流的好方法。很好,您正在检查\n EOF(这确实是强制性的);你使用int 而不是char 来代替f 也很好,因为EOF 可能不适合char。另请注意,此代码没有输入验证;例如,用户可以输入a 而不是最后输入的数字。这意味着没有分配给m,使其值不确定(前面可能存在未定义的行为)。
    • 您可以查看scanf()的返回值,看看是否进行了分配。
    • "我们如何安全地一起使用 scanf 和 fgets。" + 不检查scanf() 的返回值不是“安全的”。
    【解决方案3】:

    一个简单的技巧是编写一个函数来解释换行符。在每个 scanf 之后调用 clear()

    void clear (void){
        int c = 0;
        while ((c = getchar()) != '\n' && c != EOF);
    }
    

    更多解释请参考这个问题:C: Multiple scanf's, when I enter in a value for one scanf it skips the second scanf

    【讨论】:

      猜你喜欢
      • 2011-02-20
      • 1970-01-01
      • 2023-03-24
      • 1970-01-01
      • 1970-01-01
      • 2015-06-26
      • 2013-05-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多