函数fgets跳过用户输入?

5
当我使用函数fgets时,程序会跳过用户输入,影响程序的其余部分。具有此效果的示例程序如下:
#include <stdio.h>

int main() {
    char firstDigit[2];
    char secondDigit[2];

    printf("Enter your first digit: ");
    fgets(firstDigit, 1, stdin);
    printf("\nEnter your second digit: ");
    fgets(secondDigit, 1, stdin);
    printf("\n\nYour first digit is %s and your second digit is %s.\n", firstDigit, secondDigit);
}

我随后想到问题可能在于fgets会写入换行符,因此我更改了代码以解决这个问题:

#include <stdio.h>

int main() {
    char firstDigit[3];
    char secondDigit[3];

    printf("Enter your first digit: ");
    fgets(firstDigit, 2, stdin);
    printf("\nEnter your second digit: ");
    fgets(secondDigit, 2, stdin);
    printf("\n\nYour first digit is %c and your second digit is %c.\n", firstDigit[0], secondDigit[0]);
}

这次,第一个输入正常工作,但第二个输入被跳过了。 我做错了什么?

1
你的输入缓冲区太小了。使用char firstLine[4096];fgets(firstLine, sizeof(firstLine), stdin);,然后再考虑如何处理任何多余的字符(忽略它们可能是合适的)。这允许用户输入前导空格。 - Jonathan Leffler
1
请注意,您应该指定缓冲区的完整大小(通常为sizeof(array) - 尽管在这里有从size_tint的转换)。您不需要担心“off by one”的问题; 指定完整大小是因为 fgets() 不会溢出缓冲区。使用大小为1只允许 fgets() 存储一个空字节 - 没有任何数据。因此它成功读取了空内容,速度非常快,并且不改变输入流。 - Jonathan Leffler
1
POSIX规定最大行大小的最小值{LINE_MAX}应为{_POSIX2_LINE_MAX},目前为2048。我将其加倍以满足我的需求。请参阅<limits.h>。不是每个人都会注意到这种细节,但除非您在一个非常有限的系统上,否则不会有任何问题。(您上次使用少于1 GB主存储器的计算机是什么时候?这使得4 KiB成为总量的微不足道的一部分)。我还这样做是为了震撼效果-以对抗您所遇到的问题。 - Jonathan Leffler
1
是的,但4 KiB只占1 GiB的0.0004%,这是噪音 - 实际上,它甚至不到那个比例。如果您正在编写一个应用程序并且缓冲区将在main()中永久分配,则可能需要担心它,但如果它在从main()调用的函数中,则无需担心-它将被释放。通过这种方式,您就不必担心处理残留物-当您希望他们输入“1”时,有人键入“123456789012345678900987654321”。人们会滥用您的程序-为输入留出足够的空间可以使他们更难以使您的程序崩溃。 - Jonathan Leffler
1
补充@JonathanLeffler的评论,你可能只需要一个大的分配给用户输入缓冲区。就像我答案的最后一个例子一样,你可以使用一个输入缓冲区,并将相关的结果存储在不同的变量中。 - ad absurdum
显示剩余5条评论
3个回答

4

char firstDigit[2]char secondDigit[2] 的空间不足以容纳一个数字、一个换行符和一个字符串结束标志符:

char firstDigit[3];
char secondDigit[3];

接下来,需要在调用fgets()函数时指定缓冲区数组的大小:

fgets(firstDigit, sizeof firstDigit, stdin);
/* ... */
fgets(secondDigit, sizeof secondDigit, stdin);

当使用fgets(firstDigit, 2, stdin);时,fgets()会将最多两个字符(包括\0字符)存储在firstDigit[]中。这意味着\n字符仍然在输入流中,影响第二次调用fgets()
回答OP的评论,如何从输入流中删除未读字符?,一个好的开始是为firstDigit[]secondDigit[]分配更大的空间。例如,char firstDigit[100],甚至char firstDigit[1000]都足够大,可以接受任何预期输入,fgets()不会留下任何字符在输入流中。为了更确保输入流为空,可使用惯用的循环解决方案:
int c;
while ((c = getchar()) != '\n' && c != EOF) {
    continue;
}

请注意,在使用getchar()函数时需要检查EOF的值,因为如果用户从键盘中信号结束文件或者stdin已被重定向,或者在输入错误的情况下,getchar()函数可能会返回这个值。但是请注意,只有在输入流中至少还有一个\n字符时才应该使用此循环。在尝试使用此方法清除输入流之前,应检查输入缓冲区是否有换行符;如果缓冲区中存在换行符,则输入流为空,不应执行循环。在下面的代码中,strchr()函数用于检查换行符。如果在输入字符串中未找到所寻字符,则该函数返回空指针。
#include <stdio.h>
#include <string.h>           // for strchr()

int main(void)
{
    char firstDigit[3];       // more generous allocations would also be good
    char secondDigit[3];      // e.g., char firstDigit[1000];

    printf("Enter your first digit: ");
    fgets(firstDigit, sizeof firstDigit, stdin);

    /* Clear input stream if not empty */
    if (strchr(firstDigit, '\n') == NULL) {
        int c;
        while ((c = getchar()) != '\n' && c != EOF) {
            continue;
        }
    }

    putchar('\n');
    printf("Enter your second digit: ");
    fgets(secondDigit, sizeof secondDigit, stdin);

    /* Clear input stream if not empty */
    if (strchr(secondDigit, '\n') == NULL) {
        int c;
        while ((c = getchar()) != '\n' && c != EOF) {
            continue;
        }
    }

    puts("\n");
    printf("Your first digit is %c and your second digit is %c.\n",
           firstDigit[0], secondDigit[0]);

    return 0;
}

使用一个单独的buffer[]来存储输入的行可能会更好,然后在char中存储单个字符。您还可以编写一个函数来清除输入流,而不是每次需要时重写相同的循环:

#include <stdio.h>
#include <string.h>           // for strchr()

void clear_stdin(void);

int main(void)
{
    char buffer[1000];
    char firstDigit;
    char secondDigit;

    printf("Enter your first digit: ");
    fgets(buffer, sizeof buffer, stdin);
    firstDigit = buffer[0];

    /* Clear input stream if not empty */
    if (strchr(buffer, '\n') == NULL) {
        clear_stdin();
    }

    putchar('\n');
    printf("Enter your second digit: ");
    fgets(buffer, sizeof buffer, stdin);
    secondDigit = buffer[0];

    /* Clear input stream if not empty */
    if (strchr(buffer, '\n') == NULL) {
        clear_stdin();
    }

    puts("\n");
    printf("Your first digit is %c and your second digit is %c.\n",
           firstDigit, secondDigit);

    return 0;
}

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

如果我使用这个程序并在第一个输入框中输入123,它会使用1作为第一个数字,跳过第二个输入框,并使用3作为第二个数字? - ericw31415
1
@ericw31415-- 这是因为 sizeof firstDigit[] 是3,所以最多只能存储3个字符在 firstDigit[] 中,包括空终止符。所以, '1''2' 被存储了,然后是 '\0'。这样留下了 '3''\n' 供第二次调用 fgets() 使用。 - ad absurdum
1
@ericw31415 这应该将 "12" 放入 firstDigit 中,将 "3\n" 放入 secondDigit 中。 - Barmar
你如何从输入流中删除未读字符? - ericw31415
@ericw31415--我已经在我的答案中添加了一些注释和代码,以回答您关于清除输入流的问题。 - ad absurdum

3
对于第一种情况,fgets(firstDigit, 1, stdin);无法从输入中读取任何内容,因为缓冲区大小仅为1字节,并且fgets()必须将空终止符存储到目标中。
对于第二种情况:fgets(firstDigit, 2, stdin);stdin读取了1个字节,即您键入的数字,并且不能读取换行符,因为目标数组已满,允许空终止符。 第二个fgets()从第一个输入中读取挂起的换行符,并出于相同的原因立即返回,不允许您键入第二个输入。
您必须允许fgets()读取至少2个字节,方法是提供至少3个字符的缓冲区大小:
#include <stdio.h>

int main(void) {
    char firstDigit[3];
    char secondDigit[3];

    printf("Enter your first digit: ");
    if (!fgets(firstDigit, sizeof firstDigit, stdin))
        return 1;
    printf("\nEnter your second digit: ");
    if (!fgets(secondDigit, sizeof secondDigit, stdin))
        return 1;
    printf("\n\nYour first digit is %s and your second digit is %s.\n",
           firstDigit, secondDigit);
    return 0;
}

请注意,如果您在按下回车键之前输入多个字符,程序仍会表现出意外的行为。

0
这是一个缓冲区问题。当你按下回车键时,不知道为什么它会保存在stdin缓冲区中。 在执行fgets(...)之后,你必须在所有情况下输入fflush(stdin);
类似于这样:
printf("Enter your first digit: ");
fgets(firstDigit, 1, stdin);
fflush(stdin);

fflush(stdin)未定义行为 - yano
@yano 好的,我们可以使用 "getchar();" 来消耗那个换行符。 - John
请参阅使用fflush(stdin)。通常情况下,这并不合适。 - Jonathan Leffler
@JonathanLeffler,就像我告诉你的那样,我们可以选择使用getchar();来解决这个问题。 - John
1
是的,你留下了评论。但你还没有将它添加到你的答案中。评论是短暂的,而答案则不是。而且你的答案仍然显示出可疑的做法。在阅读这两个链接之后,你可以在你的答案中添加一些材料——注意,如果你在Windows上使用fflush(stdin)可能没问题,但在类Unix系统上,你不会得到任何有用的东西。然后添加一个强大的替代方案。请注意,如果fgets()读取了换行符,你不想用getchar()吞掉输入,所以你必须检查从fgets()得到了什么。 - Jonathan Leffler

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