класса "поток". Поэтому изучение потоков полезно и по той причине, что позволяет
ознакомиться с элементами синтаксиса и некоторыми объектно-ориентированными
понятиями Си++.
Список функций для работы с файловыми потоками хранится в заголовочном
файле "fstream.h". Поэтому во всех рассматриваемых ниже фрагментах программ
предполагается, что в начале программы есть соответствующая директива "#include":
#include<fstream.h>
2.1 Создание потоков
В программе перед первым обращением к потоку ввода или вывода необходи-
мо "создать" поток. Операторы для создания потоков похожи на описания перемен-
42
ных, и они обычно размещаются в начале программы или функции рядом с описа-
ниями переменных. Например, операторы
ifstream in_stream;
ofstream out_stream;
создают поток с именем "in_stream", являющийся объектом класса (как типа данных)
"ifstream" (input-file-stream, файловый поток ввода), и поток с именем "out_stream",
являющийся объектом класса "ofstream" (output-file-stream, файловый поток вывода).
Аналогию между потоками и обычными переменными (типа "int", "char" и т.д.) не
следует понимать слишком буквально. Например, к потокам нельзя применять опера-
тор присваивания (например, нельзя записать "in_stream1 = in_stream2").
2.2 Подключение и отключение потоков от файлов
После создания потока его можно подключить к файлу (открыть файл) с помо-
щью функции-члена "open(...)". (В предыдущих лекциях уже использовались несколь-
ко функций-членов потоков вывода. Например, во 2-й лекции применялись
"precision(...)" и "width(...)".) Функция "open(...)" у потоков ifstream и ofstream работает
по-разному (т.е. это полиморфная функция).
Для подключения потока ifstream с именем "in_stream" к файлу с именем
"Lecture_4.txt" надо применить следующий вызов:
in_stream.open("Lecture_4.txt");
Этот оператор подключит поток "in_stream" к началу файла "Lecture_4.txt"
(графически состояние программы после выполнения оператора показано на рис. 2).
Рис. 2. Состояние программы после подключения потока ввода к файлу.
Чтобы к файлу "Lecture_4.txt" подключить поток вывода ofstream с именем
"out_stream", надо выполнить аналогичный оператор:
out_stream.open("Lecture_4.txt");
Этот оператор подключит поток "out_stream" к файлу "Lecture_4.txt", но при
этом прежнее содержимое файла будет удалено. Файл будет подготовлен к приему
новых данных (рис. 3).
Рис. 3. Состояние программы после подключения потока вывода к файлу.
Для отключения потока "in_stream" от файла, к которому он подключен (для
закрытия файла), надо вызвать функцию-член "close()":
43
in_stream.close();
После этого состояние программы по отношению к файлу будет таким, как на
рис. 4.
Рис. 4. Состояние программы после отключения потока ввода от файла.
Функция-член отключения от файла у потока вывода:
out_stream.close();
выполняет аналогичные действия, но, дополнительно, в конец файла добавляется
служебный символ "end-of-file (маркер конца файла)". Т.о., даже если в поток вывода
не записывались никакие данных, то после отключения потока "out_stream" в файле
"Lecture_4.txt" будет один служебный символ (рис. 5). В таком случае файл
"Lecture_4.txt" останется на диске, но он будет пустым.
Рис. 5. Состояние программы после отключения потока вывода от файла, в
который не было записано ни одного символа..
3. Проверка ошибок выполнения файловых операций
Файловые операции, например, открытие и закрытие файлов, известны как
один из наиболее вероятных источников ошибок. В надежных коммерческих про-
граммах всегда выполняется проверка, успешно или нет завершилась файловая опе-
рация. В случае ошибки вызывается специальная функция-обработчик ошибки.
Простейший способ проверки ошибок файловых операций заключается в вызо-
ве функции-члена "fail()". Вызов
in_stream.fail();
возвращает истинное значение (True), если последняя операция потока "in_stream"
привела к ошибке (может быть, была попытка открытия несуществующего файла).
После ошибки поток "in_stream" может быть поврежден, поэтому лучше не продол-
жать работу с ним.
В приведенном ниже фрагменте программы в случае ошибки при открытии
файла на экран выдается сообщение и программа завершает работу с помощью биб-
лиотечной функции "exit()" (она описана в файле "stdlib.h"):
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
int main()
{
44
ifstream in_stream;
in_stream.open( "Lecture_4.txt" );
if ( in_stream.fail() )
{
cout << "Извините, открыть файл не удалось!\n";
exit(1);
}
...
4. Символьный ввод/вывод
4.1 Функция ввода "get(...)"
После того, как файл для ввода данных открыт, из него можно считывать от-
дельные символы. Для этого служит функция "get(...)". У нее есть параметр типа
"char&". Если программа находится в состоянии, как на рис. 2, то после вызова:
in_stream.get(ch);
произойдет следующее: (а) переменной "ch" будет присвоено значение "'Л'", и (б) по-
ток "in_stream" будет подготовлен для чтения следующего символа (рис. 6).
Рис. 6. Состояние программы после чтения из файла первого символа.
4.2 Функция вывода "put(...)"
С помощью потока вывода класса ofstream в открытый файл можно записы-
вать отдельные символы. Для этого у класса ofstream есть функция-член "put(...)".
Записываемый символ передается ей как параметр типа "char". Если программа пре-
бывает в состоянии, представленном на рис. 3, то оператор
out_stream.put('Л');
изменит состояние на то, которое показано на рис. 7:
Рис. 7. Состояние программы после записи в файл первого символа.
4.3 Функция "putback(...)"
В Си++ у потока ifstream есть функция-член "putback(...)". На самом деле
она не "возвращает символ назад" (т.е. не изменяет содержимого файла ввода), но ве-
дет себя так, как будто это делает. На рис. 8 показано состояние, в которое перейдет
программа из состояния рис. 6 после выполнения оператора:
45
in_stream.putback(ch);
Рис. 8. Состояние программы после вызова функции "putback('Л')".
"Вернуть назад" можно любой символ. Состояние программы после вызова
in_stream.putback('7');
показано на рис. 9.
Рис. 9. Состояние программы после вызова функции "putback('7')".
5. Проверка достижения конца файла при операциях ввода
5.1 Проверка конца файла с помощью функции "eof()"
При работе с потоком ввода надо следить за тем, чтобы не пропустить момент
достижения конца файла. В большинстве реализаций Си++ (в том числе и в Microsoft
Visual C++) в класс "поток ввода" встроен флаг "конец файла (end-of-file, EOF)" и
функция-член "eof()" для чтения этого флага. С помощью функции "eof()" можно
узнать, находится ли в данный момент флаг в состоянии True (конец файла достиг-
нут) или False (конца файла пока нет).
При открытии файла, когда поток ifstream только подключается к нему, флаг
EOF сбрасывается в значение False (даже если файл пуст). Но, если ifstream
"in_stream" сейчас расположен в конце файла, и флаг EOF равен False, то после вы-
зова
in_stream.get(ch);
переменная "ch" окажется в неопределенном состоянии, а флагу EOF будет присвоено
значение True. Если флаг EOF равен True, то программа не должна пытаться выпол-
нить чтение из файла, поскольку результат чтения будет неопределенным.
Допустим, программа находится с состоянии, показанном на рис. 10.
46
Рис. 10. Состояние программы после чтения предпоследнего символа из файла.