Scanf пропускает цикл while в C

Я пытаюсь разработать простую текстовую игру с палачом, и основной цикл игры начинается с приглашения ввести предположение для каждой буквы, затем проверяется, есть ли буква в слове, и отнимает жизнь, если она нет. Однако, когда я запускаю игру, подсказка появляется дважды каждый раз, и программа не ждет ввода пользователя. Он также забирает жизнь (одну жизнь, если это был правильный ввод, и две, если это не так), поэтому все, что он принимает, не совпадает с предыдущим. Вот мой игровой цикл, немного упрощенный:

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 думает, что он что-то принял, хотя этого не произошло, но я понятия не имею, почему. Есть у кого-нибудь идеи? Я использую gcc 4.0.1 в Mac OS X 10.5.


person benwad    schedule 03.11.2009    source источник


Ответы (10)


Когда вы читаете ввод с клавиатуры с помощью scanf(), ввод считывается после нажатия клавиши ввода, но новая строка, сгенерированная клавишей ввода, не используется при вызове scanf(). Это означает, что в следующий раз, когда вы будете читать данные из стандартного ввода, вас будет ждать новая строка (что приведет к немедленному возврату следующего scanf() вызова без данных).

Чтобы этого избежать, вы можете изменить свой код примерно так:

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

%*c соответствует одному символу, но звездочка указывает, что этот символ нигде не будет сохранен. Это приводит к использованию символа новой строки, сгенерированного клавишей ввода, так что в следующий раз, когда вы вызываете scanf(), вы начинаете с пустого буфера ввода.

Предостережение: если пользователь нажимает две клавиши, а затем ввод, scanf() вернет первое нажатие клавиши, съест второе и оставит новую строку для следующего вызова ввода. Подобные причуды - одна из причин, по которой многие программисты избегают scanf() и его друзей.

person bta    schedule 03.11.2009
comment
Анонимный редактор попытался добавить текст: он должен быть% * c% c, а не% c% * c, для моего кода, по крайней мере, для этого сообщения, и изменить scanf, чтобы он соответствовал. Я отклонил правку, поскольку не уверен, что она правильная, но хочу поднять ее в качестве комментария для рассмотрения. - person Craig Ringer; 15.08.2012
comment
@Craig Ringer-% c% * c съест новую строку, прикрепленную к текущему входному символу. Вы могли бы использовать% * c% c после факта, чтобы съесть новую строку из предыдущего ввода. Оба могут быть технически правильными, но лучше убирать за собой, а не оставлять случайные символы в буфере для кого-то другого. - person bta; 16.08.2012
comment
На самом деле важно, где вы поместите% * c, рассмотрите этот фрагмент кода, где% * c% i устраняет проблему с бесконечным циклом, а% i * c не будет: getagin: printf("Please enter a number:\n"); isnumber = scanf("%*c%i", &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; } - person ilgaar; 01.01.2014
comment
Если код хочет использовать завершающий '\n', а не что-то еще, используйте "%*1[\n]" вместо "%*c". - person chux - Reinstate Monica; 17.10.2018
comment
@chux Хотя технически точнее, поскольку существует законный случай, когда вы не хотите пропускать пробелы, общий " %c" будет проще. - person alx; 08.05.2019

Новые строки.

При первом прохождении цикла scanf () считывает символ. Затем он читает новую строку. Затем он читает следующий символ; повторение.

Как исправить?

Я редко использую scanf (), но если вы используете строку формата "%.1s", она должна пропускать пробелы (включая символы новой строки), а затем читать непробельный символ. Однако он будет ожидать массив символов, а не одиночный символ:

char ibuff[2];

while ((scanf("%.1s", ibuff) == 1)
{
    ...
}
person Jonathan Leffler    schedule 03.11.2009
comment
Я думаю, мне это решение нравится больше, чем мое, поскольку оно позволяет пользователю просто вводить оставшуюся часть слова (мы играем в «Палач», помните?), Как только он узнает, что это такое. - person T.E.D.; 03.11.2009
comment
scanf(" %c", &currentguess) также должен помочь без изменения типа переменной. Пробел в строке формата scanf соответствует любому количеству пробелов во входных данных, включая отсутствие пробелов. - person caf; 03.11.2009

Разбейте проблему на более мелкие части:

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
person jheddings    schedule 03.11.2009
comment
Хороший ответ: изложите проблему до ее сути, покажите и объясните (не говоря уже о других плакатах!). Этот ответ заслуживает большего, чем моя единственная модификация. - person Roboprog; 04.11.2009
comment
Спасибо за голосование! Я хотел быть уверенным в своем ответе до того, как отправлю сообщение, и к тому времени, как я его подтвердил, он уже получил ответ. Я подумал, что было бы полезно вместо этого просто записать мой метод. - person jheddings; 04.11.2009

Просто предположение, но вы вводите один символ с помощью scanf, но пользователь должен ввести предположение плюс новую строку, которая используется как отдельный символ предположения.

person Jim Garrison    schedule 03.11.2009

scanf(" %c", &fooBar);

Обратите внимание на пробел перед %c. Это важно, потому что оно соответствует всем предшествующим пробелам.

person Nick    schedule 15.09.2011

Джим и Джонатан правы.

Чтобы ваша строка scanf делала то, что вы хотите (используйте символ новой строки, не помещая его в буфер), я бы изменил его на

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

(обратите внимание на \n)

Однако обработка ошибок здесь ужасна. По крайней мере, вы должны проверить возвращаемое значение из scanf на 1 и игнорировать ввод (с предупреждением), если он его не возвращает.

person T.E.D.    schedule 03.11.2009
comment
@AnttiHaapala - В этом случае это не нежелательно. Все, что он пытается сделать, это вводить односимвольные команды, не создавая двух входов. Поскольку пробел не является командой в этой схеме, он не хочет этого. Вероятно, поэтому он принял более сложный ответ, в котором также пропускаются ведущие пробелы. - person T.E.D.; 18.09.2017
comment
Ваш ответ совсем другой. Он будет ждать , пока не будет введен другой непробельный символ, прежде чем обрабатывать первый символ. Таким образом, это бесполезно для решения проблемы. - person Antti Haapala; 18.09.2017
comment
@AnttiHaapala - Только что попробовал в онлайн-компиляторе. Он действительно ждет второго ввода. Этот второй ввод не теряется, но после этого программа всегда на одну команду отстает в своей обработке. Не совсем неработоспособный, но определенно неоптимальный. Еще одна причина, чтобы лучше полюбить ответ Леффлера. - person T.E.D.; 18.09.2017

Я заметил пару моментов:

  • scanf("%c") прочитает 1 символ и сохранит ENTER во входном буфере до следующего раза в цикле
  • вы увеличиваете i, даже если символ, считанный пользователем, не соответствует символу в secretWord
  • когда covered[j] когда-либо будет '-'?
person pmg    schedule 03.11.2009

Я предполагаю: ваш код обрабатывает новую строку как одну из догадок при вводе данных. Я всегда избегал семейства * scanf () из-за неконтролируемой обработки ошибок. Попробуйте вместо этого использовать fgets (), а затем вытащите первый символ / байт.

person Roboprog    schedule 03.11.2009
comment
Хорошо, все перескочили на завершающий байт / символ новой строки в течение минуты. Таким образом, предлагается прочитать строку ввода, затем проверить, что у вас есть, очистить ее и при необходимости выявить ошибки. Возможно, в функции get_answer ()? - person Roboprog; 03.11.2009

Я вижу в вашем коде пару вещей:

  1. scanf возвращает количество прочитанных элементов. Вы, вероятно, захотите обработать случаи, когда он возвращает 0 или EOF.
  2. Я предполагаю, что пользователь нажимает букву + Enter, и вы получаете новую строку как второй символ. Легкий способ проверить - добавить отладочную инструкцию printf, чтобы показать, какой символ был введен.
  3. Ваш код будет соответствовать только первому вхождению совпадающей буквы, т.е. если слово было "test" и пользователь ввел 't', ваш код будет соответствовать только первому 't', а не обоим. Чтобы справиться с этим, вам нужно настроить свой первый цикл.
person sdtom    schedule 03.11.2009

Когда вы вводите символ, вы должны ввести пробельный символ, чтобы продолжить. Этот пробельный символ присутствует во входном буфере, 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.
person Mohit    schedule 03.10.2010