Лабораторная работа №9
Тема: Файлы
Ход выполнения лабораторной работы должен быть отражен в отчете. Отчет должен содержать титульный лист, номера задания, коды программ, картинку с результатом выполнения программы и ответы на контрольные вопросы.
ТЕОРЕТИЧЕСКАЯ ЧАСТЬ
Файл – это именованный объект, хранящий данные (программа или любая другая информация) на каком-либо носителе. Файл, как и массив, – это совокупность данных.
Отличия файла от массива:
1. Файлы в отличие от массивов располагаются не в оперативной памяти, а на жестких дисках или на внешних носителях, хотя файл может располагаться на так называемом электронном диске (в оперативной памяти).
2. Файл не имеет фиксированной длины, т.е. может увеличиваться и уменьшаться.
3. Перед работой с файлом его необходимо открыть, а после работы – закрыть.
Файловая система – это совокупность файлов и управляющей информации на диске для доступа к файлам. Или по-другому – это совокупность программных средств для доступа к файлам. Существует довольно много файловых систем и правила именования файлов в них могут незначительно отличаться.
Имена файлов состоят из двух частей, разделенных точкой: имя файла и расширение. Файлы хранятся в каталогах (директориях). Каталоги могут иметь такие же имена, что и файлы. Допускаются вложенные каталоги (подкаталоги).
Различают два вида файлов: текстовые и бинарные.
Текстовые файлы могут быть просмотрены и отредактированы с клавиатуры любым текстовым редактором и имеют очень простую структуру: последовательность ASCII-символов. Эта последовательность символов разбивается на строки, каждая из которых заканчивается двумя кодами: 13, 10 (0xD, 0xA). Примеры известных текстовых фалов: *.bat, *.c, *.asm, *.срр.
Бинарные файлы – это файлы, которые не имеют структуры текстовых файлов. Каждая программа для своих бинарных файлов определяет собственную структуру.
Библиотека языка C/С++ содержит функции для работы как с текстовыми, так и с бинарными файлами.
Функции открытия и закрытия файла. Перед работой с файлом, его необходимо открыть.
Функция открытия файла fopen():
FILE * fopen (char *filename, char *mode);
где FILE – структурный тип, который связан с физическим файлом и содержит всю необходимую информацию для работы с ним (указатель на текущую позицию в файле, тип доступа и др.).
char *filename - задает физическое местонахождение (путь) и имя открываемого файла;
char *mode - тип доступа к файлу, который может принимать значения, указанные в таблице.
Функция fopen() при успешном открытии файла возвращает указатель на структуру типа FILE, называемый указателем на файл. Эта структура связана с физическим файлом и содержит всю необходимую информацию для работы с ним (указатель на текущую позицию в файле, тип доступа и др.). Возвращаемое функцией значение нужно сохранить и использовать для ссылки на открытый файл. Если произошла ошибка при открытии файла, то возвращается NULL.
Таблица: Тип доступа к файлам.
“r” | Открыть файл для чтения. |
“w” | Открыть файл для записи. Если файл существует, то его содержимое теряется. |
“a” | Открыть файл для записи в конец файла. Если файл не существует, то он создается. |
“r+” | Открыть файл для чтения и записи. Файл должен существовать. |
“w+” | Открыть файл для чтения и записи. Если файл существует, то его содержимое теряется. |
“a+” | Открыть файл для чтения и записи в конец файла. Если файл не существует, то он создается. |
К комбинациям вышеперечисленных литералов могут быть добавлены также “t” либо “b”:
“t” | Открыть файл в текстовом режиме. |
“b” | Открыть файл в бинарном режиме. |
Возможны следующие режимы доступа: “ w+b ”, “ wb+ ”, “ rw+ ”, “ w+t ”, “ rt+ ” и др. Если режим не указан, то файл открывается в текстовом режиме.
После работы с файлом он должен быть закрыт функцией fclose().
Для этого необходимо в указанную функцию передать указатель на FILE, который был получен при открытии функцией fopen(). При завершении программы незакрытые файлы автоматически закрываются системой.
Стандартная последовательность операторов, необходимая для открытия и закрытия файла:
# include < stdio.h >
...
FILE *f;
if(!(f = fopen (“readme.txt”, ”r+t”)))
{
printf (“Невозможно открыть файл \n”); return;
}
... // Работа с файлом
fclose (f); // Закрытие файла
...
Функции чтения/записи в файл. Функции для работы с текстовым файлом: fprintf(), fscanf(), fgets(), fputs(). Формат параметров этих функций очень похож на формат функций printf(), scanf(), gets() и puts(). Схожи не только параметры, но и действия. Отличие лишь в том, что printf(), scanf() и другие работают по умолчанию с консолью (экран, клавиатура), а fprintf(), fscanf() – с файлами (в том числе и со стандартными потоками stdin, stdout и др.), поэтому у них добавлен параметр, являющийся указателем на структуру FILE, которая была рассмотрена выше.
fprintf() – это функция форматированного вывода в файл
Пример 1: Записать в текстовый файл числа от 0 до 1000 кратные 3.
#include "stdafx.h"
#include<stdio.h>
int _tmain(int argc, _TCHAR* argv[])
{
int a;
FILE *f;
if(!(f = fopen("test.txt", "w+t")))
{
printf("Невозможно создать файл\n"); return 0;
}
for(a=0;a<1000;a+=3)
{
fprintf(f, "%d, ",a);
}
printf("Данные записаны в файл\n");
fclose(f);
return 0;
}
Функции для работы с текстовыми файлами удобно использовать при создании текстовых файлов, ведении файлов-протоколов (log-файлов) и т.п. Но при создании баз данных целесообразно использовать функции для работы с бинарными файлами: fwrite() и fread(). Эти функции без каких-либо изменений копируют блок данных из оперативной памяти в файл и соответственно из файла – в память. Такой способ обмена данными требует меньше времени:
unsigned fread (void *ptr, unsigned size, unsigned n, FILE *stream);
unsigned fwrite (void *ptr, unsigned size, unsigned n, FILE *stream);
где: *ptr – указатель на буфер;
size – размер блока;
n – количество блоков;
*stream – указатель на структуру FILE открытого файла.
Пример 2: Записать в бинарный файл и прочитать из бинарного файла список абитуриентов, информация об абитуриенте представлена структурой.
#include "stdafx.h"
#include <stdio.h>
struct abitur
{ char name[32];
int mark[3];
};
int _tmain(int argc, _TCHAR* argv[])
{ struct abitur inf;
int a;
FILE *f;
if(!(f=fopen("inf.dat","w+")))
{ printf("Ошибка создания файла\n"); return 0; }
for(;;)
{ printf("Введите ФИО (пустая строка -- конец списка): ");
fflush(stdin);
gets(inf.name);
if(!inf.name[0]) break;
printf("\n Введите три оценки, полученные на экзаменах: ");
scanf("%d%d%d", &inf.mark[0], &inf.mark[1], &inf.mark[2]);
fwrite(&inf, 1,sizeof(inf), f);
}
fclose(f);
printf("\nСписок абитуриентов:\n");
if(!(f=fopen("inf.dat","r")))
{ printf("Ошибка создания файла\n"); return 0;}
while(1)
{ if(sizeof(inf)!= fread(&inf, sizeof(inf), 1,f))
break; /* Если не удалось прочитать необходимое
количество байт, то заканчиваем чтение */
printf("%s %d %d %d \n", inf.name, inf.mark[0], inf.mark[1], inf.mark[2]);
}
fclose(f);
return 0;
}
Важно понимать, что нет однозначного метода, который можно было бы использовать для отличия текстового файла от бинарного, поэтому любой бинарный файл может быть открыт для работы с ним как с текстовым; а текстовый может быть открыт как бинарный. Но такой вариант работы с файлом обычно приводит к ошибкам. Поэтому рекомендуется работать с конкретным файлом в том режиме, в котором он был создан.
Позиционирование в файле. Каждый открытый файл имеет, так называемый указатель на текущую позицию в файле (это нечто подобное указателю в памяти). Все операции над файлами (чтение и запись) работают с данными с этой позиции. При каждом выполнении функции чтения или записи указатель смещается на количество прочитанных или записанных байт, т.е. устанавливается сразу за прочитанным или записанным блоком данных в файле. В этом случае осуществляется так называемый последовательный доступ к данным, который очень удобен, когда нам необходимо последовательно работать с данными в файле. Это демонстрируется во всех вышеприведенных примерах чтения и записи в файл. Но иногда необходимо читать или писать данные в произвольном порядке, что достигается путем установки указателя на некоторую заданную позицию в файле функцией fseek().
int fseek (FILE *stream, long offset, int whence);
Параметр offset задает количество байт, на которое необходимо сместить указатель соответственно параметру whence. Приводим значения, которые может принимать параметр whence:
SEEK_SET | Смещение выполняется от начала файла. | |
SEEK_CUR | Смещение выполняется от текущей позиции указателя. | |
SEEK_END | Смещение выполняется от конца файла. |
Величина смещения может быть как положительной, так и отрицательной, но нельзя смещаться за пределы начала файла.
Такой доступ к данным в файле называют произвольным.
Пример 3: Найти по номеру запись из бинарного файла, который был создан в примере 2.
#include "stdafx.h"
#include <stdio.h>
struct abitur
{ char name[32];
int mark[3];
};
int _tmain(int argc, _TCHAR* argv[])
{ struct abitur inf;
int n;
FILE *f;
if(!(f=fopen("inf.dat","r")))
{ printf("Ошибка создания файла\n");
return 0;
}
while(1)
{ printf("Введите номер записи (0 - выход): ");
scanf("%d", &n);
if(!n) break;
fseek(f,sizeof(struct abitur)*(n-1),SEEK_SET);
if(sizeof(inf)!= fread(&inf,1,sizeof(inf),f))
printf("Конец списка\n");
else
printf("%s %d %d %d", inf.name, inf.mark[0], inf.mark[1], inf.mark[2]);
}
fclose(f);
return 0;
}
Иногда необходимо определить позицию указателя. Для этого можно воспользоваться функцией ftell ():
long ftell (FILE *stream);
Возвращает значение указателя на текущую позицию файла. В случае ошибки возвращает число (-1).
int fsetpos(FILE *stream, const long *pos) - устанавливает значение указателя чтения-записи (указатель на текущую позицию) файла в позицию заданную значением по указателю pos. Возвращает нуль при корректном выполнении, и любое не нулевое значение при ошибке.
int fgetpos(FILE *stream, long *pos) - помещает в переменную, на которую указывает pos, значение указателя на текущую позицию в файле. Возвращает нуль при корректном выполнении, и любое не нулевое значение при ошибке. [3]
Пример 4: Программа создает простой файл последовательного доступа, который можно использовать в программе учета оплаты счетов, помогающей следить за суммами задолженности клиентов компании. Для каждого клиента программа просит ввести номер счета, имя клиента и баланс (то есть сумму, которую клиент должен компании за товары и услуги, полученные в прошлом). Данные на каждого из клиентов образуют "запись" для этого клиента. В этом приложении в качестве ключа записи используется номер счета. Другими словами, файл будет создаваться и поддерживаться упорядоченным по номерам счетов. Эта программа подразумевает, что пользователи вводят записи в порядке возрастания номеров. В более удобной для работы системе регистрации счетов должна обеспечиваться возможность сортировки, чтобы пользователь мог вводить записи в произвольном порядке. В этом случае записи должны сначала упорядочиваться, а затем уже записываться в файл.
#include "stdafx.h"
#include <stdio.h>
int _tmain(int argc, _TCHAR* argv[])
{
int account;
char name[30];
float balance;
FILE *cfPtr;
if ((cfPtr = fopen("clients.dat", "w")) == NULL)
printf ("File could not be opened\n");
else {
printf("Enter the account, name, and balance.\n");
printf ("Enter EOF to end input (Ctrl+Z).\n");
printf("? ");
scanf("%d%s%f", &account, name, &balance);
while (!feof(stdin)) {
fprintf(cfPtr, "%d %s %.2f\n", account, name, balance);
printf("? ");
scanf("%d%s%f", &account, name, &balance);
}
fclose (cfPtr);
}
return 0;
}
Пример 5: Чтение и распечатка последовательного файла, созданного в предыдущем примере.
#include "stdafx.h"
#include <stdio.h>
int _tmain(int argc, _TCHAR* argv[])
{
int account;
char name[30];
float balance;
FILE *cfPtr;
if ((cfPtr = fopen("clients.dat", "r")) == NULL)
printf("File could not be openedXn");
else {
printf("%-10s%-13s%s\n", "Account", "Name", "Balance");
fscanf(cfPtr, "%d%s%f", &account, name, &balance);
while (!feof(cfPtr)) {
printf("%-10d%-13s%7.2f\n", account, name, balance);
fscanf(cfPtr, "%d%s%f", &account, name, &balance);
}
fclose(cfPtr);
}
return 0;
}
Пример 6: Программа позволяет менеджеру по кредиту получить список клиентов с нулевым сальдо (клиентов, которые не должны денег), клиентов с положительным сальдо (которым компания должна какую-то сумму денег) и клиентов с отрицательным сальдо (которые должны компании деньги за уже полученные товары и услуги).
#include "stdafx.h"
#include <stdio.h>
int _tmain(int argc, _TCHAR* argv[])
{
int request, account;
float balance;
char name[30];
FILE *cfPtr;
if ((cfPtr = fopen("clients.dat", "r")) == NULL)
printf("File could not be opened\n");
else {
printf("Enter request\n");
printf(" 1 - List accounts with zero balances\n");
printf(" 2 - List accounts with credit balances\n");
printf(" 3 - List accounts with debit balances\n");
printf(" 4 - End of run\n? ");
scanf("%d", &request);
while (request!= 4) {
fscanf(cfPtr, "%d%s%f", &account, name, &balance);
switch (request) {
case 1:
printf("\nAccounts with zero balances:\n");
while(!feof(cfPtr)) {
if (balance ==0)
printf("%-10d%-13s%7.2f\n", account, name, balance);
fscanf (cfPtr, "%d%s%f", &account, name, &balance);
}
break;
case 2:
printf("\nAccounts with credit balances: \n");
while (!feof(cfPtr)) {
if (balance < 0)
printf("%-10d%-13s%7.2f\n", account, name, balance);
fscanf(cfPtr, "%d%s%f", &account, name, &balance); }
break;
case 3:
printf("\nAccounts with debit balances: \n");
while (!feof(cfPtr)) {
if (balance >0)
printf("%-10d%-13s%7.2f\n", account, name, balance);
fscanf(cfPtr, "%d%s%f", &account, name, &balance);
}
break;
}
rewind(cfPtr);
printf("\n? ");
scanf("%d", &request);
}
printf("End of run.\n");
fclose(cfPtr);
}
return 0;
}