Язык программирования Си

Форматный ввод (scanf)


Функция scanf, обеспечивающая ввод, является аналогом printf; она выполняет многие из упоминавшихся преобразований, но в противоположном направлении. Ее объявление имеет следующий вид:

int scanf(char *format, ...)

Функция scanf читает символы из стандартного входного потока, интерпретирует их согласно спецификациям строки format и рассылает результаты в свои остальные аргументы. Аргумент format мы опишем позже; другие аргументы, каждый из которых должен быть указателем, определяют, где будут запоминаться должным образом преобразованные данные. Как и для printf, в этом параграфе дается сводка наиболее полезных, но отнюдь не всех возможностей данной функции.

Функция scanf прекращает работу, когда оказывается, что исчерпался формат или вводимая величина не соответствует управляющей спецификации. В качестве результата scanf возвращает количество успешно введенных элементов данных. По исчерпании файла она выдает EOF. Существенно то, что значение EOF не равно нулю, поскольку нуль scanf выдает, когда вводимый символ не соответствует первой спецификации форматной строки. Каждое очередное обращение к scanf продолжает ввод символа, следующего сразу за последним обработанным.

Существует также функция sscanf, которая читает из строки (а не из стандартного ввода).

int sscanf(char *string, char *format, arg1, arg2, ...)

Функция sscanf просматривает строку string согласно формату format и рассылает полученные значения в arg1, arg2 и т. д. Последние должны быть указателями.

Формат обычно содержит спецификации, которые используются для управления преобразованиями ввода. В него могут входить следующие элементы:

  • Пробелы или табуляции, которые игнорируются.
  • Обычные символы (исключая %), которые, как ожидается, совпадут с очередными символами, отличными от символов-разделителей входного потока.
  • Спецификации преобразования, каждая из которых начинается со знака % и завершается символом-спецификатором типа преобразования. В промежутке между этими двумя символами в любой спецификации могут располагаться, причем в том порядке, как они здесь указаны: знак * (признак подавления присваивания); число, определяющее ширину поля; буква h, l или L, указывающая на размер получаемого значения; и символ преобразования (o, d, x).


    Спецификация преобразования управляет преобразованием следующего вводимого поля. Обычно результат помещается в переменную, на которую указывает соответствующий аргумент. Однако если в спецификации преобразования присутствует *, то поле ввода пропускается и никакое присваивание не выполняется. Поле ввода определяется как строка без символов-разделителей; оно простирается до следующего символа-разделителя или же ограничено шириной поля, если она задана. Поскольку символ новой строки относится к символам- разделителям, то sscanf при чтении будет переходить с одной строки на другую. (Символами-разделителями являются символы пробела, табуляции, новой строки, возврата каретки, вертикальной табуляции и перевода страницы.)

    Символ-спецификатор указывает, каким образом следует интерпретировать очередное поле ввода. Соответствующий аргумент должен быть указателем, как того требует механизм передачи параметров по значению, принятый в Си. Символы-спецификаторы приведены в таблице 7.2.

    Перед символами-спецификаторами d, l, o, u и x может стоять буква h, указывающая на то, что соответствующий аргумент должен иметь тип short * (а не int *),или l (латинская ell), указывающая на тип long *. Аналогично, перед символами-спецификаторами e, f и g может стоять буква l, указывающая, что тип аргумента - double * (а не float *).

    Таблица 7.2 Основные преобразования scanf



    Символ

    Вводимые данные; тип аргумента

    d десятичное целое: int *
    i целое: int *. Целое может быть восьмеричным (с 0 слева) или шестнадцатеричным (с 0x или 0X слева)
    o восьмеричное целое (с нулем слева или без него); int *
    u беззнаковое десятичное целое; unsigned int *
    x шестнадцатеричное целое (с 0x или 0X слева или без них); int *
    c символы; char *. Следующие символы ввода (по умолчанию один) размещаются в указанном месте. Обычный пропуск символов- разделителей подавляется; чтобы прочесть очередной символ, отличный от символа-разделителя, используйте %1s
    s Строка символов(без обрамляющих кавычек); char *, указывающая на массив символов, достаточный для строки и завершающего символа '\0', который будет добавлен
    e, f, g число с плавающей точкой, возможно, со знаком; обязательно присутствие либо десятичной точки, либо экспоненциальной части, а возможно, и обеих вместе; float *
    % сам знак %, никакое присваивание не выполняется
    <


    Чтобы построить первый пример, обратимся к программе калькулятора из главы 4, в которой организуем ввод с помощью функции scanf:

    #include <stdio.h> main() /* программа-калькулятор */ { double sum, v;

    sum = 0; while (scanf ("%lf", &v) == 1) printf("\t%.2f\n", sum += v); return 0; }

    Предположим, что нам нужно прочитать строки ввода, содержащие данные вида

    25 дек 1988

    Обращение к scanf выглядит следующим образом:

    int day, year; /* день, год */ char monthname[20]; /* название месяца */

    scanf ("%d %s %d", &day, monthname, &year);

    Знак & перед monthname не нужен, так как имя массива есть указатель.

    В строке формата могут присутствовать символы, не участвующие ни в одной из спецификаций; это значит, что эти символы должны появиться на вводе. Так, мы могли бы читать даты вида mm/dd/yy с помощью следующего обращения к scanf:

    int day, month, year; /* день, месяц, год */ scanf("%d/%d/%d", &day, &month, &year);

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

    while (getline(line, sizeof(line)) > 0) { if (sscanf(line, "%d %s %d", &day, monthname, &year) == 3) printf("вepно: %s\r", line); /* в виде 25 дек 1968 */ else if (sscanf(line, "%d/%d/%d", &month, &day, &year) == 3) printf("вepно: %s\n", line); /* в виде mm/dd/yy */ else printf("неверно: %s\n", line); /* неверная форма даты */ }

    Обращения к scanf могут перемежаться с вызовами других функций ввода. Любая функция ввода, вызванная после scanf, продолжит чтение с первого еще непрочитанного символа.

    В завершение еще раз напомним, что аргументы функций scanf и sscanf должны быть указателями.

    Одна из самых распространенных ошибок состоит в том, что вместо того, чтобы написать

    scanf ("%d", &n);

    пишут

    scanf("%d", n);

    Компилятор о подобной ошибке ничего не сообщает.

    Упражнение 7.4. Напишите свою версию scanf по аналогии с minprintf из предыдущего параграфа.

    Упражнение 7.5. Перепишите основанную на постфиксной записи программу калькулятора из главы 4 таким образом, чтобы для ввода и преобразования чисел она использовала scanf и/или sscanf.


    Содержание раздела