Шаг 19.
Работа с файлами (окончание)

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

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

    В качестве иллюстрации основных особенностей работы с файлами рассмотрим несколько программ.

//OOР19_1.СРР - чтение текстового файла с помощью
//  операции >>.
#include <stdlib.h>     // Для функции exit().
#include <fstream.h>    // Для файловых потоков.
const int lenName = 13; // max длина имени файла. 
// Длина вспомогательного массива: 
const int lenString = 60; 
void main()
{ 
  char source[lenName]; // Массив для имени файла.
  cout << "\nВведите имя исходного файла: "; 
  cin >> source;
  ifstream inFile;      // Входной файловый поток.
  // Открыть файл source и связать его с потоком inFile: 
  inFile.open(source);
  if (!inFile)          // Проверить правильность открытия файла.
  { 
    cerr << "\nОшибка при открытии файла " << source;
    exit(1);            // Завершение программы.
  }
  // Вспомогательный массив для чтения: 
  char string[lenString]; 
  char next;
  cout << "\n Текст файла:\n\n";
  cin.get();            // Убирает код из потока cin.
  while(1)              // Неограниченный цикл.
  { // Ввод из файла одного слова до пробельного 
    // символа либо EOF: 
    inFile >> string; 
    // Проверка следующего символа: 
    next = inFile.peek();
    // Выход при достижении конца файла: 
    if (next == EOF) break;
    // Печать с добавлением разделительного пробела: 
    cout << string << " ";
    if (next == '\n')    // Обработка конца строки.
    {   
      cout << '\n';
      // 4  -  смещение для первой страницы экрана: 
      static int  i = 4;
      // Деление по страницам до 20 строк каждая: 
      if   (!(++i % 20))
      { 
        cout << "\nДля продолжения вывода " 
              "нажмите ENTER.\n"  << endl; 
        cin.get();
      }
    }
  }
}
Текст этой программы можно взять здесь.

    Результат выполнения программы - постраничный вывод на экран текстового файла, имя которого набирает на клавиатуре пользователь по "запросу" программы. Размер страницы - 20 строк. В начале первой страницы - результат диалога с пользователем и поэтому из файла читаются и выводятся только первые 16 строк.

    Программа демонстрирует неудобства чтения текста из файла с помощью операции извлечения >>, которая реагирует на каждый обобщенный пробельный символ. Между словами, прочитанными из файла, принудительно добавлено по одному пробелу. А сколько их (пробелов) было в исходном тексте, уже не известно. Тем самым искажается содержащийся в файле текст. Читать пробельные символы позволяет компонентная функция getline() класса istream, наследуемая классом ifstream. Текст из файла будет читаться и выводиться на экран (в поток cout) без искажений (без пропусков пробелов), если в предыдущей программе чтение и вывод в поток cout организовать таким образом:

    while(1)  // Неограниченный цикл.
       {   
          inFile.getline(string,lenString); 
          next = inFile.peek(); 
          if   (next == EOF)   break; 
          cout << string;
          .   .   .

    Следующая программа читает текстовый файл и разделяет его на две части - строки, не содержащие последовательности из двух символов '//', и строки, начинающиеся такой парой символов. Иначе говоря, эта программа позволяет удалить из исходного текста программы на языке С++ комментарии, начинающиеся парой символов '//' и заканчивающиеся признаком конца строки '\n'. В программе определены два выходных потока outtext и outcom, связанные соответственно с создаваемыми заново файлами text.cpp и comment. Имя входного файла с текстом анализируемой программы на языке С++ определяет (вводит с клавиатуры) пользователь. С этим файлом "связывается" функцией open() входной поток inFile. Для проверки безошибочного открытия файлов проверяются значения выражений (!имя_потока). При истинности результата вызывается вспомогательная функция errorF(). Вспомогательная переменная int len, позволяет проследить за необходимостью перехода к новой строке в потоке outtext, если во входном потоке inFile обнаружена пара символов '//'. Символы входного потока последовательно читаются в переменную simb и выводятся в нужный выходной поток. Если не встречен символ '/', то все просто - вывод идет в поток outtext. Так как обнаружение во входном отдельного символа '/' не есть признак начала комментария, то в этом случае анализируется следующий символ, читаемый из входного потока в переменную next. Если next имеет значение '/', то это начало комментария, и последующий вывод нужно вести в поток outcom, предварительно "закрыв" строку в потоке outtext символом '\n'. Комментарии в тексте программы поясняют остальные детали алгоритма.

//OOР19_2.СРР - выделение комментариев из текста на С++;
//посимвольные чтение и запись иэ текстового файла.
#include <stdlib.h> 
#include <fstream.h>
void errorF(char *ss)   // Вспомогательная функция.
{ 
  cerr << "\nОшибка при открытии файла" << ' ' << ss << '\n';
  exit(1); 
}
const int lenName = 23; // Длина массива для имени файла. 
void main() 
{ 
  char progName[lenName]; // Массив для имени файла.
  cout << "\nВведите полное имя анализируемой программы: ";
  cin >> progName;
  ifstream inFile;        // Входной поток.
  // Связываем входной поток с файлом программы:
  inFile.open(progName);
  if (!inFile) errorF(progName);
  char simb, last, next;    // Вспомогательные переменные.
  ofstream outtext, outcom; // Два выходных потока.
  // Переменная для вычисления длин строк программы:
  int len = 0;
  outtext.open("text.cpp",ios::ate);
  if (!outtext) errorF("text.cpp");
  outcom.open("comment",ios::app);
  if (!outcom) errorF("comment");
  while (inFile.get(simb))  // Читает символы до EOF.
  { 
    len++;     // Длина очередной строки программы. 
    if (simb == '\n')
      len = 0; // Начнется новая строка программы. 
    if (simb !='/')  // Это не начало комментария.
       // Вывод символа строки программы: 
       outtext.put(simb); 
    else
       // Когда simb == '/' - возможно начало комментария.
     { // Проверка на EOF:
       if (!inFile.get(next)) break; 
       if (next == '/')
         { // Теперь уже точно комментарий. 
           if (len != 1)
              // "Закрываем" строку программы: 
              outtext.put('\n'); 
           outcom.put(simb); 
           outcom.put(next); 
           // Цикл до конца комментария, 
           // т.е. до конца строки: 
           do
             { // Чтение символа из файла: 
               inFile.get(simb); 
               // Запись символа в поток: 
               outcom.put(simb); 
             } while (simb!='\n'); 
         } 
       else
        // Вывод символов, не входящих в комментарий: 
       { 
         outtext.put(simb); 
         outtext.put(next);
       }
     }
  }
}
Текст этой программы можно взять здесь.

    Результат выполнения программы - два файла text.cpp и comment в текущем каталоге, из которого "запущена" на выполнение программа. В первом файле - текст программы без комментариев, во втором - текст всех комментариев. В качестве примера можно выполнить программу для текста из файла OOP19_2.СРР, т.е. разобрать исходный текст этой же программы.

    Для разнообразия при открытии файлов text.cpp и comment в функциях open() использованы разные флаги, определяющие режим работы с соответствующим потоком. Результат одинаков - флаги ios::ate и ios::арр в этом случае неразличимы. Запись в файлы идет с их дополнением. После каждого нового выполнения программы новая "порция" текстовой информации дописывается в каждый файл. Если необходимо, чтобы сохранялся в файлах только последний результат, второй параметр функции open() проще всего задавать по умолчанию.

    Как и для других потоков, для потоков, связанных с файлами, допустима перегрузка операций обмена. Для иллюстрации приведем следующую программу OOР19_3.СРР, в которой перегружена операция включения в поток <<. Действие операции распространено на аргументы типа ofstream& и element, где element - пользовательский тип, а именно структура. В программе с помощью конструктора класса ofstream определяется поток file1 и связывается с файлом ABC.

    Текст программы:

//OOР19_3.СРР - запись структур в файл перегруженной операцией <<.
#include <fstream.h>
struct element {   // Определение некоторой структуры. 
   int nk, nl; 
	float zn;   };
// Операция-функция, расширяющая действие операции <<.
ofstream& operator << (ofstream& out, element el) 
{ 
  out << ' ' << el.nk << ' ' << el.nl <<
       ' ' << el.zn << '\n'; 
  return out; 
}
int main()
{ 
  const int numbeEl = 5; // Количество структур в массиве.
  element arel[numbeEl] = { 1, 2, 3.45, 2, 3, 4.56,
					22, 11, 45.6, 3, 24, 4.33, 3, 6, -5.3 };
  // Определяем поток и связываем его с новым файлом ABC: 
  ofstream file1("ABC");
  if (!file1)
	{ cerr << "Неудача при открытии файла ABC."; return 1; }
  // Запись в файл ABC массива структур: 
  for (int i = 0; i < numbeEl; i++)
    file1 << arel[i];
}
Текст этой программы можно взять здесь.

    Результат выполнения программы - создание файла с именем АВС в текущем каталоге и запись в этот файл элементов массива из пяти структур element. Содержимое файла АВС:

 1 2 3.45
 2 3 4.56
 22 11 45.6
 3 24 4.33
 3 6 -5.3

    Файл АВС создается заново при каждом выполнении программы. Чтобы файл создавался один раз и была возможность его дополнения, нужно добавить в конструктор второй параметр таким образом:

    ofstream file1("ABC",ios::app);

    В этом случае при двух последовательных выполнениях программы результат в файле АВС будет таким:

 1 2 3.45
 2 3 4.56
 22 11 45.6
 3 24 4.33
 3 6 -5.3
 1 2 3.45
 2 3 4.56
 22 11 45.6
 3 24 4.33
 3 6 -5.3




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