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.
7.5 Доступ к файлам
Во всех предыдущих примерах мы имели дело со стандартным вводом и стандартным выводом, которые для программы автоматически предопределены операционной системой конкретной машины.
Следующий шаг - научиться писать программы, которые имели бы доступ к файлам, заранее не подсоединенным к программам. Одна из программ, в которой возникает такая необходимость, - это программа cat, объединяющая несколько именованных файлов и направляющая результат в стандартный вывод. Функция cat часто применяется для выдачи файлов на экран, а также как универсальный "коллектор" файловой информации для тех программ, которые не имеют возможности обратиться к файлу по имени. Например, команда
cat x.c y.c
направит в стандартный вывод содержимое файлов x.c и y.c (и ничего более).
Возникает вопрос: что надо сделать, чтобы именованные файлы можно было читать; иначе говоря, как связать внешние имена, придуманные пользователем, с инструкциями чтения данных?
На этот счет имеются простые правила. Для того чтобы можно было читать из файла или писать в файл, он должен быть предварительно открыт с помощью библиотечной функции fopen. Функция fopen получает внешнее имя типа x.c или y.c, после чего осуществляет некоторые организационные действия и "переговоры" с операционной системой (технические детали которых здесь не рассматриваются) и возвращает указатель, используемый в дальнейшем для доступа к файлу.
Этот указатель, называемый указателем файла, ссылается на структуру, содержащую информацию о файле (адрес буфера, положение текущего символа в буфере, открыт файл на чтение или на запись, были ли ошибки при работе с файлом и не встретился ли конец файла). Пользователю не нужно знать подробности, поскольку определения, полученные из ‹stdio.h›, включают описание такой структуры, называемой FILE.
Единственное, что требуется для определения указателя файла, - это задать описания такого, например, вида:
FILE *fp;
FILE *fopen(char *name, char *mode);
Это говорит, что fp есть указатель на FILE, a fopen возвращает указатель на FILE. Заметим, что FILE - это имя типа) наподобие int, а не тег структуры. Оно определено с помощью typedef. (Детали того, как можно реализовать fopen в системе UNIX, приводятся в параграфе 8.5.)
Обращение к fopen в программе может выглядеть следующим образом:
fp = fopen(name, mode);
Первый аргумент - строка, содержащая имя файла. Второй аргумент несет информацию о режиме. Это тоже строка: в ней указывается, каким образом пользователь намерен применять файл. Возможны следующие режимы: чтение (read - "r"), запись (write - "w") и добавление (append - "a"), т. е. запись информации в конец уже существующего файла. В некоторых системах различаются текстовые и бинарные файлы; в случае последних в строку режима необходимо добавить букву "b" (binary - бинарный).
Тот факт, что некий файл, которого раньше не было, открывается на запись или добавление, означает, что он создается (если такая процедура физически возможна). Открытие уже существующего файла на запись приводит к выбрасыванию его старого содержимого, в то время как при открытии файла на добавление его старое содержимое сохраняется. Попытка читать несуществующий файл является ошибкой. Могут иметь место и другие ошибки; например, ошибкой считается попытка чтения файла, который по статусу запрещено читать. При наличии любой ошибки fopen возвращает NULL. (Возможна более точная идентификация ошибки; детальная информация по этому поводу приводится в конце параграфа 1 приложения B.)
Следующее, что нам необходимо знать, - это как читать из файла или писать в файл, коль скоро он открыт. Существует несколько способов сделать это, из которых самый простой состоит в том, чтобы воспользоваться функциями getc и putc. Функция getc возвращает следующий символ из файла; ей необходимо сообщить указатель файла, чтобы она знала откуда брать символ.
int getc(FILE *fp);
Функция getc возвращает следующий символ из потока, на который указывает *fp; в случае исчерпания файла или ошибки она возвращает EOF.
Функция putc пишет символ c в файл fp
int putc(int с, FILE *fp);
и возвращает записанный символ или EOF в случае ошибки. Аналогично getchar и putchar, реализация getc и putc может быть выполнена в виде макросов, а не функций.
При запуске Си-программы операционная система всегда открывает три файла и обеспечивает три файловые ссылки на них. Этими файлами являются: стандартный ввод, стандартный вывод и стандартный файл ошибок; соответствующие им указатели называются stdin, stdout и stderr; они описаны в ‹stdio.h›. Обычно stdin соотнесен с клавиатурой, а stdout и stderr - с экраном. Однако stdin и stdout можно связать с файлами или, используя конвейерный механизм, соединить напрямую с другими программами, как это описывалось в параграфе 7.1.
С помощью getc, putc, stdin и stdout функции getchar и putchar теперь можно определить следующим образом:
#define getchar() getc(stdin)
#define putchar(c) putc((c), stdout)
Форматный ввод-вывод файлов можно построить на функциях fscanf и fprintf. Они идентичны scanf и printf с той лишь разницей, что первым их аргументом является указатель на файл, для которого осуществляется ввод-вывод, формат же указывается вторым аргументом.
int fscanf(FILE *fp, char *format,…)
int fprintf(FILE *fp, char *format,…)
Вот теперь мы располагаем теми сведениями, которые достаточны для написания программы cat, предназначенной для конкатенации (последовательного соединения) файлов. Предлагаемая версия функции cat, как оказалось, удобна для многих программ. Если в командной строке присутствуют аргументы, они рассматриваются как имена последовательно обрабатываемых файлов. Если аргументов нет, то обработке подвергается стандартный ввод.