【问题标题】:Scanf skips every other while loop in CScanf 在 C 中每隔一个 while 循环跳过
【发布时间】:2010-12-12 19:01:35
【问题描述】:

我正在尝试开发一个简单的基于文本的刽子手游戏,主游戏循环以提示输入每个字母的猜测开始,然后继续检查字母是否在单词中并夺走生命如果不是,请关闭。但是,当我运行游戏时,每次都会出现两次提示,并且程序不会等待用户输入。它还夺走了一条生命(如果输入正确,则一条生命,否则两条生命),因此无论它接受什么都与先前的输入不同。这是我的游戏循环,稍微简化了一点:

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。

【问题讨论】:

    标签: c gcc scanf


    【解决方案1】:

    当您使用scanf() 读取键盘输入时,在按下回车后会读取输入,但回车键生成的换行符不会被调用scanf() 消耗。这意味着下次您从标准输入读取时,将有一个换行符在等待您(这将使下一个 scanf() 调用立即返回而没有数据)。

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

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

    %*c 匹配单个字符,但星号表示该字符不会存储在任何地方。这具有消耗由回车键生成的换行符的效果,以便下次调用 scanf() 时,您将从一个空的输入缓冲区开始。

    警告:如果用户按下两个键然后按下回车键,scanf() 将返回第一个键击,吃掉第二个键击,然后为下一个输入调用保留换行符。像这样的怪癖是许多程序员回避scanf() 和朋友的原因之一。

    【讨论】:

    • 匿名编辑试图在这篇文章中添加文本:“至少对于我的代码来说,它应该是 %*c%c,而不是 %c%*c”,并更改 scanf匹配。我拒绝了修改,因为我不相信它是正确的,但想提出它作为评论以供考虑。
    • @Craig Ringer- "%c%*c" 将吃掉附加到当前输入字符的换行符。你会在事后使用 "%*c%c" 来从 previous 输入中吃掉一个换行符。从技术上讲,两者都可能是正确的,但最好自己清理,而不是将杂散字符留在缓冲区中让其他人处理。
    • 其实放“%*c”的地方很重要,请考虑这段代码,其中“%*c%i”修复了无限循环问题,但“%i*c”不会:getagin: printf("Please enter a number:\n"); isnumber = scanf("%*c%i", &amp;number); // "%*c%i" will eat the newline attached to the current input character. and fix the problem. But not "%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; }
    • 如果代码想要使用尾随 '\n' 而不是其他东西,那么使用 "%*1[\n]" 而不是 "%*c"。
    • @chux 虽然在技术上更精确,但由于存在不希望跳过空格的合法情况,通用的" %c" 会更容易。
    【解决方案2】:

    换行符。

    第一次循环时,scanf() 读取字符。 然后它读取换行符。 然后它读取下一个字符;重复。

    如何解决?

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

    char ibuff[2];
    
    while ((scanf("%.1s", ibuff) == 1)
    {
        ...
    }
    

    【讨论】:

    • 我认为我比我更喜欢这个解决方案,因为它允许用户在知道它是什么后简单地输入单词的其余部分(我们正在玩刽子手,记得吗?)。
    • scanf(" %c", &amp;currentguess) 也应该可以解决问题,而无需更改变量的类型。 scanf 格式字符串中的空格匹配“输入中任意数量的空格,包括无空格”。
    【解决方案3】:

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

    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 数字,因此 ASCII 中的 '10' 是“输入”,因此scanf 还必须抓住“输入”键作为字符。

    果然,查看您的scanf 字符串,您每次循环都要求一个字符。控制字符也被视为字符,并将被拾取。例如,您可以在上面的循环中按“esc”然后按“enter”并获取:

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

    【讨论】:

    • 好答案:提炼问题的本质,展示和解释(同时与所有其他海报竞争,不少于!)。这个答案比我的单个 mod 点更值得。
    • 感谢您的投票!我想在发布之前确定我的答案,并且在我确认时已经回答了。我认为只是记下我的方法可能会很有用。
    【解决方案4】:

    只是猜测,但您使用 scanf 输入单个字符,但用户必须键入猜测加上换行符,该换行符被用作单独的猜测字符。

    【讨论】:

      【解决方案5】:
      scanf(" %c", &fooBar);
      

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

      【讨论】:

        【解决方案6】:

        吉姆和乔纳森说得对。

        为了让你的 scanf 行做你想做的事(使用换行符而不把它放在缓冲区中)我会把它改成

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

        (注意\n)

        不过,对此的错误处理非常糟糕。至少您应该对照 1 检查 scanf 的返回值,如果没有返回,则忽略输入(带有警告)。

        【讨论】:

        • @AnttiHaapala - 在这种情况下,这不是不可取的。他所做的只是在不生成 2 个输入的情况下接受单字符命令。由于空格不是此方案中的命令,因此他不想要它。这可能就是为什么他接受了更复杂的答案,它也跳过了 leading 空格。
        • 你的答案完全不同。在处理第一个字符之前,它会等待直到另一个非空白字符被输入。因此它对解决问题没有用处。
        • @AnttiHaapala - 刚刚在在线编译器中尝试过。它确实在等待第二次输入。第二个输入并没有丢失,但此后程序在处理过程中总是落后一个命令。并非完全不可行,但绝对是次优的。另一个更喜欢 Leffler 答案的原因。
        【解决方案7】:

        我注意到几点:

        • scanf("%c") 将读取 1 个字符并将 ENTER 保存在输入缓冲区中,以供下次循环使用
        • 即使从用户读取的字符与secretWord 中的字符不匹配,您仍在递增i
        • covered[j] 什么时候会变成“-”?

        【讨论】:

          【解决方案8】:

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

          【讨论】:

          • 好的,每个人都在一分钟内跳到尾随的换行符字节/字符上。因此,建议读取一行输入,然后检查你有什么,清理它并根据需要检测错误。也许在“get_answer()”函数中?
          【解决方案9】:

          我在您的代码中看到了几件事:

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

          【讨论】:

            【解决方案10】:

            当您输入字符时,您必须输入一个空白字符才能继续。此空白字符存在于输入缓冲区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.
            

            【讨论】:

              猜你喜欢
              • 2016-06-05
              • 2012-12-10
              • 1970-01-01
              • 2014-10-27
              • 1970-01-01
              • 1970-01-01
              • 2020-02-16
              相关资源
              最近更新 更多