Большинство компьютерных программ работают с файлами, и поэтому возникает необходимость создавать, удалять, записывать читать, открывать файлы. Что же такое файл? Файл – именованный набор байтов, который может быть сохранен на некотором накопителе. Ну, теперь ясно, что под файлом понимается некоторая последовательность байтов, которая имеет своё, уникальное имя, например файл.txt . В одной директории не могут находиться файлы с одинаковыми именами. Под именем файла понимается не только его название, но и расширение, например: file.txt и file.dat — разные файлы, хоть и имеют одинаковые названия. Существует такое понятие, как полное имя файлов – это полный адрес к директории файла с указанием имени файла, например: D:\docs\file.txt . Важно понимать эти базовые понятия, иначе сложно будет работать с файлами.
Для работы с файлами необходимо подключить заголовочный файл
Файловый ввод/вывод аналогичен стандартному вводу/выводу, единственное отличие – это то, что ввод/вывод выполнятся не на экран, а в файл. Если ввод/вывод на стандартные устройства выполняется с помощью объектов cin и cout , то для организации файлового ввода/вывода достаточно создать собственные объекты, которые можно использовать аналогично операторам cin и cout .
Например, необходимо создать текстовый файл и записать в него строку Работа с файлами в С++ . Для этого необходимо проделать следующие шаги:
Почему необходимо создавать объект класса ofstream , а не класса ifstream ? Потому, что нужно сделать запись в файл, а если бы нужно было считать данные из файла, то создавался бы объект класса ifstream .
// создаём объект для записи в файл ofstream /*имя объекта*/; // объект класса ofstream
Назовём объект – fout , Вот что получится:
Ofstream fout;
Для чего нам объект? Объект необходим, чтобы можно было выполнять запись в файл. Уже объект создан, но не связан с файлом, в который нужно записать строку.
Fout.open("cppstudio.txt"); // связываем объект с файлом
Через операцию точка получаем доступ к методу класса open(), в круглых скобочках которого указываем имя файла. Указанный файл будет создан в текущей директории с программой. Если файл с таким именем существует, то существующий файл будет заменен новым. Итак, файл открыт, осталось записать в него нужную строку. Делается это так:
Fout << "Работа с файлами в С++"; // запись строки в файл
Используя операцию передачи в поток совместно с объектом fout строка Работа с файлами в С++ записывается в файл. Так как больше нет необходимости изменять содержимое файла, его нужно закрыть, то есть отделить объект от файла.
Fout.close(); // закрываем файл
Итог – создан файл со строкой Работа с файлами в С++ .
Шаги 1 и 2 можно объединить, то есть в одной строке создать объект и связать его с файлом. Делается это так:
Ofstream fout("cppstudio.txt"); // создаём объект класса ofstream и связываем его с файлом cppstudio.txt
Объединим весь код и получим следующую программу.
// file.cpp: определяет точку входа для консольного приложения.
#include "stdafx.h"
#include
Осталось проверить правильность работы программы, а для этого открываем файл cppstudio.txt и смотрим его содержимое, должно быть — Работа с файлами в С++ .
В программе показаны два способа чтения из файла, первый – используя операцию передачи в поток, второй – используя функцию getline() . В первом случае считывается только первое слово, а во втором случае считывается строка, длинной 50 символов. Но так как в файле осталось меньше 50 символов, то считываются символы включительно до последнего. Обратите внимание на то, что считывание во второй раз (строка 17 ) продолжилось, после первого слова, а не с начала, так как первое слово было прочитано в строке 14 . Результат работы программы показан на рисунке 1.
Работа с файлами в С++ Для продолжения нажмите любую клавишу. . .
Рисунок 1 — Работа с файлами в С++
Программа сработала правильно, но не всегда так бывает, даже в том случае, если с кодом всё впорядке. Например, в программу передано имя несуществующего файла или в имени допущена ошибка. Что тогда? В этом случае ничего не произойдёт вообще. Файл не будет найден, а значит и прочитать его не возможно. Поэтому компилятор проигнорирует строки, где выполняется работа с файлом. В результате корректно завершится работа программы, но ничего, на экране показано не будет. Казалось бы это вполне нормальная реакции на такую ситуацию. Но простому пользователю не будет понятно, в чём дело и почему на экране не появилась строка из файла. Так вот, чтобы всё было предельно понятно в С++ предусмотрена такая функция — is_open() , которая возвращает целые значения: 1 — если файл был успешно открыт, 0 — если файл открыт не был. Доработаем программу с открытием файла, таким образом, что если файл не открыт выводилось соответствующее сообщение.
// file_read.cpp: определяет точку входа для консольного приложения.
#include "stdafx.h"
#include
Результат работы программы показан на рисунке 2.
Файл не может быть открыт! Для продолжения нажмите любую клавишу. . .
Рисунок 2 — Работа с файлами в С++
Как видно из рисунка 2 программа сообщила о невозможности открыть файл. Поэтому, если программа работает с файлами, рекомендуется использовать эту функцию, is_open() , даже, если уверены, что файл существует.
Режимы открытия файлов устанавливают характер использования файлов. Для установки режима в классе ios_base предусмотрены константы, которые определяют режим открытия файлов (см. Таблица 1).
Режимы открытия файлов можно устанавливать непосредственно при создании объекта или при вызове функции open() .
Ofstream fout("cppstudio.txt", ios_base::app); // открываем файл для добавления информации к концу файла fout.open("cppstudio.txt", ios_base::app); // открываем файл для добавления информации к концу файла
Режимы открытия файлов можно комбинировать с помощью поразрядной логической операции или | , например: ios_base::out | ios_base::trunc — открытие файла для записи, предварительно очистив его.
Объекты класса ofstream , при связке с файлами по умолчанию содержат режимы открытия файлов ios_base::out | ios_base::trunc . То есть файл будет создан, если не существует. Если же файл существует, то его содержимое будет удалено, а сам файл будет готов к записи. Объекты класса ifstream связываясь с файлом, имеют по умолчанию режим открытия файла ios_base::in — файл открыт только для чтения. Режим открытия файла ещё называют — флаг, для удобочитаемости в дальнейшем будем использовать именно этот термин. В таблице 1 перечислены далеко не все флаги, но для начала этих должно хватить.
Обратите внимание на то, что флаги ate и app по описанию очень похожи, они оба перемещают указатель в конец файла, но флаг app позволяет производить запись, только в конец файла, а флаг ate просто переставляет флаг в конец файла и не ограничивает места записи.
Разработаем программу, которая, используя операцию sizeof() , будет вычислять характеристики основных типов данных в С++ и записывать их в файл. Характеристики:
Запись в файл должна выполняться в таком формате:
/* data type byte max value bool = 1 255.00 char = 1 255.00 short int = 2 32767.00 unsigned short int = 2 65535.00 int = 4 2147483647.00 unsigned int = 4 4294967295.00 long int = 4 2147483647.00 unsigned long int = 4 4294967295.00 float = 4 2147483647.00 long float = 8 9223372036854775800.00 double = 8 9223372036854775800.00 */
Такая программа уже разрабатывалась ранее в разделе , но там вся информация о типах данных выводилась на стандартное устройство вывода, а нам необходимо программу переделать так, чтобы информация записывалась в файл. Для этого необходимо открыть файл в режиме записи, с предварительным усечением текущей информации файла (строка 14 ). Как только файл создан и успешно открыт (строки 16 — 20), вместо оператора cout , в строке 22 используем объект fout . таким образом, вместо экрана информация о типах данных запишется в файл.
// write_file.cpp: определяет точку входа для консольного приложения.
#include "stdafx.h"
#include
Нельзя не заметить, что изменения в программе минимальны, а всё благодаря тому, что стандартный ввод/вывод и файловый ввод/вывод используются абсолютно аналогично. В конце программы, в строке 45 мы явно закрыли файл, хотя это и не обязательно, но считается хорошим тоном программирования. Стоит отметить, что все функции и манипуляторы используемые для форматирования стандартного ввода/вывода актуальны и для файлового ввода/вывода. Поэтому не возникло никаких ошибок, когда оператор cout был заменён объектом fout .
Работа с текстовыми файлами в C++.
Существуют два основных типа файлов: текстовые и двоичные. Файлы позволяют пользователю считывать большие объемы данных непосредственно с диска, не вводя их с клавиатуры.
Текстовыми называются файлы, состоящие из любых символов. Они организуются по строкам, каждая из которых заканчивается символом «конец строки». Конец самого файла обозначается символом «конец файла». При записи информации в текстовый файл, просмотреть который можно с помощью любого текстового редактора, все данные преобразуются к символьному типу и хранятся в символьном виде.
В двоичных файлах информация считывается и записывается в виде блоков определенного размера, в которых могут храниться данные любого вида и структуры.
Для работы с файлами используются специальные типы данных , называемые потоками . Поток ifstream служит для работы с файлами в режиме чтения, а ofstream в режиме записи. Для работы с файлами в режиме как записи, так и чтения служит поток fstream .
В программах на C++ при работе с текстовыми файлами необходимо подключать библиотеки iostream и fstream.
Для того чтобы записывать данные в текстовый файл, необходимо:
описать переменную типа ofstream.
вывести информацию в файл.
обязательно закрыть файл.
Для считывания данных из текстового файла, необходимо:
описать переменную типа ifstream.
открыть файл с помощью функции open.
закрыть файл.
Запись информации в текстовый файл
Как было сказано ранее, для того чтобы начать работать с текстовым файлом, необходимо описать переменную типа ofstream. Например, так:
Будет создана переменная F для записи информации в файл.
На следующим этапе файл необходимо открыть для записи. В общем случае оператор открытия потока будет иметь вид:
F.open(«file», mode);
Здесь F - переменная, описанная как ofstream,
file - полное имя файла на диске,
mode - режим работы с открываемым файлом.
Обратите внимание на то, что при указании полного имени файла нужно ставить двойной слеш. Например, полное имя файла noobs.txt, находящегося в папке game на диске D:, нужно будет записать так:
D:\\game\\noobs.txt.
Файл может быть открыт в одном из следующих режимов:
ios::in - открыть файл в режиме чтения данных, этот режим является режимом по умолчанию для потоков ifstream;
ios::out - открыть файл в режиме записи данных (при этом информация о существующем файле уничтожается), этот режим является режимом по умолчанию для потоков ofstream;
ios::app - открыть файл в режиме записи данных в конец файла;
ios::ate - передвинуться в конец уже открытого файла;
ios::trunc - очистить файл, это же происходит в режиме ios::out;
ios::nocreate - не выполнять операцию открытия файла, если он не существует;
ios::noreplace - не открывать существующий файл.
Параметр mode может отсутствовать, в этом случае файл открывается в режиме по умолчанию для данного потока.
После удачного открытия файла (в любом режиме) в переменной F будет храниться true, в противном случае false. Это позволит проверить корректность операции открытия файла.
Открыть файл (в качестве примера возьмем файл D:\\game\\noobs.txt) в режиме записи можно одним из следующих способов:
// первый способ
ofstream F;
F.open("D:\\game\\noobs.txt", ios::out);
//второй способ, режим ios::out является режимом по умолчанию
// для потока ofstream
ofstream F;
//третий способ объединяет описание переменной и типа поток
//и открытие файла в одном операторе
ofstream F ("D:\\game\\noobs.txt", ios::out);
После открытия файла в режиме записи будет создан пустой файл, в который можно будет записывать информацию.
Если вы хотите открыть существующий файл в режиме до записи, то в качестве режима следует использовать значение ios::app.
После открытия файла в режиме записи, в него можно писать точно так же, как и на экран, только вместо стандартного устройства вывода cout необходимо указать имя открытого файла.
Например, для записи в поток F переменной a, оператор вывода будет иметь вид:
Для последовательного вывода в поток G переменных b, c, d оператор вывода станет таким:
G<
Закрытие потока осуществляется с помощью оператора:
ПРИМЕР:
Создать текстовый файл D:\\game\\noobs.txt и записать в него n вещественных чисел.
#include "stdafx.h"
#include
#include
#include
using namespace std;
int main()
setlocale (LC_ALL, "RUS");
int i, n;
double a;
//описывает поток для записи данных в файл
ofstream f ;
//открываем файл в режиме записи,
//режим ios :: out устанавливается по умолчанию
f.open("D:\\game\\noobs.txt", ios::out);
//вводим количество вещественных чисел
cout <<" n ="; cin >> n ;
//цикл для ввода вещественных чисел
//и записи их в файл
for
(i=0; i
cout<<"a=";
//ввод числа
cin>>a;
//закрытие потока
f.close();
system("pause");
return 0;
_______________________________________________________________
Для того чтобы прочитать информацию из текстового файла, необходимо описать переменную типа ifstream . После этого нужно открыть файл для чтения с помощью оператора open . Если переменную назвать F, то первые два оператора будут такими:
F.open("D:\\game\\noobs.txt", ios::in);
После открытия файла в режиме чтения из него можно считывать информацию точно так же, как и с клавиатуры, только вместо cin указать имя потока из которого будет происходить чтение данных.
Например, для чтения из потока F в переменную a, оператор ввода будет выглядеть так:
Два числа в текстовом редакторе считаются разделенными, если между ними есть хотя бы один из символов: пробел, табуляция, символ конца строки. Хорошо, если программисту заранее известно, сколько и каких значений храниться в текстовом файле. Однако часто просто известен тип значений, хранящихся в файле, при этом их количество может быть различным. При решении подобной проблемы необходимо считывать значения из файла по одному, а перед каждым считыванием проверять, достигнут ли конец файла. Для этого существует функция F . eof ().
Здесь F - имя потока функция возвращает логическое значение: true или false, в зависимости от того достигнут ли конец файла. Следовательно, цикл для чтения содержимого всего файла можно записать так:
//организуем для чтения значений из файла, выполнение
//цикла прервется, когда достигнем конец файла,
//в этом случае F.eof() вернет истину
while (!F.eof())
ПРИМЕР:
В текстовом файле D:\\game\\noobs.txt хранятся вещественные числа, вывести их на экран и вычислить их количество.
#include "stdafx.h"
#include
#include
#include
#include
using namespace std;
int main()
setlocale (LC_ALL, "RUS");
int n=0;
float a;
fstream F;
//открываем файл в режиме чтения
F.open("D:\\game\\noobs.txt");
//если открытие файла прошло корректно, то
//цикл для чтения значений из файла; выполнение цикла прервется,
//когда достигнем конца файла, в этом случае F.eof() вернет истину.
while (!F.eof())
//чтение очередного значения из потока F в переменную a
F>>a;
//вывод значения переменной a на экран
//увеличение количества считанных чисел
//закрытие потока
F.close();
//вовод на экран количества считанных чисел
cout<<"n="<
//если открытие файла прошло некорректно, то вывод
//сообщения об отсутствии такого файла
else
cout<<" Файл не существует"<
system("pause");
return 0;
C++. Обработка двоичных файлов
При записи информации в двоичный файл символы и числа записываются в виде последовательности байт.
Для того чтобы записать данные в двоичный файл, необходимо:
описать файловую переменную типа FAIL * с помощью оператора FILE *filename;. Здесь filename - имя переменной, где будет храниться указатель на файл.
записать информацию в файл с помощью функции fwrite
Для того чтобы считат ь данные из двоичного файла, необходимо:
описать переменную типа FILE *
открыть файл с помощью функции fopen
закрыть файл с помощью функции fclose
Основные функции, необходимые для работы с двоичными файлами.
Для открытия файла предназначена функция fopen.
FILE *fopen(const *filename, const char *mode)
Здесь filename - строка, в которой хранится полное имя открываемого файла, mode - строка, определяющая режим работы с файлом; возможны следующие значения:
«rb» - открываем двоичный файл в режиме чтения;
«wb» - создаем двоичный файл для записи; если он существует, то его содержимое очищается;
«ab» - создаем или открываем двоичный файл для дозаписи в конец файла;
«rb+» - открываем существующий двоичный файл в режиме чтения и записи;
«wb+» - открываем двоичный файл в режиме чтения и записи, существующий файл очищается;
«ab+» - двоичный файл открывается или создается для исправления существующий информации и добавления новой в конец файла.
Функция возвращает в файловой переменной f значение NULL в случае неудачного открытия файла. После открытия файла доступен 0-й его байт, указатель файла равен 0, значение которого по мере чтения или записи смещается на считанное (записанное) количество байт. Текущие значение указателя файла - номер байта, начиная с которого будет происходить операция чтения или записи.
Для закрытия файла предназначена функция fclose
int fclose(FILE *filename);
Возвращает 0 при успешном закрытие файла и NULL в противном случае.
Функция remove предназначена для удаления файлов.
int remove(const char *filename);
Эта функция удаляет с диска файл с именем filenema. Удаляемый файл должен быть закрыт. Функция возвращает ненулевое значение, если файл не удалось удалить.
Для переименования файлов предназначена функция rename:
int rename(const char *oldfilename, const char *newfilename);
Первый параметр - старое имя файла, второй - новое. Возвращает 0 при удачном завершении программы.
Чтение из двоичного файла осуществляется с помощью функции fread:
fread(void *ptr, size, n, FILE *filename);
Функция fread считывает из файла filename в массив ptr n элементов размера size. Функция возвращает количество считанных элементов. После чтения из файла его указатель смещается на n*size байт.
Запись в двоичный файл осуществляется с помощью функции fwrite:
fwrite(const void *ptr, size, n, FILE *filename);
Функция fwrite записывает в файл filename из массива ptr n элементов размера size. Функция возвращает количество записанных элементов. После записи информации в файл указатель смещается на n*size байт.
Для контроля достижения конца файла есть функция feof:
int feof(FILE *filename);
Она возвращает ненулевое значение если достигнут конец файла.
ПРИМЕР:
Создать двоичный файл D:\\game\\noobs.dat и записать в него целое число n и n вещественных чисел.
#include "stdafx.h"
#include
using namespace std;
int main()
setlocale (LC_ALL, "RUS");
int n, i;
double a;
//создаем двоичный файл в режиме записи
f=fopen("D:\\game\\noobs.dat", "wb");
// ввод числа n
cout<<"n="; cin>>n;
fwrite(&n, sizeof(int), 1, f);
//цикл для ввода n вещественных чисел
for
(i=0; i
//ввод очередного вещественного числа
cout<<"a=";
cin>>a;
//запись вешественного числа в двоичный файл
fwrite(&a, sizeof(double), 1, f);
// закрываем файл
fclose(f);
system("pause");
return 0;
ПРИМЕР:
Вывести на экран содержимого созданного в прошлой задаче двоичного файла D:\\game\\noobs.dat
#include "stdafx.h"
#include
using namespace std;
int main()
setlocale (LC_ALL, "RUS");
int n, i;
double *a;
FILE *f; //описываем файловую переменную
//открываем существующий двоичный файл в режиме чтения
//считываем из файла одно целое число в переменную n
//вывод n на экран
cout<<"n="<
//выделение памяти для массива из n чисел
a=new double[n];
//чтение n вещественных чисел из файла в массив a
//вывод массива на экран
for
(i=0; i
cout<
// закрываем файл
fclose(f);
system("pause");
return 0;
Двоичный файл - последовательная структура данных, после открытия файла доступен первый байт, хранящийся в нем. Можно последовательно записывать или считывать данные из файла. Допустим, необходимо считать пятнадцатое число, а затем первое. С помощью последовательного доступа это можно сделать следующим способом:
int n, i;
double a;
FILE *f;
f=fopen("D:\\game\\noobs.dat", "rb");
for (i=0; i<15; i++)
fclose(f);
f=fopen("D:\\game\\noobs.dat", "rb");
fread(&a, sizeof(double), 1, f);
fclose(f);
Как видно, такое чтение чисел из файла, а затем повторное открытие файла - не самый удобный способ. Гораздо удобнее будет использовать функцию fseek перемещения указателя файла к заданному байту.
int fseek(FILE *filename, long int offset, int origin);
Функция устанавливает указатель текущий позиции файла F в соответствии со значением начала отсчета origin и смещения offset. Параметр offset равен количеству байтов, на которые будет смещен указатель файла относительно начала отсчета, заданного параметром origin. В качестве значения для параметра origin должно быть взято одно из следующих значений отсчета смещения offset, определенных в заголовке stdio.h:
SEEK_SET - с начала файла;
SEEK_CUR - с текущей позиции;
SEEK_END - с конца файла.
Функция возвращает нулевое значение при успешном выполнение операции, ненулевое - при возникновении сбоя при выполнении смещения
Функция fseek фактически реализует прямой доступ к любому значению в файле. Необходимо только знать месторасположение (номер байта) значения в файле. Рассмотрим использование прямого доступа в двоичных файлах на примере решения следующей задачи.
ПРИМЕР
В созданном раннее двоичном файле D:\\game\\noobs.dat, поменять местами наибольшее и наименьшее из вещественных чисел.
Алгоритм решения задачи состоит из следующих этапов:
чтение вещественных из файла в массив a.
поиск в массиве а максимального (max) и минимального (min) значения и их номеров (imax, imin).
перемещения указателя файла к максимальному значению и запись min.
перемещения указателя файла к минимальному значению и запись max.
Ниже приведен текст программы решения задачи с комментариями.
#include "stdafx.h"
#include
using namespace std;
int main()
setlocale (LC_ALL, "RUS");
int n, i, imax, imin;
double *a, max, min;
FILE *f;
//открытие файла в режиме чтения и записи
f=fopen("D:\\game\\noobs.dat", "rb+");
//считываем из файла в переменную n количество
//вещественных чисел в файле
fread(&n, sizeof(int), 1, f);
cout<<"n="<
//выделяем память для хранения вещественных чисел,
//которые будут храниться в массиве a
a=new double[n];
//считываем из файла в массив а вещественные числа
fread(a, sizeof(double), n, f);
//поиск максимального и минимального элементов
//в массиве а и их индексов
for
(imax=imin=0, max=min=a, i=1; i
if (a[i]>max)
max=a[i];
if
(a[i]
min=a[i];
// перемещение указателя к максимальному элементу
fseek(f, sizeof(int)+imax*sizeof(double), SEEK_SET);
//запись min вместо максимального элемента файла
fwrite(&min, sizeof(double), 1, f);
// перемещение указателя к минимальному элементу
fseek(f, sizeof(int)+imin*sizeof(double), SEEK_SET);
//запись max вместо минимального элемента файла
fwrite(&max, sizeof(double), 1, f);
//закрытие файла
fclose(f);
//освобождение памяти
delete [ ]a;
system("pause");
Механизм ввода-вывода, разработанный , не соответствует общепринятому сегодня стилю объектно-ориентированного программирования, кроме того, он активно использует операции с указателями, считающиеся потенциально небезопасными в современных защищённых средах выполнения кода. Альтернативой при разработке прикладных приложений является механизм стандартных классов ввода-вывода, предоставляемый стандартом языка C++.
Открытие файлов
Наиболее часто применяются классы ifstream для чтения, ofstream для записи и fstream для модификации файлов.
Все поточные классы ввода-вывода являются косвенными производными от общего предка ios , полностью наследуя его функциональность. Так, режим открытия файлов задает член данных перечисляемого типа open_mode, который определяется следующим образом:
Enum open_mode { app, binary, in, out, trunc, ate };
Ниже приведены возможные значения флагов и их назначение.
Например, чтобы открыть файл с именем test.txt для чтения данных в бинарном виде, следует написать:
Ifstream file; file.open ("test.txt", ios::in | ios::binary);
Оператор логического ИЛИ (|) позволяет составить режим с любым сочетанием флагов. Так, чтобы, открывая файл по записи, случайно не затереть существующий файл с тем же именем, надо использовать следующую форму:
Ofstream file; file.open ("test.txt", ios::out | ios::app);
Предполагается, что к проекту подключён соответствующий заголовочный файл:
#include
Для проверки того удалось ли открыть файл, можно применять конструкцию
If (!file) { //Обработка ошибки открытия файла }
Операторы включения и извлечения
Переопределённый в классах работы с файлами оператор включения (<<) записывает данные в файловый поток. Как только вы открыли файл для записи, можно записывать в него текстовую строку целиком:
File << "Это строка текста";
Можно также записывать текстовую строку по частям:
File << "Это " << "строка " << "текста";
Оператор endl завершает ввод строки символом "возврат каретки":
File << "Это строка текста" << endl;
С помощью оператора включения несложно записывать в файл значения переменных или элементов массива:
Ofstream file ("Temp.txt"); char buff = "Текстовый массив содержит переменные"; int vx = 100; float pi = 3.14159; file << buff << endl << vx << endl << pi << endl;
В результате выполнения кода образуется три строки текстового файла Temp.txt:
Текстовый массив содержит переменные 100 3.14159
Обратите внимание, что числовые значения записываются в файл в виде текстовых строк, а не двоичных значений.
Оператор извлечения (>>)производит обратные действия. Казалось бы, чтобы извлечь символы из файла Temp.txt , записанного ранее, нужно написать код наподобие следующего:
Ifstream file ("Temp.txt"); char buff; int vx; float pi; file >> buff >> vx >> pi;
Однако оператор извлечения остановится на первом попавшемся разделителе (символе пробела, табуляции или новой строки). Таким образом, при разборе предложения "Текстовый массив содержит переменные" только слово "Текстовый" запишется в массив buff , пробел игнорируется, а слово "массив" станет значением целой переменной vx и исполнение кода "пойдет вразнос" с неминуемым нарушением структуры данных. Далее, при обсуждении класса ifstream , будет показано, как правильно организовать чтение файла из предыдущего примера.
Класс ifstream: чтение файлов
Как следует из расшифровки названия, класс ifstream предназначен для ввода файлового потока. Далее перечислены основные методы класса. Большая часть из них унаследована от класса istream и перегружена с расширением родительской функциональности. К примеру, функция get , в зависимости от параметра вызова, способна считывать не только одиночный символ, но и символьный блок.
Теперь понятно, как нужно модифицировать предыдущий пример, чтобы использование оператора извлечения данных давало ожидаемый результат:
Ifstream file("Temp.txt"); char buff; int vx; float pi; file.getline(buff, sizeof(buff)); file >> vx >> pi:
Метод getline прочитает первую строку файла до конца, а оператор >> присвоит значения переменным.
Следующий пример показывает добавление данных в текстовый файл с последующим чтением всего файла. Цикл while (1) используется вместо while(!file2.eof()) по причинам, которые обсуждались в .
#include
В следующем примере показан цикл считывания строк из файла test.txt и их отображения на консоли.
#include
Этот код под ОС Windows также зависит от наличия в последней строке файла символа перевода строки, надежнее было бы сделать так:
While (1) { if (file.eof()) break; file.getline(str, sizeof(str)); cout << str << endl; }
Явные вызовы методов open и close не обязательны. Действительно, вызов конструктора с аргументом позволяет сразу же, в момент создания поточного объекта file , открыть файл:
Ifstream file("test.txt");
Вместо метода close можно использовать оператор delete , который автоматически вызовет деструктор объекта file и закроет файл. Код цикла while обеспечивает надлежащую проверку признака конца файла.
Класс ofstream: запись файлов
Класс ofstream предназначен для вывода данных из файлового потока. Далее перечислены основные методы данного класса.
Описанный ранее оператор включения удобен для организации записи в текстовый файл:
Ofstream file ("temp.txt"); if (!file) return; for (int i=1; i<=3; i++) file << "Строка " << i << endl; file.close();
Бинарные файлы
В принципе, бинарные данные обслуживаются наподобие текстовых. Отличие состоит в том, что если бинарные данные записываются в определенной логической структуре, то они должны считываться из файла в переменную того же структурного типа.
Первый параметр методов write и read (адрес блока записи/чтения) должен иметь тип символьного указателя char * , поэтому необходимо произвести явное преобразование типа адреса структуры void * . Второй параметр указывает, что бинарные блоки файла имеют постоянный размер байтов независимо от фактической длины записи. Следующее приложение дает пример создания и отображения данных простейшей записной книжки. Затем записи файла последовательно считываются и отображаются на консоли.
#include
В результате выполнения этого кода образуется бинарный файл Notebook.dat из трех блоков размером по 80 байт каждый (при условии, что символы - однобайтовые). Естественно, вы можете использовать другие поточные методы и проделывать любые операции над полями определенной структуры данных.
Класс fstream: произвольный доступ к файлу
Предположим что в нашей записной книжке накопилось 100 записей, а мы хотим считать 50-ю. Конечно, можно организовать цикл и прочитать все записи с первой по заданную. Очевидно, что более целенаправленное решение - установить указатель позиционирования файла pos прямо на запись 50 и считать ее:
Ifstream ifile("Notebook.dat", ios::binary); int pos = 49 * sizeof(Notes); ifile.seekg(pos); // поиск 50-й записи Notes Note; //Notes – описанная выше структура "запись" ifile.read((char*)&Note, sizeof(Notes));
Подобные операции поиска эффективны, если файл состоит из записей известного и постоянного размера. Чтобы заменить содержимое произвольной записи, надо открыть поток вывода в режиме модификации:
Ofstream ofilе ("Notebook.dat", ios::binary | ios::ate); int pos = 49 * sizeof(Notes); ofile seekp(pos); // поиск 50-й записи Notes Note50 = {"Ельцин Борис Николаевич", "095-222-3322", 64}; ofile.write((char*)&Note, sizeof(Notes)); // замена
Если не указать флаг ios::ate (или ios::app), то при открытии бинарного файла Notebook.dat его предыдущее содержимое будет стерто!
Наконец, можно открыть файл одновременно для чтения/записи, используя методы, унаследованные поточным классом fstream от своих предшественников. Поскольку класс fstream произведен от istream и ostream (родителей ifstream и ofstream соответственно), все упомянутые ранее методы становятся доступными в приложении.
В следующем примере показана перестановка первой и третьей записей файла Notebook.dat .
#include
В конструкторе объекта file надо указать флаги ios::in и ios::out , разрешая одновременное выполнение операций чтения и записи. В результате выполнения этого кода первая и третья записи бинарного файла Notebook.dat поменяются местами.
Дополнительные примеры по теме есть .
До этого при вводе-выводе данных мы работали со стандартными потоками - клавиатурой и монитором. Теперь рассмотрим, как в языке C реализовано получение данных из файлов и запись их туда. Перед тем как выполнять эти операции, надо открыть файл и получить доступ к нему.
В языке программирования C указатель на файл имеет тип FILE и его объявление выглядит так:
FILE *myfile;
С другой стороны, функция fopen() открывает файл по указанному в качестве первого аргумента адресу в режиме чтения ("r"), записи ("w") или добавления ("a") и возвращает в программу указатель на него. Поэтому процесс открытия файла и подключения его к программе выглядит примерно так:
myfile = fopen ("hello.txt", "r");
При чтении или записи данных в файл обращение к нему осуществляется посредством файлового указателя (в данном случае, myfile).
Если в силу тех или иных причин (нет файла по указанному адресу, запрещен доступ к нему) функция fopen() не может открыть файл, то она возвращает NULL. В реальных программах почти всегда обрабатывают ошибку открытия файла в ветке if , мы же далее опустим это.
Объявление функции fopen() содержится в заголовочном файле stdio.h, поэтому требуется его подключение. Также в stdio.h объявлен тип-структура FILE.
После того, как работа с файлом закончена, принято его закрывать, чтобы освободить буфер от данных и по другим причинам. Это особенно важно, если после работы с файлом программа продолжает выполняться. Разрыв связи между внешним файлом и указателем на него из программы выполняется с помощью функции fclose() . В качестве параметра ей передается указатель на файл:
fclose(myfile);
В программе может быть открыт не один файл. В таком случае каждый файл должен быть связан со своим файловым указателем. Однако если программа сначала работает с одним файлом, потом закрывает его, то указатель можно использовать для открытия второго файла.
Функция fscanf() аналогична по смыслу функции scanf() , но в отличии от нее осуществляет форматированный ввод из файла, а не стандартного потока ввода. Функция fscanf() принимает параметры: файловый указатель, строку формата, адреса областей памяти для записи данных:
fscanf (myfile, "%s%d", str, &a);
Возвращает количество удачно считанных данных или EOF. Пробелы, символы перехода на новую строку учитываются как разделители данных.
Допустим, у нас есть файл содержащий такое описание объектов:
Apples 10 23.4 bananas 5 25.0 bread 1 10.3
#include
В данном случае объявляется структура и массив структур. Каждая строка из файла соответствует одному элементу массива; элемент массива представляет собой структуру, содержащую строковое и два числовых поля. За одну итерацию цикл считывает одну строку. Когда встречается конец файла fscanf() возвращает значение EOF и цикл завершается.
Функция fgets() аналогична функции gets() и осуществляет построчный ввод из файла. Один вызов fgets() позволят прочитать одну строку. При этом можно прочитать не всю строку, а лишь ее часть от начала. Параметры fgets() выглядят таким образом:
fgets (массив_символов, количество_считываемых_символов, указатель_на_файл)
Например:
fgets (str, 50, myfile)
Такой вызов функции прочитает из файла, связанного с указателем myfile, одну строку текста полностью, если ее длина меньше 50 символов с учетом символа "\n", который функция также сохранит в массиве. Последним (50-ым) элементом массива str будет символ "\0", добавленный fgets() . Если строка окажется длиннее, то функция прочитает 49 символов и в конце запишет "\0". В таком случае "\n" в считанной строке содержаться не будет.
#include
В этой программе в отличие от предыдущей данные считываются строка за строкой в массив arr. Когда считывается следующая строка, предыдущая теряется. Функция fgets() возвращает NULL в случае, если не может прочитать следующую строку.
Функция getc() или fgetc() (работает и то и другое) позволяет получить из файла очередной один символ.
while ((arr[ i] = fgetc (file) ) != EOF) { if (arr[ i] == "\n " ) { arr[ i] = "\0 " ; printf ("%s\n " , arr) ; i = 0 ; } else i++; } arr[ i] = "\0 " ; printf ("%s\n " , arr) ;
Приведенный в качестве примера код выводит данные из файла на экран.
Также как и ввод, вывод в файл может быть различным.
Ниже приводятся примеры кода, в которых используются три способа вывода данных в файл.
Запись в каждую строку файла полей одной структуры:
file = fopen ("fprintf.txt" , "w" ) ; while (scanf ("%s%u%f" , shop[ i] .name , & (shop[ i] .qty ) , & (shop[ i] .price ) ) != EOF) { fprintf (file, "%s %u %.2f\n " , shop[ i] .name , shop[ i] .qty , shop[ i] .price ) ; i++; }
Построчный вывод в файл (fputs() , в отличие от puts() сама не помещает в конце строки "\n"):
while (gets (arr) != NULL) { fputs (arr, file) ; fputs ("\n " , file) ; }
Пример посимвольного вывода:
while ((i = getchar () ) != EOF) putc (i, file) ;
С файлом можно работать не как с последовательностью символов, а как с последовательностью байтов. В принципе, с нетекстовыми файлами работать по-другому не возможно. Однако так можно читать и писать и в текстовые файлы. Преимущество такого способа доступа к файлу заключается в скорости чтения-записи: за одно обращение можно считать/записать существенный блок информации.
При открытии файла для двоичного доступа, вторым параметром функции fopen() является строка "rb" или "wb".
Тема о работе с двоичными файлами достаточно сложная, для ее изучения требуется отдельный урок. Здесь будут отмечены только особенности функций чтения-записи в файл, который рассматривается как поток байтов.
Функции fread() и fwrite() принимают в качестве параметров:
Эти функции возвращают количество успешно прочитанных или записанных данных. Т.е. можно "заказать" считывание 50 элементов данных, а получить только 10. Ошибки при этом не возникнет.
Пример использования функций fread() и fwrite() :
#include
Здесь осуществляется попытка чтения из первого файла 50-ти символов. В n сохраняется количество реально считанных символов. Значение n может быть равно 50 или меньше. Данные помещаются в строку. То же самое происходит со вторым файлом. Далее первая строка присоединяется ко второй, и данные сбрасываются в третий файл.
Рассмотрим работу с текстовым файлом в Си на примере. Создайте на диске С текстовый файл с именем TextFile.txt. Наберите в этом файле такие строки:
String_1 123 String_11, 456
String_2
String_3
Сохраните файл.
А это код программы на C, которая открывает наш файл и считывает из него строки:
/*
*Author: @author Subbotin B.P..h>
#include
Чтоб открыть текстовый файл в C используем функцию fopen:
FILE *pTextFile = fopen("C:\\TextFile.txt", "r");
первый аргумент функции fopen указывает на файл, а второй говорит, что файл открыт для чтения из него.
Строки считываем с помощью функции fgets:
fgets(cArray, LEN, pTextFile);
первый аргумент функции fgets указвает на массив символов, в котором будут сохранятся полученные строки, второй аргумент - это максимальное количество символов для считывания, третий - наш файл.
После завершения работы с файлом, его надо закрыть:
fclose(pTextFile);
Получаем:
Русские буквы в строках тоже проходят.
Кстати, эту программу я сделал в Eclipse. Как работать с C/C++ в Eclipse можно посмотреть .
Итак, мы открыли и считали данные из текстового файла.
Теперь научимся программно создавать текстовый файл и записывать в него данные.
/*
Author: @author Subbotin B.P..h>
#include
Создаем текстовый файл для записи в него данных:
FILE *pTextFile = fopen("C:\\TextFileW.txt", "w");
если файл уже имеется, то он будет открыт, и все данные из него будут удалены.
C-строка cString, и число nVal записываются программой в текстовый файл. cNewLine - это просто переход на новую строку.
Записываем данные в текстовый файл с помощью функции fprintf:
fprintf(pTextFile, "%s%c", cString, cNewLine);
первый аргумент здесь - наш файл, второй - форматная строка, третий и более - нужное для этого формата количество аргументов.