
C_Kurs_Lekt / C_II_семестр / 10_Файлы_потоки
.pdfЛысый Д.А. Основы программирования. Ввод, вывод, потоки, файлы. часть2 |
1 |
ПОТОКОВЫЙ ВВОД-ВЫВОД
На уровне потокового ввода-вывода обмен данными производится побайтно. Такой ввод-вывод возможен как для собственно устройств побайтового обмена (печатающее устройство, дисплей), так и для файлов на диске, хотя устройства внешней памяти, строго говоря, являются устройствами поблочного обмена, т.е. за одно обращение к устройству производится считывание или запись фиксированной порции данных. Чаще всего минимальной порцией данных, участвующей в обмене с внешней памятью, являются блоки в 512 байт или 1024 байта. При вводе с диска (при чтении из файла) данные помещаются в буфер операционной системы, а затем побайтно или определенными порциями передаются программе пользователя. При выводе данных в файл они накапливаются в буфере, а при заполнении буфера записываются в виде единого блока на диск за одно обращение к последнему. Буферы операционной системы реализуются в виде участков основной памяти. Поэтому пересылки между буферами ввода-вывода и выполняемой программой происходят достаточно быстро в отличие от реальных обменов с физическими устройствами.
Функции библиотеки ввода-вывода языка Си, поддерживающие обмен данными с файлами на уровне потока, позволяют обрабатывать данные различных размеров и форматов, обеспечивая при этом буферизованный ввод и вывод. Таким образом, поток - это файл вместе с предоставляемыми средствами буферизации.
При работе с потоком можно производить следующие действия.
−открывать и закрывать потоки (связывать указатели на потоки с конкретными файлами);
−вводить и выводить: символ, строку, форматированные данные, порцию данных произвольной длины;
−анализировать ошибки потокового ввода-вывода и условие достижения конца потока (конца файла);
−управлять буферизацией потока и размером буфера;
−получать и устанавливать указатель (индикатор) текущей позиции в потоке.
Для того чтобы можно было использовать функции библиотеки ввода-вывода языка Си, в программу необходимо включить заголовочный файл stdio.h (#include <stdio.h>), который содержит прототипы функций вводавывода, а также определения констант, типов и структур, необходимых для работы функций обмена с потоком.
Открытие и закрытие потока
Прежде чем начать работать с потоком, его необходимо инициализировать, т.е. открыть. При этом поток связывается в исполняемой программе со структурой предопределенного типа FILE. Определение структурного типа FILE находится в заголовочном файле stdio.h. В структуре FILE содержатся компоненты, с помощью которых ведется работа с потоком, в частности: указатель на буфер, указатель (индикатор) текущей позиции в потоке и другая информация.
При открытии потока в программу возвращается указатель на поток, являющийся указателем на объект структурного типа FILE. Этот указатель идентифицирует поток во всех последующих операциях.
Указатель на поток, например fp, должен быть объявлен в программе следующим образом:
#include <stdio.h> FILE *fp;
Указатель на поток приобретает значение в результате выполнения функции открытия потока:
fp = fopen (имя_файлата, режим_отêрытия);
Параметры имя_файла è режим_открытия являются указателями на массивы символов, содержащих соответственно имя файла, связанного с потоком, и строку режимов открытия. Однако эти параметры могут задаваться и непосредственно в виде строк при вызове функции открытия файла:
fp = fopen("t.txt", "r");
|
где t. txt - имя некоторого файла, связанного с потоком; |
|
|
r - обозначение одного из режимов работы с файлом (тип доступа к потоку). |
|
|
Стандартно файл, связанный с потоком, можно открыть в одном из следующих шести режимов: |
|
"w" |
|
новый текстовый (см. ниже) файл открывается для записи. Если файл уже существовал, то предыдущее |
|
|
содержимое стирается, файл создается заново; |
"r" |
|
существующий текстовый файл открывается только для чтения; |
"à" |
|
текстовый файл открывается (или создается, если файла нет) для добавления в него новой порции инфор- |
|
|
мации (добавление в конец файла). В отличие от режима "w" режим "а" позволяет открывать уже сущест- |
|
|
вующий файл, не уничтожая его предыдущей версии, и писать в продолжение файла; |
"w+" |
|
новый текстовый файл открывается для записи и последующих многократных исправлений. Если файл уже |
|
|
существует, то предыдущее содержимое стирается. Последующие после открытия файла запись и чтение из |
|
|
него допустимы в любом месте файла, в том числе запись разрешена и в конец файла, т.е. файл может уве- |
|
|
личиваться ("расти"); |
"r+" |
|
существующий текстовый файл открывается как для чтения, так и для записи в любом месте файла; однако |
|
|
в этом режиме невозможна запись в конец файла, т.е. недопустимо увеличение размеров файла; |
"à+" |
|
текстовый файл открывается или создается (если файла нет) и становится доступным для изменений, т.е. |
|
|
для записи и для чтения в любом месте; при этом в отличие от режима "w+" можно открыть существующий |
|
|
файл и не уничтожать его содержимого; в отличие от режима "r+" в режиме "а+" можно вести запись в ко- |
|
|
нец файла, т.е. увеличивать его размеры. |
Поток можно открыть в текстовом либо двоичном (бинарном) режиме.
В текстовом режиме прочитанная из потока комбинация символов CR (значение 13) и LF (значение 10), то есть управляющие коды "возврат каретки" и "перевод строки", преобразуется в один символ новой строки '\n' (значение 10, совпадающее с LF). При записи в поток в текстовом режиме осуществляется обратное преобразование, т.е. символ новой строки '\n' (LF) заменяется последовательностью CR и LF.
Если файл, связанный с потоком, хранит не текстовую, а произвольную двоичную информацию, то указанные преобразования не нужны и могут быть даже вредными. Обмен без такого преобразования выполняется при выборе двоичного или бинарного режима, который обозначается буквой b. Например, "r+b" или "wb". В некоторых компиляторах текстовый режим обмена обозначается буквой t, т.е. записывают "a+t" или "rt".
Лысый Д.А. Основы программирования. Ввод, вывод, потоки, файлы. часть2 |
2 |
Если поток открыт для изменений, т.е. в параметре режима присутствует символ "+", то разрешены как вывод в поток, так и чтение из него. Однако смена режима (переход от записи к чтению и обратно) должна происходить только после установки Указателя потока в нужную позицию.
При открытии потока могут возникнуть следующие ошибки: указанный файл, связанный с потоком, не найден (для режима "чтение"); диск заполнен или диск защищен от записи и т.п. Необходимо также отметить, что при выполнении функции fopen() происходит выделение динамической памяти. При ее отсутствии устанавливается признак ошибки "Not enough memory" (недостаточно памяти). В перечисленных случаях указатель на поток приобретает значение NULL. Заметим, что указатель на поток в любом режиме, отличном от аварийного, никогда не бывает равным NULL.
Приведем типичную последовательность операторов, которая используется при открытии файла, связанного с потоком:
if ((fp = fopen("t.txt", "w"))== NULL) { perror("ошибêа при отêрытии файла t.txt \n"); exit(O); }
ãäå NULL - нулевой указатель, определенный в файле stdio.h.
Для вывода на экран дисплея сообщения об ошибке при открытии потока используется стандартная библиотечная функция perror(), прототип которой в stdio.h имеет вид:
void perror (const char * s);
Функция perror() выводит строку символов, адресуемую указателем s, за которой размещаются: двоеточие, пробел и сообщение об ошибке. Содержимое и формат сообщения определяются реализацией системы программирования. Текст сообщения об ошибке выбирается функцией perror() на основании номера ошибки. Номер ошибки заносится в переменную int errno (определенную в заголовочном файле errno.h) рядом функций библиотеки языка Си, в том числе и функциями ввода-вывода.
После того как файл открыт, с ним можно работать, записывая в него информацию или считывая ее (в зависимости от режима).
Открытые на диске файлы после окончания работы с ними рекомендуется закрыть явно. Для этого используется библиотечная функция
int fclose (óêазатель_на_потоê);
Открытый файл можно открыть повторно (например, для изменения режима работы с ним) только после того, как файл будет закрыт с помощью функции fclose().
Иногда, перед критическими операциями имеет смысл сбросить данные, хранящиеся в буферах обмена потоков на диск. Для этого предназначены функции принудительной записи данных из буфера (прототипы хранятся в <stdio.h>:
int fflush(óêазатель_на_потоê); - записывает информаццию хранящуюся в буфере в файл, поток при этом не закрывается. Возвращает 0 в случае успеха и EOF если ошибка. Не работает с небуферизированными потоками. fflush(stdin) – очистить входной поток.
int flushall(void); - записывает информаццию хранящуюся в буферах обмена открытых потоков в соответствующие им файлы. Потоки после этой операции остаются открытыми. Возвращает число открытых потоков ввода/вывода.
Для управления размерами буфера можно использовать следующие функции (вместо автоматической установки буфера):
void setbuf(FILE *stream, char *buf) - устанавливает буферизацию потока
int setvbuf(FILE *stream, char *buf, int type, size_t size) - назначает буферизацию потока
Стандартные потоки
Когда программа начинает выполняться, автоматически открываются пять потоков, из которых основными являются:
−стандартный поток ввода (на него ссылаются, используя предопределенный указатель на поток stdin);
−стандартный поток вывода (stdout);
−стандартный поток вывода сообщений об ошибках (stderr).
По умолчанию стандартному потоку ввода stdin ставится в соответствие клавиатура, а потокам stdout è stderr соответствует экран дисплея.
Для ввода-вывода данных с помощью стандартных потоков в библиотеке языка Си определены следующие функции:
−getchar( )/putchar() - ввод-вывод отдельного символа;
−gets( )/puts() - ввод-вывод строки;
−scanf( )/printf() - ввод-вывод в режиме форматирования данных.
Ввод-вывод отдельных символов в стандартные потоки.
Ввод-вывод одного символа осуществляется с помощью библиотечных функций getchar() и putchar(). Прототи-
пы функций: int getchar(void); int putchar(int с);
Функция getchar() осуществляет ввод одного символа. При обращении она возвращает в вызвавшую ее функцию один введенный символ.
Функция putchar( ) выводит в стандартный поток один символ, при этом также возвращает в вызвавшую ее функцию только что выведенный символ.
Функция getchar( ) вводит очередной байт информации (символ) в виде значения типа int ,что гарантирует успешность распознавания ситуации "достигнут конец файла". Если при чтении из файла с помощью функции getchar() может быть достигнут конец файла. В этом случае операционная система в ответ на попытку чтения символа передает функции getchar() значение EOF (End of File). Константа EOF определена в заголовочном файле stdio.h и в разных операционных системах имеет значение 0 или -1.
Применение константы EOF вместо конкретных целых значений, возвращаемых при достижении конца файла, делает программу мобильной (не зависимой от конкретной операционной системы).
В случае ошибки при вводе функция getchar() также возвращает EOF.
Лысый Д.А. Основы программирования. Ввод, вывод, потоки, файлы. часть2 |
3 |
Функции getchar() считывает очередной байт информации из стандартного ввода лишь в том случае, если буфер операционной системы окажется пустым. Если буфер не пуст, то в программу пользователя будет передан оче- редной символ из буфера.
При наборе текста на клавиатуре коды символов записываются во внутренний буфер операционной системы. Одновременно они отображаются на экране дисплея. Набранные на клавиатуре символы можно редактировать (удалять и набирать новые). Фактический перенос символов из внутреннего буфера в программу происходит при нажатии клавиши <Enter>. При этом код клавиши <Enter> также заносится во внутренний буфер. Таким образом, при нажатии на клавишу 'А' и клавишу <Enter> (завершение ввода) во внутреннем буфере оказываются: код символа 'А' и код клавиши <Enter>. Это необходимо учитывать при вводе функцией getchar() одиночного символа.
Приведем фрагмент программы, в которой функция getchar(.) используется для временной приостановки работы программы с целью анализа промежуточных значений контролируемых переменных.
#include <stdio.h> |
|
int main() { |
|
printf("a"); |
|
getchar(); |
/* #1 */ |
printf("b") ; |
|
getchar(); |
/* #2 */ |
printf("c"); |
|
return 0; } |
|
Сначала на экран выводится символ 'а', и работа программы приостанавливается до ввода очередного (в данном случае - любого) символа. Если, как это делается обычно, нажать, например, клавишу <q> и затем клавишу <Enter> (для завершения ввода), то на следующей строке появятся символы bc, и программа завершит свою работу. Первая (в программе) функция getchar() (#1) прочитала из внутреннего буфера код символа 'q', и следующая за ней функция printf() вывела на экран символ ‘q’. Остановки работы программы не произошло, потому что вторая функция getchar() (#2) прочитала код клавиши <Enter> из внутреннего буфера, а не очередной символ с клавиатуры. Произошло это потому, что к моменту вызова функции getchar() внутренний буфер не был пуст.
Приведенная программа будет работать правильно, если в момент остановки программы нажимать только клавишу <Enter>.
Функция putchar( ) служит для вывода на устройство стандартного вывода одного символа, заданного в каче- стве параметра. Ниже приведены примеры задания выводимого символа:
int c ;
c = getchar(); putchar(c); putchar{'A'); putchar('\007'); putchar('\t');
В строке 2 фрагмента программы на экран дисплея выводится символ, введенный в предыдущей строке функцией getchar( ). В строке 3 выводится символ 'А', в строке 4 выводится управляющий символ, заданный кодом 007 (звуковой сигнал). В последней строке выводится неотображаемый (управляющий) символ табуляции, перемещающий курсор в следующую позицию табуляции. Обычно эти позиции задаются следующим числовым рядом: 1,9,17,25...
Приведем в качестве примера применения функций getchar() è putchar( ) программу копирования данных из стандартного ввода в стандартный вывод, которую можно найти практически в любом пособии по программированию на Си:
#include <stdio.h> int main() { int c;
while {(c = getchar()) != EOF) putchar(c); return 0; }
Для завершения приведенной выше программы копирования необходимо ввести с клавиатуры сигнал прерывания Ctrl+C (одновременно нажать клавиши <Ctrl> и <С>).
Ввод-вывод строк в стандартные потоки
В библиотеку Си для обмена данными через стандартные потоки ввода-вывода включены функции вводавывода строк gets() è puts(), прототипы которых имеют следующий вид:
char * gets (char * s); |
/* |
Фóнêция |
ввода */ |
int puts (char * s); |
/* |
Фóнêция |
вывода */ |
Обе функции имеют только один аргумент - указатель s на массив символов. Если строка прочитана удачно, функция gets() возвращает адрес того массива s, в который производился ввод строки. Если произошла ошибка, то возвращается NULL.
Функция puts() в случае успешного завершения возвращает последний выведенный символ, который всегда является символом '\n'. Если произошла ошибка, то возвращается EOF.
Пример использования этих функций.
#include <stdio.h>
char strl[ ] = "Введите фамилию сотрóдниêа:"; int main() {
char name[80]; puts(strl); gets(name); return 0; }
Напомним, что любая строка символов в языке Си должна заканчиваться нуль-символом '\0'. В последний элемент массива strl нуль-символ будет записан автоматически во время трансляции при инициализации массива. Для функции puts( ) наличие нуль-символа в конце строки является обязательным. В противном случае, т.е. при отсутствии в символьном массиве символа '\0', программа может завершиться аварийно, так как функция puts() в поисках нуль-символа будет перебирать всю доступную память байт за байтом, начиная в нашем примере с адреса strl. Об этом необходимо помнить, если в программе происходит формирование строки для вывода ее на экран дисплея. Функция gets() завершает свою работу при вводе символа '\n', который автоматически передается от клавиатуры в ЭВМ при нажатии на клавишу <Enter>. При этом сам символ '\n' во вводимую строку не записывается. Вместо него
Лысый Д.А. Основы программирования. Ввод, вывод, потоки, файлы. часть2 |
4 |
в строку помещается нуль-символ '\0'. Таким образом, функция gets() производит ввод "правильной" строки, а не просто последовательности символов.
Здесь следует обратить внимание на следующую особенность ввода данных с клавиатуры. Функция gets( ) на- чинает обработку информации от клавиатуры только после нажатия клавиши <Enter>. Таким образом, она "ожидает", пока не будет набрана нужная информация и нажата клавиша <Enter>. Только после этого начинается ввод данных в программу.
В приведенную выше программу вкралась неявная ошибка: Длина строки на которую ссылается указатель name = 80 символов (последний – '\0'). Если длина вводимой строки будет превышать 80 символов, то строка "выйдет" за пределы, выделенные для нее памяти и испортит участок памяти следующий далее. Это приведет к непредсказуемым последствиям. Избежать этого можно используя функцию fgets() (прототип: char *fgets(char *s, int n, FILE *stream). Например: fgets( strl, 80 stdin ); fgets( strl, strlen(strl)+1, stdin ); или в данном примере вводимая строка усекается до 79 символов и к ней добавляется 80й -'\0' ( в случае если вводимая строка превышает 79 символов).
РАБОТА С ФАЙЛАМИ НА ДИСКЕ
Аналогичным образом (так же как это делается при работе со стандартными потоками ввода-вывода stdin è stdout) можно осуществлять работу с файлами на диске. Для этой цели в библиотеку языка Си включены следующие функции:
fgetc(), getc() - ввод (чтение) одного символа из файла; fputc(), putc() - запись одного символа в файл; fprintf() - форматированный вывод в файл;
fscanf() - форматированный ввод (чтение) из файла; fgets() - ввод (чтение) строки из файла;
fputs() - запись строки в файл;
fwrite() - пересылает в файл заданное число байт, начиная с указанного адреса памяти; fread() пересылает заданное число байт из файла в массив в памяти.
Как было указано выше – поток (файл) можно открыть в текстовом либо двоичном (бинарном) режиме. В текстовом режиме прочитанная из потока комбинация символов CR (значение 13) и LF (значение 10), то есть управляющие коды "возврат каретки" и "перевод строки", преобразуется в один символ новой строки '\n' (значение 10, совпадающее с LF). При записи в поток в текстовом режиме осуществляется обратное преобразование, т.е. символ новой строки '\n' (LF) заменяется последовательностью CR и LF. Обычно в текстовом режиме с файлом работают функциями fprintf(), fscanf(), fgets(), fputs(), fgetc(), fputc(). В двоичном режиме применяют fgetc(), fputc(), fwrite(), fread().
В С не предусмотрено возможности задания структуры файла. Другими словами, не существует никаких операций с записями в файле, реализованных как часть языка С. Поэтому программист должен сам позаботиться о создании структуры файла, отвечающей требованиям конкретного приложения.
Для файлов, чьи записи имеют разную длину, обычно организовывают последовательный доступ. Т.е. когда записи обрабатываются последовательно друг за другом, начиная с начала файла.
И напротив, отдельные записи в файле с произвольным доступом обычно имеют фиксированную длину, что позволяет получить к ним непосредственный (и следовательно, быстрый) доступ без поиска по всем записям, т.е. можно вычислить точное положение записи относительно начала файла.
Двоичный режим обмена с файлами.
Двоичный (побайтный) режим обмена организуется с помощью функций getc( ) è putc(), обращение к которым имеет следующий формат:
с= getc(fp); putc(c, fp);
где fp - указатель на поток;
с - переменная типа int для приема очередного символа из файла или для записи ее значения в файл. Прототипы функции:
int getc (FILE * stream );
int putc (int c, FILE *stream );
Программа ввода читает символы с клавиатуры и записывает их в файл. Пусть признаком завершения ввода служит поступивший от клавиатуры символ '0'. Имя файла запрашивается у пользователя. Если при вводе последовательности символов была нажата клавиша <Enter>, служащая разделителем строк при вводе с клавиатуры, то в файл записываются управляющие коды "Возврат каретки" (CR - значение 13) и "Перевод строки" (LF - значение 10). Код CR в дальнейшем при выводе вызывает перевод маркера (курсора) в начало строки экрана дисплея. Код LF служит для перевода маркера на новую строку дисплея. Значения этих кодов в тексте программы обозначены соответственно идентификаторами CR и LF, т.е. CR и LF - именованные константы. Запись управляющих кодов CR и LF в файл позволяет при последующем выводе файла на экран отделить строки друг от друга или читать построчно функцией fgets().
В приводимых ниже программах используются уже рассмотренные выше функции getchar(), putchar() для посимвольного обмена со стандартными потоками stdin, stdout.
#include <stdio.h> /* Проãрамма ввода символов */ int main() {
FILE *fp; /* Уêазатель на потоê */ char с;
const char CR='\13'; const char LF = '\10';
char fname[20]; |
/* Массив для имени файла */ |
|
||
puts("Введите имя файла: \n"); gets(fname); /* Запрос имени файла: */ |
||||
if ((fp = fopen(fname,"w")) = NULL) |
/* Отêрыть |
файл для Записи: */ |
||
{ perror(fname); return 1; } |
|
|
||
while |
((c = getchar()) != '0') { /* |
Циêл ввода |
и записи в файл символов: */ |
|
if |
(с == '\n'){putc(CR, fp); putc(LF, fp);} |
|
||
else putc(c, |
fp); } |
|
|
|
fclose(fp); /* |
Циêл ввода завершен; |
заêрыть файл: */ |
Лысый Д.А. Основы программирования. Ввод, вывод, потоки, файлы. часть2 |
5 |
return 0; }
Следующая программа читает поток символов из ранее созданного файла и выводит его на экран дисплея:
#include <stdio.h> int main() {
FILE *fp; /* Уêазатель на потоê */ char с;
char fname[20]; /* Массив для имени файла */ puts("введите имя файла: \n "); gets(fname);
if ((fp = fopen(fname,"r")) = = NULL) /* Отêрыть файл для чтения: */ { perror(fname); return 1; }
while ((c = getc(fp)) != EOF) /* Циêл чтения из файла и вывода символов на эêран: */ putchar{c);
fclose(fp);
Строковый обмен с файлами.
Для работы с текстовыми файлами, кроме перечисленных выше, удобно использовать функции fgets() è fputs(). Прототипы этих функций в файле stdio.h имеют следующий вид:
int fputs(const char *s, FILE *stream); char * fgets(char *s, int n, FILE *stream);
Функция fputs() записывает ограниченную символом '\0' строку (на которую указывает s) в файл, определенный указателем stream на поток, и возвращает неотрицательное целое. При ошибках функция возвращает EOF. Символ '\0' в файл не переносится, и символ '\n' не записывается в конце строки вместо '\0'.
Функция fgets() читает из определенного указателем stream файла не более (n-1) символов и записывает их в строку, на которую указывает s. Функция прекращает чтение, как только прочтет (n-1) символов или встретит символ новой строки '\n', который переносится в строку s. Дополнительно в конец каждой строки записывается признак окончания строки '\0'. В случае успешного завершения функция возвращает указатель s. При ошибке или при достижении конца файла, при условии, что из файла не прочитан ни один символ, возвращается значение NULL. В этом случае содержимое массива, который адресуется указателем s, остается без изменений. Напомним, что в отли- чие от fgets() функция gets() отбрасывает символ '\n'.
Пример программы, которая переписывает текст из одного файла в другой. Предположим что мы хотим с ее помощью переписать содержимое файла f1.dat в файл f2.txt.
#include <stdio.h> main(){
char cc[256];/* Массив для обмена с файлами */ FILE *f1, *f2; /* Уêазатели на потоêи */
if ((f1 = fopen("f1.dat", "r")) == NULL) { perror("f1.dat\n"); return 1; } if ((f2 = fopen("f2.dat", "w")) == NULL) { perror("f2.dat\n"); return 1; } while (fgets(cc, 256, f1) != NULL)
fputs(cc, f2);
fclose(f1); fclose(f2); return 0; }
Если входной файл не существует или нет места на диске для создания выходного файла, то выдаются соответствующие сообщения:
Ошибêа при отêрытии файла f1.dat
èëè
Ошибêа при отêрытии файла f2.txt
Режим форматного обмена с файлами.
Если необходимо записывать информацию в файл в виде, пригодном для непосредственного отображения на экран дисплея, т.е. в символьном виде. То в этом случае можно воспользоваться функциями форматного ввода (вывода) в файл fprintf() è fscanf(), которые имеют следующие прототипы:
int fprintf (óêазатель_на_потоê, форматная-строêа, списоê-переменных);
int fscanf (óêазатель_на_потоê, форматная_строêа, списоê_адресов_переменных);
Как и в функциях printf() è scanf(), форматная строка может быть определена вне вызова функции, а в каче- стве фактического параметра в этом случае будет указатель на нее.
От рассмотренных выше функций printf(), scanf() для форматного обмена с дисплеем и клавиатурой функции fprintf() è fscanf( ) отличаются лишь тем, что в качестве первого параметра в них необходимо задавать указатель на поток, с которым производится обмен. В качестве примера приведем программу, создающую файл int.dat и записывающую в него символьные изображения чисел от 1 до 10 и их квадратов:
#include <stdio.h> int main() {
FILE *fp; /* Уêазатель на потоê */ int n;
if ((fp = fopen("int.dat","w")) == NULL) { perror("int.dat") ; return 1; }
for (n=1; |
n<11; |
n++) fprintf(fp, "%d |
%d\n", n, |
n*n); |
fclose(fp); |
return |
0; } |
|
|
Программа,форматного чтения данных из файла: |
|
|
#include <stdio.h> int main() {
FILE *fp; /* Уêазатель на потоê */ int n, nn, i;
if ((fp = fopen("int.dat","r")) == NULL) { perror("int.dat") ; return 1; } while (!feof(fp))
{ fscanf(fp,' "%d %d", &n, &nn); printf(" %d %d \n",n, nn); } fclose(fp); return 0; }
Строка while (!feof(fp)) использует функцию feof, чтобы определить, установлен ли индикатор конца файла, на который ссылается fp. Индикатор конца файла сообщает программе, что чтение данных закончено( если пользователь вводит данные с клавиатуры, то индикатор конца файла для стандартного ввода устанавливается, когда пользователь вводит соответствующую комбинацию клавиш). Аргумент, передаваемый функции feof, — указа-
Лысый Д.А. Основы программирования. Ввод, вывод, потоки, файлы. часть2 |
6 |
тель на файл, для которого необходимо проверить индикатор конца файла. Функция возвращает ненулевое значе- ние, (true), если индикатор конца файла установлен, в противном случае возвращается нуль. В данной программе цикл while, включающий в себя вызов feof, будет продолжать выполняться, пока не будет установлен индикатор конца файла.
1.Комбинации клавиш для конца файла в различных популярных системах
Компьютерные системы |
Комбинация клавиш |
|
UNIX |
<return> <ctrl>d |
|
IBM PC и совместимые |
<ctrl>z |
|
Macintosh |
<ctrl>d |
|
VAX(VMS) |
|
<ctrl>z |
Позиционирование в потоке.
Рассмотреные выше - посимвольный, построчный и форматный обмены с файлами, организованными в виде потока байтов Эти средства позволяли записать в файл данные и читать из него информацию только последовательно.
Операция чтения (или записи) для потока всегда производится, начиная с текущей позиции в потоке. Начальная позиция чтения/записи в потоке устанавливается при открытии потока и может соответствовать начальному или конечному байту потока в зависимости от режима открытия потока. При открытии потока в режимах "r" и "w" указатель текущей позиции чтения/записи в потоке устанавливается на начальный байт потока, а при открытии в режиме "а" - в конец файла (за конечным байтом) При выполнении каждой операции ввода-вывода указатель текущей позиции в потоке перемещается на новую текущую позицию в соответствии с числом прочитанных (записанных) байтов.
Рассмотрим средства позиционирования в потоке, позволяющие перемещать указатель (индикатор) текущей позиции в потоке на нужный байт. Эти средства дают возможность работать с файлом на диске, как с обычным массивом, осуществляя доступ к содержимому файла в произвольном порядке.
В библиотеку языка Си включена функция fseek() для перемещения (установки) указателя текущей позиции в потоке на нужный байт потока (файла). Она имеет следующий прототип:
int fseek (óêазателъ_на_потоê, смещение, начало_отсчета);
Смещение задается переменной или выражением типа long и может быть отрицательным, т.е. возможно перемещение по файлу в прямом и обратном направлениях. Начало отсчета задается одной из предопределенных констант, размещенных в заголовочном файле stdio.h:
SEEK_SET (имеет значение 0) - начало файла;
SEEK_CUR (имеет значение 1) - теêóщая позиция; SEEK_END (имеет значение 2) - êонец файла.
Функция fseek() возвращает 0, если перемещение в потоке (файле) выполнено успешно, в противном случае возвращается ненулевое значение.
Приведем примеры использования функции fseek(). Перемещение к началу потока (файла) из произвольной позиции: fseek(fp, 0L, SEEK_SET);
Перемещение к концу потока (файла) из произвольной позиции: fseek(fp, 0L, SEEK_END); В обоих примерах смещение задано явно в виде нулевой десятичной константы 0L типа long.
При использовании сложных типов данных (таких, как структура) можно перемещаться в потоке (файле) на то количество байтов, которое занимает этот тип данных. Пусть, например, определена структура: struct str {..}
st;
Тогда следующее обращение к функции fseek() переводит указатель текущей позиции в потоке на одну структуру вперед относительно текущей позиции: fseek(fp, (long)Sizeof(st), SEEK_CUR);
Кроме рассмотренной функции fseek(), в библиотеке функций языка Си находятся следующие функции для работы с указателями текущей позиции в потоке:
long ftell(FILE *) - получить значение указателя текущей позиции в потоке;
void rewind(FILE *) - установить указатель текущей позиции в потоке на начало потока.
Необходимо иметь в виду, что недопустимо использовать функции работы с указателем текущей позиции в потоке для потока, связанного не с файлом, а с устройством. Поэтому применение описанных выше функций с любым из стандартных потоков приводит к неопределенным результатам.
Файлы произвольного доступа
Рассмотрим некоторые функции и приемы, применяемые к файлам произвольного доступа.
Функция fwrite() пересылает в файл заданное число байт, начиная с указанного адреса памяти. Данные записываются с того места в файле, которое обозначено указателем позиции файла. Функция fread() пересылает заданное число байт из места в файле, определенного указателем позиции файла, в массив в памяти, начинающийся с указанного адреса. Теперь при записи целого числа вместо оператора
fprintf (fPtr, "%d", number);
который может напечатать от одной до одиннадцати цифр (десять цифр плюс знак, требующие по одному байту памяти каждый) для четырехбайтного целого, мы можем применить оператор
fwrite(&number, sizeof(int), 1, fPtr);
который всегда записывает четыре байта (или два байта для систем с двухбайтовым целым) из переменной number в файл, определяемый fPtr. В дальнейшем, вызвав fread, можно прочитать эти четыре байта в переменную number. Õîòÿ fread è fwrite считывают и записывают данные, такие как целые числа, в формате с фиксированным, а не переменным, размером, данные, с которыми они оперируют, обрабатываются в форме “сырых данных” (то есть в виде байтов), а не в привычном для человека века формате, с которым оперируют printf è scanf.
Функции fwrite è fread дают возможность чтения и записи массивов данных с диска и на диск. Третий аргумент как в fread, òàê è â fwrite — это число элементов массива, которые необходимо считать с диска или записать на диск. Представленный выше вызов функции fwrite записывает на диск одно целое поэтому третий аргумент равен 1 (как если бы записывался один элемент массива).
Лысый Д.А. Основы программирования. Ввод, вывод, потоки, файлы. часть2 |
7 |
Программы обработки файлов редко записывают в файл единственное поле. Обычно они записывают за один раз переменную типа struct.
Мы уже говорили, что отдельные записи в файле с произвольным доступом обычно имеют фиксированную длину. Это позволяет получить к ним непосредственный (следовательно, быстрый) доступ без поиска по всем записям, т.е. можно вычислить точное положение записи относительно начала файла.
Размер одной записи, обычно, вычисляется оператором sizeof(struct type), в случае если все записи имеют фиксированную длину и не содержат ссылок.
Вычислить число записей в файле можно, используя следующий прием – вычисляем объем файла в байтах и делим его на размер одной записи:
while(1) { |
|
|
fgetc(cfPtr); |
/* cfPtr – óêазатель на потоê связанный с файлом */ |
|
if(feof(cfPtr)) break; /* если данные исчерпаны – то выход */ |
||
j++; } |
/* переменная-счетчиê для подсчета размера файла */ |
|
Таêже, можно подсчитать объем сразó в записях: |
|
|
while(1) { |
|
|
if (fread(&its, sizeof(student), 1, cfPtr) == |
NULL) break; |
|
j++; } /* , ãде its – стрóêтóра типа student |
*/ |
Приведенные выше фрагменты теряют свою актуальность, если размер файла во время исполнения программы изменяется другим процессом. В этом случае можно использовать второй фрагмент, который обрабатывает записи последовательно, начиная с текущей позиции в файле и заканчивая последней.
while(1) {
if (fread(&its, sizeof(student), 1, cfPtr) == NULL) break; { /* обработêа записей */ } }
Для перехода к произвольной записи можно использовать оператор fseek. Например:
fseek(cfPtr, (Num -1) * sizeof(student), SEEK_SET);
Этот оператор устанавливает указатель позиции файла, на который ссылается cfPtr на байт, определяемый выражением (Num-1) * sizeof(struct student); (значение этого выражения называется смещением), т.е. перед записью с номером Num, которую мы хотим обрабатывать. Таким образом, для первой записи указатель позиции файла устанавливается на байт 0 файла.
Рассмотрим пример:
Создать программу для работы со списком студентов, способную хранить данные на диске и выводить их на печать, предусмотреть возможность добавления и корректировки данных.
Для удобства разобьем программу на несколько функций: Create_File – создает файл, заданного размера;
Udate_stud – позволяет обновлять данные указанных записей; Print_stud – считывает данные с диска и выводит на печать.
#include <stdio.h> #include <stdlib.h> #include <conio.h>
void Create_File(char *, int); void Udate_stud(char *, int); void Print_stud(char *);
typedef struct { int number; char name[15];
char surname[15];
long n_book; } student;
main () {
char *fn="stud.dat";
int kol_stud=5; /* первоначальное êоличество стóдентов */ clrscr();
Create_File( fn, kol_stud); /* создаем файл из пóстых записей */
Udate_stud(fn, kol_stud); /* редаêтирóем сóществóющие записи или добавляем новые*/ Print_stud(fn); /* распечатываем данные из файла */
return 0; }
/* Последовательное создание файла с произвольным достóпом */ void Create_File(char *name, int kol) {
int i; |
|
student its |
= { 0,"","",0}; |
FILE *cfPtr; |
|
if ((cfPtr = |
fopen(name, "w")) == NULL) perror(name); |
else {
for (i = 1; i <= kol; i++)
fwrite(&its, sizeof(student), 1, cfPtr); fclose(cfPtr); }
}
void Udate_stud(char *name, int kol) { FILE *cfPtr;
student its;
if ((cfPtr = fopen(name, "r+")) == NULL) perror(name); else {
printf("Введите номер стóдента (1 to %d, 0 - заêончить ввод)?\n", kol); scanf("%d", &its.number);
while (its.number !=0) {
printf("Введите имя, Фамилию, номер зачетêи ?\n"); scanf("%s%s %ld", &its.name, &its.surname, &its.n_book);
Лысый Д.А. Основы программирования. Ввод, вывод, потоки, файлы. часть2 |
8 |
|
fseek(cfPtr, (its.number-1)*sizeof(student), SEEK_SET); |
|
|
fwrite(&its, sizeof(student), 1, cfPtr); |
|
|
printf("Введите номер стóдента ?\n "); |
|
|
scanf("%d", &its.number); |
|
|
} |
|
|
} |
|
|
fclose (cfPtr); |
|
|
} |
|
|
void Print_stud(char *name) { |
|
|
FILE *cfPtr; |
|
|
student |
its; |
|
int n,j=0; |
|
|
if ((cfPtr = fopen(name, "r")) == NULL) perror(name); |
|
|
printf(" |
Списоê стóдентов ãрóппы?\n"); |
|
while (1)) {
if (fread(&its, sizeof(student), 1, cfPtr) == NULL) break; if (its.number !=0)
printf("%5d%17s%17s%10ld\n",its.number, its.name, its.surname, its.n_book);
}
fclose (cfPtr);
}