Форматный ввод есть специальная разновидность буферизованного ввода, где чтение информации из потока сопровождают форматные преобразования данных базовых типов. Эти преобразования обеспечивает синтаксический анализ символьных данных из потока ввода. В системе программирования C форматный®Швод реализуют функции функции fscanf, scanf и sscanf.
Универсальную возможность форматирования ввода символьной информации из файлового или стандартного потока предоставляет использование функции fscanf. Спецификация формата ее вызова имеет вид:
int fscanf(FILE* stream, const char* format[, ptr]...);
Функция fscanf читает символьную информацию из потока ввода, который обозначает указатель stream, преобразует ее по спецификации, заданной в символьной строке format, и адресует результат форматных преобразований через набор своих необязательных аргументов ptr. Функция fscanf может иметь произвольное число аргументов, через которые ей передаются адреса переменных базовых типов данных для сохранения результатов форматных преобразований. Все аргументы ptr должны быть указателями базовых типов данных. Каждый указатель ptr должен соответствовать по типу результату форматных преобразований, который он адресует.
Форматные преобразования потока ввода должны обеспечивать представление входной символьной информации в формате базовых типов данных языка C. Для выполнения форматных преобразований поток ввода рассматривается как последовательность информационных полей, которые могут быть разделены необязательными промежутками из произвольного числа символов перевода строки, табуляции или пробела. Обработка промежутков сводится к тому, чтобы пропустить все символы до начала очередного информационного поля потока ввода. Каждое информационное поле определяется как максимальная последовательность символов, которые допустимы по заданной спецификации форматных преобразований в символьной строке format для представления в формате соответствующего базового типа данных при вводе из потока.
Для управления форматным преобразованием потока ввода символьная строка format может содержать два типа объектов: обычные символы и спецификаторы формата ввода. Функция fscanf последовательно рассматривает их, чтобы интерпретировать символы информационных полей потока ввода. Эти объекты форматной строки могут разделять произвольное число промежутков из символов пробела, табуляции и перевода строки, которые соответствуют пустым позициям при вводе.
Обработка обычного символа форматной строки вызывает попытку прочитать очередной символ из потока ввода без преобразования и сохранения через указатели ptr. Чтение завершается корректно, если текущие символы из потока ввода и форматной строки format совпадают. Если они различны, происходит аварийное завершение функции fscanf без изменения позиции указателя потока ввода. Таким образом, обычные символы форматной строки требуют, чтобы поток ввода содержал точно такие же литеры.
Спецификатор формата ввода декларирует характер преобразования текущего информационного поля потока ввода к формату базового типа. Когда данное преобразование может быть выполнено, его результат будет сохранен по адресу, который передает соответствующий по порядку указатель ptr, если сохранение не подавлено принудительно. Когда содержимое информационного поля потока ввода не соответсвует заданному формату его преобразования, выполнение функции fscanf будет аварийно завершено. Каждый спецификатор формата ввода должен начинаться с символа '%'. После него могут быть перечислены параметры, которые задают флаг отмены сохранения, ширину поля ввода и тип форматного преобразования. В общем случае спецификатор формата имеет следующий вид:
%[*][width]type
Необязательный флаг * обозначает принудительный пропуск при вводе поля, которое определяет данный спецификатор. Вводимое значение не должно быть передано никакой переменной через набор указателей ptr. Таким образом, флаг * подавляет ввод данных.
Необязательный параметр width определяет максимальное число символов, которые могут быть введены по данному спецификатору для преобразования и передачи через соответствующий указатель ptr.
Параметр type спецификатора формата задает буквенное обозначение типа преобразований, который указывает способ интерпретации соответствующего информационного поля потока ввода. Может быть задано преобразование к формату целого числа со знаком или без знака в системе счисления по основанию 8, 10, 16 и вещественного числа с фиксированной точкой или в экспоненциальной форме. Кроме того, поле информационное потока ввода может быть интерпретировано как отдельный символ, строка символов или класс символов.
Чтобы декларировать преобразование поля потока ввода к различным формам представления целых чисел, параметр type должен иметь значение d, i, u, o или x. Буквы d и i представляют информационное поле ввода в формате целого десятичного числа с необязательным знаком. Указатель ptr, через который передаются результаты преобразований, должен иметь тип (int* ). Буквы u, o и x применяются, чтобы интерпретировать поле ввода в формате целого числа без знака в системах счисления по основанию 10, 8 и 16, соответственно. Указатель ptr, через который передаются результаты этих преобразований, должен иметь тип (unsigned* ).
Форматные преобразования при вводе целочисленных данных демонстрирует следующий фрагмент исходного кода, где функция scanf получает значения трех целых чисел в системах счисления по основаниям 8, 10 и 16 из потока стандартного ввода:
unsigned oct, dec, hex;
fscanf(stdin, "%o%u%*d%x", &oct, &dec, &hex);
При стандартном вводе, например, следующих чисел: 33 27 99 1b, все переменные данного фрагмента программы получат одинаковое значение, равное 27, а информационное поле 99 будет пропущено из-за наличая флага '*', который подавляет его обработку.
Значения параметра типа целочисленных преобразований могут быть указаны с модифицирующим префиксом h или l, например, hx или ld. Модификаторы h и l декларируют, что поле ввода преобразуется к формату короткого или длинного целого, соответственно. Результаты преобразований адресуются через указатели ptr, которые имеют типа (short* ) или (long* ). Длинное целое будет также получено, если параметр типа задан заглавной буквой D, U, O или X без модификатора l.
Для преобразования поля ввода к формату вещественного числа в параметре type используются буквы f, e или g, которые позволяют интерпретировать вещественные числа с фиксированной точкой или в экспоненциальной форме. Указатель ptr, через который передаются результаты этих преобразований, должен иметь тип (float* ). Например, следующая спецификация в функции fscanf: "%3f%f" предназначена для получения значений двух вещественных чисел, причем длина информационного поля первого числа ограничена тремя знаками. В частности, входной поток, символьный набор 388388E-3, будет интепретирован по данной спецификации как пара вещественных значений: 388.0 и 0.388, которые должны быть переданы по адресам двух переменных типа float.
Спецификация параметра типа для вещественных преобразований, также как для целочисленных, может содержать модифицирующий префикс l, который допускает представление очередного поля ввода в формате действительного числа с двойной точностью. Результат преобразований адресуется через указатель ptr типа (double* ). Если модификатор задан заглавной буквой L вместо строчной, то поле ввода преобразуется к типу (long double).
Чтобы обеспечить ввод строки символов, параметр typerКолжен быть задан буквой s. Соответствующий указатель ptr должен иметь тип (char *) и адресовать массив символов. Размер адресуемого массива символов должен быть по крайней мере на единицу больше, чем длина поля ввода, чтобы гарантировать сохранение всех введенных символов и кода '\0', который добавляется автоматически. При данной спецификации границей поля ввода считается промежуток. Поэтому входная информация не может содержать символов пробела, табуляции и перевода строки.
Для ввода одиночных символов следует применить спецификатор формата %c. Эта спецификация автоматически подавляет пропуск начальных промежутков. Чтобы отменить подавление, когда нужно получить из потока ввода первый символ, отличный от промежутка, следует декларировать спецификатор %1s. Если нужно прочитать из потока ввода заданное число символов, включая промежутки, необходимо указать значение параметра ширины поля ввода. Во всех случаях результат ввода сохранятся через указатель типа (char* ), который адресует либо один символ, либо массив символов соответствующей длины.
Наиболее универсальный способ обработки ввода обеспечивает спецификация класса допустимых символов входного потока. Класс символов определяется как конечное множество символов, из которых может состоять поле ввода. Чтобы задать класс символов, небходимо перечислить допустимые символы в спецификаторе формата ввода внутри пары квадратных скобок [...]. Вместо перечисления, в квадратных скобках можно указать диапазон символов, где первый и последний допустимый символы разделяет знак “тире”. Такая запись означает, что любой символ, код которого не меньше кода первого и не больше кода последнего символа диапазона, будет считаться допустимым в поле ввода. Например, спецификация %[0123456789] эквивалентна записи %[0-9]. Символ “тире” обозначает самого себя, если в паре разделенных им символов код первого символа больше кода второго, либо когда “тире” стоит на первом или на последнем месте в скобках класса символов. Кроме того, в классе символов можно специфицировать больше, чем один диапазон, если это необходимо. Например, спецификация %[a-zA-Z] допускает ввод любого символа, который является буквой латинского алфавита.
В некоторых случаях более удобно задать класс символов, указав символы, недопустимые для ввода из данного поля входного потока. Все остальные символы считаются допустимыми для ввода в данном классе. Недопустимые символы перечисляются в квадратных скобках после служебной литеры '^', которая должна занимать первую позицию в скобках.
Аналогичным образом можно исключить диапазон недопустимых символов класса, определенный по спецификации с символом “тире”. Например, спецификация %[^0123456789] или %[^0-9] обозначает, что любой символ, кроме цифры, является допустимым для ввода.
Результаты форматных преобразований поля ввода по любой спецификации класса символов должны передаваться через указатель ptr типа (char* ), который адресует массив символов. Размер адресуемого массива символов должен быть достаточным для сохранения входного поля, плюс завершающий символ с кодом '\0', который добавляется автоматически.
Кроме перечисленных выше форматных преобразований для различных базовых типов данных предусмотрена возможность измерения объема информации в потоке ввода. Чтобы выполнить такое измерение, значение параметр type спецификатора формата ввода должно быть задано буквой n. Результаты измерений передаются через указатель на целое число, которое сохраняет количество символов, прочитанных из полей потока ввода по спецификации форматной строки до момента измерения. Измерение не приводит к чтению информации из потока ввода.
Применение форматных преобразований особенно удобно, когда поток ввода образует смесь данных различных типов, которые упорядочены определенным образом. Применение комплексной спецификации преобразований формата иллюстрирует исходный код прикладной функция chkdate, которая осуществляет разбор строки символического представления даты из входного потока дат, возвращая ее длину после примитивного анализа диапазона числовых полей. При разборе входного потока ввода предполагается, что строка даты состоит из семи информационных алфавитно-цифровых полей. Информационные поля строки даты должны содержать, соответственно, название месяца, число месяца, день недели, время суток (часы, минуты, секунды) и год. Названия месяца и дня недели должны быть заданы в общепринятой сокращенной нотации и обработаны по спецификациям символьных данных. Остальные информационные поля строки даты должны быть интерпретированы по формату целых чисел без знака. Для разделения полей времени используются символы двоеточия, которые должны быть пребразованы по спецификациям одиночных символов. Для разделения остальных полей могут быть использованы стандартные промежутки из произвольного числа символов пробела и табуляции. Приведенный ниже исходный код прикладной функции chkdate обеспечивает интерпретацию любой строки даты в рассмотренном формате, используя вызов функция scanf с комплексной спецификаций алфавитно-цифровых преобразований.
/* Разбор полей строки даты */
int chkdate(FILE* stream) {
unsigned monthday;            /* число месяца */
char weekday[4];              /* день недели  */
char month[4];                /* месяц */
unsigned year;                /* год */
unsigned hour;                /* часы */
unsigned minute;              /* минуты */
unsigned second;              /* секунды */
char colon[2];                /* двоеточие */
int length;                   /* длина строки даты */
/* Ввод даты */
fprintf(stream, "%3s%2u%3[a-zA-Z]%2u%c%2u%c%2u%u%n",
        month, &monthday, weekday,
        &hour, &colon[0], &minute, &colon[1], &second,
        &year,
        &length);
/* Анализ числовых полей даты */
if((monthday < 1) || (monthday > 31) || (year < 1) ||
  (hour > 23) || (minute > 59) || (second > 59))
  length = 0;
/* Возврат длины строки даты */
return(length);
} /* chkdate */

Функция chkdate осуществляет разбор строки даты входного потока, который адресует указатель stream, интерпретируя содержимое ее информационных полей, чтобы вычислить их суммарную длину с помощью комбинированной спецификации формата функции fscanf. Например, следующая строка потока ввода:
Sep 1 Sat 08:30:00 2001
будет интерпретирована как 1-е сентября 2001 года, суббота, 8 часов, 30 минут, 00 секунд. Для получения названий месяца (Sep) и дня недели (Sat) функция fscanf адресует символьные массивы из четырех байтов по форматам: %3s и %3[a-zA-Z], соответственно. Для получения целочисленных значений дня месяца (1) и времени (08:30:00) использован формат %2u. Разделители полей времени идентифицируются через символьный формат %c. Завершающее поле года (2001) обрабатывается по спецификации %u для целых чисел без знака. Вычисление суммарной длины рассмотренных полей даты гарантирует измерительный спецификатор %n. Для приведенной выше строки даты будет вычислено значение 23.
Кроме функции fscanf, форматирование ввода обеспечивают функции scanf и sscanf, которые идентичны функции fscanf по спецификациям форматных преобразований и необязательных аргументов. Их применяют для форматной обработки потока стандартного ввода и строки символов.
В частности, функция scanf выполняет форматные преобразования данных из потока стандартного ввода. Обращение к ней эквивалентно вызову функции fscanf, когда указатель потока (stream) специфицирован значением stdin. Так как поток ввода функции scanf определен однозначно, то при вызове ей не передается указатель потока. Следует учитывать, что после sEрматных преобразований строки потока стандартного ввода, как правило, остается непрочитанный символ перевода строки.
Когда входная информация для форматных преобразований сосредоточена в строке символов вместо файлового потока или потока стандартного ввода, применяется функция sscanf. Вместо указателя потока (stream) ей должен быть передан адрес строки символов, которая содержит входную информацию для форматных преобразований. Форматные преобразования строки символов, которые реализует функция sscanf, эквивалентны обработке потока ввода в функциях fscanf или scanf. Достижение конца строки, который стандартно обозначает нулевой код '\0', эквивалентно концу потока ввода.
При успешном завершении любая функция форматного ввода возвращает число информационных полей входного потока, которые были корректно прочитаны и сохранены по адресам переменных соответствующих базовых типов. Это значение можно использовать для оценки количества введенных полей. Оно может быть меньше ожидаемого значения, в случае неадекватности или при отсутствии информации в потока ввода. При достижении конца потока ввода или ошибках преобразования возвращается значение константы EOF. Ошибка преобразования возникает, когда входные данные не могут быть корректно интерпретированы по заданной спецификации форматной строки. Оставшаяся информация сохраняется во входном потоке, начиная с места ошибки. Она может быть прочитана последующими запросами ввода. В некоторых случаях даже необходимо ликвидировать остаток потока ввода, чтобы обеспечить корректный ввод при повторных запросах данных.
Технологию обработки результатов форматного ввода для организации цикла интерактивных запросов данных, пока не получена требуемая информация, демонстрирует исходный код прикладной функции scani, которая должна обеспечить стандартный ввод целочисленного значения c очисткой входного потока при неудачных попытках.
/* Ввода целого числа с анализом результата */
int scani(int* value) {
char rest;   /* символ из остатка потока ввода  */
int num;     /* число корректных полей потока ввода */
/* Цикл попыток ввода числа */
while((num = scanf("%d", value)) != 1) {
  do { /* Очистка потока при ошибке ввода */
     scanf("%c", &rest);
  } while (rest != '\n'); /* do-while */
} /* while */
return(num);
} /* scani */
При вызове в функцию scani нужно передать адрес переменной типа int для получения целого числа из потока стандартного ввода. Корректность ввода гарантирует циклический вызов функции scanf c анализом результата через код возврата. Цикл завершается, когда из потока ввода будет прочитано одно информационное поле, которое можно преобразовать по спецификации целого числа, или получен признак конца потока стандартного ввода. Для очистки потока при вводе нечисловых данных предусмотрен внутренний цикл вызова функции scanf по спецификации %c, который аннулирует неудачную попытку, "отъедая" все введенные символы до символа перевода строки, включительно. Без чистки потока ввод числа становится невозможным после первой ошибки, так как нецифровые символы, оставшиеся в потоке, будут блокировать форматные преобразования по спецификации "%d". Это приводит к бесконечному циклу вызовов функции scanf, которая безуспешно пытается прочитать ошибочные данные, возвращая значение 0. Очистка потока ввода гарантирует возможность получить требуемый результат, который функция scani передает через указатель value, возвращая значение 1. При этом в потоке стандартного ввода остается, по крайней мере, символ перевода строки. В случае отказа от ввода числа функция scani возвращает значение EOF.