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