在C语言中,Scanf跳过了每个其他的while循环

18
我正在开发一个简单的基于文本的“猜词谜”游戏,其中主要游戏循环从提示输入每个字母的猜测开始,然后继续检查该字母是否在单词中,如果不是,则减去一条命。然而,当我运行游戏时,每次会出现两次提示,程序也不等待用户的输入。此外,它还会减少一条生命(如果它不是正确的输入,则减少两条生命),因此它所读取的内容与先前的输入不同。这是我的游戏循环,稍微简化了一下:
while (!finished)
{
    printf("Guess the word '%s'\n",covered);

    scanf("%c", &currentGuess);

    i=0;
    while (i<=wordLength)
    {
        if (i == wordLength)
        {
            --numLives;
            printf("Number of lives: %i\n", numLives);
            break;
        } else if (currentGuess == secretWord[i]) {
            covered[i] = secretWord[i];
            secretWord[i] = '*';
            break;
        }
        ++i;
    }

    j=0;
    while (j<=wordLength)
    {
        if (j == (wordLength)) {
            finished = 1;
            printf("Congratulations! You guessed the word!\n");
            break;
        } else {
            if (covered[j] == '-') {
                break;
            }
        }
        ++j;

        if (numLives == 0) {
            finished = 1;
        }

    }
}

我猜测问题出在scanf认为已经输入了内容,但实际上并没有输入。但我不知道原因。有人知道吗?我在Mac OS X 10.5上使用gcc 4.0.1。

10个回答

22

使用scanf()读取键盘输入时,输入是在按下回车键后读取的,但回车键生成的换行符并不被scanf()函数获取。这意味着下一次从标准输入读取时会有一个等待你的换行符(这将导致下一个 scanf() 调用立即返回且没有数据)。

为了避免这种情况,您可以将代码修改为以下形式:

scanf("%c%*c", &currentGuess);

%*c 匹配一个字符,但星号表示这个字符不会被存储。 这样做的效果是消耗掉由回车键生成的换行符,以便下次调用 scanf() 时,您将从空输入缓冲区开始。

注意:如果用户按下两个键,然后按下回车键,scanf() 将返回第一个按键,吞咽第二个按键,并留下换行符供下一次输入调用使用。 这种类型的问题是许多程序员避免使用 scanf() 等函数的原因之一。


1
一个匿名编辑试图添加文本:“对于我的代码来说,应该是%*c%c,而不是%c%*c”,并更改'scanf'以匹配。我拒绝了此编辑,因为我不确定它是否正确,但希望将其作为评论提出以供考虑。 - Craig Ringer
2
@Craig Ringer- "%c%c" 会吃掉当前输入字符附带的换行符。您可以在事后使用 "%c%c",从之前的输入中吃掉一个换行符。两者都可能是正确的,但最好的做法是清理自己留下的杂散字符,而不是让其他人处理缓冲区中的杂散字符。 - bta
实际上,"%*c"的位置很重要,考虑以下代码片段,其中"%c%i"修复了无限循环问题,但"%ic"则不行:getagin: printf("Please enter a number:\n"); isnumber = scanf("%*c%i", &number); // "%*c%i"将吃掉当前输入字符附加的换行符,并解决问题。但不是"%i%*c" if(isnumber) { printf("You enterd a number and it was %i\n", number); } else { printf("You did not eneter a number.\n"); goto getagin; } - ilgaar
1
如果代码想要消耗一个尾随的 '\n' 而不是其他东西,那么请使用 "%*1[\n]" 而不是 "%*c" - chux - Reinstate Monica
@chux 尽管从技术上讲更加精确,因为有合理的情况需要不跳过空格,但通用的“%c”会更容易。 - alx - recommends codidact

8

换行符。

第一次循环,scanf()读取字符,然后读取换行符,再读取下一个字符;重复此过程。

如何解决?

我很少使用scanf(),但如果使用格式化字符串"%.1s",它应该跳过空格(包括换行符),然后读取一个非空格字符。然而,它将期望一个字符数组而不是单个字符:

char ibuff[2];

while ((scanf("%.1s", ibuff) == 1)
{
    ...
}

我认为这个解决方案比我的更好,因为它允许用户在知道单词的其余部分后(我们在玩Hangman,记得吗?)直接输入。 - T.E.D.
2
scanf(" %c", &currentguess)也可以解决问题,无需更改变量类型。在scanf格式字符串中的空格匹配“输入中任意数量的空格,包括没有空格”。 - caf

7

将问题分解为更小的部分:

int main(void) {
    char val;
    while (1) {
        printf("enter val: ");
        scanf("%c", &val);
        printf("got: %d\n", val);
    }
}

这里的输出是:
enter val: g
got: 103
enter val: got: 10

为什么scanf会在里面再次给出'10'?

因为我们打印了我们值的ASCII数字,而'10'在ASCII中代表"换行",所以scanf也必须将"换行"作为一个字符抓取。

毫无疑问,看着你的scanf字符串,每次循环都要求一个单个字符。控制字符也被视为字符,并将被捕获。例如,您可以在上述循环中按"esc"然后按"enter",并得到:

enter val: ^[
got: 27
enter val: got: 10

好的回答:将问题精简到其本质,展示并解释(同时还要赶在其他帖子之前!)。这个回答值得不止我的一个点赞。 - Roboprog
感谢您的投票!在我发布答案之前,我想要确保我的答案是正确的,但当我确认它时,已经有人回答了。因此,我认为仅记录下我的方法可能会有所帮助。 - jheddings

3
只是猜测,您正在使用scanf输入单个字符,但用户必须输入猜测并加上换行符,这将被视为单独的猜测字符。

3
scanf(" %c", &fooBar);

注意在%c之前有一个空格。这很重要,因为它匹配所有之前的空格。


2

Jim和Jonathan是正确的。

为了让你的scanf行按照你想要的方式工作(消耗换行符而不将其放入缓冲区),我会将其更改为

scanf("%c\n", &currentGuess);

请注意\n的存在。

但是它的错误处理非常糟糕。至少应该检查从scanf返回的值是否为1,如果不是则忽略输入(并发出警告)。


@AnttiHaapala - 在这种情况下,这并不是不可取的。他所要做的就是在不生成2个输入的情况下接受单字符命令。由于在此方案中空格不是一个命令,因此他不需要它。这可能就是为什么他接受了更复杂的答案,该答案还跳过了前导空格的原因。 - T.E.D.
你的答案完全不同。它会等待输入另一个非空格字符,然后再处理第一个字符。因此,它在解决问题方面没有用处。 - Antti Haapala -- Слава Україні
@AnttiHaapala - 刚刚在在线编译器中尝试了一下。它确实等待第二个输入。第二个输入并没有丢失,但此后程序始终落后于其处理的一个命令。虽然不是完全无法工作,但肯定不是最佳选择。这是更喜欢Leffler答案的另一个原因。 - T.E.D.

1
我注意到了一些问题:
  • scanf("%c") 会读取一个字符,并将ENTER键保留在输入缓冲区中以供下次循环使用
  • 即使从用户读取的字符与 secretWord 中的字符不匹配,你也会增加 i 的值
  • covered[j] 什么时候才会变成“-”?

1
我猜测:您的代码在输入数据时将换行符视为其中一个猜测。由于无法控制错误处理,我一直避免使用*scanf()系列函数。尝试改用fgets(),然后提取第一个字符/字节。

好的,大家都在一分钟内解决了尾随换行字节/字符的问题。因此,建议先读取一行输入,然后检查您拥有的内容,根据需要进行清理并检测错误。也许可以使用一个“get_answer()”函数来实现? - Roboprog

0

当您输入字符时,必须输入一个空格字符以继续。这个空格字符存在于输入缓冲区、stdin文件中,并由scanf()函数读取。 可以通过使用getchar()函数来消耗这个额外的字符来解决这个问题。

scanf("%c",&currentGuess);  
getchar();   // To consume the whitespace character.

我建议你避免使用scanf(),而改用getchar()。因为scanf()需要大量的内存空间,而getchar()是一个轻量级的函数。所以你也可以使用-
char currentGuess;  
currentGuess=getchar();  
getchar();  // To consume the whitespace character.

0

我在你的代码中看到了几个问题:

  1. scanf返回它读取的项目数。你可能需要处理它返回0或EOF的情况。
  2. 我的猜测是用户按下字母+回车,你得到的换行符作为第二个字符。一个简单的检查方法是添加一个调试printf语句,显示输入的字符是什么。
  3. 你的代码只会匹配第一个匹配的字母,即如果单词是“test”,用户输入了't',你的代码只会匹配第一个't',而不是两个。你需要调整第一个循环来处理这个问题。

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