scanf: %[^\n] пропускает второй ввод, а %[^\n] — нет. Зачем?

Рассмотрим следующий код:

#include <stdio.h>

int main (void)
{
  char str1[128], str2[128], str3[128];

  printf ("\nEnter str1: ");
  scanf ("%[^\n]", str1);
  printf ("\nstr1 = %s", str1);

  printf ("\nEnter str2: ");
  scanf ("%[^\n]", str2);
  printf ("\nstr2 = %s", str2);

  printf ("\nEnter str3: ");
  scanf ("%[^\n]", str3);
  printf ("\nstr3 = %s", str3);

  printf ("\n");
  return 0;
}

Когда он выполняется, только первые scanf останавливаются для подсказки. Программа не останавливается в течение следующих scanf с. Но если изменить строку формата с "%[^\n]" на " %[^\n]" (обратите внимание на пробел перед %), то все работает нормально. Принимается ли автоматически какой-либо существующий символ новой строки из предыдущего входного буфера? Но сброс stdin этого не решает.

Какова причина этого.


person phoxis    schedule 21.05.2011    source источник
comment
Не ответ, но ваша жизнь будет намного проще, если вы посмотрите на fgets для этого. Обычно я рекомендую избегать scanf в большинстве ситуаций, но особенно здесь вы используете очень мощную и сложную в использовании функцию для очень простой задачи.   -  person Chris Lutz    schedule 22.05.2011
comment
это нормально, с моей стороны нет проблем с вводом строки, но есть любопытство по поводу поведения или этой функции.   -  person phoxis    schedule 22.05.2011


Ответы (6)


Вам просто нужно «использовать» символ '\n' после того, как вы прочитали то, что хотите. Используйте следующую директиву формата:

"%[^\n]%*c"

Который будет читать все до новой строки в строку, которую вы передаете, а затем будет потреблять один символ (новая строка), не назначая его чему-либо (это '*' является «подавлением присваивания»).

В противном случае новая строка остается во входном потоке, ожидая немедленного завершения последующих директив формата "%[^\n]".

Проблема с добавлением символа пробела в директиву формата (" %[^\n]") заключается в том, что пробел будет соответствовать любому пробелу. Таким образом, он съест новую строку с конца предыдущего ввода, но также съест любые другие пробелы (включая несколько новых строк).

Обновите свой пример:

  char* fmt = "%[^\n]%*c";

  printf ("\nEnter str1: ");
  scanf (fmt, str1);
  printf ("\nstr1 = %s", str1);

  printf ("\nEnter str2: ");
  scanf (fmt, str2);
  printf ("\nstr2 = %s", str2);

  printf ("\nEnter str3: ");
  scanf (fmt, str3);
  printf ("\nstr2 = %s", str3);

  printf ("\n");
person Michael Burr    schedule 21.05.2011
comment
подавление присваивания - хорошее решение, на самом деле я впервые увидел его в хорошем использовании. - person phoxis; 22.05.2011
comment
... ‹headdesk› Я провел последний месяц, используя для этого %*1[\n]; +1 и спасибо. - person David X; 22.05.2011
comment
Является ли это де-факто лучшим подходом, чем вызов getchar для использования завершающей новой строки? Требует ли эта спецификация формата определенного стандарта (например, C99)? Спасибо! - person Cloud; 08.09.2014
comment
@Dogbert: если честно, я не большой поклонник использования scanf() для анализа ввода. В большинстве случаев я, вероятно, предпочел бы полностью прочитать строку ввода в буфер, а затем проанализировать этот буфер, используя sscanf(), strtok() или что-то еще лучше. - person Michael Burr; 09.09.2014
comment
@MichaelBurr Я тоже так подумал. Я просто не имел большого опыта, если вообще имел дело с этим спецификатором формата printf, за исключением *, и всегда приятно посмотреть, есть ли лучшая привычка, которую я должен приобрести. Спасибо! - person Cloud; 09.09.2014
comment
Проблема с этой версией заключается в том, что она не будет соответствовать или потреблять, если ввод является простым /n (пользователь нажимает ввод без единого символа) - person lerosQ; 26.11.2016

Когда вы используете scanf() для чтения строк, ваша строка формата (%[^\n]) указывает функции читать каждый символ, кроме '\n'. Это оставляет символ '\n' во входном буфере. Поэтому, когда вы пытаетесь прочитать str2 и str3, scanf() каждый раз обнаруживает, что первым в буфере является '\n', и из-за строки формата не удаляет его из входного буфера. Что вам нужно, так это getchar() между моментами чтения из входного буфера (часто помещается сразу после scanf()). Поскольку в буфере уже есть '\n', ваша программа не будет казаться зависшей, потому что ей не придется ждать ввода для получения getchar(). Попытайся. :)

Для тех, кто не знает, что делает этот модификатор scanf(), вот соответствующая выдержка из http://linux.die.net/man/3/scanf -

[

Соответствует непустой последовательности символов из указанного набора допустимых символов; следующий указатель должен быть указателем на char, и должно быть достаточно места для всех символов в строке плюс завершающий нулевой байт. Обычный пропуск начального пробела подавляется. Строка должна состоять из символов в (или не в) определенном наборе; набор определяется символами между символом открывающей скобки [ и закрывающей скобкой ]. Набор исключает эти символы, если первый символ после открывающей скобки является циркумфлексом (^). Чтобы включить в набор закрывающую скобку, сделайте ее первым символом после открывающей скобки или циркумфлекса; любая другая позиция завершит сет. Символ дефиса - тоже специальный; при размещении между двумя другими символами он добавляет все промежуточные символы в набор. Чтобы включить дефис, сделайте его последним символом перед последней закрывающей скобкой. Например, [^]0-9-] означает набор «все, кроме закрывающей скобки, от нуля до девяти и дефиса». Строка заканчивается появлением символа, не входящего в набор (или, с циркумфлексом, в) или когда заканчивается ширина поля.

person Community    schedule 21.05.2011
comment
тот же вопрос и к вам: тогда что означает конечный пробел в %[^\n] . Это будет работать правильно. - person phoxis; 21.05.2011
comment
Когда вы добавляете начальный пробел в строку формата, он оценивается как любой символ, для которого функция isspace() вернет истинное значение, включая '\n', ' ', '\t' и т. д. Это означает, что пробел перед следующим непробельным символом в строке формата игнорируется. - person ; 21.05.2011
comment
Если вы используете завершающий пробел (пробел в конце строки формата), ваша программа зависает. Вместо того, чтобы вводить пустую строку или использовать символы пробела, вы должны указать, каким будет значение str2. То же самое для str3, и когда он запрашивает значение str3, вы вводите значение, которое не будет отображаться. Например, используйте "%[^\n] " вместо "%[^\n]"/" %[^\n]". Ваша новая строка будет проигнорирована, как указано... и съедена конечным пробелом в строке формата. Так как scanf не получил символ '\n', ему потребуются дополнительные данные. str2 будет иметь это значение, а str3 будет иметь значение str2. - person ; 21.05.2011
comment
Обратите внимание, что стандарт C99 на самом деле не определяет поведение "%[". - person Chris Lutz; 22.05.2011
comment
@Chris Lutz: Совершенно верно. Однако может показаться, что это часть спецификации POSIX.1, поскольку оба MSDN и SuSv2 указывают Это. - person ; 22.05.2011
comment
@Chris - что ты имеешь в виду, C99 не указывает "%["? Это в 17.9.6.2 Функция fscanf. - person Michael Burr; 22.05.2011
comment
Я посмотрю, что стандарты говорят о скансете. - person phoxis; 22.05.2011
comment
@Michael Burr - моя копия IIRC (предварительный черновик) оставила поведение реализации "%[" определенным. - person Chris Lutz; 22.05.2011
comment
@Chris: есть части, которые определяются реализацией, например, использование '-' для указания диапазона. - person Michael Burr; 22.05.2011

ТАКЖЕ: Чтобы прочитать строку:

scanf("%[^\n]\n", a);

// это означает чтение до тех пор, пока вы не встретите '\n', а затем удалите это '\n'

:)

person MLSC    schedule 05.01.2014
comment
Окончательный '\n' использует не только '\n', но и любое количество пробелов char, таких как '\t' и ' ', а также '\n'. Он будет продолжать потреблять (выбрасывать) пробелы до тех пор, пока не будет найден не пробел, который будет помещен обратно в stdin для следующей операции ввода-вывода. - person chux - Reinstate Monica; 06.01.2014
comment
что вы имеете в виду под мусором? Вы имеете в виду, что он устранен? - person lmiguelvargasf; 07.04.2016

Просто добавим немного дальше к приведенному выше ответу. Если мы хотим удалить какой-то определенный шаблон, предположим, числа 0-9, из входного потока, тогда нам придется использовать getchar() для очистки буфера.

scanf("%[^0-9\n]", str1);
while(getchar() != '\n'); // this approach is much better bcz it will
                         // remove any number of left characters in buffer.
scanf("%c", &ch);

Так что здесь, если вы передадите ashish019, то только ashish будет скопирован в str, а 019 останется в буфере, поэтому для очистки вам нужно несколько раз получить char().

person Ashish Maurya    schedule 13.11.2017

Просто используйте getchar() после функции scanf().

person Abdus    schedule 04.12.2017
comment
Главный смысл вопроса в то время заключался в том, чтобы понять поведение. - person phoxis; 05.12.2017

используйте fflush(stdin) для очистки входного буфера после чтения каждого ввода.

person Murtaza Amravatiwala    schedule 15.12.2012
comment
fflush() предназначен для использования только с выходными потоками. Может показаться, что это работает, но стандарт определяет неопределенное поведение при использовании со стандартным вводом. Поэтому не используйте его со стандартным вводом. - person Maxim Chetrusca; 16.11.2014