netlib.narod.ru< Назад | Оглавление | Далее >

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

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

  int scanf(char *format, ...)

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

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

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

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

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

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

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

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


Таблица 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 *
% сам знак %, никакое присваивание не выполняется

Перед символами-спецификаторами d, l, o, u и x может стоять буква h, указывающая на то, что соответствующий аргумент должен иметь тип short * (а не int *), или l, указывающая на тип long *. Аналогично, перед символами-спецификаторами e, f и g может стоять буква l, указывающая, что тип аргумента — double * (а не 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("Верно: %s\n", line);   /* в виде 25 дек 1988 */
      else if (sscanf(line, "%d/%d/%d", &month, &day, &year) == 3)
          printf("Верно: %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.



netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz