Последнее обновление: 31.10.2015
Класс FileStream представляет возможности по считыванию из файла и записи в файл. Он позволяет работать как с текстовыми файлами, так и с бинарными.
Рассмотрим наиболее важные его свойства и методы:
Свойство Length : возвращает длину потока в байтах
Свойство Position : возвращает текущую позицию в потоке
Метод Read : считывает данные из файла в массив байтов. Принимает три параметра: int Read(byte array, int offset, int count) и возвращает количество успешно считанных байтов. Здесь используются следующие параметры:
array - массив байтов, куда будут помещены считываемые из файла данные
offset представляет смещение в байтах в массиве array, в который считанные байты будут помещены
count - максимальное число байтов, предназначенных для чтения. Если в файле находится меньшее количество байтов, то все они будут считаны.
Метод long Seek(long offset, SeekOrigin origin) : устанавливает позицию в потоке со смещением на количество байт, указанных в параметре offset.
Метод Write : записывает в файл данные из массива байтов. Принимает три параметра: Write(byte array, int offset, int count)
array - массив байтов, откуда данные будут записываться в файла
offset - смещение в байтах в массиве array, откуда начинается запись байтов в поток
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()
Теги: Текстовые файлы, fopen, fclose, feof, setbuf, setvbuf, fflush, fgetc, fprintf, fscanf, fgets, буферизированный поток, небуферизированный поток.
Р абота с текстовым файлом похожа работу с консолью: с помощью функций форматированного ввода мы сохраняем данные в файл, с помощью функций форматированного вывода считываем данные из файла. Есть множество нюансов, которые мы позже рассмотрим. Основные операции, которые необходимо проделать, это
Кроме того, существует ряд задач, когда нам не нужно обращаться к содержимому файла: переименование, перемещение, копирование и т.д. К сожалению, в стандарте си нет описания функций для этих нужд. Они, безусловно, имеются для каждой из реализаций компилятора. Считывание содержимого каталога (папки, директории) – это тоже обращение к файлу, потому что папка сама по себе является файлом с метаинформацией.
Иногда необходимо выполнять некоторые вспомогательные операции: переместиться в нужное место файла, запомнить текущее положение, определить длину файла и т.д.
Для работы с файлом необходим объект FILE. Этот объект хранит идентификатор файлового потока и информацию, которая нужна, чтобы им управлять, включая указатель на его буфер, индикатор позиции в файле и индикаторы состояния.
Объект FILE сам по себе является структурой, но к его полям не должно быть доступа. Переносимая программа должна работать с файлом как с абстрактным объектом, позволяющим получить доступ до файлового потока.
Создание и выделение памяти под объект типа FILE осуществляется с помощью функции fopen или tmpfile (есть и другие, но мы остановимся только на этих).
Функция fopen открывает файл. Она получает два аргумента – строку с адресом файла и строку с режимом доступа к файлу. Имя файла может быть как абсолютным, так и относительным. fopen возвращает указатель на объект FILE, с помощью которого далее можно осуществлять доступ к файлу.
FILE* fopen(const char* filename, const char* mode);
Например, откроем файл и запишем в него Hello World
#include
Функция 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
Вместо функции fgets можно было использовать fscanf, но нужно помнить, что она может считать строку только до первого пробела.
fscanf(file, "%127s", buffer);
Также, вместо того, чтобы открывать и закрывать файл можно воспользоваться функцией freopen, которая «переоткрывает» файл с новыми правами доступа.
#include
Функции fprintf и fscanf отличаются от printf и scanf только тем, что принимают в качестве первого аргумента указатель на FILE, в который они будут выводить или из которого они будут читать данные. Здесь стоит сразу же добавить, что функции printf и scanf могут быть без проблем заменены функциями fprintf и fscanf. В ОС (мы рассматриваем самые распространённые и адекватные операционные системы) существует три стандартных потока: стандартный поток вывода stdout, стандартный поток ввода stdin и стандартный поток вывода ошибок stderr. Они автоматически открываются во время запуска приложения и связаны с консолью. Пример
#include
Если вызов функции fopen прошёл неудачно, то она возвратит NULL. Ошибки во время работы с файлами встречаются достаточно часто, поэтому каждый раз, когда мы окрываем файл, необходимо проверять результат работы
#include
Проблему вызывает случай, когда открывается сразу несколько файлов: если один из них нельзя открыть, то остальные также должны быть закрыты
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) и т.п.
Как уже говорилось ранее, когда мы выводим данные, они сначала помещаются в буфер. Очистка буфера осуществляется
Форсировать выгрузку буфера можно с помощью вызова функции fflush(File *). Рассмотрим два примера – с очисткой и без.
#include
Раскомментируйте вызов 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 может принимать следующие значения
Пример: зададим свой буфер и посмотрим, как осуществляется чтение из файла. Пусть файл короткий (что-нибудь, типа Hello, World!), и считываем мы его посимвольно
#include
Видно, что данные уже находятся в буфере. Считывание посимвольно производится уже из буфера.
Функция int feof (FILE * stream); возвращает истину, если конец файла достигнут. Функцию удобно использовать, когда необходимо пройти весь файл от начала до конца. Пусть есть файл с текстовым содержимым text.txt. Считаем посимвольно файл и выведем на экран.
#include
Всё бы ничего, только функция feof работает неправильно... Это связано с тем, что понятие "конец файла" не определено. При использовании feof часто возникает ошибка, когда последние считанные данные выводятся два раза. Это связано с тем, что данные записывается в буфер ввода, последнее считывание происходит с ошибкой и функция возвращает старое считанное значение.
#include
Этот пример сработает с ошибкой (скорее всего) и выведет последний символ файла два раза.
Решение – не использовать feof. Например, хранить общее количество записей или использовать тот факт, что функции fscanf и пр. обычно возвращают число верно считанных и сопоставленных значений.
#include
1. В одном файле записаны два числа - размерности массива. Заполним второй файл массивом случайных чисел.
#include
2. Пользователь копирует файл, при этом сначала выбирает режим работы: файл может выводиться как на консоль, так и копироваться в новый файл.
#include
3. Пользователь вводит данные с консоли и они записываются в файл до тех пор, пока не будет нажата клавиша esc. Проверьте программу и посмотрите. как она себя ведёт в случае, если вы вводите backspace: что выводится в файл и что выводится на консоль.
#include
4. В файле записаны целые числа. Найти максимальное из них. Воспользуемся тем, что функция fscanf возвращает число верно прочитанных и сопоставленных объектов. Каждый раз должно возвращаться число 1.
#include
Другое решение считывать числа, пока не дойдём до конца файла.
#include
5. В файле записаны слова: русское слово, табуляция, английское слово, в несколько рядов. Пользователь вводит английское слово, необходимо вывести русское.
Файл с переводом выглядит примерно так
Солнце sun
карандаш pen
шариковая ручка pencil
дверь door
окно windows
стул chair
кресло armchair
и сохранён в кодировке cp866 (OEM 866). При этом важно: последняя пара cлов также заканчивается переводом строки.
Алгоритм следующий - считываем строку из файла, находим в строке знак табуляции, подменяем знак табуляции нулём, копируем русское слово из буфера, копируем английское слово из буфера, проверяем на равенство.
#include
6. Подсчитать количество строк в файле. Будем считывать файл посимвольно, считая количество символов "\n" до тех пор, пока не встретим символ EOF. EOF - это спецсимвол,
который указывает на то, что ввод закончен и больше нет данных для чтения. Функция возвращает отрицательное значение в случае ошибки.
ЗАМЕЧАНИЕ: EOF имеет тип int, поэтому нужно использовать int для считывания символов. Кроме того, значение EOF не определено стандартом.
#define _CRT_SECURE_NO_WARNINGS
#include
Ru-Cyrl 18- tutorial Sypachev S.S. 1989-04-14 [email protected] Stepan Sypachev students
Всё ещё не понятно? – пиши вопросы на ящик
– сравнение для выявления равенства либо неравенства.
Практическое назначение перечисления – определение множества различающихся символических констант целого типа.
Пример использования переменных перечислимого типа:
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 дней (дня).
Файл – это набор данных, размещенный на внешнем носителе и рассматриваемый в процессе обработки как единое целое. В файлах размещаются данные, предназначенные для длительного хранения.
Различают два вида файлов: текстовые и бинарные. Текстовые файлы представляют собой последовательность ASCII символов и могут быть просмотрены и отредактированы с помощью любого текстового редактора.
Бинарные (двоичные) файлы представляют собой последовательность данных, структура которых определяется программно.
В языке Си имеется большой набор функций для работы с файлами, большинство которых находятся в библиотеках stdio.h иio.h .
Каждому файлу присваивается внутреннее логическое имя, используемое в дальнейшем при обращении к нему. Логическое имя (идентификатор файла) – это
указатель на файл, т.е. на область памяти, где содержится вся необходимая информация о файле. Формат объявления указателя на файл следующий:
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");
После работы с файлом доступ к нему необходимо закрыть. Это выполняет функция int fclose (указатель файла ). Например, из предыдущего примера файл закрывается так: fclose (f);
Для закрытия нескольких файлов введена функция, объявленная следующим образом: void fcloseall (void );
Если требуется изменить режим доступа к файлу, то для этого сначала необходимо закрыть данный файл, а затем вновь его открыть, но с другими правами доступа. Для этого используют стандартную функцию:
FILE* freopen (char*имя_файла , char *режим , FILE *указатель_файла );
Эта функция сначала закрывает файл, объявленный указателем_файла (как это делает функцияfopen ), а затем открывает файл сименем_файла и правами доступа «режим ».
В языке Си имеется возможность работы с временными файлами, которые нужны только в процессе работы программы. В этом случае используется функция
FILE* tmpfile (void);
которая создает на диске временный файл с правами доступа «w+b», после завершения работы программы или после закрытия временного файла он автоматически удаляется.
Все действия по чтению-записи данных в файл можно разделить на три группы: операции посимвольного ввода-вывода; операции построчного вводавывода; операции ввода-вывода по блокам.
Рассмотрим основные функции, применяемые в каждой из указанных трех групп операций.
Посимвольный ввод-вывод
В функциях посимвольного ввода-вывода происходит прием одного символа из файла или передача одного символа в файл:
Построчный ввод-вывод
В функциях построчного ввода-вывода происходит перенос из файла или в
Блоковый ввод-вывод
В функциях блокового ввода-вывода работа происходит с целыми блоками
информации: | |
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. |
Форматированный ввод-вывод производится функциями.
Рассмотрим работу с текстовым файлом в Си на примере. Создайте на диске С текстовый файл с именем TextFile.txt. Наберите в этом файле такие строки:
String_1 123 String_11, 456
String_2
String_3
Сохраните файл.
А это код программы на C, которая открывает наш файл и считывает из него строки:
/*
*Author: @author Subbotin B.P..h>
#include
Чтоб открыть текстовый файл в C используем функцию fopen:
FILE *pTextFile = fopen("C:\\TextFile.txt", "r");
первый аргумент функции fopen указывает на файл, а второй говорит, что файл открыт для чтения из него.
Строки считываем с помощью функции fgets:
fgets(cArray, LEN, pTextFile);
первый аргумент функции fgets указвает на массив символов, в котором будут сохранятся полученные строки, второй аргумент - это максимальное количество символов для считывания, третий - наш файл.
После завершения работы с файлом, его надо закрыть:
fclose(pTextFile);
Получаем:
Русские буквы в строках тоже проходят.
Кстати, эту программу я сделал в Eclipse. Как работать с C/C++ в Eclipse можно посмотреть .
Итак, мы открыли и считали данные из текстового файла.
Теперь научимся программно создавать текстовый файл и записывать в него данные.
/*
Author: @author Subbotin B.P..h>
#include
Создаем текстовый файл для записи в него данных:
FILE *pTextFile = fopen("C:\\TextFileW.txt", "w");
если файл уже имеется, то он будет открыт, и все данные из него будут удалены.
C-строка cString, и число nVal записываются программой в текстовый файл. cNewLine - это просто переход на новую строку.
Записываем данные в текстовый файл с помощью функции fprintf:
fprintf(pTextFile, "%s%c", cString, cNewLine);
первый аргумент здесь - наш файл, второй - форматная строка, третий и более - нужное для этого формата количество аргументов.
До этого при вводе-выводе данных мы работали со стандартными потоками - клавиатурой и монитором. Теперь рассмотрим, как в языке C реализовано получение данных из файлов и запись их туда. Перед тем как выполнять эти операции, надо открыть файл и получить доступ к нему.
В языке программирования C указатель на файл имеет тип FILE и его объявление выглядит так:
FILE *myfile;
С другой стороны, функция fopen() открывает файл по указанному в качестве первого аргумента адресу в режиме чтения ("r"), записи ("w") или добавления ("a") и возвращает в программу указатель на него. Поэтому процесс открытия файла и подключения его к программе выглядит примерно так:
myfile = fopen ("hello.txt", "r");
При чтении или записи данных в файл обращение к нему осуществляется посредством файлового указателя (в данном случае, myfile).
Если в силу тех или иных причин (нет файла по указанному адресу, запрещен доступ к нему) функция fopen() не может открыть файл, то она возвращает NULL. В реальных программах почти всегда обрабатывают ошибку открытия файла в ветке if , мы же далее опустим это.
Объявление функции fopen() содержится в заголовочном файле stdio.h, поэтому требуется его подключение. Также в stdio.h объявлен тип-структура FILE.
После того, как работа с файлом закончена, принято его закрывать, чтобы освободить буфер от данных и по другим причинам. Это особенно важно, если после работы с файлом программа продолжает выполняться. Разрыв связи между внешним файлом и указателем на него из программы выполняется с помощью функции fclose() . В качестве параметра ей передается указатель на файл:
fclose(myfile);
В программе может быть открыт не один файл. В таком случае каждый файл должен быть связан со своим файловым указателем. Однако если программа сначала работает с одним файлом, потом закрывает его, то указатель можно использовать для открытия второго файла.
Функция fscanf() аналогична по смыслу функции scanf() , но в отличии от нее осуществляет форматированный ввод из файла, а не стандартного потока ввода. Функция fscanf() принимает параметры: файловый указатель, строку формата, адреса областей памяти для записи данных:
fscanf (myfile, "%s%d", str, &a);
Возвращает количество удачно считанных данных или EOF. Пробелы, символы перехода на новую строку учитываются как разделители данных.
Допустим, у нас есть файл содержащий такое описание объектов:
Apples 10 23.4 bananas 5 25.0 bread 1 10.3
#include
В данном случае объявляется структура и массив структур. Каждая строка из файла соответствует одному элементу массива; элемент массива представляет собой структуру, содержащую строковое и два числовых поля. За одну итерацию цикл считывает одну строку. Когда встречается конец файла fscanf() возвращает значение EOF и цикл завершается.
Функция fgets() аналогична функции gets() и осуществляет построчный ввод из файла. Один вызов fgets() позволят прочитать одну строку. При этом можно прочитать не всю строку, а лишь ее часть от начала. Параметры fgets() выглядят таким образом:
fgets (массив_символов, количество_считываемых_символов, указатель_на_файл)
Например:
fgets (str, 50, myfile)
Такой вызов функции прочитает из файла, связанного с указателем myfile, одну строку текста полностью, если ее длина меньше 50 символов с учетом символа "\n", который функция также сохранит в массиве. Последним (50-ым) элементом массива str будет символ "\0", добавленный fgets() . Если строка окажется длиннее, то функция прочитает 49 символов и в конце запишет "\0". В таком случае "\n" в считанной строке содержаться не будет.
#include
В этой программе в отличие от предыдущей данные считываются строка за строкой в массив arr. Когда считывается следующая строка, предыдущая теряется. Функция fgets() возвращает NULL в случае, если не может прочитать следующую строку.
Функция getc() или fgetc() (работает и то и другое) позволяет получить из файла очередной один символ.
while ((arr[ i] = fgetc (file) ) != EOF) { if (arr[ i] == "\n " ) { arr[ i] = "\0 " ; printf ("%s\n " , arr) ; i = 0 ; } else i++; } arr[ i] = "\0 " ; printf ("%s\n " , arr) ;
Приведенный в качестве примера код выводит данные из файла на экран.
Также как и ввод, вывод в файл может быть различным.
Ниже приводятся примеры кода, в которых используются три способа вывода данных в файл.
Запись в каждую строку файла полей одной структуры:
file = fopen ("fprintf.txt" , "w" ) ; while (scanf ("%s%u%f" , shop[ i] .name , & (shop[ i] .qty ) , & (shop[ i] .price ) ) != EOF) { fprintf (file, "%s %u %.2f\n " , shop[ i] .name , shop[ i] .qty , shop[ i] .price ) ; i++; }
Построчный вывод в файл (fputs() , в отличие от puts() сама не помещает в конце строки "\n"):
while (gets (arr) != NULL) { fputs (arr, file) ; fputs ("\n " , file) ; }
Пример посимвольного вывода:
while ((i = getchar () ) != EOF) putc (i, file) ;
С файлом можно работать не как с последовательностью символов, а как с последовательностью байтов. В принципе, с нетекстовыми файлами работать по-другому не возможно. Однако так можно читать и писать и в текстовые файлы. Преимущество такого способа доступа к файлу заключается в скорости чтения-записи: за одно обращение можно считать/записать существенный блок информации.
При открытии файла для двоичного доступа, вторым параметром функции fopen() является строка "rb" или "wb".
Тема о работе с двоичными файлами достаточно сложная, для ее изучения требуется отдельный урок. Здесь будут отмечены только особенности функций чтения-записи в файл, который рассматривается как поток байтов.
Функции fread() и fwrite() принимают в качестве параметров:
Эти функции возвращают количество успешно прочитанных или записанных данных. Т.е. можно "заказать" считывание 50 элементов данных, а получить только 10. Ошибки при этом не возникнет.
Пример использования функций fread() и fwrite() :
#include
Здесь осуществляется попытка чтения из первого файла 50-ти символов. В n сохраняется количество реально считанных символов. Значение n может быть равно 50 или меньше. Данные помещаются в строку. То же самое происходит со вторым файлом. Далее первая строка присоединяется ко второй, и данные сбрасываются в третий файл.