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

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

» » Как выглядит бинарный файл. Как прочитать бинарный файл

Как выглядит бинарный файл. Как прочитать бинарный файл

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

name, publisher item Вариантная часть

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

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

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

Типизированные файлы

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

age: set of 0..18; {в файле задано границами}

то придется написать следующий код:

c: char; i,j,min,max: integer;

a: array of toy;

begin assign(f,input); reset(f);

for i:=1 to 100 do if not eof(f)

then with a[i] do

begin readln(f,name,price,min,max); age:=;

for j:= min to max do age:=age+[j];

Как видим, такое поэлементное считывание весьма неудобно и трудоемко.

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

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

<начало_структуры> + <номер_компонента>*<длина_компонента>

Описание типизированных файлов

В разделе var файловые переменные, предназначенные для работы стипизированными файлами , описываются следующим образом:

var <файловая_перем>: file of <тип_элементов_файла>;

Никакая файловая переменная не может быть задана константой.

Назначение типизированного файла

С этого момента и до конца раздела под словом "файл" мы будем подразумевать "бинарный типизированный файл " (разумеется, если специально не оговорено иное).

Команда assign(f,"<имя_файла>"); служит для установления связи между файловой переменнойf и именем того файла, за работу с которым эта переменная будет отвечать.

Строка "<имя_файла> " может содержать полный путь к файлу. Если путь не указан, файл считается расположенным в той же директории, что и исполняемый модуль программы.

Открытие и закрытие типизированного файла

В зависимости от того, какие действия ваша программа собирается производить с открываемым файлом, возможно двоякое его открытие:

reset(f); - открытие файла для считывания из него информации и одновременно длязаписи в него (если такого файла не существует, попытка открытия вызовет ошибку). Эта же команда служит для возвращения указателя на начало файла;

rewrite(f); - открытие файла длязаписи в него информации; если такого файла не существует, он будет создан; если файл с таким именем уже есть, вся содержавшаяся в нем ранее информация исчезнет.

Закрываются типизированные файлы процедуройclose(f) , общей для всех типов файлов.

Считывание из типизированного файла

Чтение из файла, открытого для считывания, производится с помощью команды read() . В скобках сначала указывается имя файловой переменной, а затем - список ввода1) :

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

type toy = record name: string; price: real;

age: set of 0..18; {задано границами}

var f: file of toy;

a: array of toy; begin

assign(f,input);

for i:=1 to 100 do

if not eof(f) then read(f,a[i]); close(f);

Поиск в типизированном файле

Уже знакомая нам функция eof(f:file):boolean сообщает о достигнутом конце файла. Все остальные функции "поиска конца" (eoln() ,seekeof() иseekeoln() ), свойственные текстовым файлам, нельзя применять к файламтипизированным .

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

1. Функция filepos(f:file):longint сообщит текущее положение указателя в файлеf . Если он указывает на самый конец файла, содержащегоN элементов, то эта функция выдаст результатN . Это легко объяснимо: элементы файла нумеруются начиная с нуля, поэтому последний элемент имеет номер N-1 . А номерN принадлежит, таким образом, "несуществующему" элементу - признаку конца файла.

2. Функция filesize(f:file):longint вычислит длину файлаf .

3. Процедура seek(f:file,n:longint) передвинет указатель в файлеf на началозаписи с номеромN . Если окажется, чтоn больше фактической длины файла, то указатель будет передвинут и за реальный конец файла.

4. Процедура truncate(f:file) обрежет "хвост" файлаf : все элементы начиная с текущего и до конца файла будут из него удалены. На самом же деле произойдет лишь переписывание признака "конец файла" в то место, куда указывал указатель, а физически "отрезанные" значения останутся на прежних местах - просто они станут "бесхозными".

Запись в типизированный файл

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

write(f,a,b,c); - записать в файлf (предварительно открытый длязаписи командамиrewrite(f) илиreset(f) ) переменныеa ,b ,c .

Выводить в типизированный файл можно только переменные соответствующего описанию типа данных. Неименованные и нетипизированные константы нельзя выводить в

типизированный файл.

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

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

seek(f,5); {указатель будет установлен на начало 5-го элемента}

write(f,a); {указатель будет установлен на начало 6-го элемента}

Файлам. При этом с точки зрения технической реализации на уровне аппаратуры, текстовые файлы являются частным случаем двоичных файлов, и, таким образом, в широком значении слова под определение «двоичный файл» подходит любой файл.

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

Визуализация

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

октетов кол-во бит шестнадцатеричное восьмеричное десятичное
беззнаковое
десятичное
знаковое
1 8 00

FF
000

377
0

255
-128

127
2 16 0000

FFFF
000000

177777
0

65535
-32768

32767
4 32 00000000

FFFFFFFF
00000000000

37777777777
0

4294967295
-2147483648

2147483647

Нередко, помимо числовых значений байт, выводятся так же символы кодовой страницы , например печатными ASCII-символами справа) начала PNG -файла логотипа Википедии:

00000000 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 |.PNG........IHDR| 00000010 00 00 00 87 00 00 00 a0 08 03 00 00 00 11 90 8f |................| 00000020 b6 00 00 00 04 67 41 4d 41 00 00 d6 d8 d4 4f 58 |.....gAMA.....OX| 00000030 32 00 00 00 19 74 45 58 74 53 6f 66 74 77 61 72 |2....tEXtSoftwar| 00000040 65 00 41 64 6f 62 65 20 49 6d 61 67 65 52 65 61 |e.Adobe ImageRea| 00000050 64 79 71 c9 65 3c 00 00 03 00 50 4c 54 45 22 22 |dyq.e<....PLTE""| 00000060 22 56 56 56 47 47 47 33 33 33 30 30 30 42 42 42 |"VVVGGG333000BBB| 00000070 4b 4b 4b 40 40 40 15 15 15 4f 4f 4f 2c 2c 2c 3c |KKK@@@...OOO,<| 00000080 3c 3c 3e 3e 3e 3a 39 39 04 04 04 1d 1d 1d 35 35 |<<>>>:99......55| 00000090 35 51 50 50 37 37 37 11 11 11 25 25 25 0d 0d 0d |5QPP777...%%%...| 000000a0 27 27 27 1a 1a 1a 38 38 38 2a 2a 2a 08 08 08 20 |"""...888***... | 000000b0 20 20 17 17 17 2e 2e 2e 13 13 13 bb bb bb 88 88 | ..............|

Инструменты

Для визуализации

  • debug (в Microsoft Windows , частично)
  • hexdump (в GNU/Linux и т. п.)

Для редактирования

Литература

  • Webster’s New World Dictionary of Computer Terms, 4th. Ed, Prentice Hall, NY, 1992. ISBN 0-671-84651-5
  • Леонтьев Б. К. Форматы файлов Microsoft Windows XP: Справочник, М.: ЗАО «Новый издательский дом», 2005. ISBN 5-9643-0059-6

Wikimedia Foundation . 2010 .

Смотреть что такое "Бинарный файл" в других словарях:

    Сущ., м., употр. сравн. часто Морфология: (нет) чего? файла, нет? файлу, (вижу) что? файл, чем? файлом, о чём? о файле; мн. что? файлы, (нет) чего? файлов, чем? файлам, (вижу) что? файлы, чем? файлами, о чём? о файлах 1. Файлом называется массив… … Толковый словарь Дмитриева

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

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

    конфигурационный файл - Бинарный или текстовый файл, содержащий информацию, которая определяет поведение приложения, компьютера или сетевого устройства. Тематики сети вычислительные EN configuration file … Справочник технического переводчика

    У этого термина существуют и другие значения, см. IPA (значения). .IPA формат архивных файлов приложений от Apple для iPhone, iPod Touch и iPad. Файлы с данным расширением хранятся в магазине App Store и загружаются с помощью iTunes для… … Википедия

    Эта статья или раздел нуждается в переработке. В Паскале нет модулей, ООП и прочих новомодных веяний. Описание расширений должно присутствовать только в статьях о соответ … Википедия

    Pascal Семантика: процедурный Тип исполнения: компилятор Появился в: 1970 г. Автор(ы): Никлаус Вирт Паскаль (англ. Pascal) высокоуровневый язык программирования общего назначения. Один из наиболее известных языков программирования, широко… … Википедия

    Gopher Название: Gopher Порт/ID: 70/TCP Спецификация: RFC 1436 Основные реализации (клиенты): Mozilla Firefox, Microsoft Windows: IE 5.x, IE 6 (ограничено MS) Gopher сетевой протокол распределённого поиска и передачи документов, бывший широко рас … Википедия

    Название: Gopher Порт/ID: 70/TCP Спецификация: RFC 1436 Основные реализации (клиенты): Mozilla Firefox, Microsoft Windows: Internet Explorer 5.x, Internet Explorer 6 (ограничено MS) Gopher сетевой протокол распределённого поиска и передачи… … Википедия

    - / * A B C D E F G H I J K L M N O P Q R S T U V W X Y Z Примечание: Поскольку расширение имени файла может быть любым, представленный список не является полным … Википедия

Теги: Бинарные файлы, 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(); }

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

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

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

В разделе 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

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

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

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

Работа с двоичными файлами

Вся информация хранится в компьютере в виде 0 и 1, т. е. в двоичном виде. Двоичные файлы отличаются от текстовых только методами работы с ними. Например, если мы записываем в текстовый файл цифру «4», то она записывается как символ, и для ее хранения нужен один байт. Соответственно и размер файла будет равен одному байту. Текстовый файл, содержащий запись: «145687», будет иметь размер шесть байт.

Если же записать целое число 145 687 в двоичный файл, то он будет иметь размер четыре байта, так как именно столько необходимо для хранения данных типа int. То есть двоичные файлы более компактны и в некоторых случаях более удобны для обработки.

Запись стандартных типов данных в двоичные файлы

Для того чтобы открыть двоичный файл, необходимо задать режим доступа ios::binary (в некоторых компиляторах C++ - ios::bin).

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

ofstream outBinFile("out.bin", ios::out | ios::binary);

/* создание объекта класса ofstream out. bin

if (! out_f і 1) //стандартная проверка

Запись данных происходит с помощью метода write (), который имеет два параметра: первый - указатель на начало (адрес начала) записываемых данных, второй - количество записываемых байтов. При этом указатель необходимо явно преобразовать к типу char.

Пример 1. Записать в двоичный файл переменные различного типа:

ofstream outBinFile("test.bin", ios::out I

ios: :binary) ; /^создание объекта класса of stream и попытка связать его с файлом test. bin в режиме записи двоичного файла */

int а - 145687; //объявление целой переменной а

outBinFi le. write ((char*) &а, sizeof (а)) ; /^запись в файл

переменной а как потока байтов, т. е. запись в файл внутреннего представления целой переменной а */ float х - 123.25; // объявление вещественной переменной х

outBinFile .write ((char*) &х, sizeof (х)) ; /^запись в файл

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

//определение символьной переменной с и инициализация ее символом g outBinFile.write((char*)&c, sizeof(c));

//запись символа g в файл

outBinFile.close(); return 0;

Если открыть содержимое файла test .bin текстовым редактором, то он будет иметь вид:

а размер файла составит 9 байт.

Чтение стандартных типов данных из двоичных файлов

Для того чтобы открыть существующий двоичный файл для чтения, нужно создать объект:

ifstream inpBinFile("inp.bin", ios::in I ios::binary);

/* используем дизъюнкцию флагов, указывающую на то что файл открывается на чтение в двоичном виде*/

if (! inpBinFile)

coutДля чтения данных используем функцию read(), имеющую аналогичные функции write() параметры.

#include using namespace std; int main()

ifstream inpBinFile("test.bin", ios::in I

ios: : binary) ; //открываем файл на чтение в двоичном виде

int а; float х; char с = "g";

inpBinFile.read((char*)&a, sizeof(a));

//читаем целочисленную переменную inpBinFile.read((char*)&x, sizeof(x));

//читаем вещественную переменную inpBinFile.read((char*)&c, sizeof (c));

//читаем символьную переменную

inpBinFile.close(); cout

Результат работы программы:

а = 145687 х = 123.25 с = g

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

Запись и чтение пользовательских типов данных в двоичные файлы

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

Также при работе с двоичными файлами могут использоваться методы seekg(), tellg(), seekp(), tellp().

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

fstream BinFile("ankety.bin", ios::in I ios::out | ios::binary);

Anketa Gruppa = ; for (int i = 0; i

BinFile.write((char*)&Gruppa[i], sizeof(Anketa)); BinFile.close(); return 0;

Пример 4. В файле «ankety.bin» содержатся данные о группе туристов, необходимо считать их и вывести на экран.

#include using namespace std; struct Anketa {

char name; int age;

структурного типа данных Anketa на экран*/

ostream& operator

fstream BinFile("ankety.bin", ios::in | ios::out | ios::binary); if (!BinFile)

for (int i = 0; i

//сразу читаем все байты, занимаемые переменной типа Anketa BinFile.read((char*)&Gruppa[i], sizeof(Anketa));

BinFile.close(); return 0;

Результат работы программы:

Ivanov, 23 Sidorov, 21 Petrov,22

Для продолжения нажмите любую клавишу. . .

Разработка собственных классов для работы с файлами

Постоянно пользоваться методами write() и read() неудобно, гораздо приятнее иметь возможность пользоваться операциями «>» по аналогии с текстовыми файлами. Приведем пример реализации своего класса для работы с двоичными файлами.

using namespace std;

struct Anketa //объявляем структуру для хранения информации

/*перегрузка операции вставки в поток для вывода пользовательского

структурного типа данных Anketa на экран*/

ostream& operator

class outBinaryFile: public of stream /^определяем свой класс для работы с выходными бинарными файлами. Порождаем его от класса работы с выходными файловыми потоками*/

/*при описании конструктора порожденного класса не забываем вызвать конструктор базового, передав ему необходимые параметры*/

outBinaryFile(char* name) : ofstream(name, ios::out I ios::binary)

//перегружаем необходимые операции как методы класса outBinaryFile& operator

write((char*)&chislo, sizeof(chislo)); return *this;

outBinaryFile& operator

write((char*)&ank, sizeof(ank)); return *this;

class inpBinaryFile: public if stream /* определяем свои класс для работы с входными бинарными файлами. Порождаем его от класса работы с входными файловыми потоками*/

inpBinaryFile(char* name) : ifstream(name, ios::in I ios::binary)

/*вызова конструктора базового класса с необходимыми параметрами,

достаточно для конструктора порожденного класса */

//перегружаем необходимые операции

inpBinaryFile& operator >> (int& chislo)

read((char*)&chislo, sizeof(chislo)); return *this;

inpBinaryFile& operator >> (Anketa& ank)

read((char*)&ank, sizeof(ank)); return *this;

int а = 111, b = 112; outBinaryFile outFile("dannye.bin");

//открываем файл на чтение

inpBinaryFile inpFile("dannye.bin"); if (!inpFile)

for (int і = 0; i

inpFile >> a; //читаем анкету из файла

cout //и выводим ее на экран

inpFile >> anketa; cout

Результат работы программы:

Kolya, 1990, 582-78-95.

Для продолжения нажмите любую клавишу. . .

1. Можно ли в программе использовать операцию?

ios::in I ios::out

  • а) да, в любом случае;
  • б) да, но только при работе с текстовыми файлами;
  • в) нет, в любом случае.
  • 2. Укажите правильный вариант открытия текстового файла для чтения:
    • а) ifstream inpF("input.txt", ios::in);
    • б) ifstream inpF("input.txt", ios::input);
    • в) ifstream inpF(ios:in, "input.txt").

З.Что будет выведено на экран в результате выполнения следующего кода?

inputFile.get(с);

next - inputFile.peek();

if (next == EOF)

  • а) содержимое файла, связанного с потоком inputFile, выведется на экран один раз;
  • б) содержимое файла, связанного с потоком inputFile, будет выводиться на экран бесконечное число раз;
  • в) на экран ничего не будет выведено.
  • 4. Сколько символов содержится в файле?
  • 12 3 4 5 6
  • а) 6;
  • б) 7;
  • в) 11.
  • 5. Какие методы позволяют определить конец файла?
  • а) eof();
  • б) good();
  • в) оба указанных метода.
  • 6. Для чего предназначена функция getline()?
  • а) считывает слово из файла;
  • б) считывает все содержимое файла;
  • в) считывает строку из файла.
  • 7. Чтобы записывать/считывать пользовательские типы данных в файл, необходимо:
    • а) перегрузить операции «>>» и «
    • б) запись и чтение пользовательских типов данных доступны без дополнительных действий;
    • в) запись и чтение пользовательских типов данных в файл невозможны.
  • 8. Какие функции используются для записи/чтения информации в двоичном виде?
  • а) printf / scanf;
  • б) write / read;
  • в) put / get.
  • 1. Написать программу, которая записывает в файл буквы английского алфавита.
  • 2. В файле input.txt записана информация из нескольких текстовых строк. Вывести содержимое этого файла на экран, посчитать количество строк в файле.
  • 3. На диске находится файл result.txt с результатами химических экспериментов. Написать программу, создающую копию этого файла с именем copy_resylt.txt.
  • 4. С клавиатуры ввести имя файла. В указанном файле удалить все четные строки.
  • 5. Написать программу, которая в текстовом файле, заменяет все строчные буквы прописными, и наоборот.
  • 6. В исходном текстовом файле находятся числа, разделенные пробелами. Сформировать два новых файла: первый должен содержать только четные числа, а второй - нечетные.
  • 7. В файл записаны вещественные числа. Написать программу, которая отбрасывает дробную часть у этих чисел и записывает их в новый файл.
  • 8. В текстовом файле записана информация о рейсах авиакомпании. Выбрать из этих данных рейсы, вылетающие после обеда, и вывести их на экран.
  • 9. Перегрузить операторы >> и
  • 10. Написать собственный класс для работы с бинарными файлами.
  • 11. Записать список 10 учеников класса в текстовый файл и в двоичный файл. Сравнить эти файлы. Объяснить полученное отличие.
  • 12. Разработать класс, который записывает в файл информацию об автомобилях (год выпуска, марку, цвет и т. д.) в текстовый файл. При этом каждый символ информации заменяется своим АБО 1-кодом. Полученный файл вывести на экран.

Контрольные вопросы

  • 1. Какие классы используются для работы с файловыми потоками?
  • 2. Какие режимы доступа могут использоваться при работе с файлами? Приведите примеры.
  • 3. Какой метод служит для открытия файла? Приведите примеры.
  • 4. Какие операции доступны для работы с файлами? Какие функции предназначены для выполнения этих операций?
  • 5. Какие методы позволяют определить конец файла при чтении из него информации? В чем отличие этих методов? Приведите примеры.
  • 6. Каким образом можно считать переменные стандартных типов данных из текстовых файлов?
  • 7. Можно ли считывать из текстовых файлов переменные пользовательских типов данных?
  • 8. Какие функции предназначены для произвольного чтения информации из файла? Приведите примеры.
  • 9. Назовите особенности двоичных файлов. В чем преимущества использования таких файлов?
  • 10. С помощью каких функций можно записывать/считывать информацию в двоичные файлы?
  • 11. Как считать переменные стандартных типов данных из двоичного файла?
  • 12. Какие особенности нужно учитывать при чтении пользовательских типов данных из двоичных файлов?
  • "Ivanov", 23}, {"Sidorov", 21},