Работа с файлами произвольного доступа в языке программирования C++

№57-3,

Технические науки

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

Похожие материалы

В C++ каждый файл рассматривается как последовательность байтов и завершается маркером конца файла. Средства C++, предназначенные для работы с файловым вводом-выводом [1, 3-5], основываются на тех же основных определениях классов, что и объекты cin и соut.

При открытии файла создается объект, с которым связывается поток, представляющий собой последовательность байтов. В операциях ввода происходит пересылка байтов от какого-либо устройства (например, клавиатуры) в оперативную память; при выводе же байты пересылаются из оперативной памяти на устройства (например, экран, жесткий диск, принтер, и т.д.). Функции ввода-вывода позволяют выделять из потока и обрабатывать данные различных форматов и размеров, при этом обеспечивая буферизированный как форматированный, так и неформатированный ввод или вывод [1, 2].

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

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

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

#include "stdafx.h"
#include <iostream>
#include <conio.h>
#include <fstream>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
struct Library {int Number;
                         char Author[30];
                         char Name[50];
                         int Year;};
Library Lib;
void add()
{ system("cls");  // clrscr(); – для компилятора Borland C++ 5.02
  ofstream File_1("c:\\Library.dat",ios::app);
  if (!File_1.is_open()) {cerr<<"Нельзя открыть файл!\n";
system("pause");  // или _getch();
                                       exit(1);}
  cout<<"Шифр книги: ";
  cin>>Lib.Number;
  cin.sync();
  cout<<"Автор книги: ";
  gets_s(Lib.Author);
  cout<<"Название книги: ";
  gets_s(Lib.Name);
  cout<<"Год издания: ";
  cin>>Lib.Year;
  File_1.write(reinterpret_cast<const char*>(&Lib),sizeof(Library));
  File_1.close();
  system("pause"); }     // или _getch();

void view()
{ system("cls");  // clrscr(); – для компилятора Borland C++ 5.02
  ifstream File_1("c:\\Library.dat",ios::in);
  if (!File_1.is_open()) {cerr<<"Нельзя открыть файл!\n";
                                      system("pause");  // или _getch();
                                      exit(1);}
  while (!File_1.eof())
    { File_1.read(reinterpret_cast<char*>(&Lib),sizeof(Library));
      if (!File_1.eof())
        {cout<<"Шифр книги: "<<Lib.Number<<endl;
         cout<<"Автор книги: "<<Lib.Author<<endl;
         cout<<"Название книги: "<<Lib.Name<<endl;
         cout<<"Год издания: "<<Lib.Year<<"\n\n";} }
  File_1.close();
  system("pause"); }     // или _getch();
void changes()
{ int N;
  system("cls");  // clrscr(); – для компилятора Borland C++ 5.02
  ofstream File_1("c:\\Library.dat", ios_base::in | ios_base::out);
  if (!File_1.is_open()) {cerr<<"Нельзя открыть файл!\n";
                                      system("pause");  // или _getch();
                                      exit(1);}
  cout<<"Укажите номер записи, которую хотите изменить: ";
  cin>>N;
  File_1.seekp((N-1)*sizeof(Library));
  cout<<"Шифр книги: ";
  cin>>Lib.Number;
  cin.sync();
  cout<<"Автор книги: ";
  gets_s(Lib.Author);
  cout<<"Название книги: ";
  gets_s(Lib.Name);
  cout<<"Год издания: ";
  cin>>Lib.Year;
  File_1.write(reinterpret_cast<const char*>(&Lib),sizeof(Library));
  File_1.close();
  system("pause"); }     // или _getch();
void Menu()
{ char Ch=13;
  while (Ch!=27)
   { system("cls");  // clrscr(); – для компилятора Borland C++ 5.02
     cout<<"     ###  МЕНЮ  ###\n";
     cout<<"1. Добавление записи в файл.\n";
     cout<<"2. Чтение всех записей из файла.\n";
     cout<<"3. Изменение записи по шифру.\n";
     cout<<"4. Завершение работы.\n";
     Ch=_getch();
     switch (Ch) { case 49: add(); break;
                           case 50: view(); break;
                           case 51: changes(); break;
                           case 52: Ch=27; } } }
int main()
{ setlocale(LC_ALL,"Rus");
  Menu();
  system("pause"); return 0; }

Функция-член write() класса ostream выводит в заданный поток фиксированное число байт, начиная с указанного места в памяти. Если поток связан с файлом, то данные записываются, начиная с позиции в файле, определяемой функцией позиционирования указателя файла seekp. Так, в приведенной программе – это строка

File_1.seekp((N-1)*sizeof(Library));

Функция-член read() класса istream вводит из заданного потока в область памяти фиксированное число байт, начиная с указанного адреса. Если поток связан с файлом, то данные вводятся, начиная с позиции в файле, определяемой функцией позиционирования указателя файла seekg.

Тук как функция write() должна получать первый аргумент типа const char*, то в приведенном выше примере использован оператор

reinterpret_cast<const char*>

для преобразования адреса Lib в указатель const char*. Второй аргумент функции write() есть целое, и определяет число байт, которые должны быть записаны. Для функции read() сказанное также справедливо.

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

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

#include <iostream>
#include <conio.h>
#include <fstream>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
struct {long int Account;
           char Name[50];} Bank;
long int N;
bool inspection()          //Проверяет наличие номера счета в файле
{ ifstream File_1("c:\\Bank.dat",ios::in);
  if (!File_1.is_open())
    {cerr<<"Файл не найден! Создан новый файл.\n";
     ofstream File_2("c:\\Bank.dat",ios::out);
     File_2.close();
     cout<<"Номер счета: ";
     cin>>N;
     return false;}
  cout<<"Номер счета: ";
  cin>>N;
  File_1.seekg((N-1)*sizeof(Bank));
  File_1.read(reinterpret_cast<char*>(&Bank),sizeof(Bank));
  File_1.close();
  if (Bank.Account==N)
               {cout<<"Такой счет уже существует!\n";
                system("pause");
                return true;}
    else return false;}
void add()       //Добавление информации о новом клиенте
{ system("cls");
  if (!inspection())
  { ofstream File_1("c:\\Bank.dat", ios_base::in | ios_base::out);
    if (!File_1.is_open()) {cerr<<"Нельзя открыть файл!";
                                         system("pause");
                                         exit(1);}
    Bank.Account=N;
    cout<<"ФИО клиента: ";
    gets_s(Bank.Name);
    File_1.seekp((Bank.Account-1)*sizeof(Bank));
    File_1.write(reinterpret_cast<const char*>(&Bank),sizeof(Bank));
    File_1.close();
    system("pause"); } }
void view(int a)
{ system("cls");
  ifstream File_1("c:\\Bank.dat",ios::in);
  if (!File_1.is_open()) {cerr<<"Нельзя открыть файл!";
                                       system("pause");
                                       exit(1);}
  if (a==0) while (!File_1.eof())
            { File_1.read(reinterpret_cast<char*>(&Bank),sizeof(Bank));
              if (Bank.Account!=0 && !File_1.eof())
                  {cout<<"Номер счета: "<<Bank.Account<<endl;
                   cout<<"ФИО клиента: "<<Bank.Name<<endl<<"\n\n";} }
  if (a==1) {cout<<"Укажите номер счета: ";
                    cin>>N;
                   File_1.seekg((N-1)*sizeof(Bank));
                   File_1.read(reinterpret_cast<char*>(&Bank),sizeof(Bank));
                   if (Bank.Account!=0 && !File_1.eof())
                     {cout<<"Номер счета: "<<Bank.Account<<endl;
                      cout<<"ФИО клиента: "<<Bank.Name<<endl<<"\n\n";}
                   else cout<<"Нет такого номера счета!\n"; }
  File_1.close();
  system("pause"); }
void Menu()
{ char Ch=13;
  while (Ch!=27)
   { system("cls");
     cout<<" ###  МЕНЮ  ###\n";
     cout<<"1. Добавление информации о клиенте.\n";
     cout<<"2. Информация о всех клиентах банка.\n";
     cout<<"3. Информация о клиенте по номеру счета.\n";
     cout<<"4. Завершение работы.\n";
     Ch=_getch();
     switch (Ch) { case 49: add(); break;
                           case 50: view(0); break; //информация о всех клиентах
                           case 51: view(1); break; //... о конкретном клиенте
                           case 52: Ch=27; } } }
int main()
{ setlocale(LC_ALL,"Rus");
  Menu();
  system("pause"); return 0; }

Список литературы

  1. Дмитриев В.Л. Теория и практика программирования на С++. – Стерлитамак: РИО СФ БашГУ, 2013. – 308 с.
  2. Дмитриев В.Л. О некоторых методах класса istream в языке программирования C++ // NovaInfo.Ru – 2016. – № 57. – URL: http://novainfo.ru/article/9709
  3. Прата С. Язык программирования С++. Лекции и упражнения, 5-е изд.: Пер. с англ. – М.: Вильямс, 2007. – 1184 с.
  4. Страуструп Б. Язык программирования С++. Специальное издание. – М.: Бином, 2004. – 1054 с.
  5. Stroustrup Bjarne. The C++ programming language / Bjarne Stroustrup. – Fourth edition. – Boston: Addison-Wesley, 2013. – 1368 p.