Лекция № 7

Тема: «Работа с файлами в программах на С++»

 

План

1. Основные понятия при работе с файлами

2. Открытие и закрытие файла

3. Режимы открытия файлов

4. Запись данных  в файл

5. Чтение данных из файла

6. Добавление и удаление данных

7. Перемещение указателя в файле

 

1. Основные понятия при работе с файлами

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

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

Каким же образом можно сохранять введенную информацию? Решением является использование файлов. Именно файлы позволят сохранить исходные данные для последующей обработки. В последующем файл можно будет добавить новыми данными или отредактировать.

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

Рассмотрим указанные операции.

Учтем, что для работы с файлами необходимо использовать заголовочный файл fstream.h.

В <fstream.h> определены несколько классов и подключены заголовочные файлы <ifstream.h>файловый ввод и  <ofstream.h>  файловый вывод.

Файловый ввод/вывод аналогичен стандартному вводу/выводу, единственное отличие – это то, что ввод/вывод выполнятся не на экран, а в файл. Если ввод/вывод на стандартные устройства выполняется с помощью объектов cin и cout, то для организации файлового ввода/вывода достаточно создать собственные объекты, которые можно использовать аналогично операторам cin и cout.(начало)

 

2. Открытие и закрытие файла

Для работы с файлом его необходимо открыть. При этом файл может быть открыт или для чтения, или для записи. В зависимости от этого код открытия имеет разный вид.

Открытие файла для чтения

Для открытия файла для чтения используют команды:

 

//описываем переменную для чтения (вывода) из файла

ifstream имя_переменной;

//связываем переменную с файлом на диске, открываем файл для чтения

имя_переменной.open(“имя_файла”, ios::in);

 

Например, открыть файл primer.dat для чтения и связать его с переменной f1.

 

ifstream f1;

f1.open(“primer.dat”, ios::in);

 

Код открытия файла можно записать короче:

 

ifstream имя_переменной(“имя_файла”, ios::in);

 

Для нашего примера файл можно открыть так:

 

ifstream f1(“primer.dat”, ios::in);

 

После открытия из файла можно читать данные для обработки.

Открытие файла для записи

Если в файл необходимо записать данные, тот файл должен быть открыт для записи с помощью команд:

 

//описываем переменную для записи в файл

ofstream имя_переменной;

//связываем переменную с файлом на диске

имя_переменной.open(“имя_файла”, ios::binary);

 

Например, открыть файл primer.dat для записи и связать его с переменной f2.

 

ofstream f2;

f1.open(“primer.dat”, ios::binary);

 

Код открытия файла можно записать короче:

 

ofstream имя_переменной(“имя_файла”, ios::binary);

 

Для нашего примера файл можно открыть так:

 

ofstream f2(“primer.dat”, ios::binary);

 

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

Обратите внимание, что если в одной программе необходимо читать из файла и писать в него, то для каждой такой операции описывается своя переменная. Для наших примеров: f1 – для чтения, f2 – для записи.

Закрытие файла

После завершения работы с файлом его необходимо закрыть. Это позволяет корректно завершить работу с файлом и освободить переменную для связывания с другим файлом на диске (если это необходимо).

Для закрытия файла используют команду:

 

//закрытие файла

имя_переменной.close();

 

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

//разблокирование файла

имя_переменной.clear();

 

Например:

 

//разблокировать файл f1

f1.clear();

//закрыть файл f1

f1.close();

//разблокировать файл f2

f2.clear();

//закрыть файл f2

f2.close();

Открытие нескольких файлов одновременно

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

 

ifstream f1(“primer.dat”, ios::in);

ifstream f2(“bufer.dat”, ios::in);

 

Открыть для записи файлы primer.dat и bufer.dat

 

ofstream f3(“primer.dat”, ios::binary);

ofstream f4(“bufer.dat”, ios::binary);

 

Данный поход требует выделения для каждого файла дополнительной памяти под переменную.

 

 

//описываем переменную для чтения из файла

ifstream f1;

//связываем переменную с файлом

f1.open((“primer.dat”, ios::in);

//работаем с файлом

код чтения и обработки

//закрываем файл

f1.clear();

f1.close();

//связываем переменную с файлом

f1.open((“bufer.dat”, ios::binary);

//работаем с файлом

чтение и обработка

//закрываем файл

f1.clear();

f1.close();

 

Открыть для записи файлы Primer.dat и Bufer.dat

 

//описываем переменную для записи в файл

ofstream f2;

//связываем переменную с файлом

f2.open((“primer.dat”, ios::binary);

//работаем с файлом

запись данных в файл

//закрываем файл

f2.clear();

f2.close();

//связываем переменную с файлом

f2.open((“bufer.dat”, ios::binary);

//работаем с файлом

запись данных в файл

//закрываем файл

f2.clear();

f2.close(); (начало)

 

3. Режимы открытия файлов

Режимы открытия файлов устанавливают характер использования файлов. Для установки режима для ios предусмотрены константы, которые определяют режим открытия файлов.

Таблица 1 — режимы открытия файлов

Константа

Описание

ios::in

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

ios::out

открыть файл для записи

ios::ate

при открытии переместить указатель в конец файла

ios::app

открыть файл для записи в конец файла

ios::trunc

удалить содержимое файла, если он существует

ios::binary

открытие файла в двоичном режиме

 

Режимы открытия файлов можно устанавливать непосредственно при создании объекта или при вызове функции open().

 

// открываем файл для добавления информации к концу файла

ofstream fout("cppstudio.txt", ios_base::app);

 

// открываем файл для добавления информации к концу файла

fout.open("cppstudio.txt", ios_base::app);

 

Режимы открытия файлов можно комбинировать с помощью поразрядной логической операции или |, например: ios::out | ios::trunc — открытие файла для записи, предварительно очистив его.

Объекты класса ofstream, при связке с файлами по умолчанию содержат режимы открытия файлов  ios::out | ios::trunc. То есть файл будет создан, если не существует. Если же файл существует, то его содержимое будет удалено, а сам файл будет готов к записи. Объекты класса ifstream связываясь с файлом, имеют по умолчанию режим открытия файла ios::in  — файл открыт только для чтения. Режим открытия файла ещё называют — флаг, для удобочитаемости в дальнейшем будем использовать именно этот термин. В таблице 1 перечислены далеко не все флаги, но для начала этих должно хватить.

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

 

3. Запись данных в файл

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

 

имя_переменной.write((char*) &имя-переменной,sizeof имя_переменной);

 

При работе с файлом в С++ используется понятие указателя файла – специальной метки, которая указывает на текущее положение в файле. Команда записи помещает переменную в файл и смещает указатель файла на то количество байт, которое занимает записанная информация, вправо. Возникает вопрос, а как правильно определить размер переменной в байтах? Для этого в С++ имеется специальная функция вида:

 

sizeof имя_переменной

 

Например: записать в файл primer.dat переменную s:

 

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

ofstrem f2(“primer.dat”, ios::binary);

//записываем переменную s в файл

f2.write((char*) &s, sizeof s);

 

Приведем пример: пусть студент описывается свойствами: фамилия, имя, отчество, год рождения, средний балл. Записать в файл group.dat информацию об n студентах.

 

//подключаем заголовочные файлы

#include <iostream.h>

#include <fstream.h>

//описываем структуру студент

struct stud

{

char fam[15],imya[10],otch[15];

int god;

float srbal;

};

int main()

{

system("chcp 1251>nul");

//описываем переменную для работы со структурой

stud st;

//описываем вспомогательные переменные

int i,n;

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

ofstream f1("group.dat", ios::binary);

//вводим количество студентов

cout<<"Введите количество студентов: ";

cin>>n;

cin.get();

//запускаем цикл для ввода n студентов

for(i=0;i<=n-1;i++)

{

//вводим данные об i-м студенте

cout<<i+1<<"-й студент:\n";

cout<<"-фамилия: ";cin.getline(st.fam,15);

cout<<"-имя: ";cin.getline(st.imya,10);

cout<<"-отчество: ";cin.getline(st.otch,15);

cout<<"-год рождения: ";cin>>st.god;

cout<<"-средний балл: ";cin>>st.srbal;cin.get();

//записываем данные об i-м студенте в файл

f1.write((char*) &st, sizeof st);

}

//делаем паузу для просмотра

cout<<"Создание файла завершено";

system("pause");

return 0;

} (начало)

 

4. Чтение данных из файла

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

Команда чтения имеет вид:

 

имя_переменной.read((char*) &имя-переменной, sizeof имя_переменной);

 

Команда чтения считывает в переменную количество байт, равное размеру самой переменной, и смещает указатель файла на считанное количество байт, вправо. Для определения количества считываемых байт также используется функция:

 

sizeof имя_переменной

 

Например: прочитать из файла primer.dat данные в переменную s:

 

//открываем файл для чтения

ifstream f1(“primer.dat”, ios::in);    //или ifstream f1(“primer.dat”);

//читаем в переменную s данные из файла

f1.read((char*) &s, sizeof s);

 

Часто для обработки данных, записанных в файл, необходимо выполнять чтение файла от начала и до конца. Заранее количество данных в файле неизвестно, поэтому использовать обычный цикл for нельзя. Можно реализовать цикл while, в котором данные считываются до тех пор, пока не будет достигнут конец файла. Такой цикл записывается так:

 

while (переменная.read((char*) &переменная, sizeof переменная)

     код обработки считанных данных;

 

Например: на основании данных из файла group.dat определить количество хорошистов и отличников. Вывести фамилию и имя таких студентов.

 

//подключаем заголовочные файлы

#include <iostream.h>

#include <fstream.h>

//описываем структуру студент

struct stud

{

char fam[15],imya[10],otch[15];

int god;

float srbal;

};

 

int main()

{

system("chcp 1251>nul");

//описание переменной для работы со структурой

stud st;

//описание переменной для подсчета количества

int kol;

//обнуляем количество

kol=0;

//описываем переменную для чтения из файла

ifstream f2;

//связываем переменную с файлом

f2.open("group.dat",ios::in);

//выводим поясняющий заголовок

cout<<"Список хорошистов и отличников:\n";

//в цикле читаем данные из файла

while (f2.read((char*) &st, sizeof st))

     //если средний балл i-го студент >=4

if (st.srbal>=4)

{

          //увеличиваем количество хорошистов и отличников

          kol++;

          //выводим имя и фамилию такого студента

          cout<<st.fam<<" "<<st.imya<<"\n";

}

//выводим найденное количество студентов

cout<<"Всего таких студентов: "<<kol<<"\n";

//делаем паузу для просмотра

cout<<"Для продолжения нажмите любую клавишу...";

system("pause");

return 0;

}(начало)

 

5. Добавление и удаление данных

Любой файл данных не может быть постоянным. Всегда возникает необходимость добавления данных в файл или удаления данных.

Добавление данных в файл

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

При этом открыть файл в режиме добавления можно с помощью команды вида:

 

ofstream f1;

f1.open("имя_файла", ios::app);

 

 Например: в файл group.dat добавить данные об n студентах.

 

//подключаем заголовочные файлы

#include <iostream.h>

#include <fstream.h>

//описываем структуру студент

struct stud

{

char fam[15],imya[10],otch[15];

int god;

float srbal;

};

int main()

{

//описание переменной для работы со структурой

stud st;

//описываем вспомогательные переменные

int i,n;

//описываем переменную для добавления в файл

ofstream f1;

//связываем переменную с файлом на диске

f1.open("bufer.dat", ios::app);

//вводим количество студентов для записи

cout<<"Введите количество студентов: ";

cin>>n;

cin.get();

//запускаем цикл для добавления n студентов

for(i=0;i<=n-1;i++)

{

//вводим данные об iстуденте

cout<<i+1<<"-й студент:\n";

cout<<"-фамилия: ";

cin.getline(st.fam,15);

cout<<"-имя: ";

cin.getline(st.imya,10);

cout<<"-отчество: ";

cin.getline(st.otch,15);

cout<<"-год рождения: ";

cin>>st.god;

cout<<"-средний балл: ";

cin>>st.srbal;cin.get();

//записываем в файл buffer.dat i-го студента

f1.write((char*) &st, sizeof st);

}

//закрываем файл

f1.clear();

f1.close();

//делаем паузу для просмотра

cout<<"Добавление в файл завершено";

system("pause");

return 0;

}

Удаление данных из файла

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

 

Для удаления или переименования файла нужно подключить заголовочный файл <stdio.h>.

 

Например: из файла group.dat удалить данные о студентах, год рождения которых меньше заданного значения (год ввести с клавиатуры).

 

//подключаем заголовочные файлы

#include <iostream.h>

#include <fstream.h>

#include <stdio.h>

//описываем структуру студент

struct stud

{

char fam[15],imya[10],otch[15];

int god;

float srbal;

};

int main()

{

system("chcp 1251>nul");

//описание переменной для работы со структурой

stud st;

//описываем вспомогательные переменные

int god;

 

//вводим год для удаления

cout<<"Введите год рождения для удаления: ";

cin>>god;

 

//описываем переменную для записи в файл

ofstream f1;

//связываем переменную с файлом на диске

f1.open("bufer.dat", ios::binary);

//описываем переменную для чтения из файла

ifstream f2;

//связываем переменную с файлом на диске

f2.open("group.dat", ios::in);

//в цикле переписываем из файла group.dat

//в файл buffer.dat данные, у которых год

//не равен введенному пользователем

while (f2.read((char*) &st, sizeof st))

     if (st.god!=god)

          f1.write((char*) &st, sizeof st);

//закрываем файл group.dat

f2.clear();

f2.close();

//закрываем файл buffer.dat

f1.clear();

f1.close();

//удаляем файл group.dat с диска

remove("group.dat");

//переименовываем файл bufer.dat в group.dat

rename("bufer.dat","group.dat");

//делаем паузу для просмотра

cout<<"Удаление из файла завершено";

system("pause");

return 0;

}(начало)

 

6. Перемещение указателя в файле

Иногда в программе нужно прочитать из файла конкретный набор байт. Для этого перед чтением указатель файла должен быть установлен на нужное место в файле. Для выполнения такой операции используют команду:

 

переменная.seekg(номер);

 

Данная команда может быть использована для чтения из файла. Номер – это число байтов, на которые нужно переместить указатель от начала файла. Для вычисления этого числа можно использовать формулу:

 

номер=n*sizeof переменная

 

В этой формуле n – номер структуры в файле, переменная – имя переменной, в которую читается информация из файла.

 

Например: установить указатель на начало файла f1.

 

f1.seekg(0);

 

Установить указатель на пятую структуру в файле f1. Чтение из файла будет проводиться в переменную st.

 

f1.seekg(5*sizeof sr);

 

Например: из файла group.dat вывести на экран фамилию и имя студента с максимальным баллом. Предполагается, что такой студент один.

Необходимо в цикле найти студента с максимальным баллом и запомнить его позицию в файле. Затем поставить указатель в эту позицию, прочитать данные о студенте и вывести их на экран.

 

//подключаем заголовочные файлы

#include <iostream.h>

#include <fstream.h>

//описываем структуру студент

 

struct stud

{

    char fam[15],imya[10],otch[15];

    int god;

    float srbal;

};

int main()

{

system("chcp 1251>nul");

//описание переменной для работы со структурой

stud st;

//описываем вспомогательные переменные

int i,maxbal,nom;

//вначале максимальный балл равен 0

maxbal=0;

//обнуляем счетчик считанных данных из файла

i=0;

//описываем переменную для чтения из файла

ifstream f2;

//связываем переменную с файлом на диске

f2.open("group.dat", ios::in);

//в цикле читаем данные из файла

while (f2.read((char*) &st, sizeof st))

     {

          //находим студента с максимальным средним баллом

          //и записываем номер этого студента в nom

if (st.srbal>maxbal)

          {

               maxbal=st.srbal;

               nom=i;

          }

          //увеличиваем на 1 счетчик считанных данных

i++;

     }

//выводим максимальный балл на экран

cout<<"Максимальный балл="<<maxbal<<"\n";

//разблокируем файл после чтения в цикле

f2.clear();

//устанавливаем указатель на найденную позицию

f2.seekg(nom*sizeof st);

//читаем данные о студенте из файла

f2.read((char*) &st, sizeof st);

//закрываем файл

f2.close();

//выводим на экран фамилию и имя студента

cout<<"Студент с максимальным баллом:"<<st.fam<<" "<<st.imya<<"\n";

//делаем паузу для просмотра

system("pause");

return 0;

}

Эту же задачу можно решить по-другому. Можно описать еще одну переменную типа student. В цикле запоминать не номер структуры в файле, а саму структуру.

 

//подключаем заголовочные файлы

#include <iostream.h>

#include <fstream.h>

//описываем структуру студент

struct stud

{

    char fam[15],imya[10],otch[15];

    int god;

    float srbal;

};

int main()

{

system("chcp 1251>nul");

//описание переменных для работы со структурой

stud st,rez;

//описываем вспомогательные переменные

int i,maxbal;

//вначале максимальный балл равен 0

maxbal=0;

//обнуляем счетчик считанных данных из файла

i=0;

//описываем переменную для чтения из файла

ifstream f2;

//связываем переменную с файлом на диске

f2.open("group.dat", ios::in);

//в цикле читаем данные из файла

while (f2.read((char*) &st, sizeof st))

     {

          //находим студента с максимальным средним баллом

          //и записываем данные о нем в rez

if (st.srbal>maxbal)

          {

               maxbal=st.srbal;

               rez=st;

          }

          //увеличиваем на 1 счетчик считанных данных

i++;

     }

//выводим максимальный балл на экран

cout<<"Максимальный балл="<<maxbal<<"\n";

//закрываем файл

f2.close();

// из переменной rez выводим на экран

//фамилию и имя студента c максимальным баллом

cout<<"Студент с максимальным баллом:"<<rez.fam<<" "<<rez.imya<<"\n";

//делаем паузу для просмотра

system("pause");

return 0;

} (начало)