Лабораторная
 

Лабораторная работа №16

Работа с файловыми потоками в С++.
Двоичные файлы

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

Теоретические сведения

Cохранение данных в двоичных файлах

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

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

Двоичные файлы более компактны и в некоторых случаях более удобны для обработки.

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

ofstream out_fil (”Outfil.dat”,ios::out | ios::binary);

if (! out_fil) { cerr<<”Error: Outfil.dat”<<endl;

exit(1);

}

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

ifstream in_fil (”Infil.dat”, ios::in | ios::binary);

if (! in_fil) { cerr<<”Error: Infil.dat”<<endl;

exit(2);

}

К сожалению, созданные объекты in_fil и out_fil не слишком приспособлены для работы с двоичными файлами и требуют некоторых дополнительных действий, необходимых для корректной работы.

Пример 16.1. Запись значения типа double в двоичный файл.

# include <fstream>

# include <iostream>

using namespace std;

# include <stdlib.h>

class bin_outstream:public ofstream

{ public:

bin_outstream(const char *fn): ofstream(fn, ios::out | ios::binary){}

void writeOurDate(const void*, int);

ofstream &operator<<(double d) { writeOurDate(&d, sizeof(d));

return *this;

}

};

int main()

{ bin_outstream bin_out(”B_out.dat”);

if (! bin_out) { cerr<<”Unable to write to B_out.dat”<<endl;

exit(1);

}

double d = 5.252;

bin_out<<d;

bin_out<<d*d;

d = 5.2E-5;

bin_out<<d;

return 0;

}

void bin_outstream:: writeOurDate(const void *Ptr, int len)

{ if (! Ptr) return;

if (len<=0) return;

write((char*)Ptr, len);

Пример 16.2. Чтение значений типа double из двоичного файла.

# include <fstream>

# include <iostream>

using namespace std;

# include <stdlib.h>

class bin_instream: public ifstream

{ public:

bin_instream(const char *fn): ifstream(fn, ios::in | ios::binary){}

void readOurDate(void*, int);

bin_instream &operator>>(double &d) { readOurDate(&d, sizeof(d));

return *this;

}

};

int main()

{ bin_instream bin_in(”B_in.dat”);

if (! bin_in)

{ cerr<<”Unable to open B_in.dat”<<endl;

exit(1);

}

double d;

long count = 0;

bin_in>>d;

while (! bin_in.eof())

{ cout << ++count << ”:' << d << endl;

bin_in>>d;

}

return 0;

}

void bin_instream:: readOurDate(void *p, int len)

{ if (! p) return;

if (len <= 0) return;

read((char*)p, len);

}

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

Сохранение в двоичных файлах данных, имеющих тип, создаваемый пользователем.

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

Пример 16.3.

Объявим структуру

struct mountine {

char name[20]; //название горы

int altitude; //высота над уровнем моря

int complicate; //сложность

};

mountine mount;

Для сохранения информации в двоичном файле выполняют следующее:

ofstream fil_out(”mountines.txt”, ios_base::app);

fil_out << mount.name << ” ” << mount.altitude << ' ' << mount.complicate <<”\n”;

Для сохранения той же информации в двоичном файле выполняют следующее:

ofstrem fil_out(”mountines.dat”, ios_base::app | ios_base::bynary);

fil_out.write((char *) &mount, sizeof(mountine));

Метод write копирует указанное число байтов (в данном случае - sizeof(mountine)) в файл из памяти ЭВМ. Несмотря на то, что сохранение данных происходит в двоичном файле, адрес переменной преобразуется к указателю на тип char.

Для чтения данных из двоичного файла используют метод read:

ifstream fil_in(”mountines.dat”, ios_base::binary);

fil_in.read((char *) &mount, sizeof(mountine));

При записи или чтении классов, не содержащих виртуальных функций, можно использовать тот же самый подход. Чтобы сделать класс потоковым, нужно перегрузить операторы << и >>:

friend ostream &operator<<(ostream &, AnyClass &);

friend istream &operator>>(istream &, AnyClass &);

Произвольный доступ к элементам файлов

Файловый указатель.

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

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

  • seekg() - установить текущий указатель чтения;

  • tellg() - проверить текущий указатель чтения;

  • seekp() - установить текущий указатель записи;

  • tellp() - проверить текущий указатель записи.

Организация доступа к элементам двоичных файлов.

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

istream &seekg(streampos) или

istream &seekg(streamoff, ios::seek_dir);

Типы данных streampos и streamoff эквивалентны значениям типа long, но использовать long в явном виде не рекомендуется из-за неоднозначности работы различных компиляторов. Поэтому их определяют как

typedef long streampos;

typedef long streamoff;

Первая из перегруженных форм функции seekg позиционирует входной поток на заданном байте, вторая - на смещении относительно одной из трех позиций, определенных значением константы ios::seek_dir (табл. 16.1)

Таблица 16.1

Константа

значение

описание

beg

0

поиск от начала файла

cur

1

поиск от текущей позиции файла

end

2

поиск от конца файла

Для позиционирования внутреннего указателя файла для выходных потоков используют перегруженные функции выходных файловых потоков, унаследованных из класса ostream:

ostream &seekp(streampos);

ostream &seekp(streamoff, ios::seek_dir);

Пример 16.4. В двоичном файле, содержащем целые числа, заменить максимальное значение файла суммой его четных элементов.

#include <fstream>

#include <iostream>using namespace std;

# include <stdlib.h>

class bin_stream:public fstream{ public:

bin_stream(const char *fn): fstream(fn, ios::out | ios::in | ios::binary) {}

void doneOurDate(const void*, int, int);

bin_stream &operator<<(int d) { doneOurDate(&d, sizeof(d),0);

return *this;

}

bin_stream &operator>>(int &d) { doneOurDate(&d, sizeof(d),1);

return *this;

}

};

int main()

{ int i, d, max, i_max, sum_even = 0;

randomize();

bin_stream bin_out(”Bin.dat”);

if (!bin_out) { cerr << ”Unable to write to Bin.dat” << endl;

exit(1);

}

for (i = 0; i < 10; i++) { d = random(100);

bin_out << d;

if (d % 2 == 0) sum_even += d;

}

bin_out.seekp(0, ios::beg);

bin_out >> max;

i_max = 0;

for (i = 1; i < 10; i++) { bin_out >> d;

if (d > max) { max = d; i_max = i; }

}

bin_out.seekp(sizeof(int) * i_max, ios::beg);

bin_out << sum_even;

bin_out.seekp(0, ios::beg);

for (i = 0; i < 10; i++) { bin_out >> d;

cout <<d <<' ';

}

bin_out.close();

return 0;}void bin_stream:: doneOurDate(const void *Ptr, int len, int sign){ if (!Ptr) return;

if (len <= 0) return; if (sign==0) write((char*)Ptr, len);

else read((char*)Ptr, len);

}

Требования к отчету

Отчет должен содержать:

  1. наименование и цель работы;

  2. краткие теоретические сведения;

  3. формулировку задания своего варианта;

  4. текст программы для варианта задания, соответствующего номеру фамилии студента в группе (если студент закреплен за определенной ЭВМ, имеющей номер, то номеру ЭВМ);

  5. результаты выполнения программы.

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

  1. Что такое поток?

  2. Особенности работы с двоичными файлами.

  3. Что представляет собой файловый указатель?

  4. Как организовать доступ к произвольному месту двоичного файла?

Лабораторное задание

Для выполнения лабораторной работы необходимо составить программу согласно своему варианту задания.

Варианты заданий

Номер варианта

Задание

1, 14

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

2, 15

В конец двоичного файла целого типа дописать четные элементы этого файла

3, 16

В начало двоичного файла целого типа дописать нечетные элементы этого файла.

4, 17

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

5, 18

В двоичном файле целого типа поменять местами элементы, стоящие на четных местах с элементами, стоящими на нечетных местах.

6, 19

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

7, 20

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

8, 21

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

9, 22

Даны двоичные файлы f и g целого типа. Записать в начало файла f положительные компоненты файла g, а в конец файла g - отрицательные компоненты файла f с сохранением порядка их следования.

10, 23

Дан двоичный файл с целыми числами. Удалить из него число, записанное после первого нуля (принять, что нули в файле имеются). Результат записать в другой файл.

11, 24

Дан двоичный файл с целыми числами. Все его четные элементы заменить нулями. Рассмотреть 2 варианта:

  • исходный файл содержит 13 чисел;

  • размер исходного файла неизвестен.

Номер варианта

Задание

12, 25

Дан двоичный файл с целыми числами. Заменить все его элементы, порядковый номер которых кратен 7, на новые значения, которые вводятся с клавиатуры. Рассмотреть 2 варианта:

  • исходный файл содержит 20 чисел;

  • размер исходного файла неизвестен.

13, 26

Дан двоичный файл с положительными и отрицательными целыми числами. Записать в другой файл сначала отрицательные элементы, а затем положительные.

217