МЕТОДИКА ВЫПОЛНЕНИЯ РАБОТЫ

3.1.Оперирование файлами на языке программирования С++

 

Пакет классов C++ работает с файловым вводом/выводом во многом так же, как он делает это со стандартным вводом/выводом /7/. Чтобы записать данные в файл, необходимо создать потоковый объект и воспользоваться такими методами класса ostream, как операция вставки << или функция write(). Для чтения файла создается потоковый объект и применяются такие методы класса istream, как операция извлечения >> или функция get(). Однако файлы требуют более высокого уровня управления, чем стандартные потоки ввода/вывода. Например, новый открытый файл необходимо ассоциировать с потоком. Файлы можно открывать в режиме только для чтения, только для записи и в режиме чтения/записи. При записи в файл может возникнуть необходимость создать новый файл, заменить старый или добавить данные к старому файлу. Существует также возможность перемещаться по файлу в разных направлениях. Чтобы помочь справиться с этими задачами, в C++ определены несколько классов в заголовочном файле fstream, включая класс ifstream для выполнения файлового ввода и класс ofstream – для файлового вывода. C++ также определяет класс fstream для одновременного файлового ввода/вывода. Эти классы являются производными от классов в заголовочном файле iostream.

Для записи информации в файл в программе необходимо выполнить следующие действия:

- создать объект класса ofstream для управления потоком вывода;

- поставить этот объект в соответствие определенному файлу;

- использовать объект класса ofstream так же, как объект вывода cout; единственная разница заключается в том, что информация будет выводиться в файл, а не на экран.

Для выполнения этой задачи начнем с включения заголовочного файла fstream. При включении этого файла автоматически включается файл iostream почти для всех реализаций языка C++, поэтому iostream не нужно включать явно. Затем необходимо объявить объект класса ofstream. Имя объекта может быть любым допустимым именем C++, например, fout, outFile, cgate или didi. Объявление объекта с именем fout будет следующим:

Ofstream fout.

Затем необходимо связать с объектом определенный файл. Это можно сделать, воспользовавшись методом ореn(). Предположим, что необходимо открыть для записи файл, например, cookies. Для этого нужно сделать следующее:

//поставить в соответствие объекту fout файл text.txt

fout.open("text.txt");

Эти два этапа (создание объекта и его ассоциация с файлом) можно объединить в одном операторе, воспользовавшись другим конструктором:

//создать объект fout и поставить ему в соответствие файл text.txt

ofstream fout("text.txt");

После этого объект fout (или объект с любым другим именем) можно использовать точно так же, как и объект cout. Например, если требуется записать слова My name is Guzel в файл, то для этого необходимо выполнить следующие действия:

fout << "My name is Guzel";

В самом деле, поскольку класс ostream является базовым для класса ofstream, можно использовать методы (функции) класса ostream, включая различные определения операций вставки, а также методы и манипуляторы форматирования. Класс ofstream работает с буферизованным выводом, поэтому программа выделяет место для буфера вывода при создании такого объекта класса ofstream, как fout. Если создаются два объекта класса ofstream, то программа создает два буфера – отдельно для каждого объекта. Объект класса ofstream наподобие fout считывает побайтно вывод, сгенерированный программой. Затем, когда буфер заполняется, содержимое буфера целиком передается в файл назначения. Поскольку дисковые накопители предназначены для передачи данных большими блоками, а не побайтово, буферизация значительно повышает скорость пересылки данных от программы к файлу.

Открытие файла для вывода таким способом позволяет создать новый файл, если файла с таким именем не существует. Если же файл с таким именем существует, то до открытия для вывода этот файл урезается до нулевого размера и информация начинает выводиться в пустой файл.

Требования к чтению файла очень похожи на требования к записи:

1. Создать объект класса ifstream для управления потоком ввода.

2. Поставить этот объект в соответствие определенному файлу.

3. Использовать объект так же, как объект cin.

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

//два оператора

//создать объект класса ifstream с именем fin

ifstream fin;

// открыть файл text.txt для чтения

fin.open("text.txt") ;

//один оператор

//создать объект fin и поставить ему в соответствие файл text.txt

ifstream fin("text.txt");

Теперь объект fin можно использовать так же, как объект cin. Например, можно сделать следующее:

char ch ;

//считать символ из файла text.txt

fin >> ch;

char buf[80];

//считать слово из файла

fin >> buf;

//считать строку из файла

fin.getline(buf, 80);

Связь с файлом прекращается автоматически, например, по окончании выполнения программы. Кроме того, связь с файлом можно прервать явно, воспользовавшись методом close():

// прервать связь с файлом для вывода

fout.close();

// прервать связь с файлом для ввода

fin.close();

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

Рассмотрим короткий пример. Программа запрашивает имя файла. Затем она создает файл с таким именем, записывает в него некоторую информацию и закрывает файл. При закрытии файла очищается буфер – таким образом гарантируется, что файл будет обновлен. Затем программа открывает тот же файл для чтения и выводит его содержимое на экран. Заметим, что в программе используется fin и fout точно так же, как и объекты cin и cout.

Может возникнуть необходимость в том, чтобы программа открывала более одного файла. Стратегия открытия нескольких файлов зависит от того, как они будут использоваться. Если нужно, чтобы одновременно были открыты два файла, следует создать отдельный поток для каждого файла. Например, программа, которая сравнивает два отсортированных файла и записывает результаты в третий, должна создать два объекта класса ifstream для двух входных файлов и один объект класса ofstream для выходного файла. Число файлов, которые можно открыть одновременно, зависит от операционной системы, но обычно оно составляет 20.

Однако можно обрабатывать группу файлов последовательно. Например, может возникнуть необходимость подсчитать, сколько раз имя появляется в наборе из десяти файлов. Для этого можно открыть только один поток и ставить его в соответствие только одному файлу, обрабатывая файлы по очереди. При этом ресурсы компьютера используются более эффективно, чем при открытии отдельного потока для каждого файла. Для использования этого подхода необходимо объявить потоковый объект без инициализации и затем использовать метод ореn() для соединения потока с файлом. Например, вот как можно обработать два файла последовательно:

//создать поток

ifstream fin;

//поставить в соответствие потоку файл file1.dat

fin.open("file1.dat");

//обрабатывать информацию из файла

. . . .операторы обработки

// закрыть связь потока и файла file1.dat

fin.close();

//переинициализировать fin (может быть необязательным)

fin.clear();

//поставить в соответствие потоку файл file2.dat

fin.open("file2.dat");

//обрабатывать информацию

. . . . операторы обработки

fin.close();

Режим файла служит для описания характера использования файла – для чтения, для записи, для добавления и т.д. Когда поток ассоциируется с файлом, а также при инициализации файлового потокового объекта именем файла или при работе с методом ореn(), можно использовать и второй аргумент, задающий режим файла:

//конструктор с аргументом режима

ifstream fin("banjo", mode1);

ofstream fout();

//метод open() с аргументом режима

fout.open("harp", mode2);

В классе ios_base определено несколько констант, которыми можно воспользоваться для указания режима файла. В таблице 1 приведен список констант и их значений. Файловый ввод/вывод в C++ был изменен, чтобы сделать его совместимым с файловым вводом/выводом ANSI С.

 

Таблица 1. Константы режимов файла

Константа Значение
ios_base::in Открыть файл для чтения.
ios_base::out Открыть файл для записи.
Продолжение таблицы 1
Константа Значение
ios_base::ate Переместить указатель в конец файла после его открытия.
ios_base::app Добавить информацию к концу файла.
ios_base::trunc Усечь файл, если он существует.
ios_base::binary Двоичный файл.

 

Заметим, что флаг ios_base::trunc означает, что существующий файл усекается при открытии для программного вывода; таким образом, предыдущее содержимое файла удаляется. Это позволяет значительно снизить опасность переполнения дискового пространства. Однако можно представить ситуацию, когда совсем необязательно удалять содержимое файла при его открытии. C++, разумеется, обеспечивает и другие возможности. Если, например, необходимо сохранить содержимое файла и добавить новые данные к концу файла, можно воспользоваться режимом ios_base::app:

ofstream fout("text.txt", ios_base::out | ios_base::app);

И снова в программном коде используется оператор | для создания комбинации режимов. Таким образом, ios_base::out | ios_base::app означает, что будут включены и режим out, и режим арр.

Если не используется режим, заданный по умолчанию, то наиболее безопасный подход заключается в том, чтобы указать все режимы явно. Одни компиляторы не поддерживают некоторые возможности, а другие могут обеспечивать и дополнительные возможности.

В стандарте C++ компоненты ввода/вывода в языке C++ определены в терминах стандарта ANSI С. Следующий оператор C++:

ifstream fin(filename, c++mode);7

реализуется так, как если бы он использовал функцию С fopen():

fopen(filename, cmode);

Здесь c++mode имеет тип значения openmode, как ios_base::in в ANSI С, a cmode отвечает строке режима файла из С, например, строке "r". В таблице 2 показано соответствие между режимами в C++ и С. Заметим, что константа ios_base::out сама по себе задает усечение файла, но этого не происходит, если за ней следует ios_base::in. Неуспешное открытие файла обнаруживает метод is_open().

 

Таблица 2. Соответствие режимов файлов в языках C++ и С

Режим C++ Режим С
Открыть для чтения.
ios_base::in "r"
(То же самое, что и ios_base::out ios_base::trunc).
ios_base::out "w"
Открыть для записи, урезав длину файла до нуля, если он уже существует.
los_base::out | ios_base::trunc "w"
Открыть файл для записи, но только для добавления к файлу.
ios_base::out | ios_base::app "a"
Открыть для чтения и записи, причем запись разрешена в любом месте файла.
ios_base::in | ios_base::out "r+"
Открыть файл для чтения и записи, вначале урезав длину файла до нуля, если он уже существует.
ios_base::in | ios_base ::out | ios_base::trunc "w+"
Открыть файл в режиме c++mode или в отвечающем ему режиме cmode, а также в двоичном режиме; например, ios_base::in | ios_base::binary становится "rb".
c++mode | ios_base::binary "cmodeb"
Открыть в указанном режиме и перейти к концу файла. В языке выполняется вызов отдельной функции вместо указания режима. Например, ios_base::in | ios_base::ate превращается в вызов функции С с указанием режима перемещения по файлу fseek(file, 0, SEEK_ENP).
c++mode | ios_base::ate "cmode"

 

Заметим, что и ios_base::ate, и ios_base::app перемещают указатель файла в конец только что открытого файла. Разница между ними заключается в том, что режим ios_base::app позволяет добавлять данные только в конце файла, в то время как режим ios_base::ate позволяет просто помещать указатель в конец файла.