Массив указателей в файле
Массив указателей является наиболее простой и в то же время эффективной структурой данных для организации произвольного доступа к хранимым элементам. Рассмотрим, как будет выглядеть в файле структура данных, содержащая строки - записи переменной длины и массив указателей на них.
В начале файла расположена целая переменная - n - размерность массива указателей. Затем располагается сам массив файловых указателей - переменных типа long. Каждый указатель является адресом строки в файле, оформленной стандартным образом в виде записи переменной длины. Функция сохранения всей структуры данных в файле использует принцип распределения памяти в файле путем добавления соответствующей переменной в конец файла. При этом массив указателей записывается два раза - в первый раз - для распределения памяти, второй раз - уже после формирования значений указателей (обновление). Заметим, что массиву указателей в файле соответствует аналогичный динамический массив этих же самых указателей в памяти, который сначала формируется, а затем уже записывается в файл.
//------------------------------------------------------bk59-08.cpp
void save(char *p[], char *name)
{
FILE *fd;
int i,n;
long *pp;
if ((fd=fopen(name,"wb"))==NULL) return; // Создать двоичный файл
for (n=0; p[n]!=NULL; n++); // Определить размерность МУ
pp=new long[n]; // Создать динамический массив
fwrite((void*)&n,sizeof(int),1,fd); // файловых указателей в памяти
fwrite((void*)pp,sizeof(long),n,fd); // Записать в файл размерность
for (i=0; i<n; i++) // массива файловых указателей
{ // и сам массив (занять место)
pp[i]=ftell(fd); // Записать строку в файл в виде
int sz=strlen(p[i])+1; // записи переменной длины и
fwrite((void*)&sz,sizeof(int),1,fd); // сохранить адрес в массиве
fwrite((void*)p[i],sz,1,fd); // файловых указателей
}
fseek(fd,sizeof(int),SEEK_SET); // Обновить в файле массив
fwrite((void*)pp,sizeof(long),n,fd); // файловых указателей
fclose(fd);
}
Функция загрузки структуры данных иллюстрирует тот факт, что при переменной размерности она должна полностью создаваться в динамический памяти.
В нашем случае это происходит в два этапа. Сначала создается и загружается массив файловых указателей, для которого создается аналогичный массив указателей на строки, но уже в памяти. Затем читаются сами строки.
//------------------------------------------------------bk59-09.cpp
char **load(char *name) // Функция возвращает динамический
{ // массив указателей на строки
FILE *fd;
int i,n;
long *pp;
char **p;
if ((fd=fopen(name,"rb"))==NULL) return;
fread((void*)&n,sizeof(int),1,fd); // Прочитать размерность
pp=new long[n]; // Создать динамический массив
p=new char*[n+1]; // файловых указателей и указателей
fread((void*)pp,sizeof(long),n,fd); // на строки.
// Первый - прочитать из файла
for (i=0; i<n; i++)
{
int sz;
fseek(fd,pp[i],SEEK_SET); // Установиться по i-му файловому
fread((void*)&sz,sizeof(int),1,fd); // указателю и прочитать запись
p[i]=new char[sz]; // переменной длины - строку
fread((void*)p[i],sz,1,fd);
}
p[n]=NULL;
fclose(fd);
return p;
}
Следующий фрагмент иллюстрирует назначение массива указателей в файле. Он позволяет извлекать элементы данных (строки) в произвольном порядке, то есть обеспечивает в файле записей переменной длины режим произвольного доступа. Заметим, что в обычном файле записей переменной длины такое невозможно. При этом из файла извлекаются только данные, необходимые для выполнения текущей операции.
//------------------------------------------------------bk59-10.cpp
char *load(char *name, int num) // Возвращается строка =
{ // динамический массив
FILE *fd;
int i,n,sz;
long pp;
char *p;
if ((fd=fopen(name,"rb"))==NULL) return; // Режим чтения двоичного файла
fread((void*)&n,sizeof(int),1,fd); // Считать размерность МУ
if (num>=n) return NULL; // Нет записи с таким номером
fseek(fd,sizeof(int)+sizeof(long)*num,SEEK_SET); // Установить на указатель
fread((void*)&pp,sizeof(long),1,fd); // с номером n и прочитать его
fseek(fd,pp,SEEK_SET); // Установиться на запись
fread((void*)&sz,sizeof(int),1,fd); // Прочитать длину записи
p=new char[sz]; // Создать динамический массив
fread((void*)p,sz,1,fd); // Прочитать запись - строку
fclose(fd);
return p; // Возвратить указатель на строку
}