Двоичный файл - неограниченный массив байтов
Существует и другое представление файла в программе, отличное от текстового. В нем файл выступает как аналог внутренней памяти компьютера и организован соответствующим образом. Подобно тому, как в памяти программы размещаются статические переменные в процессе трансляции и динамические - во время работы программы, так и программа может размещать в таком файле любые структуры данных. Файл -образ памяти носит название ДВОИЧНОГО ФАЙЛА ПРОИЗВОЛЬНОГО ДОСТУПА и имеет следующие свойства:
-двоичный файл произвольного доступа представляет собой неограниченный массив байтов внешней памяти;
-форма представления данных в оперативной памяти компьютера (переменные) и в двоичном файле полностью идентичны ;
-программа имеет возможность при помощи функций ввода-вывода копировать любую область файла в любую область памяти без преобразования ("байт в байт"). Таким образом, можно разместить в любом месте файла любую переменную из памяти программы в том виде, в каком она присутствует в памяти и прочитать ее обратно;
-в отличие от памяти программы, которая распределяется частично транслятором (обычные переменные), частично библиотечными функциями (динамические переменные), распределение памяти в файле производится самой программой. Только она определяет способ размещения данных, метод доступа к ним и несет ответственность за корректность этого размещения.
При работе с файлом в режиме двоичного файла произвольного доступа используются функции стандартной библиотеки ввода-вывода. Прежде всего, при открытии или создании нового файла необходимо указать режим работы с файлом:
// Открыть существующий как двоичный для чтения и записи
FILE *fd; fd = fopen("a.dat","rb+wb");
// Создать новый как двоичный для записи и чтения
fd = fopen("a.dat","wb+");
С открытым файлом связано понятие ТЕКУЩЕЙ ПОЗИЦИИ (позиционера). Текущей позицией называется номер байта, начиная с которого производится очередная операция чтения - записи. При открытии файла текущая позиция устанавливается на начало файла, после чтения-записи порции данных перемещается вперед на размерность этих данных.
Из того, что функции fread,fwrite копируют данные из памяти в файл без преобразования, "байт в байт", следует естественный способ сохранения в файле переменной любого типа данных, основанный на использовании операции sizeof для определения ее размерности:
long a; // Записать в файл переменную типа long,
fseek (fd, 20L, SEEK_SET); // начиная с позиции 20
fwrite (&a, sizeof(long),1,fd);
struct man b; // Добавить в файл переменную типа man
fseek (fd,0L,SEEK_END);
fwrite (&b, sizeof b,1,fd);
double *pd; // Прочитать с начала файла динамический
pd = new double[n]; // массив в n переменных типа double
fseek(fd,0L,SEEK_SET); //
fread((void*)pd, sizeof(double),n,fd);
Номер байта (позицию) в файле, начиная с которого размещается переменная в дальнейшем будем называть также СМЕЩЕНИЕМ или АДРЕСОМ этой переменной в файле.
fseek(fd,20L,SEEK_SET);
fwrite((void*)&a, sizeof(long),1,fd);
Нетрудно заметить, что в управлении внутренней памятью (переменные, память программы) и внешней памятью (файлы) много общего. Используя возможности адресной арифметики и преобразования типов указателей, можно произвольным образом планировать память программы, размещая в ней различные переменные (см. 4.5). Аналогичная "свобода выбора" имеет место и при работе с файлами: программист имеет право произвольным образом строить в файле любые структуры данных подобно тому, как он это делает в памяти. Но с небольшой разницей: если в памяти программы структуры данных можно организовать используя обычные переменные языка, динамические переменные, указатели и стандартные операции над ними, то при работе с файлом программист всего этого лишен. Он не может присвоить имя переменной в файле и пользоваться им, он не может выполнить над ней никаких операций, кроме как прочитав ее в память программы в переменную такого же типа. Короче говоря, программа вынуждена работать со структурами данных в файле на уровне физических адресов, не имея соответствующей поддержки транслятора.
Вообще, способы распределения памяти в файле могут быть довольно сложными. Для соответствующих разъяснений следует обратиться к алгоритмам распределения памяти. Однако есть один очень простой и естественный способ - для размещения переменной в файле достаточно добавить ее в конец файла. Для этого нужно установиться на конец файла и получить значение позиционера, после чего записать в файл саму переменную.
int a;
long pos;
fseek(fd,0L,SEEK_END);
pos=ftell(fd);
fwrite((void*)&a, sizeof(int),1,fd);
Если в процессе работы с переменной в файле ее размерность не будет меняться, то можно просто переписывать обновленное значение переменной на то же самое место .
a++;
fseek(fd,pos,SEEK_SET);
fwrite((void*)&a, sizeof(int),1,fd);
Если размерность переменной увеличится, то можно еще раз добавить ее в конец файла. Проблема утилизации получающихся свободных мест ("сбор мусора") достаточно сложна, чтобы рассматривать ее в простых примерах. В качестве наиболее удобного решения можно предложить периодическое переписывание всей структуры данных в новый файл (сжатие).