А. Богатырев, 1992-95 - 148 - Си в UNIX
в буфер считывается read-ом из файла порция информации, и getc выдает ее первый байт.
При последующих вызовах getc выдаются следующие байты из буфера, а обращений к диску
уже не происходит! Лишь когда буфер будет исчерпан - произойдет очередное чтение с
диска. Таким образом, информация читается из файла с опережением, заранее наполняя
буфер; а по требованию выдается уже из буфера. Если мы читаем 1024 байта из файла
при помощи getc(), то мы 1024 раза вызываем эту функцию, но всего 2 раза системный
вызов read - для чтения двух порций информации из файла, каждая - по 512 байт.
При записи
char c; FILE *fp = ... ;
putc(c, fp);
выводимые символы накапливаются в буфере. Только когда в нем окажется большая порция
информации, она за одно обращение write записывается на диск. Буфер записи "выталки-
вается" в файл в таких случаях:
- буфер заполнен (содержит BUFSIZ символов).
- при закрытии файла (fclose или exit [*][*]).
- при вызове функции fflush (см. ниже).
- в специальном режиме - после помещения в буфер символа '\n' (см. ниже).
- в некоторых версиях - перед любой операцией чтения из канала stdin (например,
при вызове gets), при условии, что stdout буферизован построчно (режим _IOLBF,
смотри ниже), что по-умолчанию так и есть.
Приведем упрощенную схему, поясняющую взаимоотношения основных функций и макросов из
stdio (кто кого вызывает). Далее s означает строку, c - символ, fp - указатель на
структуру FILE [**][**]. Функции, работающие со строками, в цикле вызывают посимвольные
операции. Обратите внимание, что в конце концов все функции обращаются к системным
вызовам read и write, осуществляющим ввод/вывод низкого уровня.
Системные вызовы далее обозначены жирно, макросы - курсивом.
Открыть файл, создать буфер:
#include
FILE *fp = fopen(char *name, char *rwmode);
| вызывает
V
int fd = open (char *name, int irwmode);
Если открываем на запись и файл не существует (fd < 0),
то создать файл вызовом:
fd = creat(char *name, int accessmode);
fd будет открыт для записи в файл.
По умолчанию fopen() использует для creat коды доступа accessmode равные 0666 (rw-
rw-rw-).
____________________
[*][*] При выполнении вызова завершения программы exit(); все открытые файлы автомати-
чески закрываются.
[**][**] Обозначения fd для дескрипторов и fp для указателей на файл прижились и их сле-
дует придерживаться. Если переменная должна иметь более мнемоничное имя - следует
писать так: fp_output, fd_input (а не просто fin, fout).
А. Богатырев, 1992-95 - 149 - Си в UNIX
Соответствие аргументов fopen и open:
rwmode irwmode
-------------------------
"r" O_RDONLY
"w" O_WRONLY|O_CREAT |O_TRUNC
"r+" O_RDWR
"w+" O_RDWR |O_CREAT |O_TRUNC
"a" O_WRONLY|O_CREAT |O_APPEND
"a+" O_RDWR |O_CREAT |O_APPEND
Для r, r+ файл уже должен существовать, в остальных случаях файл создается, если его
не было.
Если fopen() не смог открыть (или создать) файл, он возвращает значение NULL:
if((fp = fopen(name, rwmode)) == NULL){ ...неудача... }
Итак, схема:
printf(fmt,...)--->--,----fprintf(fp,fmt,...)->--*
fp=stdout |
fputs(s,fp)--------->--|
puts(s)----------->-------putchar(c)-----,---->--|
fp=stdout |
fwrite(array,size,count,fp)->--|
|
Ядро ОС putc(c,fp)
------------------* |
|файловая---<--write(fd,s,len)------------<----БУФЕР
|система---->---read(fd,s,len)-* _flsbuf(c,fp)
| | ! |
|системные буфера ! |
| | ! V ungetc(c,fp)
|драйвер устр-ва ! | |
|(диск, терминал) ! | _filbuf(fp) |
| | ! *--------->-----БУФЕР<-*
|устройство ! |
------------------* c=getc(fp)
|
rdcount=fread(array,size,count,fp)--<--|
gets(s)-------<---------c=getchar()------,----<--|
fp=stdout |
|
fgets(sbuf,buflen,fp)-<--|
scanf(fmt,.../*ук-ли*/)--<-,--fscanf(fp,fmt,...)-*
fp=stdin
Закрыть файл, освободить память выделенную под буфер:
fclose(fp) ---> close(fd);
И чуть в стороне - функция позиционирования:
fseek(fp,long_off,whence) ---> lseek(fd,long_off,whence);
Функции _flsbuf и _filbuf - внутренние для stdio, они как раз сбрасывают буфер в файл
либо читают новый буфер из файла.
По указателю fp можно узнать дескриптор файла:
int fd = fileno(fp);
Это макроопределение просто выдает поле из структуры FILE. Обратно, если мы открыли
А. Богатырев, 1992-95 - 150 - Си в UNIX
файл open-ом, мы можем ввести буферизацию этого канала:
int fd = open(name, O_RDONLY); /* или creat() */
...
FILE *fp = fdopen(fd, "r");
(здесь надо вновь указать КАК мы открываем файл, что должно соответствовать режиму
открытия open-ом). Теперь можно работать с файлом через fp, а не fd.
В приложении имеется текст, содержащий упрощенную реализацию главных функций из
библиотеки stdio.
4.11. Функция ungetc(c,fp) "возвращает" прочитанный байт в файл. На самом деле байт
возвращается в буфер, поэтому эта операция неприменима к небуферизованным каналам.
Возврат соответствует сдвигу указателя чтения из буфера (который увеличивается при
getc()) на 1 позицию назад. Вернуть можно только один символ подряд (т.е. перед сле-
дующим ungetc-ом должен быть хоть один getc), поскольку в противном случае можно
сдвинуть указатель за начало буфера и, записывая туда символ c, разрушить память
программы.
while((c = getchar()) != '+' );
/* Прочли '+' */ ungetc(c ,stdin);
/* А можно заменить этот символ на другой! */
c = getchar(); /* снова прочтет '+' */
4.12. Очень часто делают ошибку в функции fputc, путая порядок ее аргументов. Так
ничего не стоит написать:
FILE *fp = ......;
fputc( fp, '\n' );
Запомните навсегда!
int fputc( int c, FILE *fp );
указатель файла идет вторым! Существует также макроопределение
putc( c, fp );
Оно ведет себя как и функция fputc, но не может быть передано в качестве аргумента в
функцию:
#include
putNtimes( fp, c, n, f )
FILE *fp; int c; int n; int (*f)();
{ while( n > 0 ){ (*f)( c, fp ); n--; }}
возможен вызов
putNtimes( fp, 'a', 3, fputc );
но недопустимо
putNtimes( fp, 'a', 3, putc );
Тем не менее всегда, где возможно, следует пользоваться макросом - он работает быст-
рее. Аналогично, есть функция fgetc(fp) и макрос getc(fp).
Отметим еще, что putchar и getchar это тоже всего лишь макросы
#define putchar(c) putc((c), stdout)
#define getchar() getc(stdin)
А. Богатырев, 1992-95 - 151 - Си в UNIX
4.13. Известная вам функция printf также является частью библиотеки stdio. Она вхо-
дит в семейство функций:
FILE *fp; char bf[256];
fprintf(fp, fmt, ... );
printf( fmt, ... );
sprintf(bf, fmt, ... );
Первая из функций форматирует свои аргументы в соответствии с форматом, заданным
строкой fmt (она содержит форматы в виде %-ов) и записывает строку-результат посим-
вольно (вызывая putc) в файл fp. Вторая - это всего-навсего fprintf с каналом fp
равным stdout. Третяя выдает сформатированную строку не в файл, а записывает ее в
массив bf. В конце строки sprintf добавляет нулевой байт '\0' - признак конца.
Для чтения данных по формату используются функции семейства
fscanf(fp, fmt, /* адреса арг-тов */...);
scanf( fmt, ... );
sscanf(bf, fmt, ... );
Функции fprintf и fscanf являются наиболее мощным средством работы с текстовыми фай-
лами (содержащими изображение данных в виде печатных символов).
4.14. Текстовые файлы (имеющие строчную организацию) хранятся на диске как линейные
массивы байт. Для разделения строк в них используется символ '\n'. Так, например,
текст
стр1
стрк2
кнц
хранится как массив
с т р 1 \n с т р к 2 \n к н ц длина=14 байт
!
указатель чтения/записи (read/write pointer RWptr)
(расстояние в байтах от начала файла)
При выводе на экран дисплея символ \n преобразуется драйвером терминалов в последова-
тельность \r\n, которая возвращает курсор в начало строки ('\r') и опускает курсор на
строку вниз ('\n'), то есть курсор переходит в начало следующей строки.
В MS DOS строки в файле на диске разделяются двумя символами \r\n и при выводе
на экран никаких преобразований не делается[*]. Зато библиотечные функции языка Си
преобразуют эту последовательность при чтении из файла в \n, а при записи в файл
превращают \n в \r\n, поскольку в Си считается, что строки разделяются только \n. Для
работы с файлом без таких преобразований, его надо открывать как "бинарный":
FILE *fp = fopen( имя, "rb" ); /* b - binary */
int fd = open ( имя, O_RDONLY | O_BINARY );
____________________
[*] Управляющие символы имеют следующие значения:
'\n' - '\012' (10) line feed
'\r' - '\015' (13) carriage return
'\t' - '\011' (9) tab
'\b' - '\010' (8) backspace
'\f' - '\014' (12) form feed
'\a' - '\007' (7) audio bell (alert)
'\0' - 0. null byte
А. Богатырев, 1992-95 - 152 - Си в UNIX
Все нетекстовые файлы в MS DOS надо открывать именно так, иначе могут произойти раз-
ные неприятности. Например, если мы программой копируем нетекстовый файл в текстовом
режиме, то одиночный символ \n будет считан в программу как \n, но записан в новый
файл как пара \r\n. Поэтому новый файл будет отличаться от оригинала (что для файлов
с данными и программ совершенно недопустимо!).
Задание: напишите программу подсчета строк и символов в файле. Указание: надо
подсчитать число символов '\n' в файле и учесть, что последняя строка файла может не
иметь этого символа на конце. Поэтому если последний символ файла (тот, который вы
прочитаете самым последним) не есть '\n', то добавьте к счетчику строк 1.
4.15. Напишите программу подсчета количества вхождений каждого из символов алфавита
в файл и печатающую результат в виде таблицы в 4 колонки. (Указание: заведите массив
из 256 счетчиков. Для больших файлов счетчики должны быть типа long).
4.16. Почему вводимый при помощи функций getchar() и getc(fp) символ должен описы-
ваться типом int а не char?
Ответ: функция getchar() сообщает о конце файла тем, что возвращает значение EOF
(end of file), равное целому числу (-1). Это НЕ символ кодировки ASCII, поскольку
getchar() может прочесть из файла любой символ кодировки (кодировка содержит символы
с кодами 0...255), а специальный признак не должен совпадать ни с одним из хранимых в