Керниган и Ритчи - Язык программирования Си (793773), страница 38
Текст из файла (страница 38)
(Символамиразделителями являются символы пробела, табуляции, новой строки, возврата каретки, вертикальнойтабуляции и перевода страницы.)Символ-спецификатор указывает, каким образом следует интерпретировать очередное поле ввода.Соответствующий аргумент должен быть указателем, как того требует механизм передачи параметров позначению, принятый в Си. Символы-спецификаторы приведены в таблице 7.2.Перед символами-спецификаторами d, l, о, u и x может стоять буква h, указывающая на то, чтосоответствующий аргумент должен иметь тип short * (а не int *), или l (латинская ell), указывающая натип long *. Аналогично, перед символами-спецификаторами е, f и g может стоять буква l, указывающая,что тип аргумента — double * (а не float *).Таблица 7.2.
Основные преобразования scanfСимволВводимые данные; тип аргументаdдесятичное целое; int *iцелое; int *. Целое может быть восьмеричны (с 0 слева) или шестнадцатеричным (с 0x или0X слева)oвосьмеричное целое (с нулем слева или без него); int *uбеззнаковое десятичное целое; unsigned int *xшестнадцатеричное целое (с Ох или ОХ слева или без них); int *cсимволы; char *. Следующие символы ввода (по умолчанию один) размещаются вуказанном месте. Обычный пропуск символов-разделителей подавляется; чтобы прочестьочередной символ, отличный от символа-разделителя, используйте %1ssстрока символов (без обрамляющих кавычек); 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("верно: %s\n", line); /* в виде 25 дек 1988 */else if (sscanf(line, "%d/%d/%d", &month, &day, &year) == 3)printf("верно: %s\n", line); /* в виде mm/dd/yy */elseprintf("неверно: %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 — "а"), т. е.
запись информации вконец уже существующего файла. В некоторых системах различаются текстовые и бинарные файлы; в случаепоследних в строку режима необходимо добавить букву "b" (binary — бинарный).Тот факт, что некий файл, которого раньше не было, открывается на запись или добавление, означает, что онсоздается (если такая процедура физически возможна). Открытие уже существующего файла на записьприводит к выбрасыванию его старого содержимого, в то время как при открытии файла на добавление егостарое содержимое сохраняется. Попытка читать несуществующий файл является ошибкой.
Могут иметьместо и другие ошибки; например, ошибкой считается попытка чтения файла, который по статусу запрещеночитать. При наличии любой ошибки fopen возвращает NULL. (Возможна более точная идентификацияошибки; детальная информация по этому поводу приводится в конце параграфа 1 приложения В.)Следующее, что нам необходимо знать, — это как читать из файла или писать в файл, коль скоро он открыт.Существует несколько способов сделать это, из которых самый простой состоит в том, чтобы воспользоватьсяфункциями getc и putc.
Функция getc возвращает следующий символ из файла; ей необходимо сообщитьуказатель файла, чтобы она знала, откуда брать символ.int getc(FILE *fp)Функция getc возвращает следующий символ из потока, на который указывает *fp; в случае исчерпанияфайла или ошибки она возвращает EOF.Функция putc пишет символ c в файл fpint putc(int с, FILE *fp)и возвращает записанный символ или EOF в случае ошибки. Аналогично getchar и putchar, реализацияgetc и putc может быть выполнена в виде макросов, а не функций.При запуске Си-программы операционная система всегда открывает три файла и обеспечивает три файловыессылки на них.
Этими файлами являются: стандартный ввод, стандартный вывод и стандартный файл ошибок;соответствующие им указатели называются stdin, stdout и stderr; они описаны в <stdio.h>. Обычноstdin соотнесен с клавиатурой, a 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, как оказалось, удобна для многих программ. Если в командной строке присутствуют аргументы, онирассматриваются как имена последовательно обрабатываемых файлов. Если аргументов нет, то обработкеподвергается стандартный ввод.#include <stdio.h>/* cat: конкатенация файлов, версия 1 */main(int argc, char *argv[]){FILE *fp;void filecopy(FILE *, FILE *);if (argc == 1) /* нет аргументов; копируется стандартный ввод */filecopy(stdin, stdout);elsewhile (--argc > 0)if ((fp = fopen(*++argv, "r")) == NULL) {printf("cat: не могу открыть файл %s\n", *argv);return 1;} else {filecopy(fp, stdout);fclose(fp);}return 0;}/* filecopy: копирует файл ifp в файл ofp */void filecopy(FILE *ifp, FILE *ofp){int c;while ((c = getc(ifp)) != EOF)putc(c, ofp);}Файловые указатели stdin и stdout представляют собой объекты типа FILE*.















