必须在 C 中使用 fgets() 输入两次?

Have to hit enter twice with fgets() in C?

早上好,我遇到了一些 C 代码的问题,如果输入的长度小于 'guess' 的大小,每次输入时我都必须按两次回车键。

如果输入的长度比猜的长,我只按了一次回车,就正常了。

我不确定这里的问题是什么,但我提供了我认为是问题根源的相关函数以及调用函数和 main 仅用于上下文。

输出:

Guess a number: 5555555555
Invalid guess.
Guess a number: 55555555555
Invalid guess.
Guess a number: 555

Invalid guess.
Guess a number: 

Invalid guess.
Guess a number: 5555

当我删除行时:

while((ch = getchar()) != '\n' && ch != EOF); // Flush the input buffer

并且我超出了缓冲区的大小,我收到了这个输出:

Guess a number: 5555555555555555555555555555555555
Invalid guess.
Guess a number: Invalid guess.
Guess a number: Invalid guess.
Guess a number: Invalid guess.
Guess a number: Invalid guess.

问题中的函数

char * get_input(char * guess)
{
    print_message("guess"); // Prompt user to input a guess
    fgets(guess, sizeof (guess), stdin);
    if(feof(stdin))
    {
        printf("error");
        exit(EXIT_FAILURE);
    }
    int ch = 0;
    while((ch = getchar()) != '\n' && ch != EOF); // Flush the input buffer
    guess[strlen(guess)-1] = '[=13=]'; // Erase new line character
    return guess;
}

调用函数

int make_guess(int *v_guess_count)
{
    int result = 0;
    bool valid = false;
    char guess[10] = {'[=14=]'}; // Buffer to store the guess
    do
    {
        get_input(guess); // Get the input
        if(is_valid(guess)) // Check if the input is valid
        {
            valid = true;
            *v_guess_count += 1;
        }
    }
    while (! valid); // Keep asking for input until guess is valid
    result = assign_value(guess); // Assign the guess
    return result;
}

主要

int main(int argc, char * argv[])
{
    int red = 0;
    int white = 0;
    int v_guess_count = 0;
    int target = get_target();
    bool game_won = false;
    while(game_won == false)
    {
        red, white = 0; // Reset to zero after each guess
        int guess = make_guess(&v_guess_count); // Make a guess. If it's valid, assign it.
        printf("guess is: %d\n", guess);
        compare(guess, target, &red, &white); // Check the guess with the target number.
        print_hints(&red, &white);
        if (red == 4)
        {
            game_won = true;
        }
    }
    printf("You win! It took you %d guesses.\n", v_guess_count);
    return 0;
}

您有两个 somewhat-related 问题。

一个。 在你的函数中

char * get_input(char * guess)

你的线路

fgets(guess, sizeof (guess), stdin);

并不像你想象的那样。你想告诉fgets缓冲区有多大,也就是guess指向了多少内存供fgets读入。但在函数 get_input 中,参数 guess 是一个 指针 ,因此 sizeof(guess) 将是该指针的大小, not 它指向的数组的大小。也就是说,您将获得的大小可能是 4 或 8, 而不是 make_guess 中的数组 guess 被声明为的 10。

要解决此问题,请将输入函数更改为

char * get_input(char * guess, int guess_size)

并将make_guess中的调用更改为

get_input(guess, sizeof(guess));

有关这一点的更多信息,请参阅 this question and also this answer

两个。您用于读取用户猜测的数组 guess 太小。与其将其设为 10 号,不如将其设为 500 号或其他尺寸。这样它就不会“永远”溢出。不要担心这样做会浪费内存 — 内存很便宜。

使输入缓冲区变大的原因是:如果你使缓冲区变小,你必须担心用户可能会键入 too-long 行并且 fgets 可能会出现这种情况无法阅读所有内容。另一方面,如果您使缓冲区变大,则可以声明该问题“不会发生”,因此您不必担心它。你不想担心它的原因是担心它 困难 ,并导致像你在这里遇到的问题。

严格正确使用fgets,同时担心用户输入溢出缓冲区的可能性,意味着检测到它发生了。如果 fgets 没有读取所有输入,这意味着它仍然位于输入流中,等待混淆程序的其余部分。在那种情况下,是的,您必须阅读或“冲洗”或丢弃它。这就是你的台词

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

尝试做 — 关键是你需要这样做只有当 fgets 有 not-big-enough 问题。如果 fgets 没有问题——如果缓冲区 足够大——你不想做 flush-the-input 的事情,因为它会吞噬正如您所发现的那样,将用户的下一行预期输入改为上行。

现在,说到这里,我必须提醒你。一般来说,“让你的数组变大,这样你就不必担心它们不够大”的策略不是一个好的策略。在一般情况下,由于 buffer overruns.

,该策略会导致不安全的程序和可怕的安全问题

不过,在这种情况下,问题还不算太严重。 fgets 将尽力不向目标数组写入超过目标数组可容纳的内容。 (fgets 将完美地完成这项工作 — 避免缓冲区溢出的完美工作 — 如果 您正确传递了大小,也就是说,如果您解决了第一个问题。)如果缓冲区不够大,最糟糕的问题是输入行的 too-long 部分将保留在输入流中并在以后的输入函数中读取,从而造成混乱。

所以你总是要考虑例外情况,并考虑你的程序在所有情况下将要做什么,而不仅仅是“好”的情况。对于“真正的”程序,您确实必须努力使 所有 情况下的行为都正确。不过,对于像这样的入门程序,我想大多数人会同意只使用一个巨大的缓冲区就可以了,然后就可以了。

如果您想获得额外的功劳,并完美地处理用户键入的内容超过 fgets 输入缓冲区所能容纳的情况,您首先必须检测这种情况。代码看起来像:

if(fgets(guess, guess_size, stdin) == NULL)
{
    printf("error");
    exit(EXIT_FAILURE);
}

if(guess[strlen(guess)-1] != '\n')
{
    /* buffer wasn't big enough */
    int ch = 0;
    while((ch = getchar()) != '\n' && ch != EOF); // Flush the input buffer
    /* now print an error message or something, */
    /* and ask the user to try again with shorter input */
}

但要点是,只有在 fgets 无法读取整行的情况下,您才执行 while((ch = getchar()) != '\n' && ch != EOF) 操作,而 而不是 它成功的地方。


如果你还和我在一起,这里有两个 somewhat-important 脚注。

  1. 我建议更改您的 get_input 函数以采用第二个参数 int guess_size,但事实证明更适合用于事物大小的类型是 size_t, 所以更好的声明是 size_t guess_size.

  2. 我建议测试 if(guess[strlen(guess)-1] != '\n') 来检测 fgets 无法读取整行,但在以下情况下可能会失败(非常糟糕) fgets 以某种方式 return 编辑了一个空行。在那种情况下 strlen(guess) 将是 0,我们最终会访问 guess[-1] 以查看它是否是换行符,这是未定义的并且是错误的。在实践中,fgets 到 return 一个空字符串可能是不可能的(至少,只要你给它一个大于 1 的缓冲区来读入),但将代码写成一个可能更容易比说服自己这不可能发生更安全的方法。有一堆问题e关于如何实际有效地检测 fgets 没有成功读取整行的情况,但现在我找不到任何一个。