Scanf 在循环中跳过(Hangman)

Posted

技术标签:

【中文标题】Scanf 在循环中跳过(Hangman)【英文标题】:Scanf skipped in loop (Hangman) 【发布时间】:2014-04-12 09:42:01 【问题描述】:

这个程序本质上是要求一个秘密字符串,然后要求用户反复猜测该字符串的单个字符,直到他全部猜出来。但是,它每运行一次 while 循环,它就会跳过猜测字符的用户输入。我该如何解决这个问题?

int main()
  char guess;
  char test2 [50];
  char * s = test2;

  char output [50];
  char * t = output;


  printf("Enter the secret string:\n");
  fgets(test2, 50, stdin);
  for (int i=0;i<49;i++)    //fills ouput with _ spaces
    *(output +i)='_';

  while(strcmp(s,t) != 0)
    printf("Enter a guess:");
    scanf("%c",&guess);
    printf("You entered: %c\n", guess);
    showGuess(guess,s, t );           // makes a string "output" with guesses in it
    printf("%s\n",t);
  
  printf("Well Done!");

【问题讨论】:

您还将内容与用户输入的'\n' 进行比较。 并且需要输出空终止。 每次输入字母后,您可能想更改output 的内容。否则,您将一遍又一遍地比较相同的事物。 【参考方案1】:

为了快速而肮脏的解决方案,请尝试

// the space in the format string consumes optional spaces, tabs, enters
if (scanf(" %c", &guess) != 1) /* error */;

为了获得更好的解决方案,重做您的代码以使用fgets(),然后解析输入。

【讨论】:

这些都不起作用。猜测的字符串永远不会与输入字符串匹配。 :( 完全像@User231454的解决方案,这将忽略空格。所以如果输入的字符串有空格,就无法完成游戏。否则,逻辑比我的(当前)解决方案中的逻辑要好:它实际上可以警告玩家输入无效,而不是忽略输入的无效部分。【参考方案2】:

正如其他一些答案和 cmets 所指出的,您需要在输入中“使用”“换行符”。

这样做的原因是,从键盘到程序的输入被你的 shell 缓冲,因此,在你真正告诉你的 shell“将其缓冲区的内容传递给程序”之前,程序不会看到任何东西”。此时,程序将能够读取上一个缓冲区中包含的数据,例如您的输入,后跟一个用于在 shell 中验证您的输入的字符:换行符。如果您在执行另一个scanf 之前没有“使用”换行符,那么第二个scanf 将读取换行符,从而导致您目睹的“跳过scanf”。要消耗输入中的额外字符,最好的方法是读取它们并丢弃您读取的内容(下面的代码做了什么,请注意

while(getc(stdin) != '\n');

scanf 之后的行。这一行的作用是:“当从stdin 读取的字符不是'\n' 时,什么都不做并循环。”)。

作为替代方案,您可以通过 termios(3) 函数告诉您的 shell 不缓冲输入,或者您可以使用任一 curses/ncurses 库作为 I/O。

这就是你想要的:

int main()
  char guess;
  char test2 [50];
  char * s = test2; // 3. Useless

  char output [50];
  char * t = output; // 3. Useless

  int i;  // 8. i shall be declared here.

  printf("Enter the secret string:\n");
  fgets(test2, 50, stdin);
  for (i=0;i<50;i++) if (test2[i] == '\n') test2[i] = '\0'; // 4. Remove the newline char and terminate the string where the newline char is.
  for (int i=0;i<49;i++) // 5. You should use memset here; 8. You should not declare 'i' here.
    *(output +i)='_';
   // 1. Either you close the block here, or you don't open one for just one line.
  output[49] = '\0'; // 6. You need to terminate your output string.

  while(strcmp(s,t) != 0) // 7. That will never work in the current state.
    printf("Enter a guess:");
    scanf("%c",&guess);
    while(getc(stdin) != '\n');
    printf("You entered: %c\n", guess);
    showGuess(guess,s, t );
    printf("%s\n",t);
  
  printf("Well Done!");
  return 0; // 2. int main requires that.

您的代码中的其他 cmets:

    您在for 循环之后打开了一个块,但从未关闭它。这可能会导致问题。 您将您的 main 声明为一个返回整数的函数...所以您至少应该在末尾添加 return 0;。 您似乎已经理解char * t = output; 复制output 的值并使用t 作为新副本的名称。这是错误的。您确实在复制某些内容,但您仅在 t 中复制了 output 的地址(也称为引用)。结果outputt引用的是同一个数据,如果修改outputt会被修改; 反之亦然。否则的话,那些ts 变量在当前状态下是没有用的。 您还需要从 test2 缓冲区中的输入中删除换行符。为此,我在fgets 之后添加了一行。 与其“手动”设置数组的所有字节,不如考虑使用memset 函数。 您实际上需要在“填充”后终止输出字符串,因此您应该在最后一个位置分配一个'\0'。 您将永远无法将 test2 字符串与输出字符串进行比较,因为当您的 test2 在其有意义的内容之后终止时,输出字符串用下划线填充。 虽然循环范围内的变量根据 C99 和 C11 有效,但它们在 ANSI C 中不是标准的;而且通常最好不要在循环中声明任何变量。

另外,“_ 空格”被称为“下划线”;)


这是一个可以满足您的需求的代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define LEN 50

int main()

    char phrase[LEN];
    char guessed[LEN];
    char guess;
    int i, tries = 0;

    puts("Please enter the secret string:");
    if(fgets(phrase, LEN, stdin) == NULL)
      return 1;
    for(i = 0; i < LEN && phrase[i] != '\n'; i++); // Detect the end of input data.
    for(; i < LEN; i++)                            // For the rest of the input data,
      phrase[i] = '_';                             // fill with underscores (so it can be compared with 'guessed' in the while loop).
    phrase[LEN - 1] = '\0';                        // NULL terminate 'phrase'

    memset(guessed, '_', LEN);                     // Fill 'guessed' with underscores.
    guessed[LEN - 1] = '\0';                       // NULL terminate 'guessed'

    while(strcmp(phrase, guessed) != 0)            // While 'phrase' and 'guessed' differ
    
      puts("Enter a guess (one character only):");
      if(scanf("%c", &guess) != 1)
      
        puts("Error while parsing stdin.");
        continue;
      
      if(guess == '\n')
      
        puts("Invalid input.");
        continue;
      
      while(getc(stdin) != '\n');                  // "Eat" the extra remaining characters in the input.
      printf("You entered: %c\n", guess);
      for(i = 0; i < LEN; i++)                     // For the total size,
        if(phrase[i] == guess)                     // if guess is found in 'phrase'
          guessed[i] = guess;                      // set the same letters in 'guessed'
      printf("Guessed so far: %s\n", guessed);
      tries++;
    
    printf("Well played! (%d tries)\n", tries);
    return 0;

如果您没有得到什么,请随时在 cmets 中提问。 :)

【讨论】:

你添加的while循环里面有什么?有条件但没有指令可以执行 有一个nop instruction: ;。它用于“丢弃”读数,直到满足条件。我已经编辑了我的答案来解释这一点。请注意,如果您想更容易地注意到您在循环中什么都不做,您可以将while 之后的; 替换为 不起作用:(。只是继续询问猜测而不显示输出,并且无限这样。 对不起我的错误。它几乎可以工作。它将输出字符串打印为比它长一个 _ 空格,因此字符串永远不会匹配 好的,我要编译你的代码并检查(我通常使用getc来读取字符,而不是scanf,所以也许有一个我没有想到的故障,用@ 987654360@,我马上去查)。【参考方案3】:

scanf 正在读取上一次迭代中输入的换行符。您可以使用 getc() 获取 '\n',如下所示:

scanf("%c",&guess);
getc(stdin);

..

这个改变对我有用。尽管@7heo.tk 给出了正确的解释和更简洁的代码

【讨论】:

【参考方案4】:

改变

scanf("%c",&guess);

scanf(" %c",&guess);

它应该忽略'\n'

【讨论】:

对我不起作用。输入所有字母后字符串永远不会匹配 虽然这可以忽略换行符,但它有以下缺点:它还会忽略输入中的所有空格、制表符或其他空白字符(来源:SCANF(3));它会循环遍历所有输入字符(即,如果您输入“abc”,它的行为就像您在循环中输入“a”然后“b”然后“c”一样),然后再询问用户另一个输入。

以上是关于Scanf 在循环中跳过(Hangman)的主要内容,如果未能解决你的问题,请参考以下文章

如何在循环中跳过项目

如何在 ddd (gdb) 中跳过过去的循环

使用 CSV 文件在循环中跳过第一行(字段)? [复制]

在循环中跳过一组值(在数组中) - Python3

在jquery中跳过循环中的数字

R:使用 tryCatch() 在循环中跳过请求失败 [500] 错误失败