Как работать с бинарным файлом в с
Перейти к содержимому

Как работать с бинарным файлом в с

  • автор:

Как работать с бинарным файлом в с

Работа с бинарными файлами производится посредством посимвольного чтения-записи. Поскольку один объект char соответствует одному байту, то фактически речь идет о побайтовых записи/чтения.

Запись файла

Для записи символа в файл применяется функция putc() , которая имеет следующий прототип:

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

Здесь мы пробегаемся по массиву символов message и каждый из них заносим в файл data.txt (который располагается в текущей папке), передавая каждый символ в функцию putc() .

Чтение файла

Для чтения одного символа применяется функция getc() (функция fgetc() аналогична). Она имеет следующий прототип:

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

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

40.6.5. Работа с бинарными файлами

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

 поток, связанный с бинарным файлом, должен быть открыт в бинарном режиме;

510 Часть IV. Стандартная библиотека языка программирования С++

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

Работа с бинарными файлами в стиле STL

Работа с бинарными файлами — традиционная тема при обучении программированию — по меньшей мере, в России. Не последнюю роль здесь играет широкое распространение в российских школах языка программирования Pascal, который имеет встроенную поддержку работы с так называемыми типизированными файлами (типы file of integer , file of real и т. п.). В некоторых случаях (при углублённом изучении программирования у школьников или на младших курсах университета), когда начинается изучение языка C++, возникает желание решать задачи на обработку «файлов типа T» уже в новом окружении данного языка. И тут возникает вопрос, какие средства при этом использовать.

К сожалению, для работы с бинарными файлами в языке C++ предусмотрены только низкоуровневые средства — методы read / write стандартных типов потоков istream / ostream . Кроме других очевидных недостатков этот факт не даёт использовать в полную силу программирование «в стиле STL» (то есть, в первую очередь, часть стандартной библиотеки C++, связанную с алгоритмами и итераторами).

Итак, задача состоит в том, чтобы обеспечить работу с бинарными файлами, хранящими последовательность значений типа T , как с последовательностью STL ( vector<T> и т. п.). Для T подразумеваются значения базовых типов, а также, так называемые POD-типы (везде дальше можно думать только о базовых типах, если вы не знакомы с понятием POD).

Возможные решения
ios_base::binary: fail #0

Если вы никогда не встречались с подобной задачей (что было бы странно!), возможно, вы вспомните что-то про флаг ios_base::binary , но те, кто встречался, хорошо знает, что данное средство практически никак не поможет в решении. Стандарт языка предельно краток на тему того, какого эффекта можно ожидать от указания данного флага при открытии потока, но в сети можно встретить пояснение, что он просто отключает платформенно-зависимые трансляции символов перехода на новые строки и, возможно, ещё некоторых символов, что не имеет прямого отношения к нашей задаче.

Статический полиморфизм: fail #1

Вероятно, люди, знающие стандартную библиотеку чуть глубже, чем на базовом уровне, помнят, что стандартные типы для файловых потоков ofstream / ifstream являются синонимами для явных инстанций шаблонов basic_ofstream / basic_ifstream . Эти шаблоны имеют два аналогичных типовых параметра: тип символов потока (назовём этот параметр Ch ) и тип характеристик типа символов — по умолчанию это std::char_traits<Ch> . Для ofstream / ifstream в качестве Ch взят тип char .

Здесь немедленно возникает мысль попробовать инстанцировать эти шаблоны с тем типом T , который мы хотим читать из бинарного файла, указав его в качестве значения шаблонного параметра Ch . Простейший код, пытающийся читать из потока такого типа значения типа int падает с исключением времени выполнения bad_cast . Возможно, что-то можно было бы изменить, написав свою специализацию для char_traits<int> и передав её вторым параметром шаблону класса файлового потока, однако этот путь показался беспеперспективным (писать специализацию весьма обширного шаблона char_traits для каждого типа T …) и я не стал разбираться с ним далее.

ООП и динамический полиморфизм: fail #2

После первых неудач можно прийти к мысли, что получить нужное поведение «совсем бесплатно» из стандартных средств не получится и придётся написать кое-какой код. Попробуем решить эту задачу в парадигме ООП, то есть написав пару-другую своих классов. Классов потоков, естественно. Отнаследоваться у стандартных ofstream / ifstream , сохранив максимум определений из предков, и посмотреть, что получится. (В скобках замечу, что задача эта сама по себе не лишена смысла хотя бы ввиду того, что отмечена довольно высоким рейтингом сложности в списке упражнений из книги Б. Страуструпа — упр. 15, п. 21.10 в третьем и специальном изданиях книги «Язык программирования C++».)

С самого начала очевидной была необходимость перегрузки операций << и >> для своих классов потоков. Казалось, этого будет достаточно. Проблема возникла в следующем. Чтобы работать с потоком с помощью алгоритмов стандартной библиотеки, следует использовать итераторы ввода/вывода. По заявлениям авторов STL её средства все из себя обобщённые и я ожидал, что как только мой класс потока будет удовлетворять некоторым неявным требованиям библиотеки, она с радостью заработает с ним — статический полиморфизм… В частности, я ожидал, что стандартные итераторы параметризованы типом потока, с которым они работают. Не тут-то было! В определении шаблонов итераторов ввода/вывода жёстко зашиты стандартные типы basic_ofstream / basic_ifstream .

Надежда на спасение на этом пути остаётся, если обратить внимание на одну особенность реализации операций << и >> : для базовых типов они реализованы как функции-члены шаблонов классов потоков. Если бы они, кроме того, были объявлены виртуальными, то можно было бы положиться на динамический полиморфизм (стандартные итераторы хранили бы объекты моих потоков по ссылке на базовый класс) — получилось бы частичное решение исходной задачи, работавшее только для базовых типов (int, double и т. п.). Однако указанные функции-члены не являются виртуальными. Тут можно было бы порассуждать о логике устройства стандартной библиотеки или отсутствии таковой (например, известно, что для STL изначально не предполагалось использования всей мощи ООП, наследования и полиморфизма, но ведь потоковая библиотека построена на ООП…), однако перейдём к финальному решению.

Ad-hoc полиморфизм (перегрузка): win

В конце концов, всё, что требуется — вызывать специальные версии операций << и >> , которые бы прятали низкоуровневую работу с файлами посредством read / write . Достаточно предоставить свою перегрузку этих операций и позаботиться о том, чтобы вызывалась именно она. Достичь этого можно использованием специальных типов в аргументах. Манипулировать типами потоков у нас уже не получилось — остаётся придумать специальные типы для вводимого/выводимого. Здесь напрашивается использование того, что называется «обёртками».

К счастью, нам нет нужны писать новые классы обёрток для разных типов T : можно ограничиться одним шаблоном класса, хранящим поле параметра-типа T и умеющего преобразовываться к ссылке на это поле — константной и неконстантной. Конструктор с одним параметром типа T , который не объявлен как explicit , позволит неявно преобразовывать значения типа T к типу-обёртке. Конструктор без параметров — требование STL. Результирующий код приведён ниже.

Использование static_cast требует от компилятора вызвать определённую в теле шаблона класса операцию приведения типа для получения ссылки на информационное поле, а reinterpret_cast приводит адрес этого поля к указателю на char , готовя нас к низкоуровневой работе с read / write .

Вот пример, демонстрирующий использование обёртки. Он несёт на себе отпечаток тех идей, которые закладывались изначально, а именно, программирование в стиле STL.

Заключение

Понятно, что в результате получился «абсолютный велосипед», который, наверное, писался многими программистами на C++, однако в сети или в каких-то известных библиотеках (например, Boost, в частности, Boost.Iostreams) подобного я не заметил.

Ещё я хотел бы отметить, что намерено оставил за скобками обсуждение актуальности поставленной задачи. Наверное, есть люди, не представляющие работу с файлами на более высоком, чем read / write , уровне. Возможно, найдутся те, кто скажет, что бинарные файлы это прошлое, что они жутко непереносимы или что-то похожее. Может быть, отчасти это так, однако само упражнение в решении такой задачи мне показалось интересным и познавательным.

За постановку задачи и обсуждение решения я искренне благодарю Виталия Николаевича Брагилевского.

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

Языки Си-семейства и бинарные файлы: что нужно знать

Программирование включает в себя множество разнообразных языков. С их помощью программисты и разработчики «общаются» с компьютерами и иными устройствами, а также с приложениями. У каждого – свои собственные особенности и нюансы, синтаксис и правила.

При написании софта приходится задействовать разнообразные files и функции. Чтобы создать качественную утилиту, необходимо помнить обо всех особенностях выбранного языка. Но некоторые возможности используются не слишком часто. Из-за этого они «забываются».

Современные программеры активно используют Си-семейство в своей работе. Google указывает, что сюда включают:

  • C (Си);
  • C++ (Си Плюс-Плюс);
  • C# (Си Шарп).

Эти языки стали универсальными и удобными. У них много схожих файлов и функций. Но новичкам освоить данные способы «общения» с компьютерами бывает непросто.

Терминология – что пригодится

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

Перед началом работы с бинарными файлами и Fgets требуется запомнить некоторые ключевые термины. К ним относят следующие понятия:

  • базовый класс – класс, от которого осуществляется наследование;
  • выражение – сочетание операторов, литералов и переменных, которые интерпретируются согласно установленным правилам языка;
  • деструктор – метод класса, который отвечает за деинициализацию объекта;
  • идентификатор – имя, присвоенное «элементу» в кодификации;
  • класс – некий шаблон, отвечающий за определение формы объекта;
  • конструктор – метод класса, используемый при инициализации;
  • литерал – фиксированное значение, не изменяемое приложение;
  • массив – переменные одного типа, обращение к которым производится по общему «названию»;
  • объект – своеобразный экземпляр класса;
  • параметры – переменные, получаемые из аргументов, передаваемый функции при вызове;
  • переменная (наиболее частый формат – int i) – именованная ячейка памяти;
  • строка – последовательность символов, заключенная в двойные кавычки;
  • структура – шаблон, определяющий форму объекта с открытыми членами;
  • указатель – объект, содержащий так называемый адрес памяти;
  • функция – подпрограмма, в которой находится одно или несколько приложения Си, задействованных для определенных задач.

Это – основная терминология для всего Си-семейства. Если какие-то слова при создании кодов не понятны, можно всегда обратиться к помощи Google.

Файлы – виды

C-программирование требует определенных навыков и умений. Для обучения можно использовать Google – там полно полезной информации и даже примеров кодификаций. Но без практики и первоначальных знаний добиться успехов не получится.

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

  • бинарные файлы в c;
  • текстовые файлы.

Каждый используется для чтения и записи собственного «формата». Файл сам по себе, согласно данным из Google – это способ хранения информации.

В C нет операторов для работы с файлами. Необходимые манипуляции производятся при помощи функций стандартной библиотеки <stdio.h>.

Текстовый файл — понятие

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

  • в Unix – одиночный символ перевода строки;
  • в Windows – после символа перевода строчки устанавливается знак возврата каретки.

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

Бинарный файл – что это

Если посмотреть данные в Google по file в C, можно увидеть, что есть еще и так называемые двоичные «элементы». Это – файлы, из которых осуществляется считывание или вывод байтов. Подразумевается их предоставление программеру в «сыром» виде. То есть, без связывания и подстановок.

Разница между файлами

Некоторые перед написанием кода задумываются, в чем разница между указанными видами файлов. Пример – картинка и звуковой документ. Чем они отличаются друг от друга?

Звуковой файл интерпретируется через специальные приложения в звуки, а второй «документ» — в изображение. Аналогичным образом согласно Google ситуация обстоит и рассматриваемыми files. Текстовые рассматриваются в виде «обычного текста», а бинарные интерпретируются в качестве набора двоичных чисел.

В текстовых файлах символ «\n» при записи переводится в «\r\n», при считывании осуществляется так называемая образная замена. В случае с бинарными «элементами» подобный прием не работает ни при каких обстоятельствах.

Особенности работы с бинарными «элементами»

Текстовый файл – понятие далеко не новое. Оно встречается не только в программировании. В Google можно отыскать четкое определение оному. И работа с «обычным текстом» при написании кодификаций не такая уж трудная.

Сложнее использовать двоичный файл. Он встречается в кодах чаще всего. В основном используется для записи чисел и значений.

Для того, чтобы в Си-семействе (в C++ в особенности) работать с бинарными файлами, согласно Google, используется стандартная библиотека. Для текстовых применяются:

  • fscanf;
  • fpintf.

Бинарные файлы тоже используют соответствующие «архивы», но с некоторыми корректировками. Пример – для чтения используют не параметр «r», а «rb». Работа осуществляется непосредственно с битами. Доступ к данным осуществляется произвольным образом.

Доступ к информации в функции

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

Fseek (переменная, расстояние, seek_set или seek_cur или seek_end).

Здесь имеет место следующая расшифровка:

  • seek_set – открываем файл и ведем отсчет от начала;
  • seek_cur – непосредственно от установленного курсора;
  • seek_end – конец файла.

Для того, чтобы рассчитать расстояние (битов, на «размер» которых сдвигается указатель), используют sizeof().

Запись и чтение

Немаловажным моментом является запись и чтение. При помощи Google можно разобраться с тем, как создается и используется поток поступаемой информации при обработке кода. Но без «базы» осознать соответствующую информацию не выйдет.

Для записи бинарного файла (из данных Google) используется запись:

Fwrite (ссылка на записываемую информацию, размер, количество «сведений», файловая переменна).

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

Fgets – для чего нужна функция

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

Полезной является функция fGets. Это – чтение строки из указанного потока данных с последующим сохранением в виде строчки в параметр string. Происходит это до тех пор, пока приложение не дойдет до конца файла.

Символ новой строки закрывает работу fGets, но служит допустимым. Связано это с тем, что происходит копирование оного в строчку string. Нулевой символ добавляется после прочитанных. Указывает на конец str.

Функция имеет вид: char *fgets (char *str, int num, FILE *stream) – вместо char допускается использование void. Аргументы расшифровываются следующим образом:

  • str – указатель на массив, в котором осуществится размещение считанной строчки;
  • num – предельно допустимая длина строчки, попадающей под считывание;
  • stream – поток данных (указатель на него).

Если чтение строчки успешно, Google говорит, что произойдет возвращение указателя на массив, в который помещены прочитанные сведения. В противном случае программному коду предстоит открыть файл, «просмотреть» его и вернуть значение NULL.

Когда при чтении осуществляется ошибка, возвращается NULL, а в переменную errno происходит запись кода ошибки. А состояние массива, в котором должна была сохраниться строчка, не определяется.

Пример

Вот наглядный пример использования fgets:

В ходе описанного кода произойдет считывание file.txt или первых 99 символов. Все зависит от того, какое именно из событий осуществляется раньше. Результаты выводятся на экран.

Теперь ясно, что такое двоичный файл в c, а также текстовый file. Для чего они используются, тоже. Если хочется более полно изучить соответствующую тему, рекомендуется отправиться на специализированные компьютерные курсы. Их можно пройти дистанционно. Там расскажут не только о файлах в Си-семействе, но и о fgets и иных функциях.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *