在C语言中,fgets()函数无法按预期工作

3

给定以下代码:

#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 作为新行输出。为什么?
感谢您的帮助。

1
scanf("%d%*c", &testcase); --> scanf("%d%*c", &testcase); 跳过数字后的换行符。 - BLUEPIXY
请查看此链接:https://dev59.com/kV0a5IYBdhLWcg3w07kt - WhiteSword
使用scanf而不是fgets时,putchar只会输出一个换行符。这与getchar是否相同? - SanQA
无论您在键盘上按下哪些按键,都将存储在stdin中。因此,您必须消耗掉额外的换行符'\n',因为它存在于输入流中。 - WhiteSword
1
最好不要将scanf()fgets()混合使用,以避免出现问题;尝试使用fgets()sscanf()结合使用,而不是仅使用scanf() - ad absurdum
显示剩余6条评论
3个回答

5
混合使用像fgets()scanf()getchar()这样的函数容易出错。 scanf()函数通常在输入流中留下一个\n字符,而fgets()通常不会这样做,这意味着下一次调用I/O函数可能需要处理先前调用留下的输入流中的内容。
更好的解决方案是对所有用户输入使用一种I/O函数风格。 使用与sscanf()结合使用的fgets()可以很好地解决此问题。应检查函数的返回值,并且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');
/* ... */

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

1

1

最终找到了如何安全地同时使用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循环清空缓冲区。
感谢所有人的帮助。

这种循环方式是清除输入流的好方法。很好,你检查了\nEOF两者(这真的是必须的);而且很好,你对于f使用了int而不是char,因为EOF可能无法适应char类型。还要注意,这段代码没有输入验证;例如,用户可以输入a而不是一个数字作为最后一个输入。这意味着m没有被赋值,其值是不确定的(可能会导致未定义行为)。 - ad absurdum
你可以检查scanf()的返回值来判断是否进行了赋值。 - ad absurdum
如何安全地同时使用scanf和fgets?不检查scanf()的返回值是不安全的。 - chux - Reinstate Monica

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接