Вся информация хранится в компьютере в виде 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
З.Что будет выведено на экран в результате выполнения следующего кода?
inputFile.get(с);
next - inputFile.peek();
if (next == EOF)
Контрольные вопросы
Описание и внутреннее представление файлов
Файлы отличаются друг от друга. Все файлы, хранящиеся в компьютере, имеют специальные атрибуты, т.е. специальные способы описания, позволяющие отличить один файл от другого: 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” соответственно для чтения и записи.
записей ), то вполне понятно желание как-то сократить неиспользуемый, но занимаемый объем памяти.Специально для таких случаев существуют записи с вариантной частью .
В разделе 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 |
Бинарные файлы хранят информацию в том виде, в каком она представлена в памяти компьютера, и потому неудобны для человека. Заглянув в такой файл , невозможно понять, что в нем записано; его нельзя создавать или исправлять вручную - в каком-нибудь текстовом редакторе - и т.п. Однако все эти неудобства компенсируются скоростью работы с данными.
Кроме того, текстовые файлы относятся к структурам последовательного доступа , а бинарные - прямого. Это означает, что в любой момент времени можно обратиться к любому, а не только к текущему элементу
Бинарный файл - это любой файл на вашем компьютере. Вся информация на компьютере и связанных с ним носителях записана в битах (отсюда и название). Однако, для сравнения, текстовый файл можно прочитать в соответствующих расширению ридерах (простейшие - даже в Блокноте), а исполняемый файл - нет. И хоть фактически txt-файл является тем же бинарным файлом, но когда говорят о проблеме открытия содержимого бинарных файлов, имеют ввиду исполняемые файлы, а также сжатые данные.
В приведенном выше примере самым "длинным" является вариант "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-го элемента}