Запись и чтение из файла с. Ввод данных из файла и вывод в файл. Класс ofstream: запись файлов

– сравнение для выявления равенства либо неравенства.

Практическое назначение перечисления – определение множества различающихся символических констант целого типа.

Пример использования переменных перечислимого типа:

mo=1, tu, we, th, fr, sa, su } days;

puts(“ Введите день недели (от 1 до 7) : ”); scanf(“%d”, &t_day);

w_day = su; start = mo;

end = w_day -t_day;

printf(“\n Понедельник - %d-й день недели, \ сейчас %d-й день. \n\

До конца недели %d дней (дня). ”, start, t_day, end);

Результат работы программы: Введите день недели (от 1 до 7) : 2

Понедельник - 1-й день недели, сейчас 2-й день. До конца недели 5 дней (дня).

18. Файлы в языке Си

Файл – это набор данных, размещенный на внешнем носителе и рассматриваемый в процессе обработки как единое целое. В файлах размещаются данные, предназначенные для длительного хранения.

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

Бинарные (двоичные) файлы представляют собой последовательность данных, структура которых определяется программно.

В языке Си имеется большой набор функций для работы с файлами, большинство которых находятся в библиотеках stdio.h иio.h .

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

Каждому файлу присваивается внутреннее логическое имя, используемое в дальнейшем при обращении к нему. Логическое имя (идентификатор файла) – это

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

FILE * указатель на файл;

FILE – идентификатор структурного типа, описанный в стандартной библиотеке

stdio.h и содержащий следующую информацию:

type struct {

– число оставшихся в буфере непрочитанных байт;

обычный размер буфера – 512 байт; как только level=0,

в буфер из файла читается следующий блок данных;

– флаг статуса файла – чтение, запись, дополнение;

– дескриптор файла, т.е. число, определяющее его но-

unsigned char hold;

– непереданный символ, т.е. ungetc-символ;

– размер внутреннего промежуточного буфера;

unsigned char buffer;

– значение указателя для доступа внутри буфера, т.е.

задает начало буфера, начало строки или текущее зна-

чение указателя внутри буфера в зависимости от режи-

ма буферизации;

unsigned char *curp;

– текущее значение указателя для доступа внутри бу-

фера, т.е. задает текущую позицию в буфере для обме-

на с программой;

unsigned istemp;

– флаг временного файла;

– флаг при работе с файлом;

} FILE;

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

FILE* fopen (char* имя_ файла, char* режим);

она берет внешнее представление – физическое имя файла на носителе (дискета, винчестер) и ставит ему в соответствие логическое имя.

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

– строкой, например, “a:Mas_dat.dat” – файл с именем Mas_dat.dat, находящийся на дискете, “d:\\work\\Sved.txt” – файл с именем Sved.txt, находящийся на винчестере в каталоге work.

Внимание! Обратный слеш (\), как специальный символ, в строке записывается дважды.

При успешном открытии функция fopen возвращает указатель на файл (в дальнейшем – указатель файла). При ошибке возвращаетсяNULL . Данная ситуация обычно возникает, когда неверно указывается путь к открываемому файлу. Например, если в дисплейном классе нашего университета указать путь, запрещенный для записи (обычно разрешенным является d:\work\).

Второй параметр – строка, в которой задается режим доступа к файлу:

w – файл открывается для записи; если файла с заданным именем нет, то он будет создан; если такой файл существует, то перед открытием прежняя информация уничтожается;

r – файл открывается только для чтения; если такого файла нет, то возникает ошибка;

a – файл открывается для добавления в конец новой информации;

r+ – файл открывается для редактирования данных – возможны и запись, и чтение информации;

w+ – то же, что и для r+;

a+ – то же, что и для a, только запись можно выполнять в любое место файла; доступно и чтение файла;

t – файл открывается в текстовом режиме;b – файл открывается в двоичном режиме.

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

По умолчанию файл открывается в текстовом режиме. Пример: FILE *f; – объявляется указатель на файл f;

f = fopen ("d:\\work\\Dat_sp.cpp", "w"); – открывается для записи файл с логическим именем f, имеющим физическое имя Dat_sp.cpp, находящийся на диске d, в каталоге work; или более кратко

FILE *f = fopen ("d:\\work\\Dat_sp.cpp", "w");

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

После работы с файлом доступ к нему необходимо закрыть. Это выполняет функция int fclose (указатель файла ). Например, из предыдущего примера файл закрывается так: fclose (f);

Для закрытия нескольких файлов введена функция, объявленная следующим образом: void fcloseall (void );

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

FILE* freopen (char*имя_файла , char *режим , FILE *указатель_файла );

Эта функция сначала закрывает файл, объявленный указателем_файла (как это делает функцияfopen ), а затем открывает файл сименем_файла и правами доступа «режим ».

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

FILE* tmpfile (void);

которая создает на диске временный файл с правами доступа «w+b», после завершения работы программы или после закрытия временного файла он автоматически удаляется.

18.3. Запись – чтение информации

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

Рассмотрим основные функции, применяемые в каждой из указанных трех групп операций.

Посимвольный ввод-вывод

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

Построчный ввод-вывод

В функциях построчного ввода-вывода происходит перенос из файла или в

Блоковый ввод-вывод

В функциях блокового ввода-вывода работа происходит с целыми блоками

информации:

int fread (void*p, intsize,

– считывает n блоков поsize байт каждый из фай-

int n, FILE *f)

ла f в область памяти с указателемp (необходимо

int fwrite (void*p, intsize,

заранее отвести память под считываемый блок);

– записывает n блоков по size байт каждый из об-

int n, FILE *f)

ласти памяти с указателем p в файл f.

Форматированный ввод-вывод производится функциями.

Механизм ввода-вывода, разработанный , не соответствует общепринятому сегодня стилю объектно-ориентированного программирования, кроме того, он активно использует операции с указателями, считающиеся потенциально небезопасными в современных защищённых средах выполнения кода. Альтернативой при разработке прикладных приложений является механизм стандартных классов ввода-вывода, предоставляемый стандартом языка 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 поменяются местами.

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

Функция fopen() открывает для использования поток, связывает файл с данным потоком и затем возвращает указатель FILE на данный поток. Чаще всего файл рас­сматривается как дисковый файл. Функция fopen() имеет следующий прототип:

FILE *fopen(const char *имя_файла, const char *режим);

Где режим указывает на строку, содержащую желаемый режим открытия файла. Допустимые зна­чения для режим в Borland С++ показаны в таблице. имя_файла должно быть строкой симво­лов, предоставляющей корректное имя файла операционной системе, и может содержать указа­ние пути.

Функция fopen() возвращает указатель базового типа FILE. Данный указатель идентифицирует файл и используется большинством функций файловой системы. Его никогда не следует изменять самостоятельно. Функция возвращает нулевой указатель, если файл не может быть открыт.

Как показывает таблица, файл может быть открыт или в текстовом, или в двоичном режи­ме. В текстовом режиме при вводе последовательность возврат каретки и перевод строки трансли­руется в символ новой строки. При выводе справедливо обратное: символ новой строки трансли­руется в возврат каретки и перевод строки. В двоичных файлах такого перевода не происходит. Когда в аргументе режима не указаны ни t, ни b, то статус файла текстовый/двоичный определя­ется значением глобальной переменной _fmode, определенной в Borland С++. По умолчанию fmode установлена в О_ТЕХТ, то есть устанавливается текстовый режим. Если установить _fmode в О_BINARY, то файлы будут открываться в двоичном режиме. (Эти макросы определены в fcntl.h.) Естественно, использование явно указанных t или b устраняет эффекты, связанные с переменной _fmode. Кроме этого, _fmode характерна только для продуктов Borland. Она не определена в системе ввода/вывода ANSI С.

Если необходимо открыть файл с именем test на запись, то следует написать:

Fp = fopen ("test", "w") ;

Где fp - это переменная типа FILE *. Тем не менее обычно можно увидеть следующее:

If((fp = fopen("test", "w"))==NULL) {
puts ("Cannot open file.");
exit (1);
}

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

Если fopen() используется для открытия файла на запись, то любой ранее существующий файл с указанным именем будет удален. Если файла с указанным именем не существует, то он будет создан.

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

Открытие файла на чтение требует наличия файла. Если файл не существует, то будет возвращена ошибка. Если файл открыт для операции чтения/записи, то он не удаляется при наличии, а если файл не существует, то он создается.

Таблица: Допустимые значения режимов

Значение

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

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

Присоединяет к файлу. (Открывает по умолчанию как текстовый файл.)

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

Открывает двоичный файл для записи.

Присоединяет к двоичному файлу.

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

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

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

Открывает двоичный файл для чтения/записи.

Создает двоичный файл для чтения/записи.

Присоединяет или создает двоичный файл для чтения/записи.

Создает текстовый файл для записи.

Присоединяет к текстовому файлу.

Открывает текстовый файл для чтения.

Создает текстовый файл для чтения/записи.

Открывает или создает текстовый файл для чтения/записи.

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

Когда поток открывается для ввода-вывода, он связывается со стандартной структурой типа FILE, которая определена в stdio.h. Структура FILE содержит необходимую информацию о файле.

Открытие файла осуществляется с помощью функции fopen(), которая возвращается указатель на структуру типа FILE, который можно использовать для последующих операций с файлом.

FILE *fopen (name, type);

name – имя открываемого файла (включая путь),
type - указатель на строку символов, определяющих способ доступа к файлу:

· "r" - открыть файл для чтения (файл должен существовать);

· "w" - открыть пустой файл для записи; если файл существует, то его содержимое теряется;

· "a" - открыть файл для записи в конец (для добавления); файл создается, если он не существует;

· "r+" - открыть файл для чтения и записи (файл должен существовать);

· "w+" - открыть пустой файл для чтения и записи; если файл существует, то его содержимое теряется;

· "a+" - открыть файл для чтения и дополнения, если файл не существует, то он создаётся.

Возвращаемое значение - указатель на открытый поток. Если обнаружена ошибка, то возвращается значение NULL.

Функция fclose() закрывает поток или потоки, связанные с открытыми при помощи функции fopen() файлами. Закрываемый поток определяется аргументом функции fclose().

Возвращаемое значение: значение 0, если поток успешно закрыт; константа EOF, если произошла ошибка.

#include
int main()

char name="my.txt";

if(fp = fopen(name, "r")!=NULL)

// открыть файлу далось?
... // требуемые действия над данными

else printf("Не удалось открыть файл");

Чтение символа из файла :

char fgetc(поток);

Аргументом функции является указатель на поток типа FILE. Функция возвращает код считанного символа. Если достигнут конец файла или возникла ошибка, возвращается константа EOF.
Запись символа в файл :

fputc(символ,поток);

Аргументами функции являются символ и указатель на поток типа FILE. Функция возвращает код считанного символа.

Функции fscanf() и fprintf() аналогичны функциям scanf() и printf(), но работают с файлами данных, и имеют первый аргумент - указатель на файл.

fscanf(поток, "Формат Ввода", аргументы);
fprintf(поток, "Формат Вывода", аргументы);

Функции fgets() и fputs() предназначены для ввода-вывода строк, они являются аналогами функций gets() и puts() для работы с файлами.

fgets(Указатель На Строку, Количество Символов, поток);

Символы читаются из потока до тех пор, пока не будет прочитан символ новой строки "\n", который включается в строку, или пока не наступит конец потока EOF или не будет прочитано максимальное символов. Результат помещается в указатель на строку и заканчивается нуль- символом "\0". Функция возвращает адрес строки.

fputs(Указатель На Строку, поток);

Копирует строку в поток с текущей позиции. Завершающий нуль- символ не копируется.
Пример Ввести число и сохранить его в файле s1.txt. Считать число из файла s1.txt, увеличить его на 3 и сохранить в файле s2.txt.

До этого при вводе-выводе данных мы работали со стандартными потоками - клавиатурой и монитором. Теперь рассмотрим, как в языке 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. Напишите программу, которая записывает в файл данные, полученные из другого файла и так или иначе измененные перед записью. Каждая строка данных, полученная из файла, должна помещаться в структуру.


Есть вопросы?

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: