[Язык C] Загадочный scanf и %c: вводим фразу, а не символ

Хочу отдельным постом поделиться с весьма нетривиальной с точки зрения новичка в C задачкой. Она приходятся восьмым вопросом для самоконтроля в 6 главе  книжки Стивена Прата “C Primer Plus”.

Задание таково:

Что выведут приведенные далее программы в случае ввода Go west, young man! ?
(В кодировке ASCII символ ! следует за символом пробела).

#include <stdio.h>
int main(void) {
  char ch;
  scanf("%c", &ch);
  while (ch != 'g') {
    printf("%c", ch);
    scanf("%c", &ch);
  }
  return 0;
}

Казалось бы все просто. Должно вывестись G – ведь %c в scanf является запросом одного символа. Точнее даже так – программа написана неверно, т.к. мы вводим целую фразу при помощи %c, тогда как ее надо вводить как строку %s. Ну да ладно. Это обучающий пример и все такое, поэтому предположим.. Итак, ответ G.

Не тут-то было. Программа выводит:

Go west, youn

Как так? Потыкавшись, я решил потерять девственность на stackoverflow и создал там свой первый топик. Оказалось, дело вот в чем (по мотивам ответа уважаемого Youssef13).

Удобно рассмотреть этот пример, добавив перенос строк:

#include <stdio.h>
int main(void)
{
  char ch;
  scanf("%c", &ch);
  while (ch != 'g')
  {
    printf("PRINTING: %c\n", ch);
    scanf("%c", &ch);
  }
  return 0;
}

Смысл в том, что scanf работает таким образом – образуется к “внутреннему” курсору в stdin (standard input или стандартный поток ввода), который следит за тем, что было прочитано. Когда мы вводим Go west, young man!, символ G сохраняется в переменной ch, а курсор в stdin перемещается на одну позицию, к символу o. Затем, в следующий раз, когда мы вызываем scanf и она уже смотрит что находится на позиции курсора – и там оказывается o.

Если же мы хотим в последующих scanf проигнорировать то, что было введено ранее, мы должны это прочитать (или применить fseek в stdin, который работает в Windows, но не сработает в Линукс):

#include <stdio.h>
int main(void)
{
  char ch;
  scanf("%c", &ch);
  while (getchar() != '\n'); // Get to the end of the stdin.
// The previous line can be replaced with fseek(stdin, 0, SEEK_END);
// but it will work under Windows only.

  while (ch != 'g')
  {
    printf("PRINTING: %c\n", ch);
    scanf("%c", &ch);
  }
  return 0;
}

Буду рад вашим комментариям!

This entry was posted in С (Си). Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *