Функции putchar() и getchar()

Заголовочный файл stdio.h известен не только функцией printf(), но и множеством других, которые связаны с вводом-выводом данных. Среди них выделяются функции для обработки каждого символа:  putchar() и getchar().

Функция putchar() в основном принимает символ или переменную, содержащую символ, и выводит на экран соответствующий ему символ. Можно передать этой функции и обычное целое число, однако символ может не появиться, если в таблице ASCII оно не определено. Например:

  char ch = 'c';
 
  putchar('a');
  putchar(98);
  putchar('\n');
  putchar(ch);

Результат:

ab
c

Функции putchar() и printf() некоторые задачи могут выполняться по-разному, но давать один исход. Хотя код исполнения будет разниться:

  char str[] = "Hello";
  int i;
 
  printf("%s\n", str); // первое Hello
 
  for (i = 0; str[i] != '\0'; i++) // второе Hello
    putchar(str[i]);
 
  printf("\n");

При выполнении этого кода вы увидите на экране два слова "Hello", каждое на новой строке. С putchar()  реализация задачи выглядит несколько запутанней. Строка как структура данных заканчивается нулевым по ASCII символом, который служит признаком её завершения в реализации вывода. Однако, если мы захотим добиться вывода строки с разделением символов другим символом, таким как тире, то даже с использованием  printf() это может стать сложной задачей:

  char str[] = "Hello";
  int i;
 
  for (i = 0; str[i] != '\0'; i++)
    printf("%c-",str[i]);
  printf("%c%c %c",'\b', '\0', '\n');
 
  for (i = 0; str[i] != '\0'; i++) {
    putchar(str[i]);
    putchar('-');
  }
  printf("%c%c %c",'\b', '\0', '\n');

Результат:

H-e-l-l-o
H-e-l-l-o

В выборе функции нужно ориентироваться на конкретную задачу и предпочтения.

Функция getchar() работает по принципу, отличному от putchar(), так как она не принимает аргументов. Когда getchar() вызывается, она читает из потока ввода один символ и возвращает его программе. Полученный символ можно присвоить переменной, использовать в различных выражениях, или вывести на экран через функции вывода.

  int a;
 
  a = getchar();
  printf("%c ", a);
  putchar(a);
 
  putchar('\n');

После ввода символа и нажатия клавиши Enter, этот символ дважды отобразится на экране:

u
u u

Первое отображение происходит из-за функции printf(), второе — благодаря  putchar(). Если перед Enter вы введете несколько символов, будет учтен только первый, остальное игнорируется. Вот пример кода:

  char a, b, c;
 
  a = getchar();
  putchar(a);
  b = getchar();
  putchar(b);
  c = getchar();
  putchar(c);
 
  printf("\n");

Как думаете, как он исполняется? После ввода каждого символа он сразу печатается на экране через функцию  putchar(), а затем запрашивается следующий, так происходит каждый раз, когда вызывается  getchar(). Если вы корректно введете первый символ и нажмете Enter, символ появится на экране. Введите второй и после Enter он также будет отображен. И тут программа завершится, не дав возможности ввести третий символ.

Проведем эксперимент "некорректного пользователя" и попробуем ввести несколько символов до нажатия Enter. После нажатия вы увидите только первые три символа, и программа завершится. Этот эффект объясняется не языком C, а специфическими особенностями буферизации ввода-вывода в операционных системах. Во время операций ввода-вывода выделяется буферная память, куда сначала помещаются поступающие символы, и только по специальному сигналу (например, при нажатии Enter) они передаются по месту назначения (на экран, переменную и т.д.).

Теперь, зная это, мы можем понять, что происходило в нашей программе. К примеру, второй вариант с "некорректным пользователем" проще понять. Первый введенный символ заносится в переменную a, срабатывает функция  putchar(a) , и символ отправляется в буфер. Поскольку Enter еще не был нажат, он остается в буфере, не отображаясь. По той же логике последовательно обработаны другие символы. Как только нажат Enter, буфер немедленно выводится на экран системой, а не программой, которая завершает вывод задолго до этого.

В начальном варианте программы видно только два символа, хоть было введено больше. Первый символ помещен в  a, выведен в буфер, затем Enter отправил его на экран, и символ \n присвоен b. Однако, переход на новую строку \n, видимо, не сохраняется в буфере.

Во многих учебных текстах на языке C приводится пример, где используя getchar(), вводятся символы с клавиатуры и сразу же печатаются на экране:

  int a;
 
  a = getchar();
  while (a != '\n') {
    putchar(a);
    a = getchar();
  }
  putchar('\n');

В переменной a всегда находится последний введенный символ, который, прежде чем стать новым, через  putchar()отправляется в вывод. Как только программа получает символ новой строки, работа завершается, но нажатие Enter приводит к демонстрации буфера на экране. Если условие цикла  while изменить и вместо  '\n' использовать другой символ, например, ";", программа продолжит обработку символов, даже после нажатия Enter. Таким образом можно вводить и выводить множество строк текста.

Задание:

Напишите программу, которая осуществляет ввод и вывод символов, используя любой символ, кроме '\n', в качестве признака завершения. Протестируйте ее.

При объединении функций putchar() и getchar(), часто используется более лаконичная запись. К примеру:

  while ((a = getchar()) != '~')
    putchar(a);
Задание:
  1. Объясните, почему сокращенная запись посимвольного ввода-вывода корректно работает. Подробно опишите последовательность действий в цикле while.
  2. Переделайте вашу программу на более краткую форму.

EOF

Возникает вопрос: как завершить считывание текста с клавиатуры или файла, не зная точное количество символов и не предваряя его конкретным символом? Как передать программе сигнал об окончании ввода, не используя конкретное значение?

Для этих целей в операционных системах и языках программирования предусмотрена специальная константа — EOF (end of file) — обозначающая конец потока ввода или файла. Значение EOF варьируется, однако чаще всего это число -1. В коде принято писать именно идентификатор EOF, а не чисто числовое значение. EOF определен в stdio.h.

Для передачи значения EOF функции getchar() в системе GNU/Linux используют Ctrl + D, а в Windows - Ctrl + Z.

Задание:

Измените вашу программу так, чтобы она прекращала считывание символов, когда получает сигнал EOF.

Решение задач

Хотя функции getchar() и putchar() могут показаться простыми, они часто применяются на практике, так как задача посимвольного анализа данных при вводе-выводе не так уж редка. Используя только getchar(), можно, например, сформировать массив символов (строку), отсеяв лишние символы. Пример этого подхода: из ввода выбраны только цифры, хотя могут быть введены любые символы:

span>#include <stdio.h>
         
#define N 100
 
main () {
  char ch;
  char nums[N];
  int i;
 
  i = 0;
  while ((ch = getchar()) != EOF && i < N-1)
    if (ch >= 48 && ch <= 57) {
      nums[i] = ch;
      i++;
    }
 
  nums[i] = '\0';
 
  printf("%s\n", nums);
}

Здесь ввод может прерваться не только сигналом EOF, но и по достижению предела массива (i < N-1). В условии while предусмотрен контроль, чтобы числовой код символа был в диапазоне [48, 57], который соответствует цифрам (0-9) в таблице ASCII. Если введен символ является цифрой, он добавляется в массив по индексу i, после чего i увеличивается на 1. Заканчивается алгоритм добавлением нулевого символа к массиву символов, так как по условиям строки требуется таковой (именно поэтому четко оставлено место —  N-1).

Задание:
  1. Создайте программу, которая будет считать количество символов и строк, введенных пользователем.
  2. Разработайте программу, подсчитывающую количество слов в строке.

Вопросы для самопроверки:

  1. Какие функции для обработки символов выделяются в заголовочном файле stdio.h?
  2. Какой символ используется для завершения строки в C?
  3. Чем отличается работа функций putchar() и getchar()?
  4. Что происходит, если вызвать getchar() после ввода нескольких символов перед клавишей Enter?
  5. Как передать значение EOF функции getchar() в Linux?

Программа курса:

  1. Описание курса
  2. Введение в язык программирования C
  3. Типы данных в C и форматированный вывод
  4. Символьные типы и управляющие символы в C
  5. Операторы ветвления и switch в C
  6. Циклы и операторы в языке C
  7. Битовые операции в языке C
  8. Посимвольный ввод и вывод в C - буферизация
  9. Переменные, адреса и указатели в C
  10. Передача аргументов по ссылке и значению в C
  11. Форматированный ввод данных с использованием scanf
  12. Генерация псевдослучайных чисел на C
  13. Адресная арифметика в массивах C
  14. Передача массивов в функции и указатели
  15. Строки в языке C - особенности и функции работы
  16. Функции работы со строками в C
  17. Работа со структурами в C - создание и применение
  18. Динамические структуры данных в C
  19. Ввод и вывод данных из файлов в языке C
  20. Передача аргументов в C и работа с файлами
  21. Препроцессор в языке C - директивы и макросы
  22. Создание и компиляция многофайловых программ в C
  23. Использование статических и динамических библиотек в C