Механизм ввода-вывода, разработанный , не соответствует общепринятому сегодня стилю объектно-ориентированного программирования, кроме того, он активно использует операции с указателями, считающиеся потенциально небезопасными в современных защищённых средах выполнения кода. Альтернативой при разработке прикладных приложений является механизм стандартных классов ввода-вывода, предоставляемый стандартом языка 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 #include using namespace std; int main() { ofstream file; file.open("test.txt",ios::out|ios::app); if (!file) { cout << "File error - can"t open to write data!"; cin.sync(); cin.get(); return 1; } for (int i=0; i<10; i++) file << i << endl; file.close(); ifstream file2; file2.open("test.txt", ios::in); if (!file2) { cout << "File error - can"t open to read data!"; cin.sync(); cin.get(); return 2; } int a,k=0; while (1) { file2 >> a; if (file2.eof()) break; cout << a << " "; k++; } cout << endl << "K=" << k << endl; file2.close(); cin.sync(); cin.get(); return 0; }

В следующем примере показан цикл считывания строк из файла test.txt и их отображения на консоли.

#include #include using namespace std; int main() { ifstream file; // создать поточный объект file file.open("test.txt"); // открыть файл на чтение if (!file) return 1; // возврат по ошибке отрытия char str; // статический буфер строки // Считывать и отображать строки в цикле, пока не eof while (!file.getline(str, sizeof(str)).eof()) cout << str << endl; // вывод прочитанной строки на экран cin.sync(); cin.get(); return 0; }

Этот код под ОС 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 #include #include using namespace std; struct Notes { // структура данных записной книжки char Name; // Ф.И.О. char Phone; // телефон int Age; // возраст }; int main() { setlocale(LC_ALL, "Russian"); Notes Note1= { "Грозный Иоанн Васильевич", "не установлен", 60 }; Notes Note2= { "Годунов Борис Федорович ", "095-111-2233 ", 30 }; Notes Note3= { "Романов Петр Михайлович ", "812-333-2211 ", 20 }; ofstream ofile("Notebook.dat", ios::binary); ofile.write((char*)&Note1, sizeof(Notes)); // 1-й блок ofile.write((char*)&Note2, sizeof(Notes)); // 2-й блок ofile.write((char*)&Note3, sizeof(Notes)); // 3-й блок ofile.close(); // закрыть записанный файл ifstream ifile("Notebook.dat", ios::binary); Notes Note; // структурированная переменная char str; // статический буфер строки // Считывать и отображать строки в цикле, пока не eof while (!ifile.read((char*)&Note, sizeof(Notes)).eof()) { sprintf(str, "%s\tТел: %s\tВозраст: %d", Note.Name, Note.Phone, Note.Age); cout << str << endl; } ifile.close(); // закрыть прочитанный файл cin.sync(); cin.get(); return 0; }

В результате выполнения этого кода образуется бинарный файл 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 #include #include using namespace std; struct Notes { char Name; char Phone; int Age; }; int main() { setlocale(LC_ALL, "Russian"); Notes Note1, Note3; // Открыть файл на чтение/запись одновременно fstream file("Notebook.dat", ios::binary | ios::in | ios::out); file.seekg(2 * sizeof(Notes)); // найти и считать Note3 file.read((char*)&Note3, sizeof(Notes)); file.seekg(0); // найти и считать Note1 file.read((char*)&Note1, sizeof(Notes)); file.seekg(0); // Note1 <== Note3 file.write((char*)&Note3, sizeof(Notes)); file.seekg(2 * sizeof(Notes)); // Note3 <== Note1 file.write((char*)&Note1, sizeof(Notes)); char str; // Считывать и отображать записи в цикле, пока не eof file.seekg(0); // вернуться к началу файла while (!file.read((char*)&Note1, sizeof(Notes)).eof()) { sprintf(str, "%s\tТел: %s\tВозраст: %d", Note1.Name, Note1.Phone, Note1.Age); cout << str << endl; } file.close(); cin.sync(); cin.get(); return 0; }

В конструкторе объекта file надо указать флаги ios::in и ios::out , разрешая одновременное выполнение операций чтения и записи. В результате выполнения этого кода первая и третья записи бинарного файла Notebook.dat поменяются местами.

Дополнительные примеры по теме есть .

Работа файлового ввода/вывода в C++ почти аналогична работе обычных (но с небольшими нюансами).

Классы файлового ввода/вывода

Есть три основных класса файлового ввода/вывода в C++ :

ofstream (является дочерним классу );

fstream (является дочерним классу iostream).

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

В отличие от потоков cout, cin, cerr и clog, которые сразу же можно использовать, файловые потоки должны быть явно установлены программистом. То есть, чтобы открыть файл для чтения и/или записи, нужно создать объект соответствующего класса файлового ввода/вывода, указав имя файла в качестве параметра. Затем, с помощью операторов вставки (<<) или извлечения (>>), можно записывать данные в файл или читать содержимое файла. После этого финал — нужно закрыть файл: явно вызвать метод close() или просто позволить файловой переменной ввода/вывода выйти из области видимости ( файлового класса ввода/вывода закроет этот файл автоматически вместо нас).

Файловый вывод

Для записи в файл используется класс ofstream . Например:

#include #include #include // для использования exit() int main() { using namespace std; // ofstream используется для записи данных в файл // Создаём файл SomeText.txt ofstream outf("SomeText.txt"); // Если мы не можем открыть этот файл для записи данных в него if (!outf) { // То выводим сообщение об ошибке и выполняем exit() cerr << "Uh oh, SomeText.txt could not be opened for writing!" << endl; exit(1); } // Записываем в файл следующие две строчки outf << "See line #1!" << endl; outf << "See line #2!" << endl; return 0; // Когда outf выйдет из области видимости, то деструктор класса ofstream автоматически закроет наш файл }

#include

#include

#include // для использования exit()

int main ()

using namespace std ;

// ofstream используется для записи данных в файл

// Создаём файл SomeText.txt

ofstream outf ("SomeText.txt" ) ;

// Если мы не можем открыть этот файл для записи данных в него

if (! outf )

// То выводим сообщение об ошибке и выполняем exit()

cerr << << endl ;

exit (1 ) ;

// Записываем в файл следующие две строчки

outf << "See line #1!" << endl ;

outf << "See line #2!" << endl ;

return 0 ;

// Когда outf выйдет из области видимости, то деструктор класса ofstream автоматически закроет наш файл

Если вы загляните в каталог вашего проекта (ПКМ по вкладке с названием вашего.cpp файла в Visual Studio > «Открыть содержащую папку» ), то увидите файл с именем SomeText.txt, в котором находятся следующие строчки:

See line #1!
See line #2!

Обратите внимание, мы также можем использовать метод put() для записи одного символа в файл.

Файловый ввод

#include #include #include #include // для использования exit() int main() { using namespace std; // ifstream используется для чтения содержимого файла // Попытаемся прочитать содержимое файла SomeText.txt ifstream inf("SomeText.txt"); // Если мы не можем открыть этот файл для чтения его содержимого if (!inf) { cerr << "Uh oh, SomeText.txt could not be opened for reading!" << endl; exit(1); } // Пока есть данные, которые мы можем прочитать while (inf) { // То перемещаем эти данные в строку, которую затем выводим на экран string strInput; inf >> strInput; cout << strInput << endl; } return 0; }

#include

#include

#include

#include // для использования exit()

int main ()

using namespace std ;

// ifstream используется для чтения содержимого файла

// Если мы не можем открыть этот файл для чтения его содержимого

if (! inf )

// То выводим следующее сообщение об ошибке и выполняем exit()

cerr << << endl ;

exit (1 ) ;

// Пока есть данные, которые мы можем прочитать

while (inf )

// То перемещаем эти данные в строку, которую затем выводим на экран

string strInput ;

inf >> strInput ;

cout << strInput << endl ;

return 0 ;

// Когда inf выйдет из области видимости, то деструктор класса ifstream автоматически закроет наш файл

See
line
#1!
See
line
#2!

Хм, это не совсем то, что мы хотели. Как мы уже знаем из предыдущих уроков, оператор извлечения работает с «отформатированными данными», т.е. он игнорирует все пробелы, символы табуляции и символ новой строки. Чтобы прочитать всё содержимое как есть, без его разбивки на части (как в примере выше), нам нужно использовать метод getline() :

#include #include #include #include // для использования exit() int main() { using namespace std; // ifstream используется для чтения содержимого файлов // Мы попытаемся прочитать содержимое файла SomeText.txt ifstream inf("SomeText.txt"); // Если мы не можем открыть файл для чтения его содержимого if (!inf) { // То выводим следующее сообщение об ошибке и выполняем exit() cerr << "Uh oh, SomeText.txt could not be opened for reading!" << endl; exit(1); } // Пока есть, что читать while (inf) { // То перемещаем то, что можем прочитать, в строку, а затем выводим эту строку на экран string strInput; getline(inf, strInput); cout << strInput << endl; } return 0; // Когда inf выйдет из области видимости, то деструктор класса ifstream автоматически закроет наш файл }

#include

#include

#include

#include // для использования exit()

int main ()

using namespace std ;

// ifstream используется для чтения содержимого файлов

ifstream inf ("SomeText.txt" ) ;

// Если мы не можем открыть файл для чтения его содержимого

if (! inf )

// То выводим следующее сообщение об ошибке и выполняем exit()

cerr << "Uh oh, SomeText.txt could not be opened for reading!" << endl ;

exit (1 ) ;

while (inf )

string strInput ;

getline (inf , strInput ) ;

cout << strInput << endl ;

return 0 ;

// Когда inf выйдет из области видимости, то деструктор класса ifstream автоматически закроет наш файл

Результат выполнения программы выше:

Буферизованный вывод

Вывод в C++ может быть буферизован. Это означает, что всё, что выводится в файловый поток, не может сразу же быть записанным на диск (в конкретный файл). Это сделано, в первую очередь, по соображениям производительности. Когда данные буфера записываются на диск, то это называется очисткой буфера . Одним из способов очистки буфера является закрытие файла. В таком случае всё содержимое буфера будет перемещено на диск, а затем файл будет закрыт.

Буферизация вывода обычно не является проблемой, но, при определённых обстоятельствах, она может вызвать проблемы у неосторожных новичков. Например, когда в буфере хранятся данные и программа преждевременно завершает своё выполнение (либо в результате сбоя, либо путём вызова ). В таких случаях деструкторы классов файлового ввода/вывода не выполняются, файлы никогда не закрываются, буферы не очищаются и наши данные теряются навсегда. Вот почему хорошей идеей является явное закрытие всех открытых файлов перед вызовом exit().

Также буфер можно очистить вручную, используя метод ostream::flush() или отправив std::flush в выходной поток. Любой из этих способов может быть полезен для обеспечения немедленной записи содержимого буфера на диск в случае сбоя программы.

Интересный нюанс : Поскольку std::endl; также очищает выходной поток, то его чрезмерное использование (приводящее к ненужным очисткам буфера) может повлиять на производительность программы (так как очистка буфера в некоторых случаях может быть затратной операцией). По этой причине программисты, которые заботятся о производительности своего кода, часто используют \n вместо std::endl для вставки символа новой строки в выходной поток, дабы избежать ненужной очистки буфера.

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

Что произойдёт, если мы попытаемся записать данные в уже существующий файл? Повторный запуск программы выше (самая первая) показывает, что исходный файл полностью перезаписывается при повторном запуске программы. А что, если нам нужно добавить данные в конец файла? Оказывается, файлового потока принимают необязательный второй параметр, который позволяет указать программисту способ открытия файла. В качестве этого параметра можно передавать следующие флаги (которые находятся в классе ios):

app - открывает файл в режиме добавления;

ate - переходит в конец файла перед чтением/записью;

binary - открывает файл в бинарном режиме (вместо текстового режима);

in - открывает файл в режиме чтения (по умолчанию для ifstream);

out - открывает файл в режиме записи (по умолчанию для ofstream);

trunc - удаляет файл, если он уже существует.

Можно указать сразу несколько флагов путём использования .

ifstream по умолчанию работает в режиме ios::in;

ofstream по умолчанию работает в режиме ios::out;

fstream по умолчанию работает в режиме ios::in ИЛИ ios::out, что означает, что вы можете выполнять как чтение содержимого файла, так и запись данных в файл.

Теперь давайте напишем программу, которая добавит две строки в ранее созданный файл SomeText.txt:

#include #include // для использования exit() #include int main() { using namespace std; // Передаём флаг ios:app, чтобы сообщить fstream, что мы собираемся добавить свои данные к уже существующим данным файла, // мы не собираемся перезаписывать файл. Нам не нужно передавать флаг ios::out, // поскольку ofstream по умолчанию работает в режиме ios::out ofstream outf("SomeText.txt", ios::app); // Если мы не можем открыть файл для записи данных if (!outf) { // То выводим следующее сообщение об ошибке и выполняем exit() cerr << "Uh oh, SomeText.txt could not be opened for writing!" << endl; exit(1); } outf << "See line #3!" << endl; outf << "See line #4!" << endl; return 0; // Когда outf выйдет из области видимости, то деструктор класса ofstream автоматически закроет наш файл }

#include

#include // для использования exit()

#include

int main ()

using namespace std ;

// Передаём флаг ios:app, чтобы сообщить fstream, что мы собираемся добавить свои данные к уже существующим данным файла,

// мы не собираемся перезаписывать файл. Нам не нужно передавать флаг ios::out,

// поскольку ofstream по умолчанию работает в режиме ios::out

ofstream outf ("SomeText.txt" , ios :: app ) ;

// Если мы не можем открыть файл для записи данных

if (! outf )

// То выводим следующее сообщение об ошибке и выполняем exit()

cerr << "Uh oh, SomeText.txt could not be opened for writing!" << endl ;

exit (1 ) ;

Большинство компьютерных программ работают с файлами, и поэтому возникает необходимость создавать, удалять, записывать читать, открывать файлы. Что же такое файл? Файл – именованный набор байтов, который может быть сохранен на некотором накопителе. Ну, теперь ясно, что под файлом понимается некоторая последовательность байтов, которая имеет своё, уникальное имя, например файл.txt . В одной директории не могут находиться файлы с одинаковыми именами. Под именем файла понимается не только его название, но и расширение, например: file.txt и file.dat разные файлы, хоть и имеют одинаковые названия. Существует такое понятие, как полное имя файлов – это полный адрес к директории файла с указанием имени файла, например: D:\docs\file.txt . Важно понимать эти базовые понятия, иначе сложно будет работать с файлами.

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

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

Например, необходимо создать текстовый файл и записать в него строку Работа с файлами в С++ . Для этого необходимо проделать следующие шаги:

  1. создать объект класса ofstream ;
  2. связать объект класса с файлом, в который будет производиться запись;
  3. записать строку в файл;
  4. закрыть файл.

Почему необходимо создавать объект класса 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 using namespace std; int main(int argc, char* argv) { ofstream fout("cppstudio.txt"); // создаём объект класса ofstream для записи и связываем его с файлом cppstudio.txt fout << "Работа с файлами в С++"; // запись строки в файл fout.close(); // закрываем файл system("pause"); return 0; }

Осталось проверить правильность работы программы, а для этого открываем файл cppstudio.txt и смотрим его содержимое, должно быть — Работа с файлами в С++ .

  1. создать объект класса ifstream и связать его с файлом, из которого будет производиться считывание;
  2. прочитать файл;
  3. закрыть файл.
// file_read.cpp: определяет точку входа для консольного приложения. #include "stdafx.h" #include #include using namespace std; int main(int argc, char* argv) { setlocale(LC_ALL, "rus"); // корректное отображение Кириллицы char buff; // буфер промежуточного хранения считываемого из файла текста ifstream fin("cppstudio.txt"); // открыли файл для чтения fin >> << buff << endl; // напечатали это слово fin.getline(buff, 50); // считали строку из файла fin.close(); // закрываем файл cout << buff << endl; // напечатали эту строку system("pause"); return 0; }

В программе показаны два способа чтения из файла, первый – используя операцию передачи в поток, второй – используя функцию getline() . В первом случае считывается только первое слово, а во втором случае считывается строка, длинной 50 символов. Но так как в файле осталось меньше 50 символов, то считываются символы включительно до последнего. Обратите внимание на то, что считывание во второй раз (строка 17 ) продолжилось, после первого слова, а не с начала, так как первое слово было прочитано в строке 14 . Результат работы программы показан на рисунке 1.

Работа с файлами в С++ Для продолжения нажмите любую клавишу. . .

Рисунок 1 — Работа с файлами в С++

Программа сработала правильно, но не всегда так бывает, даже в том случае, если с кодом всё впорядке. Например, в программу передано имя несуществующего файла или в имени допущена ошибка. Что тогда? В этом случае ничего не произойдёт вообще. Файл не будет найден, а значит и прочитать его не возможно. Поэтому компилятор проигнорирует строки, где выполняется работа с файлом. В результате корректно завершится работа программы, но ничего, на экране показано не будет. Казалось бы это вполне нормальная реакции на такую ситуацию. Но простому пользователю не будет понятно, в чём дело и почему на экране не появилась строка из файла. Так вот, чтобы всё было предельно понятно в С++ предусмотрена такая функция — is_open() , которая возвращает целые значения: 1 — если файл был успешно открыт, 0 — если файл открыт не был. Доработаем программу с открытием файла, таким образом, что если файл не открыт выводилось соответствующее сообщение.

// file_read.cpp: определяет точку входа для консольного приложения. #include "stdafx.h" #include #include using namespace std; int main(int argc, char* argv) { setlocale(LC_ALL, "rus"); // корректное отображение Кириллицы char buff; // буфер промежуточного хранения считываемого из файла текста ifstream fin("cppstudio.doc"); // (ВВЕЛИ НЕ КОРРЕКТНОЕ ИМЯ ФАЙЛА) if (!fin.is_open()) // если файл не открыт cout << "Файл не может быть открыт!\n"; // сообщить об этом else { fin >> buff; // считали первое слово из файла cout << buff << endl; // напечатали это слово fin.getline(buff, 50); // считали строку из файла fin.close(); // закрываем файл cout << buff << endl; // напечатали эту строку } system("pause"); return 0; }

Результат работы программы показан на рисунке 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() , будет вычислять характеристики основных типов данных в С++ и записывать их в файл. Характеристики:

  1. число байт, отводимое под тип данных
  2. максимальное значение, которое может хранить определённый тип данных.

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

/* 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 #include // работа с файлами #include // манипуляторы ввода/вывода using namespace std; int main(int argc, char* argv) { setlocale(LC_ALL, "rus"); // связываем объект с файлом, при этом файл открываем в режиме записи, предварительно удаляя все данные из него ofstream fout("data_types.txt", ios_base::out | ios_base::trunc); if (!fout.is_open()) // если файл небыл открыт { cout << "Файл не может быть открыт или создан\n"; // напечатать соответствующее сообщение return 1; // выполнить выход из программы } fout << " data type " << "byte" << " " << " max value " << endl // заголовки столбцов << "bool = " << sizeof(bool) << " " << fixed << setprecision(2) /*вычисляем максимальное значение для типа данных bool*/ << (pow(2,sizeof(bool) * 8.0) - 1) << endl << "char = " << sizeof(char) << " " << fixed << setprecision(2) /*вычисляем максимальное значение для типа данных char*/ << (pow(2,sizeof(char) * 8.0) - 1) << endl << "short int = " << sizeof(short int) << " " << fixed << setprecision(2) /*вычисляем максимальное значение для типа данных short int*/ << (pow(2,sizeof(short int) * 8.0 - 1) - 1) << endl << "unsigned short int = " << sizeof(unsigned short int) << " " << fixed << setprecision(2) /*вычисляем максимальное значение для типа данных unsigned short int*/ << (pow(2,sizeof(unsigned short int) * 8.0) - 1) << endl << "int = " << sizeof(int) << " " << fixed << setprecision(2) /*вычисляем максимальное значение для типа данных int*/ << (pow(2,sizeof(int) * 8.0 - 1) - 1) << endl << "unsigned int = " << sizeof(unsigned int) << " " << fixed << setprecision(2) /*вычисляем максимальное значение для типа данных unsigned int*/ << (pow(2,sizeof(unsigned int) * 8.0) - 1) << endl << "long int = " << sizeof(long int) << " " << fixed << setprecision(2) /*вычисляем максимальное значение для типа данных long int*/ << (pow(2,sizeof(long int) * 8.0 - 1) - 1) << endl << "unsigned long int = " << sizeof(unsigned long int) << " " << fixed << setprecision(2) /*вычисляем максимальное значение для типа данных undigned long int*/ << (pow(2,sizeof(unsigned long int) * 8.0) - 1) << endl << "float = " << sizeof(float) << " " << fixed << setprecision(2) /*вычисляем максимальное значение для типа данных float*/ << (pow(2,sizeof(float) * 8.0 - 1) - 1) << endl << "long float = " << sizeof(long float) << " " << fixed << setprecision(2) /*вычисляем максимальное значение для типа данных long float*/ << (pow(2,sizeof(long float) * 8.0 - 1) - 1) << endl << "double = " << sizeof(double) << " " << fixed << setprecision(2) /*вычисляем максимальное значение для типа данных double*/ << (pow(2,sizeof(double) * 8.0 - 1) - 1) << endl; fout.close(); // программа больше не использует файл, поэтому его нужно закрыть cout << "Данные успешно записаны в файл data_types.txt\n"; system("pause"); return 0; }

Нельзя не заметить, что изменения в программе минимальны, а всё благодаря тому, что стандартный ввод/вывод и файловый ввод/вывод используются абсолютно аналогично. В конце программы, в строке 45 мы явно закрыли файл, хотя это и не обязательно, но считается хорошим тоном программирования. Стоит отметить, что все функции и манипуляторы используемые для форматирования стандартного ввода/вывода актуальны и для файлового ввода/вывода. Поэтому не возникло никаких ошибок, когда оператор cout был заменён объектом fout .

До этого при вводе-выводе данных мы работали со стандартными потоками - клавиатурой и монитором. Теперь рассмотрим, как в языке 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()

Функция fscanf() аналогична по смыслу функции scanf() , но в отличии от нее осуществляет форматированный ввод из файла, а не стандартного потока ввода. Функция fscanf() принимает параметры: файловый указатель, строку формата, адреса областей памяти для записи данных:
fscanf (myfile, "%s%d", str, &a);

Возвращает количество удачно считанных данных или EOF. Пробелы, символы перехода на новую строку учитываются как разделители данных.

Допустим, у нас есть файл содержащий такое описание объектов:

Apples 10 23.4 bananas 5 25.0 bread 1 10.3

#include main () { FILE * file; struct food { char name[ 20 ] ; unsigned qty; float price; } ; struct food shop[ 10 ] ; char i= 0 ; file = fopen ("fscanf.txt" , "r" ) ; while (fscanf (file, "%s%u%f" , shop[ i] .name , & (shop[ i] .qty ) , & (shop[ i] .price ) ) != EOF) { printf ("%s %u %.2f\n " , shop[ i] .name , shop[ i] .qty , shop[ i] .price ) ; i++; } }

В данном случае объявляется структура и массив структур. Каждая строка из файла соответствует одному элементу массива; элемент массива представляет собой структуру, содержащую строковое и два числовых поля. За одну итерацию цикл считывает одну строку. Когда встречается конец файла fscanf() возвращает значение EOF и цикл завершается.

fgets()

Функция fgets() аналогична функции gets() и осуществляет построчный ввод из файла. Один вызов fgets() позволят прочитать одну строку. При этом можно прочитать не всю строку, а лишь ее часть от начала. Параметры fgets() выглядят таким образом:
fgets (массив_символов, количество_считываемых_символов, указатель_на_файл)

Например:
fgets (str, 50, myfile)

Такой вызов функции прочитает из файла, связанного с указателем myfile, одну строку текста полностью, если ее длина меньше 50 символов с учетом символа "\n", который функция также сохранит в массиве. Последним (50-ым) элементом массива str будет символ "\0", добавленный fgets() . Если строка окажется длиннее, то функция прочитает 49 символов и в конце запишет "\0". В таком случае "\n" в считанной строке содержаться не будет.

#include #define N 80 main () { FILE * file; char arr[ N] ; file = fopen ("fscanf.txt" , "r" ) ; while (fgets (arr, N, file) != NULL) printf ("%s" , arr) ; printf ("\n " ) ; fclose (file) ; }

В этой программе в отличие от предыдущей данные считываются строка за строкой в массив arr. Когда считывается следующая строка, предыдущая теряется. Функция fgets() возвращает NULL в случае, если не может прочитать следующую строку.

getc() или fgetc()

Функция 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) ;

Приведенный в качестве примера код выводит данные из файла на экран.

Запись в текстовый файл

Также как и ввод, вывод в файл может быть различным.

  • Форматированный вывод. Функция fprintf (файловый_указатель, строка_формата, переменные) .
  • Посточный вывод. Функция fputs (строка, файловый_указатель) .
  • Посимвольный вывод. Функция fputc() или putc(символ, файловый_указатель) .

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

Запись в каждую строку файла полей одной структуры:

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() принимают в качестве параметров:

  1. адрес области памяти, куда данные записываются или откуда считываются,
  2. размер одного данного какого-либо типа,
  3. количество считываемых данных указанного размера,
  4. файловый указатель.

Эти функции возвращают количество успешно прочитанных или записанных данных. Т.е. можно "заказать" считывание 50 элементов данных, а получить только 10. Ошибки при этом не возникнет.

Пример использования функций fread() и fwrite() :

#include #include main () { FILE * file; char shelf1[ 50 ] , shelf2[ 100 ] ; int n, m; file = fopen ("shelf1.txt" , "rb" ) ; n= fread (shelf1, sizeof (char ) , 50 , file) ; fclose (file) ; file = fopen ("shelf2.txt" , "rb" ) ; m= fread (shelf2, sizeof (char ) , 50 , file) ; fclose (file) ; shelf1[ n] = "\0 " ; shelf2[ m] = "\n " ; shelf2[ m+ 1 ] = "\0 " ; file = fopen ("shop.txt" , "wb" ) ; fwrite (strcat (shelf2, shelf1) , sizeof (char ) , n+ m, file) ; fclose (file) ; }

Здесь осуществляется попытка чтения из первого файла 50-ти символов. В n сохраняется количество реально считанных символов. Значение n может быть равно 50 или меньше. Данные помещаются в строку. То же самое происходит со вторым файлом. Далее первая строка присоединяется ко второй, и данные сбрасываются в третий файл.

Решение задач

  1. Напишите программу, которая запрашивает у пользователя имя (адрес) текстового файла, далее открывает его и считает в нем количество символов и строк.
  2. Напишите программу, которая записывает в файл данные, полученные из другого файла и так или иначе измененные перед записью. Каждая строка данных, полученная из файла, должна помещаться в структуру.

Теги: Текстовые файлы, fopen, fclose, feof, setbuf, setvbuf, fflush, fgetc, fprintf, fscanf, fgets, буферизированный поток, небуферизированный поток.

Работа с текстовыми файлами

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

  • 1. Открыть файл, для того, чтобы к нему можно было обращаться. Соответственно, открывать можно для чтения, записи, чтения и записи, переписывания или записи в конец файла и т.п. Когда вы открываете файл, может также произойти куча ошибок – файла может не существовать, это может быть файл не того типа, у вас может не быть прав на работу с файлом и т.д. Всё это необходимо учитывать.
  • 2. Непосредственно работа с файлом - запись и чтение. Здесь также нужно помнить, что мы работаем не с памятью с произвольным доступом, а с буферизированным потоком, что добавляет свою специфику.
  • 3. Закрыть файл. Так как файл является внешним по отношению к программе ресурсом, то если его не закрыть, то он продолжит висеть в памяти, возможно, даже после закрытия программы (например, нельзя будет удалить открытый файл или внести изменения и т.п.). Кроме того, иногда необходимо не закрывать, а "переоткрывать" файл для того, чтобы, например, изменить режим доступа.

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

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

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

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

Создание и выделение памяти под объект типа FILE осуществляется с помощью функции fopen или tmpfile (есть и другие, но мы остановимся только на этих).

Функция fopen открывает файл. Она получает два аргумента – строку с адресом файла и строку с режимом доступа к файлу. Имя файла может быть как абсолютным, так и относительным. fopen возвращает указатель на объект FILE, с помощью которого далее можно осуществлять доступ к файлу.

FILE* fopen(const char* filename, const char* mode);

Например, откроем файл и запишем в него Hello World

#include #include #include void main() { //С помощью переменной file будем осуществлять доступ к файлу FILE *file; //Открываем текстовый файл с правами на запись file = fopen("C:/c/test.txt", "w+t"); //Пишем в файл fprintf(file, "Hello, World!"); //Закрываем файл fclose(file); getch(); }

Функция fopen сама выделяет память под объект, очистка проводится функцией fclose. Закрывать файл обязательно, самостоятельно он не закроется.

Функция fopen может открывать файл в текстовом или бинарном режиме. По умолчанию используется текстовый. Режим доступа может быть следующим

Параметры доступа к файлу.
Тип Описание
r Чтение. Файл должен существовать.
w Запись нового файла. Если файл с таким именем уже существует, то его содержимое будет потеряно.
a Запись в конец файла. Операции позиционирования (fseek, fsetpos, frewind) игнорируются. Файл создаётся, если не существовал.
r+ Чтение и обновление. Можно как читать, так и писать. Файл должен существовать.
w+ Запись и обновление. Создаётся новый файл. Если файл с таким именем уже существует, то его содержимое будет потеряно. Можно как писать, так и читать.
a+ Запись в конец и обновление. Операции позиционирования работают только для чтения, для записи игнорируются. Если файл не существовал, то будет создан новый.

Если необходимо открыть файл в бинарном режиме, то в конец строки добавляется буква b, например “rb”, “wb”, “ab”, или, для смешанного режима “ab+”, “wb+”, “ab+”. Вместо b можно добавлять букву t, тогда файл будет открываться в текстовом режиме. Это зависит от реализации. В новом стандарте си (2011) буква x означает, что функция fopen должна завершиться с ошибкой, если файл уже существует. Дополним нашу старую программу: заново откроем файл и считаем, что мы туда записали.

#include #include #include void main() { FILE *file; char buffer; file = fopen("C:/c/test.txt", "w"); fprintf(file, "Hello, World!"); fclose(file); file = fopen("C:/c/test.txt", "r"); fgets(buffer, 127, file); printf("%s", buffer); fclose(file); getch(); }

Вместо функции fgets можно было использовать fscanf, но нужно помнить, что она может считать строку только до первого пробела.
fscanf(file, "%127s", buffer);

Также, вместо того, чтобы открывать и закрывать файл можно воспользоваться функцией freopen, которая «переоткрывает» файл с новыми правами доступа.

#include #include #include void main() { FILE *file; char buffer; file = fopen("C:/c/test.txt", "w"); fprintf(file, "Hello, World!"); freopen("C:/c/test.txt", "r", file); fgets(buffer, 127, file); printf("%s", buffer); fclose(file); getch(); }

Функции fprintf и fscanf отличаются от printf и scanf только тем, что принимают в качестве первого аргумента указатель на FILE, в который они будут выводить или из которого они будут читать данные. Здесь стоит сразу же добавить, что функции printf и scanf могут быть без проблем заменены функциями fprintf и fscanf. В ОС (мы рассматриваем самые распространённые и адекватные операционные системы) существует три стандартных потока: стандартный поток вывода stdout, стандартный поток ввода stdin и стандартный поток вывода ошибок stderr. Они автоматически открываются во время запуска приложения и связаны с консолью. Пример

#include #include #include void main() { int a, b; fprintf(stdout, "Enter two numbers\n"); fscanf(stdin, "%d", &a); fscanf(stdin, "%d", &b); if (b == 0) { fprintf(stderr, "Error: divide by zero"); } else { fprintf(stdout, "%.3f", (float) a / (float) b); } getch(); }

Ошибка открытия файла

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

#include #include #include #define ERROR_OPEN_FILE -3 void main() { FILE *file; char buffer; file = fopen("C:/c/test.txt", "w"); if (file == NULL) { printf("Error opening file"); getch(); exit(ERROR_OPEN_FILE); } fprintf(file, "Hello, World!"); freopen("C:/c/test.txt", "r", file); if (file == NULL) { printf("Error opening file"); getch(); exit(ERROR_OPEN_FILE); } fgets(buffer, 127, file); printf("%s", buffer); fclose(file); getch(); }

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

FILE *inputFile, *outputFile; unsigned m, n; unsigned i, j; inputFile = fopen(INPUT_FILE, READ_ONLY); if (inputFile == NULL) { printf("Error opening file %s", INPUT_FILE); getch(); exit(3); } outputFile = fopen(OUTPUT_FILE, WRITE_ONLY); if (outputFile == NULL) { printf("Error opening file %s", OUTPUT_FILE); getch(); if (inputFile != NULL) { fclose(inputFile); } exit(4); } ...

В простых случаях можно действовать влоб, как в предыдущем куске кода. В более сложных случаях используются методы, подменяющиее RAII из С++: обёртки, или особенности компилятора (cleanup в GCC) и т.п.

Буферизация данных

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

  • 1) Если он заполнен
  • 2) Если поток закрывается
  • 3) Если мы явно указываем, что необходимо очистить буфер (здесь тоже есть исключения:)).
  • 4) Также очищается, если программа завершилась удачно. Вместе с этим закрываются и все файлы. В случае ошибки выполнения этого может не произойти.

Форсировать выгрузку буфера можно с помощью вызова функции fflush(File *). Рассмотрим два примера – с очисткой и без.

#include #include #include void main() { FILE *file; char c; file = fopen("C:/c/test.txt", "w"); do { c = getch(); fprintf(file, "%c", c); fprintf(stdout, "%c", c); //fflush(file); } while(c != "q"); fclose(file); getch(); }

Раскомментируйте вызов fflush. Во время выполнения откройте текстовый файл и посмотрите на поведение.

Буфер файла можно назначить самостоятельно, задав свой размер. Делается это при помощи функции

Void setbuf (FILE * stream, char * buffer);

которая принимает уже открытый FILE и указатель на новый буфер. Размер нового буфера должен быть не меньше чем BUFSIZ (к примеру, на текущей рабочей станции BUFSIZ равен 512 байт). Если передать в качестве буфера NULL, то поток станет небуферизированным. Можно также воспользоваться функцией

Int setvbuf (FILE * stream, char * buffer, int mode, size_t size);

которая принимает буфер произвольного размера size. Режим mode может принимать следующие значения

  • _IOFBF - полная буферизация. Данные записываются в файл, когда он заполняется. На считывание, буфер считается заполненным, когда запрашивается операция ввода и буфер пуст.
  • _IOLBF - линейная буферизация. Данные записываются в файл когда он заполняется, либо когда встречается символ новой строки. На считывание, буфер заполняется до символа новой строки, когда запрашивается операция ввода и буфер пуст.
  • _IONBF – без буферизации. В этом случае параметры size и buffer игнорируются.
В случае удачного выполнения функция возвращает 0.

Пример: зададим свой буфер и посмотрим, как осуществляется чтение из файла. Пусть файл короткий (что-нибудь, типа Hello, World!), и считываем мы его посимвольно

#include #include #include void main() { FILE *input = NULL; char c; char buffer = {0}; input = fopen("D:/c/text.txt", "rt"); setbuf(input, buffer); while (!feof(input)) { c = fgetc(input); printf("%c\n", c); printf("%s\n", buffer); _getch(); } fclose(input); }

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

feof

Функция int feof (FILE * stream); возвращает истину, если конец файла достигнут. Функцию удобно использовать, когда необходимо пройти весь файл от начала до конца. Пусть есть файл с текстовым содержимым text.txt. Считаем посимвольно файл и выведем на экран.

#include #include #include void main() { FILE *input = NULL; char c; input = fopen("D:/c/text.txt", "rt"); if (input == NULL) { printf("Error opening file"); _getch(); exit(0); } while (!feof(input)) { c = fgetc(input); fprintf(stdout, "%c", c); } fclose(input); _getch(); }

Всё бы ничего, только функция feof работает неправильно... Это связано с тем, что понятие "конец файла" не определено. При использовании feof часто возникает ошибка, когда последние считанные данные выводятся два раза. Это связано с тем, что данные записывается в буфер ввода, последнее считывание происходит с ошибкой и функция возвращает старое считанное значение.

#include #include #include void main() { FILE *input = NULL; char c; input = fopen("D:/c/text.txt", "rt"); if (input == NULL) { printf("Error opening file"); _getch(); exit(0); } while (!feof(input)) { fscanf(input, "%c", &c); fprintf(stdout, "%c", c); } fclose(input); _getch(); }

Этот пример сработает с ошибкой (скорее всего) и выведет последний символ файла два раза.

Решение – не использовать feof. Например, хранить общее количество записей или использовать тот факт, что функции fscanf и пр. обычно возвращают число верно считанных и сопоставленных значений.

#include #include #include void main() { FILE *input = NULL; char c; input = fopen("D:/c/text.txt", "rt"); if (input == NULL) { printf("Error opening file"); _getch(); exit(0); } while (fscanf(input, "%c", &c) == 1) { fprintf(stdout, "%c", c); } fclose(input); _getch(); }

Примеры

1. В одном файле записаны два числа - размерности массива. Заполним второй файл массивом случайных чисел.

#include #include #include #include //Имена файлов и права доступа #define INPUT_FILE "D:/c/input.txt" #define OUTPUT_FILE "D:/c/output.txt" #define READ_ONLY "r" #define WRITE_ONLY "w" //Максимальное значение для размера массива #define MAX_DIMENSION 100 //Ошибка при открытии файла #define ERROR_OPEN_FILE -3 void main() { FILE *inputFile, *outputFile; unsigned m, n; unsigned i, j; inputFile = fopen(INPUT_FILE, READ_ONLY); if (inputFile == NULL) { printf("Error opening file %s", INPUT_FILE); getch(); exit(ERROR_OPEN_FILE); } outputFile = fopen(OUTPUT_FILE, WRITE_ONLY); if (outputFile == NULL) { printf("Error opening file %s", OUTPUT_FILE); getch(); //Если файл для чтения удалось открыть, то его необходимо закрыть if (inputFile != NULL) { fclose(inputFile); } exit(ERROR_OPEN_FILE); } fscanf(inputFile, "%ud %ud", &m, &n); if (m > MAX_DIMENSION) { m = MAX_DIMENSION; } if (n > MAX_DIMENSION) { n = MAX_DIMENSION; } srand(time(NULL)); for (i = 0; i < n; i++) { for (j = 0; j < m; j++) { fprintf(outputFile, "%8d ", rand()); } fprintf(outputFile, "\n"); } //Закрываем файлы fclose(inputFile); fclose(outputFile); }

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

#include #include #include #define ERROR_FILE_OPEN -3 void main() { FILE *origin = NULL; FILE *output = NULL; char filename; int mode; printf("Enter filename: "); scanf("%1023s", filename); origin = fopen(filename, "r"); if (origin == NULL) { printf("Error opening file %s", filename); getch(); exit(ERROR_FILE_OPEN); } printf("enter mode: "); scanf("%d", &mode); if (mode == 1) { printf("Enter filename: "); scanf("%1023s", filename); output = fopen(filename, "w"); if (output == NULL) { printf("Error opening file %s", filename); getch(); fclose(origin); exit(ERROR_FILE_OPEN); } } else { output = stdout; } while (!feof(origin)) { fprintf(output, "%c", fgetc(origin)); } fclose(origin); fclose(output); getch(); }

3. Пользователь вводит данные с консоли и они записываются в файл до тех пор, пока не будет нажата клавиша esc. Проверьте программу и посмотрите. как она себя ведёт в случае, если вы вводите backspace: что выводится в файл и что выводится на консоль.

#include #include #include #define ERROR_FILE_OPEN -3 void main() { FILE *output = NULL; char c; output = fopen("D:/c/test_output.txt", "w+t"); if (output == NULL) { printf("Error opening file"); _getch(); exit(ERROR_FILE_OPEN); } for (;;) { c = _getch(); if (c == 27) { break; } fputc(c, output); fputc(c, stdout); } fclose(output); }

4. В файле записаны целые числа. Найти максимальное из них. Воспользуемся тем, что функция fscanf возвращает число верно прочитанных и сопоставленных объектов. Каждый раз должно возвращаться число 1.

#include #include #include #define ERROR_FILE_OPEN -3 void main() { FILE *input = NULL; int num, maxn, hasRead; input = fopen("D:/c/input.txt", "r"); if (input == NULL) { printf("Error opening file"); _getch(); exit(ERROR_FILE_OPEN); } maxn = INT_MIN; hasRead = 1; while (hasRead == 1) { hasRead = fscanf(input, "%d", &num); if (hasRead != 1) { continue; } if (num >

Другое решение считывать числа, пока не дойдём до конца файла.

#include #include #include #include #define ERROR_FILE_OPEN -3 void main() { FILE *input = NULL; int num, maxn, hasRead; input = fopen("D:/c/input.txt", "r"); if (input == NULL) { printf("Error opening file"); _getch(); exit(ERROR_FILE_OPEN); } maxn = INT_MIN; while (!feof(input)) { fscanf(input, "%d", &num); if (num > maxn) { maxn = num; } } printf("max number = %d", maxn); fclose(input); _getch(); }

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

Файл с переводом выглядит примерно так

Солнце sun
карандаш pen
шариковая ручка pencil
дверь door
окно windows
стул chair
кресло armchair

и сохранён в кодировке cp866 (OEM 866). При этом важно: последняя пара cлов также заканчивается переводом строки.

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

#include #include #include #include #define ERROR_FILE_OPEN -3 void main() { FILE *input = NULL; char buffer; char enWord; char ruWord; char usrWord; unsigned index; int length; int wasFound; input = fopen("D:/c/input.txt", "r"); if (input == NULL) { printf("Error opening file"); _getch(); exit(ERROR_FILE_OPEN); } printf("enter word: "); fgets(usrWord, 127, stdin); wasFound = 0; while (!feof(input)) { fgets(buffer, 511, input); length = strlen(buffer); for (index = 0; index < length; index++) { if (buffer == "\t") { buffer = "\0"; break; } } strcpy(ruWord, buffer); strcpy(enWord, &buffer); if (!strcmp(enWord, usrWord)) { wasFound = 1; break; } } if (wasFound) { printf("%s", ruWord); } else { printf("Word not found"); } fclose(input); _getch(); }

6. Подсчитать количество строк в файле. Будем считывать файл посимвольно, считая количество символов "\n" до тех пор, пока не встретим символ EOF. EOF - это спецсимвол, который указывает на то, что ввод закончен и больше нет данных для чтения. Функция возвращает отрицательное значение в случае ошибки.
ЗАМЕЧАНИЕ: EOF имеет тип int, поэтому нужно использовать int для считывания символов. Кроме того, значение EOF не определено стандартом.

#define _CRT_SECURE_NO_WARNINGS #include #include #include int cntLines(const char *filename) { int lines = 0; int any; //any типа int, потому что EOF имеет тип int! FILE *f = fopen(filename, "r"); if (f == NULL) { return -1; } do { any = fgetc(f); //printf("%c", any);//debug if (any == "\n") { lines++; } } while(any != EOF); fclose(f); return lines; } void main() { printf("%d\n", cntLines("C:/c/file.txt")); _getch(); }

Ru-Cyrl 18- tutorial Sypachev S.S. 1989-04-14 [email protected] Stepan Sypachev students

Всё ещё не понятно? – пиши вопросы на ящик


Close