Шаг 18.
Работа с файлами

    На этом шаге мы рассмотрим организацию связи файлов с потоками.

    Потоки для работы с файлами создаются как объекты следующих классов:

    Чтобы использовать эти классы, в текст программы необходимо включить дополнительный заголовочный файл fstream.h. После этого в программе можно определять конкретные файловые потоки, соответствующих типов (объекты классов ofstream, ifstream, fstream), например, таким образом:

    ofstream outFile; // Определяется выходной файловый поток. 
    ifstream inFile;  // Определяется входной файловый поток.
    fstream  ioFile;  // Определяется файловый поток для ввода и вывода.

    Создание файлового потока (объекта соответствующего класса) связывает имя потока с выделяемым для него буфером и инициализирует переменные состояния потока. Так как перечисленные классы файловых потоков наследуют свойства класса ios, то и переменные состояния каждого файлового потока наследуются из этого базового класса. Так как файловые классы являются производными от классов ostream (класс ofstream), istream (класс ifstream), stream (класс fstream), то они поддерживают описанный в предыдущих шагах форматированный и бесформатный обмен с файлами. Однако прежде чем выполнить обмен, необходимо открыть соответствующий файл и связать его с файловым потоком.

    Открытие файла в самом общем смысле означает процедуру, информирующую систему о тех действиях, которые предполагается выполнять с файлом. Существуют функции стандартной библиотеки языка С для открытия файлов fopen(), open(). Но работая с файловыми потоками библиотеки ввода-вывода языка С++, удобнее пользоваться компонентными функциями соответствующих классов.

    Создав файловый поток, можно "присоединить" его к конкретному файлу с помощью компонентной функции open(). Функция open() унаследована каждым из файловых классов ofstream, ifsream, fstream от класса fstreambase. С ее помощью можно не только открыть файл, но и связать его с уже определенным потоком. Формат функции:

    void open(const char  *fileName,
                        int mode = умалчиваемое_значение,
                        int protection = умалчиваемое_значение);

    Первый параметр - fileName - имя уже существующего или создаваемого заново файла. Это строка, определяющая полное или сокращенное имя файла в формате, регламентированном операционной системой. Второй параметр - mode (режим) - дизъюнкция флагов, определяющих режим работы с открываемым файлом (например, только запись или только чтение). Флаги определены следующим образом:

enum ios::open_mode {
 in        = 0x01, // Открыть только для чтения.
 out       = 0x02, // Открыть только для записи.
 ate       = 0x04, // При открытии искать конец файла.
 арр       = 0x08, // Дописывать данные в конец файла.
 trunc     = 0x10, // Вместо существующего создать новый файл.
 nocreate  = 0x20, // Не открывать новый файл (Для
                   //  несуществующего файла функция open выдаст ошибку).
 noreplace = 0x40, // Не открывать существующий файл.
                   //  (Для существующего выходного файла, не имеющего режимов ate 
                   //  или арр, выдать ошибку).
 binary   = 0x80   // Открыть для двоичного (не текстового) обмена.
};

    Назначения флагов поясняют комментарии, однако надеяться, что именно такое действие на поток будет оказывать тот или иной флаг в конкретной реализации библиотеки ввода-вывода, нельзя. Даже сам автор языка С++ Б.Страуструп говорит о том, что смысл значений open_mode, скорее всего, будет зависеть от реализации. Например, различие между флагами ios::ate и ios::app проявляется весьма редко, и часто они действуют одинаково. Однако ниже в пояснениях к программе OOР19_3.СРР приведен пример использования флага ios:: арр в конструкторе класса ofstream, где использование ios::ate приведет к ошибке открытия файла. Умалчиваемое значение параметра mode зависит от типа потока, для которого вызывается функция open().

    Третий параметр - protection (защита) - определяет защиту и достаточно редко используется. Точнее, он устанавливается по умолчанию и умалчиваемое значение обычно устраивает программиста.

    Как обычно вызов функции open() осуществляется с помощью уточненного имени

    имя_объекта класса.вызов_принадлежащей_классу_функции

    Итак, открытие и присоединение файла к конкретному файловому потоку обеспечивается таким вызовом функции open():

    имя_потока.open(имя_файла,   режим,   защита);

    Здесь имя_потока - имя одного из объектов, принадлежащих классам ofstream, ifstream, fstream. Примеры вызовов для определенных выше потоков:

    outFile.open("С:\\USER\\RESULT.DAT");
    inFile.open("DATA.TXT");
    ioFile.open("CHANGE.DAT",ios::out);

    При открытии файлов с потоками класса ofstream второй параметр по умолчанию устанавливается равным ios::out, т.е. файл открывается только для вывода. Таким образом, файл C:\USER\RESULT.DAT после удачного выполнения функции open() будет при необходимости (если он не существовал ранее) создан, а затем открыт для вывода (записи) данных в текстовом режиме обмена и присоединен к потоку outFile. Теперь к потоку outFile может применяться, например, операция включения <<, как к стандартным выходным потокам cout, cerr.

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

    Для проверки удачности завершения функции open() используется перегруженная операция !. Если унарная операция ! применяется к потоку, то результат ненулевой при наличии ошибок. Если ошибок не было, то выражение !имя_потока имеет нулевое значение. Таким образом, можно проверить результат выполнения функции open():

    .    .    .    .
    if (!inFile)
      { cerr << "Ошибка при открытии файла!\n"; 
         exit(l);
      }
    .    .    .    .

    Для потоков класса fstream второй аргумент функции ореn() должен быть задан явно, так как по умолчанию неясно, в каком направлении предполагается выполнять обмен с потоком. В примере файл CHANGE.DAT открывается для записи и связывается с потоком ioFile, который будет выходным потоком до тех пор, пока с помощью повторного открытия файла явно не изменится направление обмена с файлом или потоком. (Чтобы изменить режимы доступа к файлу, его нужно предварительно закрыть с помощью функции close(), унаследованной всеми тремя файловыми классами из базового класса fstreambase.)

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

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

#include <fstream.h>    // Классы файловых потоков.
#include <sys\stat.h>  // Константы режимов доступа к файлам.
.    .    .    .    .
  char name[20];   // Вспомогательный массив.
  cin >> name;     // Ввести имя создаваемого файла.
  int descrip = create(name,S_WRITE);     // Создать файл. 
  if   (descrip == -1)
    { cout <<  "\n Ошибка при  создании файла"; 
      exit();
    }
  // Определение выходного файлового потока: 
  ofstream fileOut; 
  // Присоединение потока к файлу: 
  fileOut.attach(descrip); 
  if (!fileOut)
    { cerr << "\nОшибка присоединения файла!";
       exit(l);
     }
.    .    .    .    .

    В классах ifstream, ofstream, fstream определены конструкторы, позволяющие по-иному выполнять создание и открытие файлов. Типы конструкторов для потоков разных классов очень похожи:

имя_класса();
создает поток, не присоединяя его ни к какому файлу;
имя_класса(int fd);
создает поток и присоединяет его к уже открытому файлу, дескриптор которого используется в качестве параметра fd;
имя_класса(int fd, char *buf, int);
создает поток, присоединяя его к уже открытому файлу с дескриптором fd, и использует явно заданный буфер (параметр buf);
имя_класса(char *FileName, int mode, int = ...);
создает поток, присоединяет его к файлу с заданным именем FileName, а при необходимости предварительно создает файл с таким именем.

    Детали и особенности перечисленных конструкторов лучше изучать по документации конкретной библиотеки ввода-вывода.

    Работая со средствами библиотечных классов ввода-вывода, чаще всего употребляют конструктор без параметров и конструктор, в котором явно задано имя файла. Примеры обращений к конструкторам без параметров:

    ifstream fi; // Создает входной файловый поток  fi.
    оstream  fo; // Создает выходной файловый поток fo.
    fstream  ff; // Создает файловый поток ввода-вывода ff.

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

    void open(char *FileName, int режим,   int защита); 

    Примеры:

    fi.open("File1.txt",ios::in); //  Поток fi соединен с файлом File1.txt.
    fi.close();   //  Разорвана связь потока fi с файлом File1.txt.
    fi.open("File2.txt");   // Поток fi присоединен к файлу File2.txt.
    fо.open("NewFile");     // Поток  fo присоединяется к  файлу NewFile;  
                            // если такой файл отсутствует -  он будет создан.

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

    Примеры:

ifstream flow1("File.1");
создает входной файловый поток с именем flow1 для чтения данных. Разыскивается файл с названием File.1. Если такой файл не существует, то конструктор завершает работу аварийно. Проверка:
    if   (!flow1)   cerr <<   "Не открыт файл File.1";
ofstream flow2("File.2");
создается выходной файловый поток с именем flow2 для записи информации. Если файл с названием File.2 не существует, он будет создан, открыт и соединен с потоком flow2. Если файл уже существует, то предыдущий вариант будет удален и пустой файл создается заново. Проверка:
    if (!flow2) cerr << "Не открыт файл File.2!";
fstream flow3("File.3");
создается файловый поток flow3, открывается файл File.3 и присоединяется к потоку flow3.

    На следующем шага мы закончим рассматривать работу с файлами.




Предыдущий шаг Содержание Следующий шаг