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

Порядок выполнения лабораторной работы

1. Изучить теоретические сведения. Разобрать представленные примеры. Запустить первый, второй и пятый примеры, продемонстрировать их преподавателю, если примеры не были показаны на занятии, результаты их работы должны быть включены в отчет.

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

3. Реализовать полученные задания. Для выбора решения задачи пользователем реализовать меню. Любое общение с пользователем должно сопровождаться поясняющимся текстом. Также в программе должно быть реализовано минимум 2 исключительных ситуации. Исключительные ситуации должны обрабатываться в отдельном классе. Обязательна оконная (формочная) реализация программы с использованием элементов управления.

4. Показать результат работы программы преподавателю.

5. Защитить лабораторную работу.

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

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

1. Цель работы.

2. Задание.

3. Словесное описание исключительных ситуаций.

4. UML диаграмма классов.

5. Блок-схема программы.

6. Текст программы на языке C++ с комментариями.

7. Тесты (копии выходных форм программы/сформированных файлов).

8. Выводы.

 

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

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 не слишком приспособлены для работы с двоичными файлами и требуют некоторых дополнительных действий, необходимых для корректной работы.

 

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

 

# include <fstream>

# include <iostream>

# include <stdlib.h>

using namespace std;

 

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;

        system("pause");

        exit(1);

  }

  double d = 5.252;

  bin_out << d;

  bin_out << d*d;

  d = 5.2E-5;

  bin_out << d;

  system("pause");

  return 0;

}

 

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

{

  if (!Ptr) return;

  if (len <= 0) return;

  write((char*)Ptr, len);

}

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

 

# include <fstream>

# include <iostream>

# include <stdlib.h>

 

using namespace std;

 

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;

        system("pause");

        exit(1);

  }

  double d;

  long count = 0;

  bin_in >> d;

  while (!bin_in.eof())

  {

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

        bin_in >> d;

  }

  system("pause");

  return 0;

}

 

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

{

  if (!p) return;

  if (len <= 0) return;

  read((char*)p, len);

}

 

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

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

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

 

Пример 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 &);

Пример 4. Чтение и запись структуры данных в файл.

//перечисляемый тип с оценками

enum mark {very_bad = 1, bad = 2, satisfactory = 3, good = 4, excellent = 5};  

struct rec //структура данных

{ char name[10];

  char subname[15];

  int clas;

  mark marks[4];

};

//открытие "типизированного" файла для записи

void WriteFile(int n, rec &wrec)

{ FILE * fo;

  fo = fopen("type.txt","wb"); //открываем (создаем) файл для записи

  if (fo == NULL)

      { cout << "Ошибка открытия файла для записи" << endl;

                   exit(EXIT_FAILURE);

  }

  for(int i = 0; i < n; i++)

  { int temp=0; //временная переменная для оценок

        cout << "Введите имя ученика: "; cin >> wrec.name;

        cout << "Введите фамилию ученика: "; cin >> wrec.subname;

        cout << "Введите класс, где обучается ученик: "; cin >> wrec.clas;

        cout << "Введите оценки ученика (4 штуки от 1 до 5)" << endl;

        for(int j = 0; j < 4; j++)

        { cout << "Введите " << j+1 << " оценку: "; cin >> temp;

               wrec.marks[j] = (mark)temp;

        }

        WriteElement(wrec, fo); //запись одной структуры в файл

        cout << endl;

  }

  fclose(fo); //закрываем файл                                                        

  cout << endl;

}

//запись структуры в файл

void WriteElement(rec &wrec, FILE * f1)

{     

fwrite(&wrec,sizeof(rec),1,f1); //записываем одну структуру

}

//чтение структуры из файла

void ReadElement(rec & rrec, FILE * f1)

{     

fread(&rrec, sizeof(rec), 1, f1); //читаем одну структуру

}

 

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

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

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

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

- 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 (табл. 1)

Таблица 1

Константа Значение Описание
beg 0 поиск от начала файла
cur 1 поиск от текущей позиции файла
end 2 поиск от конца файла

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

ostream &seekp(streampos);

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

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

 

#include <fstream>

#include <iostream>

 

using namespace std;

 

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;

  bin_stream bin_out("Bin.dat");

  if (!bin_out) {

        cerr << "Unable to write to Bin.dat" << endl;

        system("pause");

        exit(1);

  }

  for (i = 0; i < 10; i++) {

        d = rand() % 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();

  cout << endl;

  system("pause");

  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. Как организовать доступ к произвольному месту двоичного файла?

Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: