Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

C_Kurs_Lekt / C_II_семестр / 10_Файлы_потоки

.pdf
Скачиваний:
11
Добавлен:
13.02.2016
Размер:
172.46 Кб
Скачать

Лысый Д.А. Основы программирования. Ввод, вывод, потоки, файлы. часть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); /*

Циêл ввода завершен;

заêрыть файл: */

return 0; }

Лысый Д.А. Основы программирования. Ввод, вывод, потоки, файлы. часть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);

}