Сайт о телевидении

Сайт о телевидении

» » Что двоичный файл. Двоичный файл

Что двоичный файл. Двоичный файл

Рассматриваемые нами до этого времени примеры демонстрировали форматированный ввод/вывод информации в файлы. Форматированный файловый ввод/вывод чисел целесообразно использовать только при их небольшой величине и малом количестве, а также при необходимости обеспечения возможности просмотра файлов не программными средствами. В противном случае, конечно, гораздо эффективнее использовать двоичный ввод/вывод, при котором числа хранятся таким же образом, как в ОП компьютера, а не в виде символьных строк. Напомню, что целочисленное (int) или вещественное (float) значение занимает в памяти 4 байта, значение типа double – 8 байт, а символьное значение типа char - 1 байт. Например, число 12345 в текстовом (форматированном) файле занимает 5 байт, а в бинарном файле – 4 байта.

Бинарные файлы , т.е. файлы, в которых информация хранится во внутренней форме представления, применяются для последующего использования программными средствами, их невозможно просматривать не программными средствами. Преимущество бинарных файлов состоит в том, что, во-первых, при чтении/записи не тратится время на преобразование данных из символьной формы представления во внутреннюю и обратно, а во-вторых, при этом не происходит потери точности вещественных чисел. Как в случае форматированного ввода/вывода, так и в случае бинарного ввода/вывода для "правильной" обработки информации из файла необходимо знать какие типы данных, каким образом и в какой последовательности записаны в бинарный файл, тем более, что просмотр бинарного файла с помощью текстового редактора ничего не даст.

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

#include

#include

#include

using namespace std;

cout << "Vvedite kol-vo elementov celochisl. massiva: "; cin >> N;

int *mas = new int [N];

for(i=0; i

cout << " Vvedite " << i << "-i element: "; cin >> mas[i];

cout << "\nIdet zapis dannyh v fail..." << endl;

ofstream fout("c:\\os\\bin.dat", ios::binary); //созд. вых. бинарного потока

if(!fout) { cout << "\n Oshibka otkrytiya faila!"; getch(); return 1; }

fout.write(reinterpret_cast(mas), N*sizeof(int)); //запись массива в файл

fout.close(); //закрытие потока

cout << "Dannye uspeshno zapisany!" << endl;

for(i=0; i

ifstream fin("c:\\os\\bin.dat", ios::binary); //создание потока для чтения файла

if(!fin) { cout << "\n Oshibka otkrytiya faila!"; getch(); return 1; }

cout << "Fail sodergit:" << endl;

fin.read(reinterpret_cast(mas), N*sizeof(int)); //считывание массива из файла

for(i=0; i

getch(); return 0;

Особое внимание в данной программе надо уделить использованию функций write() (метод класса ofstream) и read() (метод класса ifstream). Эти функции думают о данных в терминах байтов и предназначены для переноса определённого количества байт из буфера данных в файл и обратно. Параметрами этих функций являются адрес буфера и его длина в байтах.

Функция write() предназначена для записи в файл указанного во втором параметре числа байт из указанного в первом параметре адреса буфера данных, а функция read() предназначена для считывания данных из файла. Здесь необходимо отметить, что эти функции работают с буфером данных только типа char. В связи с этим, в данной программе мы использовали оператор reinterpret_cast<> , который преобразует буфер наших данных типа int (mas) в буфер типа char.

Необходимо помнить, что приведение типа с помощью оператора reinterpret_cast необходимо только в тех случаях, когда первый параметр функций write() и read() не является символьным массивом (ведь символ типа char занимает только 1 байт). Кроме того, если необходимо записать или прочитать не массив, а отдельные переменные, то нужно использовать ссылочный механизм (ссылку на адрес буфера данных), например:

ofstream fout(filename, ios::app | ios::binary);

fout.write(reinterpret_cast(& cb), sizeof(float));

Теперь необходимо обсудить второй параметр рассматриваемых функций. В данной программе, в качестве второго параметра мы использовали выражение N*sizeof(int), с помощью которого вычислили количество байт. Например, если у нас 5 целочисленных элементов массива, то число байт будет равно 20. Функция sizeof() возвращает количество байт, отводимое под указанный в качестве параметра тип данных. Например, sizeof(int ) вернёт 4.

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

Теперь, представим себе, что необходимо написать программу позволяющую считывать данные из файла bin.dat, причём мы знаем только то, что в данном файле записаны элементы целочисленного массива в бинарном виде. Количество записанных элементов (N) нам не известно . При создании программы мы не имеем права использовать константный массив, т.е. выделять память под него на этапе создания программы. Это приведет к ошибочному результату. Поскольку слишком малое значение N приведёт к тому, что считаются не все элементы массива, а слишком большое значение N приведёт к заполнению лишних ячеек случайными значениями.

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

#include

#include

#include

using namespace std;

int N, i, sum=0, dfb; //dfb - длина файла в байтах

ifstream fin("c:\\os\\bin.dat", ios::binary );

if(!fin) { cout << "Oshibka otkrytiya faila!"; getch(); return 1; }

fin.seekg(0, ios::end); //устанавливаем позицию чтения на конец файла (от конца 0 байт)

dfb = fin.tellg(); //получаем значение позиции конца файла (в байтах)

N=dfb/4; //зная, что целое число занимает 4 байта, вычисляем кол-во чисел

int *arr = new int [N]; //создаём динамический массив

fin.seekg(0, ios::beg); //перед чтением данных, перемещаем текущую позицию на начало файла

fin.read(reinterpret_cast(arr), dfb);

cout << "Iz faila schitano " << N << " elementov:" << endl;

for(i=0; i

for(i=0; i

cout << "\n Ih summa = " << sum;

getch(); return 0;

Рассмотрим детально данную программу, в которой мы активно использовали функции seekg() и tellg(), являющиеся методами класса ifstream. Здесь необходимо отметить, что с любым файлом при его открытии связывается так называемая текущая позиция чтения или записи . Когда файл открывается для чтения, эта позиция по умолчанию устанавливается на начало файла. Но достаточно часто бывает нужно контролировать позицию вручную, чтобы иметь возможность читать и писать, начиная с произвольного места файла. Функции seekg() и tellg() позволяют устанавливать и проверять текущий указатель чтения, а функции seekp() и tellp() – выполнять те же действия для указателя записи.

Метод seekg(1_параметр, 2_параметр) перемещает текущую позицию чтения из файла на указанное в 1_параметре число байт относительно указанного во 2_параметре места. 2_параметр может принимать одно из трёх значений:

ios::beg – от начала файла;

ios::cur – от текущей позиции;

ios::end – от конца файла.

Здесь beg, cur и end – являются константами, определёнными в классе ios, а символы:: означают операцию доступа к этому классу. Например, оператор fin.seekg(-10, ios::end); позволяет установить текущую позицию чтения из файла за 10 байтов до конца файла.

Теперь вернёмся к описанию работы программы. Исходя из того, что нам не известно количество чисел записанных в файл, вначале необходимо узнать число чисел. Для этого, с помощью fin.seekg(0, ios::end); мы перемещаемся в конец файла и посредством функции tellg() возвращаем в переменную dfb длину файла в байтах. Функция tellg() возвращает текущую позицию указателя в байтах. Так как длина одного целого числа в байтах нам известна (4 байта), нетрудно вычислить количество записанных в файл чисел, зная длину файла в байтах (N=dfb/4; ). Узнав количество чисел, создаём динамический массив и перемещаемся в начало файла для того, чтобы начать считывание данных с помощью функции read(). После того, как указанное нами число байт данных (dfb) перенесено в буфер данных (arr), считанные таким образом данные приобретают структуру массива и становятся полностью пригодны для каких укодно операций и преобразований.

записей ), то вполне понятно желание как-то сократить неиспользуемый, но занимаемый объем памяти.

Специально для таких случаев существуют записи с вариантной частью .

Описание записи с вариантной частью

В разделе var запись с вариантной частью описывают так:

var <имя_записи>: record <поле1>: <тип1>; [<поле2>: <тип2>;] [...] case <поле_переключатель>: <тип> of <варианты1>: (<поле3>: <тип3>; <поле4>: <тип4>; ...); <варианты2>: (<поле5>: <тип5>; <поле6>: <тип6>; ...); [...] end;

Невариантная часть записи (до ключевого слова case ) подчиняется тем же правилам, что и обычная запись . Вообще говоря, невариантная часть может и вовсе отсутствовать.

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

Пример . Для того чтобы описать содержимое библиотеки, необходима следующая информация:

Графы "Название" и "Издательство" являются общими для всех трех вариантов, а остальные поля зависят от типа печатного издания. Для реализации этой структуры воспользуемся записью с вариантной частью :

type biblio = record name,publisher: string; case item: char of "b": (author: string; year: 0..2004); "n": (data: date); "m": (year: 1700..2004; month: 1..12; number: integer); end;

В зависимости от значения поля item , в записи будет содержаться либо 4, либо 5, либо 6 полей .

Механизм использования записи с вариантной частью

Количество байтов, выделяемых компилятором под запись с вариантной частью , определяется самым "длинным" ее вариантом. Более "короткие" наборы полей из других вариантов занимают лишь некоторую часть выделяемой памяти.

В приведенном выше примере самым "длинным" является вариант " b ": для него требуется 23 байта (21 байт для строки и 2 байта для целого числа). Для вариантов " n " и " m " требуется 4 и 5 байт соответственно (см. таблицу).

name, publisher item Вариантная часть
... "b" author year
... "n" data
... "m" year month number
... "b" author year

Бинарные файлы

Бинарные файлы хранят информацию в том виде, в каком она представлена в памяти компьютера, и потому неудобны для человека. Заглянув в такой файл , невозможно понять, что в нем записано; его нельзя создавать или исправлять вручную - в каком-нибудь текстовом редакторе - и т.п. Однако все эти неудобства компенсируются скоростью работы с данными.

Кроме того, текстовые файлы относятся к структурам последовательного доступа , а бинарные - прямого. Это означает, что в любой момент времени можно обратиться к любому, а не только к текущему элементу

Теги: Бинарные файлы, fseek, ftell, fpos, fread, fwrite

Бинарные файлы

Т екстовые файлы хранят данные в виде текста (sic!). Это значит, что если, например, мы записываем целое число 12345678 в файл, то записывается 8 символов, а это 8 байт данных, несмотря на то, что число помещается в целый тип. Кроме того, вывод и ввод данных является форматированным, то есть каждый раз, когда мы считываем число из файла или записываем в файл происходит трансформация числа в строку или обратно. Это затратные операции, которых можно избежать.

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

#include #include #include #define ERROR_FILE_OPEN -3 void main() { FILE *output = NULL; int number; output = fopen("D:/c/output.bin", "wb"); if (output == NULL) { printf("Error opening file"); getch(); exit(ERROR_FILE_OPEN); } scanf("%d", &number); fwrite(&number, sizeof(int), 1, output); fclose(output); _getch(); }

Выполните программу и посмотрите содержимое файла output.bin. Число, которое ввёл пользователь записывается в файл непосредственно в бинарном виде. Можете открыть файл в любом редакторе, поддерживающем представление в шестнадцатеричном виде (Total Commander, Far) и убедиться в этом.

Запись в файл осуществляется с помощью функции

Size_t fwrite (const void * ptr, size_t size, size_t count, FILE * stream);

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

Запись в бинарный файл объекта похожа на его отображение: берутся данные из оперативной памяти и пишутся как есть. Для считывания используется функция fread

Size_t fread (void * ptr, size_t size, size_t count, FILE * stream);

Функция возвращает число удачно прочитанных элементов, которые помещаются по адресу ptr. Всего считывается count элементов по size байт. Давайте теперь считаем наше число обратно в переменную.

#include #include #include #define ERROR_FILE_OPEN -3 void main() { FILE *input = NULL; int number; input = fopen("D:/c/output.bin", "rb"); if (input == NULL) { printf("Error opening file"); getch(); exit(ERROR_FILE_OPEN); } fread(&number, sizeof(int), 1, input); printf("%d", number); fclose(input); _getch(); }

fseek

Одной из важных функций для работы с бинарными файлами является функция fseek

Int fseek (FILE * stream, long int offset, int origin);

Эта функция устанавливает указатель позиции, ассоциированный с потоком, на новое положение. Индикатор позиции указывает, на каком месте в файле мы остановились. Когда мы открываем файл, позиция равна 0. Каждый раз, записывая байт данных, указатель позиции сдвигается на единицу вперёд.
fseek принимает в качестве аргументов указатель на поток и сдвиг в offset байт относительно origin. origin может принимать три значения

  • SEEK_SET - начало файла
  • SEEK_CUR - текущее положение файла
  • SEEK_END - конец файла. К сожалению, стандартом не определено, что такое конец файла, поэтому полагаться на эту функцию нельзя.

В случае удачной работы функция возвращает 0.

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

#include #include #include #define ERROR_FILE_OPEN -3 void main() { FILE *iofile = NULL; int number; iofile = fopen("D:/c/output.bin", "w+b"); if (iofile == NULL) { printf("Error opening file"); getch(); exit(ERROR_FILE_OPEN); } scanf("%d", &number); fwrite(&number, sizeof(int), 1, iofile); fseek(iofile, 0, SEEK_SET); number = 0; fread(&number, sizeof(int), 1, iofile); printf("%d", number); fclose(iofile); _getch(); }

Вместо этого можно также использовать функцию rewind, которая перемещает индикатор позиции в начало.

В си определён специальный тип fpos_t, который используется для хранения позиции индикатора позиции в файле.
Функция

Int fgetpos (FILE * stream, fpos_t * pos);

используется для того, чтобы назначить переменной pos текущее положение. Функция

Int fsetpos (FILE * stream, const fpos_t * pos);

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

Long int ftell (FILE * stream);

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

Рассмотрим пример: пользователь вводит числа. Первые 4 байта файла: целое, которое обозначает, сколько чисел было введено. После того, как пользователь прекращает вводить числа, мы перемещаемся в начало файла и записываем туда число введённых элементов.

#include #include #include #define ERROR_OPEN_FILE -3 void main() { FILE *iofile = NULL; unsigned counter = 0; int num; int yn; iofile = fopen("D:/c/numbers.bin", "w+b"); if (iofile == NULL) { printf("Error opening file"); getch(); exit(ERROR_OPEN_FILE); } fwrite(&counter, sizeof(int), 1, iofile); do { printf("enter new number? "); scanf("%d", &yn); if (yn == 1) { scanf("%d", &num); fwrite(&num, sizeof(int), 1, iofile); counter++; } else { rewind(iofile); fwrite(&counter, sizeof(int), 1, iofile); break; } } while(1); fclose(iofile); getch(); }

Вторая программа сначала считывает количество записанных чисел, а потом считывает и выводит числа по порядку.

#include #include #include #define ERROR_OPEN_FILE -3 void main() { FILE *iofile = NULL; unsigned counter; int i, num; iofile = fopen("D:/c/numbers.bin", "rb"); if (iofile == NULL) { printf("Error opening file"); getch(); exit(ERROR_OPEN_FILE); } fread(&counter, sizeof(int), 1, iofile); for (i = 0; i < counter; i++) { fread(&num, sizeof(int), 1, iofile); printf("%d\n", num); } fclose(iofile); getch(); }

Примеры

1. Имеется бинарный файл размером 10*sizeof(int) байт. Пользователь вводит номер ячейки, после чего в неё записывает число. После каждой операции выводятся все числа. Сначала пытаемся открыть файл в режиме чтения и записи. Если это не удаётся, то пробуем создать файл, если удаётся создать файл, то повторяем попытку открыть файл для чтения и записи.

#include #include #include #define SIZE 10 void main() { const char filename = "D:/c/state"; FILE *bfile = NULL; int pos; int value = 0; int i; char wasCreated; do { wasCreated = 0; bfile = fopen(filename, "r+b"); if (NULL == bfile) { printf("Try to create file...\n"); getch(); bfile = fopen(filename, "wb"); if (bfile == NULL) { printf("Error when create file"); getch(); exit(1); } for (i = 0; i < SIZE; i++) { fwrite(&value, sizeof(int), 1, bfile); } printf("File created successfully...\n"); fclose(bfile); wasCreated = 1; } } while(wasCreated); do { printf("Enter position "); scanf("%d", &pos); if (pos < 0 || pos >= SIZE) { break; } printf("Enter value "); scanf("%d", &value); fseek(bfile, pos*sizeof(int), SEEK_SET); fwrite(&value, sizeof(int), 1, bfile); rewind(bfile); for (i = 0; i < SIZE; i++) { fread(&value, sizeof(int), 1, bfile); printf("%d ", value); } printf("\n"); } while(1); fclose(bfile); }

2. Пишем слова в бинарный файл. Формат такой - сначало число букв, потом само слово без нулевого символа. Ели длина слова равна нулю, то больше слов нет. Сначала запрашиваем слова у пользователя, потом считываем обратно.

#include #include #include #include #define ERROR_FILE_OPEN -3 void main() { const char filename = "C:/c/words.bin"; const char termWord = "exit"; char buffer; unsigned int len; FILE *wordsFile = NULL; printf("Opening file...\n"); wordsFile = fopen(filename, "w+b"); if (wordsFile == NULL) { printf("Error opening file"); getch(); exit(ERROR_FILE_OPEN); } printf("Enter words\n"); do { scanf("%127s", buffer); if (strcmp(buffer, termWord) == 0) { len = 0; fwrite(&len, sizeof(unsigned), 1, wordsFile); break; } len = strlen(buffer); fwrite(&len, sizeof(unsigned), 1, wordsFile); fwrite(buffer, 1, len, wordsFile); } while(1); printf("rewind and read words\n"); rewind(wordsFile); getch(); do { fread(&len, sizeof(int), 1, wordsFile); if (len == 0) { break; } fread(buffer, 1, len, wordsFile); buffer = "\0"; printf("%s\n", buffer); } while(1); fclose(wordsFile); getch(); }

3. Задача - считать данные из текстового файла и записать их в бинарный. Для решения зачи создадим функцию обёртку. Она будет принимать имя файла, режим доступа, функцию, которую необходимо выполнить, если файл был удачно открыт и аргументы этой функции. Так как аргументов может быть много и они могут быть разного типа, то их можно передавать в качестве указателя на структуру. После выполнения функции файл закрывается. Таким образом, нет необходимости думать об освобождении ресурсов.

#include #include #include #define DEBUG #ifdef DEBUG #define debug(data) printf("%s", data); #else #define debug(data) #endif const char inputFile = "D:/c/xinput.txt"; const char outputFile = "D:/c/output.bin"; struct someArgs { int* items; size_t number; }; int writeToFile(FILE *file, void* args) { size_t i; struct someArgs *data = (struct someArgs*) args; debug("write to file\n") fwrite(data->items, sizeof(int), data->number, file); debug("write finished\n") return 0; } int readAndCallback(FILE *file, void* args) { struct someArgs data; size_t size, i = 0; int result; debug("read from file\n") fscanf(file, "%d", &size); data.items = (int*) malloc(size*sizeof(int)); data.number = size; while (!feof(file)) { fscanf(file, "%d", &data.items[i]); i++; } debug("call withOpenFile\n") result = withOpenFile(outputFile, "w", writeToFile, &data); debug("read finish\n") free(data.items); return result; } int doStuff() { return withOpenFile(inputFile, "r", readAndCallback, NULL); } //Обёртка - функция открывает файл. Если файл был благополучно открыт, //то вызывается функция fun. Так как аргументы могут быть самые разные, //то они передаются через указатель void*. В качестве типа аргумента //разумно использовать структуру int withOpenFile(const char *filename, const char *mode, int (*fun)(FILE* source, void* args), void* args) { FILE *file = fopen(filename, mode); int err; debug("try to open file ") debug(filename) debug("\n") if (file != NULL) { err = fun(file, args); } else { return 1; } debug("close file ") debug(filename) debug("\n") fclose(file); return err; } void main() { printf("result = %d", doStuff()); getch(); }

4. Функция saveInt32Array позволяет сохранить массив типа int32_t в файл. Обратная ей loadInt32Array считывает массив обратно. Функция loadInt32Array сначала инициализирует переданный ей массив, поэтому мы должны передавать указатель на указатель; кроме того, она записывает считанный размер массива в переданный параметр size, из-за чего он передаётся как указатель.

#include #include #include #include #define SIZE 100 int saveInt32Array(const char *filename, const int32_t *a, size_t size) { FILE *out = fopen(filename, "wb"); if (!out) { return 0; } //Записываем длину массива fwrite(&size, sizeof(size_t), 1, out); //Записываем весь массив fwrite(a, sizeof(int32_t), size, out); fclose(out); return 1; } int loadInt32Array(const char *filename, int32_t **a, size_t *size) { FILE *in = fopen(filename, "rb"); if (!in) { return 0; } //Считываем длину массива fread(size, sizeof(size_t), 1, in); //Инициализируем массив (*a) = (int32_t*) malloc(sizeof(int32_t) * (*size)); if (!(*a)) { return 0; } //Считываем весь массив fread((*a), sizeof(int32_t), *size, in); fclose(in); return 1; } void main() { const char *tmpFilename = "tmp.bin"; int32_t exOut; int32_t *exIn = NULL; size_t realSize; int i; for (i = 0; i < SIZE; i++) { exOut[i] = i*i; } saveInt32Array(tmpFilename, exOut, SIZE); loadInt32Array(tmpFilename, &exIn, &realSize); for (i = 0; i < realSize; i++) { printf("%d ", exIn[i]); } _getch(); }

5. Создание таблицы поиска. Для ускорения работы программы вместо вычисления функции можно произвести сначала вычисление значений функции на интервале с определённой точностью, после чего брать значения уже из таблицы. Программа сначала производит табулирование функции с заданными параметрами и сохраняет его в файл, затем подгружает предвычисленный массив, который уже используется для определения значений. В этой программе все функции возвращают переменную типа Result, которая хранит номер ошибки. Если функция отработала без проблем, то она возвращает Ok (0).

#define _CRT_SECURE_NO_WARNINGS //Да, это теперь обязательно добавлять, иначе не заработает #include #include #include #include #include //Каждая функция возвращает результат. Если он равен Ok, то функция //отработала без проблем typedef int Result; //Возможные результаты работы #define Ok 0 #define ERROR_OPENING_FILE 1 #define ERROR_OUT_OF_MEMORY 2 //Функция, которую мы будем табулировать double mySinus(double x) { return sin(x); } Result tabFunction(const char *filename, double from, double to, double step, double (*f)(double)) { Result r; FILE *out = fopen(filename, "wb"); double value; if (!out) { r = ERROR_OPENING_FILE; goto EXIT; } fwrite(&from, sizeof(from), 1, out); fwrite(&to, sizeof(to), 1, out); fwrite(&step, sizeof(step), 1, out); for (from; from < to; from += step) { value = f(from); fwrite(&value, sizeof(double), 1, out); } r = Ok; EXIT: fclose(out); return r; } Result loadFunction(const char *filename, double **a, double *from, double *to, double *step) { Result r; uintptr_t size; FILE *in = fopen(filename, "rb"); if (!in) { r = ERROR_OPENING_FILE; goto EXIT; } //Считываем вспомогательную информацию fread(from, sizeof(*from), 1, in); fread(to, sizeof(*to), 1, in); fread(step, sizeof(*step), 1, in); //Инициализируем массив size = (uintptr_t) ((*to - *from) / *step); (*a) = (double*) malloc(sizeof(double)* size); if (!(*a)) { r = ERROR_OUT_OF_MEMORY; goto EXIT; } //Считываем весь массив fread((*a), sizeof(double), size, in); r = Ok; EXIT: fclose(in); return r; } void main() { const char *tmpFilename = "tmp.bin"; Result r; double *exIn = NULL; int accuracy, option; double from, to, step, arg; uintptr_t index; //Запрашиваем параметры для создания таблицы поиска printf("Enter parameters\nfrom = "); scanf("%lf", &from); printf("to = "); scanf("%lf", &to); printf("step = "); scanf("%lf", &step); r = tabFunction(tmpFilename, from, to, step, mySinus); if (r != Ok) { goto CATCH_SAVE_FUNCTION; } //Обратите внимание на формат вывода. Точность определяется //во время работы программы. Формат * подставит значение точности, //взяв его из списка аргументов accuracy = (int) (-log10(step)); printf("function tabulated from %.*lf to %.*lf with accuracy %.*lf\n", accuracy, from, accuracy, to, accuracy, step); r = loadFunction(tmpFilename, &exIn, &from, &to, &step); if (r != Ok) { goto CATCH_LOAD_FUNCTION; } accuracy = (int)(-log10(step)); do { printf("1 to enter values, 0 to exit: "); scanf("%d", &option); if (option == 0) { break; } else if (option != 1) { continue; } printf("Enter value from %.*lf to %.*lf: ", accuracy, from, accuracy, to); scanf("%lf", &arg); if (arg < from || arg > to) { printf("bad value\n"); continue; } index = (uintptr_t) ((arg - from) / step); printf("saved %.*lf\ncomputed %.*lf\n", accuracy, exIn, accuracy, mySinus(arg)); } while (1); r = Ok; goto EXIT; CATCH_SAVE_FUNCTION: { printf("Error while saving values"); goto EXIT; } CATCH_LOAD_FUNCTION: { printf("Error while loading values"); goto EXIT; } EXIT: free(exIn); _getch(); exit(r); }

6. У нас имеются две структуры. Первая PersonKey хранит логин, пароль, id пользователя и поле offset. Вторая структура PersonInfo хранит имя и фамилию пользователя и его возраст. Первые структуры записываются в бинарный файл keys.bin, вторые структуры в бинарный файл values.bin. Поле offset определяет положение соответствующей информации о пользователе во втором файле. Таким образом, получив PersonKey из первого файла, по полю offset можно извлечь из второго файла связанную с данным ключом информацию.

Зачем так делать? Это выгодно в том случае, если структура PersonInfo имеет большой размер. Извлекать массив маленьких структур из файла не накладно, а когда нам понадобится большая структура, её можно извлечь по уже известному адресу в файле.

#define _CRT_SECURE_NO_WARNINGS #include #include #include #include typedef struct PersonKey { long long id; char login; char password; long offset;//Положение соответствующих значений PersonInfo } PersonKey; typedef struct PersonInfo { unsigned age; char firstName; char lastName; } PersonInfo; /* Функция запрашивает у пользователя данные и пишет их подряд в два файла */ void createOnePerson(FILE *keys, FILE *values) { static long long id = 0; PersonKey pkey; PersonInfo pinfo; pkey.id = id++; //Так как все значения пишутся друг за другом, то текущее положение //указателя во втором файле будет позицией для новой записи pkey.offset = ftell(values); printf("Login: "); scanf("%63s", pkey.login); printf("Password: "); scanf("%63s", pkey.password); printf("Age: "); scanf("%d", &(pinfo.age)); printf("First Name: "); scanf("%63s", pinfo.firstName); printf("Last Name: "); scanf("%127s", pinfo.lastName); fwrite(&pkey, sizeof(pkey), 1, keys); fwrite(&pinfo, sizeof(pinfo), 1, values); } void createPersons(FILE *keys, FILE *values) { char buffer; int repeat = 1; int counter = 0;//Количество элементов в файле //Резервируем место под запись числа элементов fwrite(&counter, sizeof(counter), 1, keys); printf("CREATE PERSONS\n"); do { createOnePerson(keys, values); printf("\nYet another one? "); scanf("%1s", buffer); counter++; if (buffer != "y" && buffer != "Y") { repeat = 0; } } while(repeat); //Возвращаемся в начало и пишем количество созданных элементов rewind(keys); fwrite(&counter, sizeof(counter), 1, keys); } /* Создаём массив ключей */ PersonKey* readKeys(FILE *keys, int *size) { int i; PersonKey *out = NULL; rewind(keys); fread(size, sizeof(*size), 1, keys); out = (PersonKey*) malloc(*size * sizeof(PersonKey)); fread(out, sizeof(PersonKey), *size, keys); return out; } /* Функция открывает сразу два файла. Чтобы упростить задачу, возвращаем массив файлов. */ FILE** openFiles(const char *keysFilename, const char *valuesFilename) { FILE **files = (FILE**)malloc(sizeof(FILE*)*2); files = fopen(keysFilename, "w+b"); if (!files) { return NULL; } files = fopen(valuesFilename, "w+b"); if (!files) { fclose(files); return NULL; } return files; } /* Две вспомогательные функции для вывода ключа и информации */ void printKey(PersonKey pk) { printf("%d. %s [%s]\n", (int)pk.id, pk.login, pk.password); } void printInfo(PersonInfo info) { printf("%d %s %s\n", info.age, info.firstName, info.lastName); } /* Функция по ключу (вернее, по его полю offset) достаёт нужное значение из второго файла */ PersonInfo readInfoByPersonKey(PersonKey pk, FILE *values) { PersonInfo out; rewind(values); fseek(values, pk.offset, SEEK_SET); fread(&out, sizeof(PersonInfo), 1, values); return out; } void getPersonsInfo(PersonKey *keys, FILE *values, int size) { int index; PersonInfo p; do { printf("Enter position of element. To exit print bad index: "); scanf("%d", &index); if (index < 0 || index >= size) { printf("Bad index"); return; } p = readInfoByPersonKey(keys, values); printInfo(p); } while (1); } void main() { int size; int i; PersonKey *keys = NULL; FILE **files = openFiles("C:/c/keys.bin", "C:/c/values.bin"); if (files == 0) { printf("Error opening files"); goto FREE; } createPersons(files, files); keys = readKeys(files, &size); for (i = 0; i < size; i++) { printKey(keys[i]); } getPersonsInfo(keys, files, size); fclose(files); fclose(files); FREE: free(files); free(keys); _getch(); }

Описание и внутреннее представление файлов

Файлы отличаются друг от друга. Все файлы, хранящиеся в компьютере, имеют специальные атрибуты, т.е. специальные способы описания, позволяющие отличить один файл от другого: 1)имя; 2)размер; 3)дата и время; 4)значок.

У каждого файла есть имя - имя файла. Имя файла описывает его содержимое или подсказывает, для чего он может использоваться. Имя присваивается файлу при его создании. Это относится ко всем файлам.

Каждый файл имеет физический размер. Файл занимает некоторый объем памяти компьютера и некоторый объем дискового пространства.

В момент создания файла операционная система на него ставит печать с указанием даты и времени создания. Это позволяет сортировать файлы по дате и времени и таким образом наводить порядок в компьютере. Также отмечается дата и время обновления или изменения файла.

Каждый файл относится к определенному типу, тесно связанному со значком файла, который мы видим. Тип файла зависит от его содержимого. Каждая программа присваивает созданному документу определенный тип и соответствующий значок.

Размер файла измеряется в байтах, как и объем памяти.

Размер файла может составлять 0 байт, это значит, что файл существует, но ничего в себе не содержит. S Максимальный размер файла - 4 Гбайт. Но такие огромные файлы встречаются очень редко.

Встроенные часы компьютера нужны, в частности, для присвоения файлам времени и даты их создания. Этим объясняется то, как важно правильно настроить эти часы. Есть еще и дополнительные атрибуты для описания файлов, например системные файлы, скрытые файлы, файлы, предназначенные только для чтения, архивные файлы и т.д. Операционная система с этим разберется сама.

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

Текстовые и бинарные файлы

Файлы позволяют пользователю считывать большие объемы данных непосредственно с диска, не вводя их с клавиатуры. Существуют два основных типа файлов: текстовые и двоичные.

Текстовыми называются файлы, состоящие из любых символов. Они организуются по строкам, каждая из которых заканчивается символом «конца строки» . Конец самого файла обозначается символом «конца файла» . При записи информации в текстовый файл, просмотреть который можно с помощью любого текстового редактора, все данные преобразуются к символьному типу и хранятся в символьном виде.

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

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

В программах на C++ при работе с текстовыми файлами необходимо подключать библиотекиifstream и iostream.

Для того чтобы записывать данные в текстовый файл, необходимо: 1)описать переменную типа ofstream open ; 3)вывести информацию в файл; 4)обязательно закрыть файл.

Для считывания данных из текстового файла, необходимо:

1)описать переменную типа ifstream ; 2)открыть файл с помощью функции open ; 3)считать информацию из файла, при считывании каждой порции данных необходимо проверять, достигнут ли конец файла; 4)закрыть файл.

Следует отметить, что во всех рассмотренных выше примерах функция fopen() в режимах “r” и “w” открывает текстовый файл на чтение и запись соответственно. Это означает, что некоторые символы форматирования текста, например возврат каретки ‘\r’ не могут быть считаны как отдельные символы, их как бы ни существует в файле, но при этом они там есть. Это особенность текстового режима файла. Для более «тонкой» работы с содержимым файлов существует бинарный режим, который представляет содержимое файла как последовательность байтов, где все возможные управляющие коды являются просто числами. Именно в этом режиме возможно удаление или добавление управляющих символов недоступных в текстовом режиме. Для того чтобы открыть файл в бинарном режиме используется также функция fopen() с последним параметром равным “rb” и “wb” соответственно для чтения и записи.