Урок 10.3. Ввод-вывод символа

Урок 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().

Урок 10.5. Ввод-вывод форматный. Файл последовательного доступа

10.5. Ввод-вывод форматный. Файл последовательного доступа

При работе с файлами для форматного ввода и вывода данных предназначены функции fscanf() и fprintf() соответственно. Обращения к этим функциям задаются следующим образом:

fscanf(<указатель_файла>,<управляющая_строка>,<список_ввода>);
fprintf(<указатель_файла>,<строка_вывода>,<список_вывода>);

Легко заметить, что функции fscanf() и fprintf() аналогичны известным уже функциям scanf() и printf(), c той лишь разницей, что первым аргументом является указатель файла, из которого будут читаться или в который будут записываться данные. Таким образом, эти функции являются полными аналогами рассмотренных ранее функций форматного ввода-вывода, но работают не со стандартным системным вводом-выводом, а с конкретными текстовыми файлами. Ниже приведен пример печати (добавления) в существующий файл work.dta значения целой переменной cost:

FILE *fp;
int cost=12;
fp=fopen("work.dta","a");
fprintf(fp,"%5d",cost);
fclose(fp);

В конец файла будет добавлено 5 байтов точно так же, как это было бы выведено на экран или на принтер. Пример чтения данного из файла work.dta в переменную cost может быть следующим:

FILE *fp;
int cost;
fp=fopen("work.dta","r");
//или так, для VisualC++:
//fopen_s(&fp, "work.dta", "r");
fscanf(fp,"%d",&cost);
fscanf_s(fp, "%d",&cost); //для VisualC++:
fclose(fp);

В результате выполнения этого фрагмента программы самое первое целое число, записанное в файле work.dta в таком же виде, как оно бы вводилось с клавиатуры, станет значением переменной cost.

Рассмотрим более полный пример. В этом примере данные читаются с клавиатуры и записываются в файл последовательного доступа. В функции scanf_s() макрос _countof(fam) используется для подсчета количества символов в строке(массиве) с целью безопасного ввода. В этой программе с клавиатуры вводится номер студента, его фамилия и оценка, затем это все записывается в файл.

//Запись данных в файл последовательного доступа 
#include <stdio.h>
#include <conio.h>  
#include <locale.h>
#include <stdlib.h>  //для _countof(fam) – макроса, вычисляющего
	                   //количество символов в статическом массиве. 
int main(void)
{
  int n = 0;    //номер
  char fam[15]; //фамилия
  int grade = 0; //оценка
  FILE *f; //FileStreamInput Output
  char *s = "Note.txt";
 
        setlocale(LC_ALL,"Russian"); 
	//открываем/создаем файл для записи
	errno_t err;
	err = fopen_s(&f, s, "wt");
	if (err != 0) printf("Файл не открыт\n");
        else {        printf("Файл открыт\n" );
	        printf("Вводите номер, фамилию, оценку\n"
		       "или введите конец файла(Ctrl-z)\n"
		       "для окончания ввода\n");
	        printf(">");
	        scanf_s("%d%s%d", &n, fam, _countof(fam), &grade);
		while(!feof(stdin))
		{  //заполняем файл данными
		   fprintf(f, "%d %s %d\n", n, fam, grade);
		   printf(">");
		   scanf_s("%d%s%d", &n, fam, _countof(fam), &grade);
		 }
	  fclose(f);
	}//конец else
 
	puts("Завершение программы");
	_getch();
	return 0;
}

В следующем примере из файла, созданного в предыдущем примере (Note.txt) считываются данные и на экран выводятся только студенты с оценкой 0.

//чтение и вывод на экран
//файла последовательного доступа
#include <stdio.h>
#include <conio.h>  
#include <locale.h>
#include <stdlib.h> 
int main(void)
{ int n;        //номер
  char fam[15]; //фамилия
  int grade = 0; //оценка
  FILE *f; //FileStreamInputOutput
	   //char *s = "Note.txt";
	setlocale(LC_ALL,"Russian"); 
	//открываем/создаем файл для записи
	errno_t err;
	err = fopen_s(&f, "Note.txt", "r");
	if (err != 0) printf("Файл не открыт\n");
        else {        printf("Файл открыт\n" );
		printf("Номер Фамилия Оценка\n");
		fscanf_s(f, "%d%s%d", &n, fam, _countof(fam), &grade);
	        while(!feof(f))
		{   printf("%2d%-15s%2d\n",n, fam, grade);
		    fscanf_s(f, "%d%s%d", &n, fam, _countof(fam), &grade);
		}
	        rewind(f);
                printf( "\nСтуденты без оценки\n" );
		printf("Номер Фамилия Оценка\n");
		fscanf_s(f, "%d%s%d", &n, fam,_countof(fam), &grade);
	        while(!feof(f))
   		{ if(grade == 0)
                  printf("%2d%-15s%2d\n",n, fam, grade);
                  fscanf_s(f, "%d%s%d", &n, fam, _countof(fam), &grade);
		}
 
		fclose(f);
	}
 	puts("Завершение программы");
	_getch();
	return 0;
}

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

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

 

Урок 10.3. Ввод-вывод символа

10. Файлы
10.3. Ввод-вывод символа
10.4. Ввод-вывод строк

10.3. Ввод-вывод символа

Прочитать символ из уже открытого файла или записать в него символ можно различными способами, из которых самыми простыми являются использование встроенных функций getc() и putc(). Эти функции работают аналогично известным нам функциям getchar() и putchar(), с той лишь разницей, что последние взаимодействуют только со стандартным вводом-выводом, а getc() и putc() – с файлами. Обращения к функциям getc() и putc() имеют следующий вид:

<переменная> = getc(<указатель файла>);

putc(<переменная>,<указатель файла> );

Например, для чтения символа из файла, на который указывает in, в переменную ch можно использовать оператор ch=getc(in);. Кстати, напомним, что получение символа от стандартного ввода осуществляется очень похожим оператором сh=getchar(); Для записи символа, содержащегося в переменной ch, в файл, на который указывает out, используется оператор putc(ch,out); а для записи этого же символа на стандартный вывод используется оператор putchar(ch);

Секрет такой "схожести" функций обмена с файлами и стандартным вводом-выводом прост. Все дело в том, что функции getchar() и putchar() в действительности определены в системе через файловые функции getc() и putc() следующим образом:

#define getchar() getc(stdin)
#define putchar(c) putc(c,stdout)

где stdin и stdout – указатели на стандартный системный ввод и вывод соответственно. Признаком конца файла при вводе с клавиатуры служит комбинация Ctrl-Z для IBM-PC, VAX, VMS, Ctrl-d для Macintosh, Enter-Ctrl-d для Unix. Проверка конца файла в этом случае выглядит так:

while(!feof(stdin)) …

Какой же символ читается из файла и в какое место файла он записывается при выполнении операций обмена данными между файлом и программой? Это определяется указателем на текущую позицию файла, который представляет собой целое число. Этот указатель всегда показывает на байт в файле, начиная с которого будут читаться данные из файла или записываться данные в файл. После выполнения операции обмена между файлом и программой указатель позиции файла смещается в направлении от начала к концу файла ровно на столько байт, сколько только что было прочитано из файла или записано в него. Таким образом, указатель позиции файла всегда показывает на байт в файле, начиная с которого осуществляется обмен данными. При открытии существующего файла для чтения из него данных (тип файла "r"), указатель позиции файла устанавливается в начало файла, при открытии нового файла для записи в него (тип файла "w"), указатель позиции файла также устанавливается в начало файла, при открытии существующего файла для добавления в него информации (тип файла "а"), указатель позиции файла устанавливается в конец файла, показывая на первый свободный байт. Указатель на текущую позицию файла представлен в системном шаблоне структуры FILE полем unsigned char *curp;.

10.4. Ввод-вывод строк

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

char * fgets(<указатель_на_строку>,<длина_строки>,<указатель_файла>);

Первый аргумент функции является указателем на местоположение строки, в которую читаются данные. Второй аргумент задает максимальную длину считываемой строки. Функция прекращает свою работу после считывания из файла символа новой строки или считывания заданного числа символов минус один. После того как строка прочитана, в конец строки всегда добавляется нуль-символ. Третий аргумент определяет файл, из которого производится чтение. Например, прочитать из файла строку символов можно следующим образом:

FILE *fp;
char string[20];
fp=fopen("FILE1.TXT","r");
fgets(string,19,fp);
fclose(fp);

Функция fgets() возвращает нулевой указатель (значение NULL) в случае возникновения сбоя при чтении или обнаружения признака конца файла (значение EOF).

Функция записи строки в файл, в отличие от функции чтения, содержит два аргумента:

int fputs(<указатель_на_строку>,<указатель_на_файл>);

Например, записать в файл строку символов можно так:

FILE *fp; fp=fopen("FILE2.TXT","w");
fputs("Cтрока данных",fp); fclose(fp);

Функция fputs() возвращает значение EOF в случае обнаружения конца файла или ошибки записи.
При чтении из файла функция feof() используется для обнаружения конца файла.

Рассмотрим пример программы, в которой в файл записывается несколько строк, а затем они же читаются и выводятся на экран.

#include <stdio.h>
#include <string.h>
#include <conio.h>
#include <locale.h>
 
int main(void)
{ 
	setlocale(LC_ALL, "Russian");
	FILE *pfs; //Указатель на файловый поток
	char rbuf[127] = "";
	errno_t err;
	//pfs = fopen("dat.txt", "wt"); //fopen() небезопасная функция
	if ((err = fopen_s(&pfs, "dat.txt", "wt")) != 0)
		printf("Файл не был открыт\n");
	fputs("Пример работы с файлом\n", pfs);
	fputs("Еще одна строка\n", pfs);
	fputs("Последняя строка файла", pfs);
	puts("Произведена запись строк в файл");
	fclose(pfs);
	//pfs = fopen("dat.txt", "rt"); //не безопасная функция fopen()
	fopen_s(&pfs, "dat.txt", "rt"); //безопасная функция fopen()
	puts("\nСодержимое файла:");
	while(!feof(pfs))
	{
		fgets(rbuf, sizeof(rbuf)-1, pfs);
		printf("Очередная строка: %s", rbuf);
	}
        fclose(pfs);
	puts("\nКонец программы");
	_getch();
	return 0;
}

 

Урок 10.1. Описание файлов

10. Файлы
10.1. Описание файлов
10.2. Открытие и закрытие файлов

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

Файл - это такой же объект программы, как и любые другие, но разница заключается в том, что данные файла находятся не в оперативной памяти, а вне ее, и, таким образом, файл является долговременным "хранилищем" данных вне программы. Отключение питания компьютера не приводит к разрушению данных файла, если в момент отключения над ним не производились операции. Си допускает файлы двух разновидностей – текстовые и двоичные (бинарные).

Текстовый файл состоит из последовательности символов, разбитых на строки. Для стандартного деления на строки используется символ “новая строка ”, который в файле представляется двумя символами '\n' (новая строка) и '\r' (возврат каретки). Строки в файле представляют собой последовательности непробельных символов. Кроме символа “пробел”, ‘\n’, и ‘\r’ пробельными символами являются символы ‘\t’, ‘\v’, ‘\f’ – горизонтальная и вертикальная табуляция, перевод страницы.

В текстовом файле хранятся только символы, т.е. если в файле записано 123, то это не целое число 123, занимающее 1 или 4 байта, а три символа '1','2','3', занимающие 3 байта. Хотя при чтении из файла эта последовательность может быть преобразована в число типа double или int, char, long.

Двоичный (бинарный) файл состоит из последовательности байтов, между которыми нет никаких разделителей. Если в двоичном файле попадается байт с кодом управляющего символа новой строки, то он не имеет никакого специального назначения и рассматривается в файле точно так же, как и другие байты файла. Если в бинарный файл записать отрицательное целое число -12345, то оно запишется в файл в дополнительном коде, в котором представляются отрицательные числа в памяти компьютера, и будет занимать 4 байта. При чтении это число нет необходимости преобразовывать, как в случае текстового файла. Если же записать в бинарный файл последовательность символов -12345, то она будет занимать 6 байтов.

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

Последовательность действий по обработке файла достаточно стандартна и проста. Прежде всего, как и любой другой объект программы, файл необходимо описать, а затем открыть его, установив тем самым связь пользовательской программы с внешним носителем данных. После этого следует собственно обработка файла – запись в него данных, сформированных в программе, чтение данных из файла и ряд других операций, связанных, например, с поиском в файле нужных данных. И, наконец, завершая работу с файлом, его необходимо закрыть.

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

10.1. Описание файлов

Программа получает доступ к файлу через указатель на структуру типа FILE, определенную в файле stdio.h. Тип FILE определяется оператором typedef как тип структурного шаблона. В пользовательской программе создается указатель на такую структуру и, таким образом, описание файла сводится для программиста к описанию указателя типа FILE и вся дальнейшая работа с файлом происходит через этот указатель. Примеры структурных шаблонов типа файл:

//для BorlandC
typedef struct  {
  int             level;        /* fill/empty level of buffer */
  unsigned        flags;        /* File status flags          */
  char            fd;           /* File descriptor            */
  unsigned char   hold;         /* Ungetc char if no buffer   */
  int             bsize;        /* Buffer size                */
  unsigned char   _FAR *buffer; /* Data transfer buffer       */
  unsigned char   _FAR *curp;   /* Current active pointer     */
  unsigned        istemp;       /* Temporary file indicator   */
  short           token;        /* Used for validity checking */
} FILE;                         /* This is the FILE object    */
 
//для MS VisualC
struct _iobuf {
        char *_ptr;
        int   _cnt;
        char *_base;
        int   _flag;
        int   _file;
        int   _charbuf;
        int   _bufsiz;
        char *_tmpfname;
        };
typedef struct _iobuf FILE;

Оператор описания файлов имеет следующую общую форму записи:

FILE *<имя1>,*<имя2>,...,*<имяN>;

из которой видно, что использованные имена являются указателями на файловый тип. Имена, заданные в операторе описания файлов, будем называть указателями файлов. Например, оператор FILE *fp1,*fp2; приводит к выделению памяти под два указателя файла – fp1 и fp2, которые будут использованы встроенными функциями обработки файлов.

10.2. Открытие и закрытие файлов

Прежде чем начать обработку файла, его следует открыть. Сущность открытия файла заключается в том, что описанный в программе указатель файла должен быть связан с конкретным файлом на диске. Это делается путем использования встроенной функции fopen() в операторе вида:

<указатель файла> = fopen(<имя файла>,<режим открытия>);

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

Таблица. Режимы открытия файла

Режимы Описание режима Позиция ввода-вывода
r Открытие файла только для чтения. Файл должен существовать. Начало файла
w Создание файла для записи. Если файл с указанным именем существовал, то его содержимое теряется. Начало файла
a Открытие файла для добавления в конец файла. Если файл с указанным именем не существовал, то он создается. Конец файла
r+ Открытие существующего файла для обновления (чтение и запись). Файл должен существовать. Конец файла
w+ Создание файла для обновления. Если файл с указанным именем существовал, то его содержимое теряется. Начало файла
a+ Открыть файл для чтения и добавления данных. Все операции записи выполняются в конец файла, защищая предыдущее содержания файла от случайного изменения. Вы можете изменить позицию (FSEEK, перемотка назад) внутреннего указателя на любое место файла только для чтения, операции записи будет перемещать указатель в конец файла, и только после этого дописывать новую информацию. Файл создается, если он не существует. Конец файла

Дополнительно к каждой строке <режим открытия> можно добавить символ t или b (по умолчанию t); t указывает, что файл открыт в текстовом режиме, а b указывает на то, что открывается двоичный файл, при этом символы новой строки и ввода подавляются. Возможны также и другие режимы открытия файла, например, "wt+,ccs=UNICODE" – открытие файла на запись в кодировке UNICODE.

Когда файл открывается, ему ставится в соответствие поток. В начале исполнения программы автоматически открывается три файла и связанные с ним потоки стандартный ввод, стандартный вывод и стандартная ошибка. Потоки обеспечивают каналы передачи данных между файлами и программами. Например, стандартный поток ввода позволяет программе считывать данные с клавиатуры, а стандартный поток вывода позволяет выводить данные на экран. Функция fopen() возвращает указатель на структуру FILE, которая содержит информацию, используемую при работе с файлом. Эта структура содержит дескриптор файла, поля для хранения информации о текущем указателе буфера ввода-вывода, текущем счетчике байтов, базовом адресе буфера ввода-вывода, типе файла и другой информации, необходимой для реализации операций по обработке файла.

 

Дескриптор файла является индексом в массиве операционной системы, называемом таблицей открытых файлов. Каждый элемент массива содержит блок управления файлом (FCB – File Control Block), который используется операционной системой для доступа к конкретному файлу. Для обращения к стандартному вводу, стандартному выводу и стандартному потоку ошибок следует воспользоваться указателями файлов  stdin, stdout и stderr соответственно.

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

FILE *in;
in = fopen("data.txt","r");

После выполнения функции fopen() переменная in будет указывать на структуру, отражающую информацию о файле data.txt, и все действия с ним будут осуществляться посредством этого указателя, а не по имени файла. На рисунке 10.1 проиллюстрирована связь между указателем на FILE, структурой типа FILE и FCB в памяти компьютера.

Ris10.1File_FCB

Рис. 10.1. Связь между указателем на файл, структурой типа FILE и FCB

Важно учитывать и тот факт, что если функция fopen() не способна по каким-либо причинам открыть файл, то она возвращает указатель на "пустой" адрес (значение NULL). Проверка такой ситуации проиллюстрирована в следующем примере, в котором функция fopen() используется для создания и открытия нового файла, предназначенного для записи выходных данных из программы:

FILE *out;
if((out = fopen("outfile.txt","w"))==NULL)
     printf("Файл не может быть создан");

Заметим, что если в функции в качестве аргумента используется "w" для уже существующего файла, то старая версия его уничтожается, и информация существующего файла будет потеряна. Кроме того, обратим внимание на строку, задающую имя файла. Если в этой строке содержится только имя файла, как это имеет место в рассмотренном примере, то файл создается в текущем каталоге. В этой строке может быть указан полный путь, и тогда файл будет создан на определенном диске и в определенном каталоге:

FILE *out;
if((out = fopen("D:\\COURSE\\outfile.txt","w"))==NULL)
     printf("Файл не может быть создан");
 
//Имя файла в функции fopen() можно задать указателем на строку, 
//например, следующим образом:
 
//Открытие файлов
int i;
FILE *f[3]; // Массив указателей открываемых файлов 
char *file_name[] = // Массив указателей на имена 
                    // открываемых файлов 
	{"d:\\outfile1.txt",
	 "d:\\outfile2.txt",
	 "d:\\outfile3.txt"
	};
for(i=0;i<3;i++) {
 if((f[i] = fopen(file_name[i],"w"))==NULL)
 printf("Файл %s не может быть создан\n", file_name[i]);
}

 

Пример иллюстрирует открытие в цикле трех файлов. В результате откроются файлы с указателями файлов f[0], f[1], f[2]. Если какие-либо из этих файлов не могут быть открыты, будут напечатаны имена неоткрытых файлов.

 

Для закрытия файла используется функция fclose(), обращение к которой имеет вид:

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

Для контроля успешного закрытия файла желательно также анализировать возвращаемое значение функции fclose(). Функция возвращает значение 0, если файл закрыт успешно, и 1(EOF) в противном случае:

if(fclose(fsio)==EOF) printf("\n Файл не закрыт.");
 else printf("\n Файл закрыт.");

Не закрытые программой файлы операционная система сама закрывает по завершению программы. При этом, в случае буферизованного вывода, часть информации может не попасть в файл, а остаться в системном буфере. Операционная система использует буфер для оптимизации чтения-записи данных в файл. Например, если вы хотите прочитать или записать в файл 1000 целых чисел, это не означает, что система будет 1000 раз записывать или читать числа, образно говоря, не будет 1000 раз “дергать” диск. Данные будут перенесены в буфер c запасом для возможного последующего чтения, либо будут накапливаться в буфере для записи. Запись будет производиться, когда будет заполнен буфер, когда будет закрыт или сброшен поток (буфер), когда программа завершится нормально без закрытия потока. Поэтому критические важные данные можно принудительно переместить из буфера в файл при помощи функции fflush(). В случае чтения fflush() очищает буфер.

Translate Переводчик

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

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

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