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