File c тип данных. Работа с файлами в языке си
До этого при вводе-выводе данных мы работали со стандартными потоками - клавиатурой и монитором. Теперь рассмотрим, как в языке 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
В данном случае объявляется структура и массив структур. Каждая строка из файла соответствует одному элементу массива; элемент массива представляет собой структуру, содержащую строковое и два числовых поля. За одну итерацию цикл считывает одну строку. Когда встречается конец файла fscanf() возвращает значение EOF и цикл завершается.
fgets()
Функция fgets() аналогична функции gets() и осуществляет построчный ввод из файла. Один вызов fgets() позволят прочитать одну строку. При этом можно прочитать не всю строку, а лишь ее часть от начала. Параметры fgets() выглядят таким образом:
fgets (массив_символов, количество_считываемых_символов, указатель_на_файл)
Например:
fgets (str, 50, myfile)
Такой вызов функции прочитает из файла, связанного с указателем myfile, одну строку текста полностью, если ее длина меньше 50 символов с учетом символа "\n", который функция также сохранит в массиве. Последним (50-ым) элементом массива str будет символ "\0", добавленный fgets() . Если строка окажется длиннее, то функция прочитает 49 символов и в конце запишет "\0". В таком случае "\n" в считанной строке содержаться не будет.
#include
В этой программе в отличие от предыдущей данные считываются строка за строкой в массив arr. Когда считывается следующая строка, предыдущая теряется. Функция fgets() возвращает NULL в случае, если не может прочитать следующую строку.
getc() или fgetc()
Функция 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() принимают в качестве параметров:
- адрес области памяти, куда данные записываются или откуда считываются,
- размер одного данного какого-либо типа,
- количество считываемых данных указанного размера,
- файловый указатель.
Эти функции возвращают количество успешно прочитанных или записанных данных. Т.е. можно "заказать" считывание 50 элементов данных, а получить только 10. Ошибки при этом не возникнет.
Пример использования функций fread() и fwrite() :
#include
Здесь осуществляется попытка чтения из первого файла 50-ти символов. В n сохраняется количество реально считанных символов. Значение n может быть равно 50 или меньше. Данные помещаются в строку. То же самое происходит со вторым файлом. Далее первая строка присоединяется ко второй, и данные сбрасываются в третий файл.
Решение задач
- Напишите программу, которая запрашивает у пользователя имя (адрес) текстового файла, далее открывает его и считает в нем количество символов и строк.
- Напишите программу, которая записывает в файл данные, полученные из другого файла и так или иначе измененные перед записью. Каждая строка данных, полученная из файла, должна помещаться в структуру.
Последнее обновление: 31.10.2015
Класс FileStream представляет возможности по считыванию из файла и записи в файл. Он позволяет работать как с текстовыми файлами, так и с бинарными.
Рассмотрим наиболее важные его свойства и методы:
array - массив байтов, куда будут помещены считываемые из файла данные
offset представляет смещение в байтах в массиве array, в который считанные байты будут помещены
count - максимальное число байтов, предназначенных для чтения. Если в файле находится меньшее количество байтов, то все они будут считаны.
array - массив байтов, откуда данные будут записываться в файла
offset - смещение в байтах в массиве array, откуда начинается запись байтов в поток
count - максимальное число байтов, предназначенных для записи
Свойство Length : возвращает длину потока в байтах
Свойство Position : возвращает текущую позицию в потоке
Метод Read : считывает данные из файла в массив байтов. Принимает три параметра: int Read(byte array, int offset, int count) и возвращает количество успешно считанных байтов. Здесь используются следующие параметры:
Метод long Seek(long offset, SeekOrigin origin) : устанавливает позицию в потоке со смещением на количество байт, указанных в параметре offset.
Метод Write : записывает в файл данные из массива байтов. Принимает три параметра: Write(byte array, int offset, int count)
FileStream представляет доступ к файлам на уровне байтов, поэтому, например, если вам надо считать или записать одну или несколько строк в текстовый файл, то массив байтов надо преобразовать в строки, используя специальные методы. Поэтому для работы с текстовыми файлами применяются другие классы.
В то же время при работе с различными бинарными файлами, имеющими определенную структуру FileStream может быть очень даже полезен для извлечения определенных порций информации и ее обработки.
Посмотрим на примере считывания-записи в текстовый файл:
Console.WriteLine("Введите строку для записи в файл:"); string text = Console.ReadLine(); // запись в файл using (FileStream fstream = new FileStream(@"C:\SomeDir\noname\note.txt", FileMode.OpenOrCreate)) { // преобразуем строку в байты byte array = System.Text.Encoding.Default.GetBytes(text); // запись массива байтов в файл fstream.Write(array, 0, array.Length); Console.WriteLine("Текст записан в файл"); } // чтение из файла using (FileStream fstream = File.OpenRead(@"C:\SomeDir\noname\note.txt")) { // преобразуем строку в байты byte array = new byte; // считываем данные fstream.Read(array, 0, array.Length); // декодируем байты в строку string textFromFile = System.Text.Encoding.Default.GetString(array); Console.WriteLine("Текст из файла: {0}", textFromFile); } Console.ReadLine();
Разберем этот пример. И при чтении, и при записи используется оператор using . Не надо путать данный оператор с директивой using, которая подключает пространства имен в начале файла кода. Оператор using позволяет создавать объект в блоке кода, по завершению которого вызывается метод Dispose у этого объекта, и, таким образом, объект уничтожается. В данном случае в качестве такого объекта служит переменная fstream .
Объект fstream создается двумя разными способами: через конструктор и через один из статических методов класса File.
Здесь в конструктор передается два параметра: путь к файлу и перечисление FileMode . Данное перечисление указывает на режим доступа к файлу и может принимать следующие значения:
Append : если файл существует, то текст добавляется в конец файл. Если файла нет, то он создается. Файл открывается только для записи.
Create : создается новый файл. Если такой файл уже существует, то он перезаписывается
CreateNew : создается новый файл. Если такой файл уже существует, то он приложение выбрасывает ошибку
Open : открывает файл. Если файл не существует, выбрасывается исключение
OpenOrCreate : если файл существует, он открывается, если нет - создается новый
Truncate : если файл существует, то он перезаписывается. Файл открывается только для записи.
Статический метод OpenRead класса File открывает файл для чтения и возвращает объект FileStream.
Конструктор класса FileStream также имеет ряд перегруженных версий, позволяющий более точно настроить создаваемый объект. Все эти версии можно посмотреть на msdn.
И при записи, и при чтении применяется объект кодировки Encoding.Default из пространства имен System.Text . В данном случае мы используем два его метода: GetBytes для получения массива байтов из строки и GetString для получения строки из массива байтов.
В итоге введенная нами строка записывается в файл note.txt . По сути это бинарный файл (не текстовый), хотя если мы в него запишем только строку, то сможем посмотреть в удобочитаемом виде этот файл, открыв его в текстовом редакторе. Однако если мы в него запишем случайные байты, например:
Fstream.WriteByte(13); fstream.WriteByte(103);
То у нас могут возникнуть проблемы с его пониманием. Поэтому для работы непосредственно с текстовыми файлами предназначены отдельные классы - StreamReader и StreamWriter.
Произвольный доступ к файлам
Нередко бинарные файлы представляют определенную стрктуру. И, зная эту структуру, мы можем взять из файла нужную порцию информации или наоброт записать в определенном месте файла определенный набор байтов. Например, в wav-файлах непосредственно звуковые данные начинаются с 44 байта, а до 44 байта идут различные метаданные - количество каналов аудио, частота дискретизации и т.д.
С помощью метода Seek() мы можем управлять положением курсора потока, начиная с которого производится считывание или запись в файл. Этот метод принимает два параметра: offset (смещение) и позиция в файле. Позиция в файле описывается тремя значениями:
SeekOrigin.Begin : начало файла
SeekOrigin.End : конец файла
SeekOrigin.Current : текущая позиция в файле
Курсор потока, с которого начинается чтение или запись, смещается вперед на значение offset относительно позиции, указанной в качестве второго параметра. Смещение может отрицательным, тогда курсор сдвигается назад, если положительное - то вперед.
Рассмотрим на примере:
Using System.IO; using System.Text; class Program { static void Main(string args) { string text = "hello world"; // запись в файл using (FileStream fstream = new FileStream(@"D:\note.dat", FileMode.OpenOrCreate)) { // преобразуем строку в байты byte input = Encoding.Default.GetBytes(text); // запись массива байтов в файл fstream.Write(input, 0, input.Length); Console.WriteLine("Текст записан в файл"); // перемещаем указатель в конец файла, до конца файла- пять байт fstream.Seek(-5, SeekOrigin.End); // минус 5 символов с конца потока // считываем четыре символов с текущей позиции byte output = new byte; fstream.Read(output, 0, output.Length); // декодируем байты в строку string textFromFile = Encoding.Default.GetString(output); Console.WriteLine("Текст из файла: {0}", textFromFile); // worl // заменим в файле слово world на слово house string replaceText = "house"; fstream.Seek(-5, SeekOrigin.End); // минус 5 символов с конца потока input = Encoding.Default.GetBytes(replaceText); fstream.Write(input, 0, input.Length); // считываем весь файл // возвращаем указатель в начало файла fstream.Seek(0, SeekOrigin.Begin); output = new byte; fstream.Read(output, 0, output.Length); // декодируем байты в строку textFromFile = Encoding.Default.GetString(output); Console.WriteLine("Текст из файла: {0}", textFromFile); // hello house } Console.Read(); } }
Консольный вывод:
Текст записан в файл Текст из файл: worl Текст из файла: hello house
Вызов fstream.Seek(-5, SeekOrigin.End) перемещает курсор потока в конец файлов назад на пять символов:
То есть после записи в новый файл строки "hello world" курсор будет стоять на позиции символа "w".
После этого считываем четыре байта начиная с символа "w". В данной кодировке 1 символ будет представлять 1 байт. Поэтому чтение 4 байтов будет эквивалентно чтению четырех сиволов: "worl".
Затем опять же перемещаемся в конец файла, не доходя до конца пять символов (то есть опять же с позиции символа "w"), и осуществляем запись строки "house". Таким образом, строка "house" заменяет строку "world".
Закрытие потока
В примерах выше дл закрытия потока применяется конструкция using . После того как все операторы и выражения в блоке using отработают, объект FileStream уничтожается. Однако мы можем выбрать и другой способ:
FileStream fstream = null; try { fstream = new FileStream(@"D:\note3.dat", FileMode.OpenOrCreate); // операции с потоком } catch(Exception ex) { } finally { if (fstream != null) fstream.Close(); }
Если мы не используем конструкцию using, то нам надо явным образом вызвать метод Close() : fstream.Close()
Для удобства обращения информация в запоминающих устройствах хранится в виде файлов.
Файл – именованная область внешней памяти, выделенная для хранения массива данных. Данные, содержащиеся в файлах, имеют самый разнообразный характер: программы на алгоритмическом или машинном языке; исходные данные для работы программ или результаты выполнения программ; произвольные тексты; графические изображения и т. п.
Каталог (папка , директория ) – именованная совокупность байтов на носителе информации, содержащая название подкаталогов и файлов, используется в файловой системе для упрощения организации файлов.
Файловой системой называется функциональная часть операционной системы, обеспечивающая выполнение операций над файлами. Примерами файловых систем являются FAT (FAT – File Allocation Table, таблица размещения файлов), NTFS, UDF (используется на компакт-дисках).
Существуют три основные версии FAT: FAT12, FAT16 и FAT32. Они отличаются разрядностью записей в дисковой структуре, т.е. количеством бит, отведённых для хранения номера кластера. FAT12 применяется в основном для дискет (до 4 кбайт), FAT16 – для дисков малого объёма, FAT32 – для FLASH-накопителей большой емкости (до 32 Гбайт).
Рассмотрим структуру файловой системы на примере FAT32.
Файловая структура FAT32
Устройства внешней памяти в системе FAT32 имеют не байтовую, а блочную адресацию. Запись информации в устройство внешней памяти осуществляется блоками или секторами.
Сектор – минимальная адресуемая единица хранения информации на внешних запоминающих устройствах. Как правило, размер сектора фиксирован и составляет 512 байт. Для увеличения адресного пространства устройств внешней памяти сектора объединяют в группы, называемые кластерами.
Кластер
– объединение нескольких секторов, которое может рассматриваться как самостоятельная единица, обладающая определёнными свойствами. Основным свойством кластера является его размер, измеряемый в количестве секторов или количестве байт.
Файловая система FAT32 имеет следующую структуру.
Нумерация кластеров, используемых для записи файлов, ведется с 2. Как правило, кластер №2 используется корневым каталогом, а начиная с кластера №3 хранится массив данных. Сектора, используемые для хранения информации, представленной выше корневого каталога, в кластеры не объединяются.
Минимальный размер файла, занимаемый на диске, соответствует 1 кластеру.
Загрузочный сектор начинается следующей информацией:
- EB 58 90 – безусловный переход и сигнатура;
- 4D 53 44 4F 53 35 2E 30 MSDOS5.0;
- 00 02 – количество байт в секторе (обычно 512);
- 1 байт – количество секторов в кластере;
- 2 байта – количество резервных секторов.
Кроме того, загрузочный сектор содержит следующую важную информацию:
- 0x10 (1 байт) – количество таблиц FAT (обычно 2);
- 0x20 (4 байта) – количество секторов на диске;
- 0x2С (4 байта) – номер кластера корневого каталога;
- 0x47 (11 байт) – метка тома;
- 0x1FE (2 байта) – сигнатура загрузочного сектора (55 AA ).
Сектор информации файловой системы содержит:
- 0x00 (4 байта) – сигнатура (52 52 61 41 );
- 0x1E4 (4 байта) – сигнатура (72 72 41 61 );
- 0x1E8 (4 байта) – количество свободных кластеров, -1 если не известно;
- 0x1EС (4 байта) – номер последнего записанного кластера;
- 0x1FE (2 байта) – сигнатура (55 AA ).
Таблица FAT содержит информацию о состоянии каждого кластера на диске. Младшие 2 байт таблицы FAT хранят F8 FF FF 0F FF FF FF FF (что соответствует состоянию кластеров 0 и 1, физически отсутствующих). Далее состояние каждого кластера содержит номер кластера, в котором продолжается текущий файл или следующую информацию:
- 00 00 00 00 – кластер свободен;
- FF FF FF 0F – конец текущего файла.
- 8 байт – имя файла;
- 3 байта – расширение файла;
Корневой каталог содержит набор 32-битных записей информации о каждом файле, содержащих следующую информацию:
В случае работы с длинными именами файлов (включая русские имена) кодировка имени файла производится в системе кодировки UTF-16. При этого для кодирования каждого символа отводится 2 байта. При этом имя файла записывается в виде следующей структуры:
- 1 байт последовательности;
- 10 байт содержат младшие 5 символов имени файла;
- 1 байт атрибут;
- 1 байт резервный;
- 1 байт – контрольная сумма имени DOS;
- 12 байт содержат младшие 3 символа имени файла;
- 2 байта – номер первого кластера;
- остальные символы длинного имени.
Работа с файлами в языке Си
Для программиста открытый файл представляется как последовательность считываемых или записываемых данных. При открытии файла с ним связывается поток ввода-вывода . Выводимая информация записывается в поток, вводимая информация считывается из потока.
Когда поток открывается для ввода-вывода, он связывается со стандартной структурой типа FILE , которая определена в stdio.h . Структура FILE содержит необходимую информацию о файле.
Открытие файла осуществляется с помощью функции fopen() , которая возвращает указатель на структуру типа FILE , который можно использовать для последующих операций с файлом.
FILE *fopen(name, type);
name – имя открываемого файла (включая путь),
type — указатель на строку символов, определяющих способ доступа к файлу:
- "r" - открыть файл для чтения (файл должен существовать);
- "w" - открыть пустой файл для записи; если файл существует, то его содержимое теряется;
- "a" - открыть файл для записи в конец (для добавления); файл создается, если он не существует;
- "r+" - открыть файл для чтения и записи (файл должен существовать);
- "w+" - открыть пустой файл для чтения и записи; если файл существует, то его содержимое теряется;
- "a+" - открыть файл для чтения и дополнения, если файл не существует, то он создаётся.
Возвращаемое значение — указатель на открытый поток. Если обнаружена ошибка, то возвращается значение NULL .
Функция fclose() закрывает поток или потоки, связанные с открытыми при помощи функции fopen() файлами. Закрываемый поток определяется аргументом функции fclose() .
Возвращаемое значение: значение 0, если поток успешно закрыт; константа EOF
, если произошла ошибка.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include
int
main() {
FILE *fp;
char
name = "my.txt"
;
if
((fp = fopen(name, "r"
)) == NULL
)
{
printf("Не удалось открыть файл"
);
getchar();
return
0;
}
// открыть файл удалось
... // требуемые действия над данными
fclose(fp);
getchar();
return
0;
}
Чтение символа из файла
:
char
fgetc(поток);
Аргументом функции является указатель на поток типа FILE . Функция возвращает код считанного символа. Если достигнут конец файла или возникла ошибка, возвращается константа EOF .
Запись символа в файл
:
fputc(символ,поток);
Аргументами функции являются символ и указатель на поток типа FILE . Функция возвращает код считанного символа.
Функции fscanf()
и fprintf()
аналогичны функциям scanf()
и printf()
, но работают с файлами данных, и имеют первый аргумент - указатель на файл.
fscanf(поток, "ФорматВвода"
, аргументы);
Работа с текстовыми файлами в C++.
Существуют два основных типа файлов: текстовые и двоичные. Файлы позволяют пользователю считывать большие объемы данных непосредственно с диска, не вводя их с клавиатуры.
Текстовыми называются файлы, состоящие из любых символов. Они организуются по строкам, каждая из которых заканчивается символом «конец строки». Конец самого файла обозначается символом «конец файла». При записи информации в текстовый файл, просмотреть который можно с помощью любого текстового редактора, все данные преобразуются к символьному типу и хранятся в символьном виде.
В двоичных файлах информация считывается и записывается в виде блоков определенного размера, в которых могут храниться данные любого вида и структуры.
Для работы с файлами используются специальные типы данных , называемые потоками . Поток ifstream служит для работы с файлами в режиме чтения, а ofstream в режиме записи. Для работы с файлами в режиме как записи, так и чтения служит поток fstream .
В программах на C++ при работе с текстовыми файлами необходимо подключать библиотеки iostream и fstream.
Для того чтобы записывать данные в текстовый файл, необходимо:
описать переменную типа ofstream.
вывести информацию в файл.
обязательно закрыть файл.
Для считывания данных из текстового файла, необходимо:
описать переменную типа ifstream.
открыть файл с помощью функции open.
закрыть файл.
Запись информации в текстовый файл
Как было сказано ранее, для того чтобы начать работать с текстовым файлом, необходимо описать переменную типа ofstream. Например, так:
Будет создана переменная F для записи информации в файл.
На следующим этапе файл необходимо открыть для записи. В общем случае оператор открытия потока будет иметь вид:
F.open(«file», mode);
Здесь F - переменная, описанная как ofstream,
file - полное имя файла на диске,
mode - режим работы с открываемым файлом.
Обратите внимание на то, что при указании полного имени файла нужно ставить двойной слеш. Например, полное имя файла noobs.txt, находящегося в папке game на диске D:, нужно будет записать так:
D:\\game\\noobs.txt.
Файл может быть открыт в одном из следующих режимов:
ios::in - открыть файл в режиме чтения данных, этот режим является режимом по умолчанию для потоков ifstream;
ios::out - открыть файл в режиме записи данных (при этом информация о существующем файле уничтожается), этот режим является режимом по умолчанию для потоков ofstream;
ios::app - открыть файл в режиме записи данных в конец файла;
ios::ate - передвинуться в конец уже открытого файла;
ios::trunc - очистить файл, это же происходит в режиме ios::out;
ios::nocreate - не выполнять операцию открытия файла, если он не существует;
ios::noreplace - не открывать существующий файл.
Параметр mode может отсутствовать, в этом случае файл открывается в режиме по умолчанию для данного потока.
После удачного открытия файла (в любом режиме) в переменной F будет храниться true, в противном случае false. Это позволит проверить корректность операции открытия файла.
Открыть файл (в качестве примера возьмем файл D:\\game\\noobs.txt) в режиме записи можно одним из следующих способов:
// первый способ
ofstream F;
F.open("D:\\game\\noobs.txt", ios::out);
//второй способ, режим ios::out является режимом по умолчанию
// для потока ofstream
ofstream F;
//третий способ объединяет описание переменной и типа поток
//и открытие файла в одном операторе
ofstream F ("D:\\game\\noobs.txt", ios::out);
После открытия файла в режиме записи будет создан пустой файл, в который можно будет записывать информацию.
Если вы хотите открыть существующий файл в режиме до записи, то в качестве режима следует использовать значение ios::app.
После открытия файла в режиме записи, в него можно писать точно так же, как и на экран, только вместо стандартного устройства вывода cout необходимо указать имя открытого файла.
Например, для записи в поток F переменной a, оператор вывода будет иметь вид:
Для последовательного вывода в поток G переменных b, c, d оператор вывода станет таким:
G<
Закрытие потока осуществляется с помощью оператора:
ПРИМЕР:
Создать текстовый файл D:\\game\\noobs.txt и записать в него n вещественных чисел.
#include "stdafx.h"
#include
#include
#include
using namespace std;
int main()
setlocale (LC_ALL, "RUS");
int i, n;
double a;
//описывает поток для записи данных в файл
ofstream f ;
//открываем файл в режиме записи,
//режим ios :: out устанавливается по умолчанию
f.open("D:\\game\\noobs.txt", ios::out);
//вводим количество вещественных чисел
cout <<" n ="; cin >> n ;
//цикл для ввода вещественных чисел
//и записи их в файл
for
(i=0; i
cout<<"a=";
//ввод числа
cin>>a;
//закрытие потока
f.close();
system("pause");
return 0;
_______________________________________________________________
Для того чтобы прочитать информацию из текстового файла, необходимо описать переменную типа ifstream . После этого нужно открыть файл для чтения с помощью оператора open . Если переменную назвать F, то первые два оператора будут такими:
F.open("D:\\game\\noobs.txt", ios::in);
После открытия файла в режиме чтения из него можно считывать информацию точно так же, как и с клавиатуры, только вместо cin указать имя потока из которого будет происходить чтение данных.
Например, для чтения из потока F в переменную a, оператор ввода будет выглядеть так:
Два числа в текстовом редакторе считаются разделенными, если между ними есть хотя бы один из символов: пробел, табуляция, символ конца строки. Хорошо, если программисту заранее известно, сколько и каких значений храниться в текстовом файле. Однако часто просто известен тип значений, хранящихся в файле, при этом их количество может быть различным. При решении подобной проблемы необходимо считывать значения из файла по одному, а перед каждым считыванием проверять, достигнут ли конец файла. Для этого существует функция F . eof ().
Здесь F - имя потока функция возвращает логическое значение: true или false, в зависимости от того достигнут ли конец файла. Следовательно, цикл для чтения содержимого всего файла можно записать так:
//организуем для чтения значений из файла, выполнение
//цикла прервется, когда достигнем конец файла,
//в этом случае F.eof() вернет истину
while (!F.eof())
ПРИМЕР:
В текстовом файле D:\\game\\noobs.txt хранятся вещественные числа, вывести их на экран и вычислить их количество.
#include "stdafx.h"
#include
#include
#include
#include
using namespace std;
int main()
setlocale (LC_ALL, "RUS");
int n=0;
float a;
fstream F;
//открываем файл в режиме чтения
F.open("D:\\game\\noobs.txt");
//если открытие файла прошло корректно, то
//цикл для чтения значений из файла; выполнение цикла прервется,
//когда достигнем конца файла, в этом случае F.eof() вернет истину.
while (!F.eof())
//чтение очередного значения из потока F в переменную a
F>>a;
//вывод значения переменной a на экран
//увеличение количества считанных чисел
//закрытие потока
F.close();
//вовод на экран количества считанных чисел
cout<<"n="<
//если открытие файла прошло некорректно, то вывод
//сообщения об отсутствии такого файла
else
cout<<" Файл не существует"<
system("pause");
return 0;
C++. Обработка двоичных файлов
При записи информации в двоичный файл символы и числа записываются в виде последовательности байт.
Для того чтобы записать данные в двоичный файл, необходимо:
описать файловую переменную типа FAIL * с помощью оператора FILE *filename;. Здесь filename - имя переменной, где будет храниться указатель на файл.
записать информацию в файл с помощью функции fwrite
Для того чтобы считат ь данные из двоичного файла, необходимо:
описать переменную типа FILE *
открыть файл с помощью функции fopen
закрыть файл с помощью функции fclose
Основные функции, необходимые для работы с двоичными файлами.
Для открытия файла предназначена функция fopen.
FILE *fopen(const *filename, const char *mode)
Здесь filename - строка, в которой хранится полное имя открываемого файла, mode - строка, определяющая режим работы с файлом; возможны следующие значения:
«rb» - открываем двоичный файл в режиме чтения;
«wb» - создаем двоичный файл для записи; если он существует, то его содержимое очищается;
«ab» - создаем или открываем двоичный файл для дозаписи в конец файла;
«rb+» - открываем существующий двоичный файл в режиме чтения и записи;
«wb+» - открываем двоичный файл в режиме чтения и записи, существующий файл очищается;
«ab+» - двоичный файл открывается или создается для исправления существующий информации и добавления новой в конец файла.
Функция возвращает в файловой переменной f значение NULL в случае неудачного открытия файла. После открытия файла доступен 0-й его байт, указатель файла равен 0, значение которого по мере чтения или записи смещается на считанное (записанное) количество байт. Текущие значение указателя файла - номер байта, начиная с которого будет происходить операция чтения или записи.
Для закрытия файла предназначена функция fclose
int fclose(FILE *filename);
Возвращает 0 при успешном закрытие файла и NULL в противном случае.
Функция remove предназначена для удаления файлов.
int remove(const char *filename);
Эта функция удаляет с диска файл с именем filenema. Удаляемый файл должен быть закрыт. Функция возвращает ненулевое значение, если файл не удалось удалить.
Для переименования файлов предназначена функция rename:
int rename(const char *oldfilename, const char *newfilename);
Первый параметр - старое имя файла, второй - новое. Возвращает 0 при удачном завершении программы.
Чтение из двоичного файла осуществляется с помощью функции fread:
fread(void *ptr, size, n, FILE *filename);
Функция fread считывает из файла filename в массив ptr n элементов размера size. Функция возвращает количество считанных элементов. После чтения из файла его указатель смещается на n*size байт.
Запись в двоичный файл осуществляется с помощью функции fwrite:
fwrite(const void *ptr, size, n, FILE *filename);
Функция fwrite записывает в файл filename из массива ptr n элементов размера size. Функция возвращает количество записанных элементов. После записи информации в файл указатель смещается на n*size байт.
Для контроля достижения конца файла есть функция feof:
int feof(FILE *filename);
Она возвращает ненулевое значение если достигнут конец файла.
ПРИМЕР:
Создать двоичный файл D:\\game\\noobs.dat и записать в него целое число n и n вещественных чисел.
#include "stdafx.h"
#include
using namespace std;
int main()
setlocale (LC_ALL, "RUS");
int n, i;
double a;
//создаем двоичный файл в режиме записи
f=fopen("D:\\game\\noobs.dat", "wb");
// ввод числа n
cout<<"n="; cin>>n;
fwrite(&n, sizeof(int), 1, f);
//цикл для ввода n вещественных чисел
for
(i=0; i
//ввод очередного вещественного числа
cout<<"a=";
cin>>a;
//запись вешественного числа в двоичный файл
fwrite(&a, sizeof(double), 1, f);
// закрываем файл
fclose(f);
system("pause");
return 0;
ПРИМЕР:
Вывести на экран содержимого созданного в прошлой задаче двоичного файла D:\\game\\noobs.dat
#include "stdafx.h"
#include
using namespace std;
int main()
setlocale (LC_ALL, "RUS");
int n, i;
double *a;
FILE *f; //описываем файловую переменную
//открываем существующий двоичный файл в режиме чтения
//считываем из файла одно целое число в переменную n
//вывод n на экран
cout<<"n="<
//выделение памяти для массива из n чисел
a=new double[n];
//чтение n вещественных чисел из файла в массив a
//вывод массива на экран
for
(i=0; i
cout<
// закрываем файл
fclose(f);
system("pause");
return 0;
Двоичный файл - последовательная структура данных, после открытия файла доступен первый байт, хранящийся в нем. Можно последовательно записывать или считывать данные из файла. Допустим, необходимо считать пятнадцатое число, а затем первое. С помощью последовательного доступа это можно сделать следующим способом:
int n, i;
double a;
FILE *f;
f=fopen("D:\\game\\noobs.dat", "rb");
for (i=0; i<15; i++)
fclose(f);
f=fopen("D:\\game\\noobs.dat", "rb");
fread(&a, sizeof(double), 1, f);
fclose(f);
Как видно, такое чтение чисел из файла, а затем повторное открытие файла - не самый удобный способ. Гораздо удобнее будет использовать функцию fseek перемещения указателя файла к заданному байту.
int fseek(FILE *filename, long int offset, int origin);
Функция устанавливает указатель текущий позиции файла F в соответствии со значением начала отсчета origin и смещения offset. Параметр offset равен количеству байтов, на которые будет смещен указатель файла относительно начала отсчета, заданного параметром origin. В качестве значения для параметра origin должно быть взято одно из следующих значений отсчета смещения offset, определенных в заголовке stdio.h:
SEEK_SET - с начала файла;
SEEK_CUR - с текущей позиции;
SEEK_END - с конца файла.
Функция возвращает нулевое значение при успешном выполнение операции, ненулевое - при возникновении сбоя при выполнении смещения
Функция fseek фактически реализует прямой доступ к любому значению в файле. Необходимо только знать месторасположение (номер байта) значения в файле. Рассмотрим использование прямого доступа в двоичных файлах на примере решения следующей задачи.
ПРИМЕР
В созданном раннее двоичном файле D:\\game\\noobs.dat, поменять местами наибольшее и наименьшее из вещественных чисел.
Алгоритм решения задачи состоит из следующих этапов:
чтение вещественных из файла в массив a.
поиск в массиве а максимального (max) и минимального (min) значения и их номеров (imax, imin).
перемещения указателя файла к максимальному значению и запись min.
перемещения указателя файла к минимальному значению и запись max.
Ниже приведен текст программы решения задачи с комментариями.
#include "stdafx.h"
#include
using namespace std;
int main()
setlocale (LC_ALL, "RUS");
int n, i, imax, imin;
double *a, max, min;
FILE *f;
//открытие файла в режиме чтения и записи
f=fopen("D:\\game\\noobs.dat", "rb+");
//считываем из файла в переменную n количество
//вещественных чисел в файле
fread(&n, sizeof(int), 1, f);
cout<<"n="<
//выделяем память для хранения вещественных чисел,
//которые будут храниться в массиве a
a=new double[n];
//считываем из файла в массив а вещественные числа
fread(a, sizeof(double), n, f);
//поиск максимального и минимального элементов
//в массиве а и их индексов
for
(imax=imin=0, max=min=a, i=1; i
if (a[i]>max)
max=a[i];
if
(a[i]
min=a[i];
// перемещение указателя к максимальному элементу
fseek(f, sizeof(int)+imax*sizeof(double), SEEK_SET);
//запись min вместо максимального элемента файла
fwrite(&min, sizeof(double), 1, f);
// перемещение указателя к минимальному элементу
fseek(f, sizeof(int)+imin*sizeof(double), SEEK_SET);
//запись max вместо минимального элемента файла
fwrite(&max, sizeof(double), 1, f);
//закрытие файла
fclose(f);
//освобождение памяти
delete [ ]a;
system("pause");
Механизм ввода-вывода, разработанный , не соответствует общепринятому сегодня стилю объектно-ориентированного программирования, кроме того, он активно использует операции с указателями, считающиеся потенциально небезопасными в современных защищённых средах выполнения кода. Альтернативой при разработке прикладных приложений является механизм стандартных классов ввода-вывода, предоставляемый стандартом языка C++.
Открытие файлов
Наиболее часто применяются классы ifstream для чтения, ofstream для записи и fstream для модификации файлов.
Все поточные классы ввода-вывода являются косвенными производными от общего предка ios , полностью наследуя его функциональность. Так, режим открытия файлов задает член данных перечисляемого типа open_mode, который определяется следующим образом:
Enum open_mode { app, binary, in, out, trunc, ate };
Ниже приведены возможные значения флагов и их назначение.
Например, чтобы открыть файл с именем test.txt для чтения данных в бинарном виде, следует написать:
Ifstream file; file.open ("test.txt", ios::in | ios::binary);
Оператор логического ИЛИ (|) позволяет составить режим с любым сочетанием флагов. Так, чтобы, открывая файл по записи, случайно не затереть существующий файл с тем же именем, надо использовать следующую форму:
Ofstream file; file.open ("test.txt", ios::out | ios::app);
Предполагается, что к проекту подключён соответствующий заголовочный файл:
#include
Для проверки того удалось ли открыть файл, можно применять конструкцию
If (!file) { //Обработка ошибки открытия файла }
Операторы включения и извлечения
Переопределённый в классах работы с файлами оператор включения (<<) записывает данные в файловый поток. Как только вы открыли файл для записи, можно записывать в него текстовую строку целиком:
File << "Это строка текста";
Можно также записывать текстовую строку по частям:
File << "Это " << "строка " << "текста";
Оператор endl завершает ввод строки символом "возврат каретки":
File << "Это строка текста" << endl;
С помощью оператора включения несложно записывать в файл значения переменных или элементов массива:
Ofstream file ("Temp.txt"); char buff = "Текстовый массив содержит переменные"; int vx = 100; float pi = 3.14159; file << buff << endl << vx << endl << pi << endl;
В результате выполнения кода образуется три строки текстового файла Temp.txt:
Текстовый массив содержит переменные 100 3.14159
Обратите внимание, что числовые значения записываются в файл в виде текстовых строк, а не двоичных значений.
Оператор извлечения (>>)производит обратные действия. Казалось бы, чтобы извлечь символы из файла Temp.txt , записанного ранее, нужно написать код наподобие следующего:
Ifstream file ("Temp.txt"); char buff; int vx; float pi; file >> buff >> vx >> pi;
Однако оператор извлечения остановится на первом попавшемся разделителе (символе пробела, табуляции или новой строки). Таким образом, при разборе предложения "Текстовый массив содержит переменные" только слово "Текстовый" запишется в массив buff , пробел игнорируется, а слово "массив" станет значением целой переменной vx и исполнение кода "пойдет вразнос" с неминуемым нарушением структуры данных. Далее, при обсуждении класса ifstream , будет показано, как правильно организовать чтение файла из предыдущего примера.
Класс ifstream: чтение файлов
Как следует из расшифровки названия, класс ifstream предназначен для ввода файлового потока. Далее перечислены основные методы класса. Большая часть из них унаследована от класса istream и перегружена с расширением родительской функциональности. К примеру, функция get , в зависимости от параметра вызова, способна считывать не только одиночный символ, но и символьный блок.
Теперь понятно, как нужно модифицировать предыдущий пример, чтобы использование оператора извлечения данных давало ожидаемый результат:
Ifstream file("Temp.txt"); char buff; int vx; float pi; file.getline(buff, sizeof(buff)); file >> vx >> pi:
Метод getline прочитает первую строку файла до конца, а оператор >> присвоит значения переменным.
Следующий пример показывает добавление данных в текстовый файл с последующим чтением всего файла. Цикл while (1) используется вместо while(!file2.eof()) по причинам, которые обсуждались в .
#include
В следующем примере показан цикл считывания строк из файла test.txt и их отображения на консоли.
#include
Этот код под ОС Windows также зависит от наличия в последней строке файла символа перевода строки, надежнее было бы сделать так:
While (1) { if (file.eof()) break; file.getline(str, sizeof(str)); cout << str << endl; }
Явные вызовы методов open и close не обязательны. Действительно, вызов конструктора с аргументом позволяет сразу же, в момент создания поточного объекта file , открыть файл:
Ifstream file("test.txt");
Вместо метода close можно использовать оператор delete , который автоматически вызовет деструктор объекта file и закроет файл. Код цикла while обеспечивает надлежащую проверку признака конца файла.
Класс ofstream: запись файлов
Класс ofstream предназначен для вывода данных из файлового потока. Далее перечислены основные методы данного класса.
Описанный ранее оператор включения удобен для организации записи в текстовый файл:
Ofstream file ("temp.txt"); if (!file) return; for (int i=1; i<=3; i++) file << "Строка " << i << endl; file.close();
Бинарные файлы
В принципе, бинарные данные обслуживаются наподобие текстовых. Отличие состоит в том, что если бинарные данные записываются в определенной логической структуре, то они должны считываться из файла в переменную того же структурного типа.
Первый параметр методов write и read (адрес блока записи/чтения) должен иметь тип символьного указателя char * , поэтому необходимо произвести явное преобразование типа адреса структуры void * . Второй параметр указывает, что бинарные блоки файла имеют постоянный размер байтов независимо от фактической длины записи. Следующее приложение дает пример создания и отображения данных простейшей записной книжки. Затем записи файла последовательно считываются и отображаются на консоли.
#include
В результате выполнения этого кода образуется бинарный файл Notebook.dat из трех блоков размером по 80 байт каждый (при условии, что символы - однобайтовые). Естественно, вы можете использовать другие поточные методы и проделывать любые операции над полями определенной структуры данных.
Класс fstream: произвольный доступ к файлу
Предположим что в нашей записной книжке накопилось 100 записей, а мы хотим считать 50-ю. Конечно, можно организовать цикл и прочитать все записи с первой по заданную. Очевидно, что более целенаправленное решение - установить указатель позиционирования файла pos прямо на запись 50 и считать ее:
Ifstream ifile("Notebook.dat", ios::binary); int pos = 49 * sizeof(Notes); ifile.seekg(pos); // поиск 50-й записи Notes Note; //Notes – описанная выше структура "запись" ifile.read((char*)&Note, sizeof(Notes));
Подобные операции поиска эффективны, если файл состоит из записей известного и постоянного размера. Чтобы заменить содержимое произвольной записи, надо открыть поток вывода в режиме модификации:
Ofstream ofilе ("Notebook.dat", ios::binary | ios::ate); int pos = 49 * sizeof(Notes); ofile seekp(pos); // поиск 50-й записи Notes Note50 = {"Ельцин Борис Николаевич", "095-222-3322", 64}; ofile.write((char*)&Note, sizeof(Notes)); // замена
Если не указать флаг ios::ate (или ios::app), то при открытии бинарного файла Notebook.dat его предыдущее содержимое будет стерто!
Наконец, можно открыть файл одновременно для чтения/записи, используя методы, унаследованные поточным классом fstream от своих предшественников. Поскольку класс fstream произведен от istream и ostream (родителей ifstream и ofstream соответственно), все упомянутые ранее методы становятся доступными в приложении.
В следующем примере показана перестановка первой и третьей записей файла Notebook.dat .
#include
В конструкторе объекта file надо указать флаги ios::in и ios::out , разрешая одновременное выполнение операций чтения и записи. В результате выполнения этого кода первая и третья записи бинарного файла Notebook.dat поменяются местами.
Дополнительные примеры по теме есть .