Урок 10.6. Файлы произвольного доступа. Позиционирование доступа

10.6. Файлы произвольного доступа. Позиционирование доступа
10.6.1. Создание файла произвольного доступа
10.6.2. Произвольная запись данных в файл произвольного доступа
10.6.3. Чтение данных из файла произвольного доступа

Созданные при помощи функции форматированного вывода printf() записи в файле не обязаны иметь одинаковую длину. Записи в файле с произвольным доступом обычно имеют фиксированную длину, что позволяет получить к ним непосредственный и быстрый доступ без поиска по всем записям. Существуют и другие способы обеспечения произвольного доступа к записям в файле, например в базах данных. Т.к. все записи имеют одинаковую длину, то можно вычислить точное положение записи относительно начала файла как функцию ключа записи, например по её порядковому номеру.

В файле с произвольным доступом можно заменять (обновлять) данные, не разрушая при этом те, что уже находятся в файле и, не перезаписывая весь файл. Можно также “удалять” записи, пометив их каким-либо образом как удаленные. Для того чтобы физически удалить записи из файла его нужно перезаписать, исключая записи помеченные как удаленные.

10.6.1. Создание файла произвольного доступа

Для записи данных в файл произвольного доступа будем использовать функцию fwrite(), а для чтения – fread().

fwrite(<буфер>,<размер элемента>,<число элементов>,<поток>);
fread(<буфер>,<размер элемента>,<число элементов>,<поток>);

 <буфер> – указатель на записываемые данные, для fread() место куда прочитаются данные.
<размер элемента> – размер элемента в байтах,
<число элементов> – максимальное число записываемых/читаемых  элементов,
<поток> – указатель на структуру FILE.

Например, для записи целого числа вместо оператора

fprintf(f, ”%d”, number);

который записывает различное число байт в зависимости от числа цифр в числе, будем использовать:

fwrite(number, sizeof(int), 1, f)

который всегда записывает 4 байта (для четырехбайтового целого) из переменной number в файл в том виде, в каком число представляется в памяти компьютера.

Рассмотрим проблемы работы с файлами произвольного доступа на примере задачи записи данных о служащих и их зарплатах в файл, с последующей обработкой этих данных. Задачу разобьем на следующие подзадачи:

  • создание и форматирование файла;
  • заполнение файла данными;
  • вывод ведомости на зарплату

Вот пример функции создания и форматирования (заполнения “пустыми” записями) файла произвольного доступа.

#include <stdio.h>
#include <conio.h>  
#include <locale.h>
#include <stdlib.h>
//Последовательное создание файла с произвольным доступом 
//(форматирование файла).
void CreateFormatFile(char * filename)
{   struct EMPLOYEE{ //служащий и его зарплата
	   int number;
	   char lastName[15];
	   char firstName[10];
	   float salary;
    };
 
	int i;
	//”пустые” записи
	struct EMPLOYEE blank = {0, "", "", 0.0};
 
	FILE *fptr;
	//создаем файл для записи
	errno_t err;
	err = fopen_s(&fptr, filename, "w");
	if (err != 0) printf("Файл не создан\n");
	else {        printf("Файл создан\n" );
		 //заполнение файла "пустыми" записями
		 for(i = 1; i <= 100; i++)
	         fwrite(&blank,sizeof(struct EMPLOYEE),1,fptr);
         }
	fclose(fptr);
 
	puts("\nЗавершение создания файла\n");
    return;
}

Функция fwrite()записывает содержимое структурной переменной blank размером sizeof(struct EMPLOYEE) байт в количестве одной единицы в файл, на который указывает fptr.

10.6.2. Произвольная запись данных в файл произвольного доступа

Данные о сотрудниках будем записывать в файл в соответствие с номером сотрудника, таким образом, каждая запись попадет на отведенное для нее место. Некоторые записи могут остаться незаполненными в случае отсутствия сотрудника с соответствующим номером. Для установки указателя файла на соответствующую запись нам понадобится функция fseek().

Функция fseek() имеет три аргумента, и обращение к ней в общем виде представляется следующим образом:

fseek(<указатель файла>,<смещение>,<код откуда>);

Первый аргумент функции определяет файл, в котором осуществляется позиционирование. Второй аргумент определяет, на какое количество байт необходимо сместиться указателю на текущую позицию файла относительно текущей позиции (от исходной байта). Заметим, что величина смещения может быть как положительной (перемещение к концу файла – вперед), так и отрицательной (перемещение к началу файла – назад). Смещение должно иметь тип int long. Третий аргумент является кодом, определяющим начальную точку отсчета:

Константа Значение Положение точки отсчета
SEEK_SET 0 Начало файла
SEEK_CUR 1 Текущая позиция
SEEK_END 2 Конец файла

Например, оператор

fseek(fptr,(empl.number-1)*sizeof(struct EMPLOYEE), SEEK_SET);

переходит в файле, на который указывает указатель fptr, на позицию, отстоящую на ((empl.number-1)*sizeof( EMPLOYEE)) байт от начала (SEEK_SET) файла. Выражение((empl.number-1)*sizeof(struct EMPLOYEE)) означает: умножить номер сотрудника (-1 – т.к. байты нумеруются с нуля) на размер записи, содержащей данные о сотруднике.

Еще одним удобным средством позиционирования доступа к файлу является функция rewind(). Использование этой функции позволяет переместить указатель на текущую позицию файла из любого его положения к началу файла. Обращение к функции имеет вид:

rewind(<указатель_файла>);

Очевидно, что действие функции rewind() аналогично действию fseek(<указатель_файла>, 0L, 0);

Далее приводим текст функции, в которой данные записываются в созданный файл произвольного доступа в соответствие с введенным номером сотрудника.

//Функция записи в файл с произвольным доступом
#include <stdio.h>
#include <conio.h>  
#include <stdlib.h>  
//Заполнение файла данными
void WriteDataFile(char * filename)
{   struct EMPLOYEE{ //служащий и его зарплата
	   int number;
	   char lastName[15];
	   char firstName[10];
	   float salary;
    };
 	struct EMPLOYEE empl;
	FILE *fptr;
 
	errno_t err;
	err = fopen_s(&fptr, filename, "r+");
	if (err != 0) printf("Файл не открыт\n");
	else {        printf("Файл открыт\n" );
	//Ввод данных файл
		printf("Введите номер сотрудника (от 1 до 100)"
		       "или 0 для завершения ввода\n>" );
		scanf_s("%d", &empl.number);
		while(empl.number !=0 ){
		  printf("Введите фамилию, имя и зарплату ”
	                 “\n(дробные числа через ,(зпт), т.к.” 
	                 “setlocale установлен в” “Russian”)\n>");
	          scanf_s("%s%s%f",empl.lastName,_countof(empl.lastName),
	                          empl.firstName,_countof(empl.firstName), 
	                          &empl.salary); 
	          //позиционирование на указанную запись
	          fseek(fptr, (empl.number-1)*sizeof(struct EMPLOYEE), 
	                                                     SEEK_SET); 
	         //запись данных в файл
	         fwrite(&empl, sizeof(struct EMPLOYEE), 1, fptr); 
	         printf("Введите номер сотрудника(от 1 до 100) или 0\n>");
	         scanf_s("%d", &empl.number);
	       }
	} //конец  else и if
	fclose(fptr);
 
	puts("\nЗавершение записи данных в файл");
	return;
}

10.6.3. Чтение данных из файла произвольного доступа

В этой функции считываются данные о сотрудниках функцией fread(). Данные о сотрудниках с ненулевой зарплатой выводятся на экран. Функция fread() считывает заданное число байт из файла в переменную, например:

fread(&empl, sizeof(struct EMPLOYEE), 1, fptr);

считывает одну запись размером (sizeof(struct EMPLOYEE)) байт из файла на который указывает fptr в переменную empl, адрес которой передается функции. Записи считываются подряд с начала файла. Функция проверяет начислена ли сотруднику зарплата: if(empl.number != 0).

#include <stdio.h>
#include <conio.h>  
//Сформируем ведомость по зарплате сотрудников (pay-sheet)
void ReadDataFile(char * filename)
{   struct EMPLOYEE{ //служащий и его зарплата
	   int number;
	   char lastName[15];
	   char firstName[10];
	   float salary;
    };
   	struct EMPLOYEE empl;
	FILE *fptr;
 
	errno_t err;
	err = fopen_s(&fptr, filename, "r");  //rewind(fptr); 
	if (err != 0) printf("Файл не открыт для чтения\n");
        else {        printf("\nФайл открыт для чтения\n" );
	   float total = 0;
	   printf("%-6s%-16s%-13s%-10s\n", "Номер", "Фамилия",
	                                   "Имя", "Зарплата");
	   while(!feof(fptr)){
		   fread(&empl, sizeof(struct EMPLOYEE), 1, fptr);
                   if(feof(fptr)) break;//!т.к. после прочтения последней записи
                                        //нужна еще одна попытка чтения, чтобы попасть на конец файла
		   if(empl.number != 0) {
			    total += empl.salary;
			    printf("%-6d%-16s%-11s%10.2f\n", 
	                            empl.number, empl.lastName, 
	                            empl.firstName, empl.salary);
	           }
	   }
	printf("\n%33s%10.2f\n", "Итого: ",total);
	}  
	fclose(fptr);
	puts("\nЗавершение формирование ведомости");
	return;
}

 Если теперь к этим функциям добавить функцию main(), содержащую программное меню, а также функцию вывода текста меню на экран Menu(), то получится почти полная программа обработки файла произвольного доступа.

#include <stdio.h>
#include <conio.h> 
#include <locale.h>
#include <stdlib.h>  
 
int main(void)
{   
    setlocale(LC_ALL,"Russian"); 
 
	void CreateFormatFile(char *filename);
	void WriteDataFile(char *filename);
	void ReadDataFile(char *filename);
	int Menu(void);
 
	char *fileName = "employ.dat";
	int choice;
	while ((choice = Menu()) != 4){
		switch(choice){
			case 1: CreateFormatFile(fileName);
				    break;
			case 2: WriteDataFile(fileName);
				    break;
			case 3: ReadDataFile(fileName);
				    break;
	    }
	}
	puts("Завершение программы");
	_getch();
	return 0;
}
 
 
#include <stdio.h>
int Menu(void){
int menuChoice;
  printf("\nВыберите пункт меню\n");
  printf("1 - Создание и форматирование файла\n"
	 "2 - Ввести данные в файл\n"
	 "3 - Прочитать данные из файла и напечатать ведомость\n"
	 "4 - Выход\n>"
	    );
  scanf_s("%d", &menuChoice);
return menuChoice;
}
 
#include <stdio.h>
#include <conio.h> 
#include <locale.h>
#include <stdlib.h> 
//Последовательное создание файла с произвольным доступом (форматирование файла).
void CreateFormatFile(char * filename)
{   struct EMPLOYEE{ //служащий и его зарплата
	   int number;
	   char lastName[15];
	   char firstName[10];
	   float salary;
    };
 
	int i;
	struct EMPLOYEE blank = {0, "", "", 0.0};
 
	FILE *fptr;
	//создаем файл для записи
	errno_t err;
	err = fopen_s(&fptr, filename, "w");
	if (err != 0) printf("Файл не создан\n");
        else {        printf("Файл создан\n" );
                 //заполнение файла "пустыми" записями
		 for(i = 1; i <= 100; i++) 	     	   
                   fwrite(&blank, sizeof(struct EMPLOYEE), 1, fptr);         
        }
 	fclose(fptr); 	
        puts("\nЗавершение создания файла\n");     
        return; 
} 
#include <stdio.h> 
#include <conio.h>  
#include <stdlib.h>   
//Заполнение файла данными void WriteDataFile(char * filename) 
{   struct EMPLOYEE{ //служащий и его зарплата 	   
                     int number; 	  
                     char lastName[15]; 	  
                     char firstName[10];
                     float salary;     
};  	
struct EMPLOYEE empl; 	
FILE *fptr; 	
 
errno_t err; 	
err = fopen_s(&fptr, filename, "w"); 	
if (err != 0) printf("Файл не открыт\n");        
else {        printf( "Файл открыт\n" ); 	
//Ввод данных файл 		   
                   printf("Введите номер сотрудника (от 1 до 100) \n" 
                          "или 0 для завершения ввода\n>" );
		   scanf_s("%d", &empl.number);
		   while(empl.number !=0 )
                   {  printf("Введите фамилию, имя и зарплату \n(дробные числа" 
	                      "через ,(зпт)), т.к. setlocale установлен в" 
	                      "Russian)\n>");
	              scanf_s("%s%s%f",empl.lastName, _countof(empl.lastName),
	                               empl.firstName,_countof(empl.firstName), 
	                               &empl.salary); 
		      fseek(fptr, (empl.number-1)*sizeof(struct EMPLOYEE), 
	                      SEEK_SET);//позиционирование на указанную запись
	              //запись данных в файл
		      fwrite(&empl, sizeof(struct EMPLOYEE), 1, fptr);
		      printf("Введите номер сотрудника (от 1 до 100) " 
	                      "или 0\n>");
		      scanf_s("%d", &empl.number);
		    }
	     }//end of else and if
     fclose(fptr);
     puts("\nЗавершение записи данных в файл");
     return;
}
 
#include <stdio.h>
#include <conio.h>  
//Сформируем ведомость по зарплате сотрудников (pay-sheet)
void ReadDataFile(char * filename)
{   struct EMPLOYEE{ //служащий и его зарплата
	   int number;
	   char lastName[15];
	   char firstName[10];
	   float salary;
    };
 
	struct EMPLOYEE empl;
	FILE *fptr;
 
	errno_t err;
	err = fopen_s(&fptr, filename, "r");  //rewind(fptr); 
	    if (err != 0) printf("Файл не открыт для чтения\n");
            else {        printf( "\nФайл открыт для чтения\n" );
		    float total = 0;
		    printf("%-6s%-16s%-13s%-10s\n", "Номер", "Фамилия", 
	                                            "Имя", "Зарплата");
		    while(!feof(fptr)){
		      fread(&empl, sizeof(struct EMPLOYEE), 1, fptr);
		      if(empl.number != 0) {total += empl.salary;
			                    printf("%-6d%-16s%-11s%10.2f\n", 
	                                    empl.number, empl.lastName, 
	                                    empl.firstName, empl.salary);
		      }
		    }
	            printf("\n%33s%10.2f\n", "Итого: ",total);
	    }  
	fclose(fptr);
 
	puts("\nЗавершение чтения данных из файла и формирование ведомости");
	return;
}

Здесь неплохо было бы добавить функцию корректировки записей и сжатия файла.

Например, чтобы исправить или заменить имя сотрудника, нужна функция, которая будет читать данные о сотруднике показывать их и запрашивать новые данные. Новые данные записываются в обычном порядке.

Можно также сжимать файл, освобождая его от неиспользуемых (пустых) записей. Для этого необходимо создать fopen() новый файл с временным именем, например другим расширением имени файла, затем переписать  в него данные, отсеяв ненужные, удалить старый файл remove() или _unlink() и переименовать новый rename().

рассказать друзьям и получить подарок

Оставить комментарий

Ваш email не будет опубликован. Обязательные поля отмечены *

Вы можете использовать это HTMLтеги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Translate Переводчик

Подписка на новости

SmartResponder.ru
Ваш e-mail: *
Ваше имя: *

Хостинг для Wordpress сайтов