Эта статья написана для того, чтобы поверхностно ознакомить новичков со способом создания и обработки бинарного файла на языке С++.
В нашем, человеческом представлении, файлы в компьютере могут хранится в двух вариантах: понятно компьютеру и понятно нам. Понятные нам файлы — это текстовые файлы, понятные компьютеру — это бинарные файлы.
В зависимости от задачи бывает удобно выбрать один из этих вариантов. Например, для настроек какой-нибудь игры или программы может быть удобно хранить файлы в текстовом виде, чтобы можно было взять открыть любым текстовым редактором и поменять какую-нибудь настройку быстро и просто. А вот сохранения игр удобнее хранить в бинарных файлах. Как минимум это лёгкая защита от применения читов. Ведь что за игра, если тебе система дала 100 монет, а ты открываешь файл, пишешь 1000.000 монет, покупаешь самое крутое и такой прям классный-преклассный игрок. Я к тому, что выбор двоичного или текстового файла может зависеть от ситуации.
Обычно в двоичных файлах хранят какие-то определённые структуры данных. Многие из моих читателей до структур и классов ещё не дошли, поэтому приводимый мной пример максимально простой.
Чаще всего работа с бинарными файлами происходит в стиле языка С.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//clang Работа с бинарными файлами Запись Листинг #1
#include <fstream>
usingnamespacestd;
intmain(){
constchar*FName="C:\\FromC\\1.txt";//Путь к файлу
intx=100;//Переменные для записи
doubley=5.988;
/*РАБОТА С БИНАРНЫМ ФАЙЛОМ*/
ofstreamout(FName,ios::binary);//Ставим режим "бинарный файл"
out.write((char*)&x, sizeof(x));//Записываем в файл значение "x"
out.write((char*)&y, sizeof(y));//Записываем в файл значение "y"
out.close();//Сохраняем файл
/*КОНЕЦ РАБОТЫ С БИНАРНЫМ ФАЙЛОМ*/
}
К сожалению, в С++ есть небольшой непорядок, иногда вводящий нас, новичков, в уныние.
Когда мы говорим о бинарных данных, тип char выступает в роли типа byte
В С++ нет типа byte, а вместо типа byte используют тип char. Согласно правилам языка С++, тип char оказывается наименьшей единицей информации. Из-за этого восприятие нами ситуации в целом немного искажается, ведь мы видим char*, а на самом деле это как byte*
В наших компьютерах используется такая модель памяти, где одна ячейка занимает 1 байт. А один символ char гарантировано, как утверждают в стандарте, должен залезать в эту ячейку. Поэтому, в зависимости от ситуации, тип char можно трактовать или как символьный тип, или как байтовый тип.
Файлы, хранящие последовательность байтов, называют бинарными файлами.
Это связано с тем, что байт делится на биты, а биты представляют собой двоичные цифры. Слово бинарный переводится как двоичный.
Чтобы мы могли записать какое-нибудь значение в бинарном представлении, нам нужно для начала вывести это бинарное представление, а чтобы записалось правильное количество байт, нужно явно указывать это количество. Это выглядит приблизительно следующим образом:
(char*)&x
Так мы делаем строку байтов для того, чтобы отдать потоку, открытому в двоичном режиме
sizeof(x)
Так мы ограничиваем число уходящих в поток байтов нужным числом
Здесь есть аналогия с символьным массивом, но поскольку у нас не символьная строка, а байтовая строка, то ограничение происходит не нуль-символом, а явным указанием числа числа байт. В зависимости от заявленного для переменной типа переменная может занимать от одного до нескольких байт в памяти компьютера, и при записи переменных мы должны внимательно следить, чтобы число байтов, указываемое нами, строго било с числом байт, занимаемых переменной в памяти. Мы переносим байты из памяти в файл. Чтобы байты переносились как именно байты, мы интерпретируем переменную как строку байтов.
Приблизительно такие ужасы у нас происходят при записи в бинарном формате данных в файл.
Для того, чтобы правильно читать данные из файлов, хранящих данные в бинарном виде, нужно знать общую структуру хранения внутри файла. В нашем случае структура хранения внутри файла весьма проста: в файле хранится два числа, одно целое, одно дробное. Т. е. нужно знать, что хранится внутри файла, и в каком порядке оно хранится. Чтобы получить возможность читать данные, хранимые внутри файлов, нам нужно подключить заголовочный файл fstream и использовать тип ifstream или тип fstream. Я буду использовать первый.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//clang Работа с бинарными файлами Чтение Листинг #2
#include <fstream> //Для работы с файлом
#include <iostream>
usingnamespacestd;
intmain(){
constchar*FName="C:\\FromC\\1.txt";//Путь к файлу
intx=0;//Переменные
doubley=0;
/*НАЧАЛО РАБОТЫ С ФАЙЛОМ*/
ifstreamin(FName,ios::binary);
in.read((char*)&x, sizeof(x));//перенос байтов из файла в "х"
in.read((char*)&y, sizeof(y));//перенос байтов из файла в "y"
in.close();
/*КОНЕЦ РАБОТЫ С ФАЙЛОМ*/
cout<<x<<'\n'<<y<<'\n';
cin.get();
}
Всё очень напоминает запись в файл, только происходит обратный процесс. Опять же, мы следим за числом цепляемых байтов и переносим их из файла в память компьютера.
ifstream — специальный тип, объекты которого выполняют роль файловых переменных, читающих файлы.
ofstream — специальный тип, объекты которого выполняют роль файловых переменных, записывающих файлы.
Для того, чтобы вам проще было работать с бинарными файлами, нужно запомнить две простые истины:
Выводите строку байтов
Следите за размерами
На самом деле для компьютеров нет различия между бинарными и текстовыми файлами, это различие существует только из-за нашего восприятия мира. Вы легко можете прочитать самый простейший текстовый файл при бинарном подходе, при этом вам текст будет точно так же понятен, как он понятен и сам по себе, в блокноте. Текстовые файлы — это только-лишь частный случай бинарных файлов. Просто создайте какой-нибудь текстовый файл в удобном месте (нежелательно, чтобы путь к текстовому файлу содержал символы русского алфавита). Например, я создам файл "C:\MyFiles\text.txt", в этот файл запишу какой-то текст. Советую сначала не использовать русские символы, потому что у некоторых из вас могут быть трудности с кодировками, а я объяснить решения не смогу. В общем, пишу в текстовый файл текст: "Hello, my file!" и сохраняю этот файл. Теперь я буду читать этот файл в бинарном режиме.Вы можете записать в файл несколько строчек.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//clang Работа с бинарными файлами Чтение текстового файла Листинг #3
#include <fstream>
#include <iostream>
usingnamespacestd;
intmain(){
constchar*FName="C:\\MyFiles\\text.txt";//Путь к файлу
charS[255]={};//Символьный массив в 255 символов
/*НАЧАЛО РАБОТЫ С ФАЙЛОМ*/
ifstreamin(FName,ios::binary);
in.read((char*)&S, sizeof(S));//перенос байтов из файла в "х"
//in.read((char*)S, sizeof(S)); //в случае с массивом можно и так
in.close();
/*КОНЕЦ РАБОТЫ С ФАЙЛОМ*/
cout<<S<<'\n';
cin.get();
}
Тут вам как и в обычном случае: нужно заполнять байтовую строку байтами, получаемыми из файла, и следить за числом передаваемых байт. Только нужно сделать дополнительное обнуление символьного массива, чтобы при выводе на экран к полученному из файла тексту не прилипал мусор.
Подводный камень, который может вас ждать в ближайшем будущем — это попытка записать в файл значение, на которое ссылается указательная переменная. В случае работы с указателями в файл будет записываться не значение, с которым связана указательная переменная, а сама указательная переменная. Поэтому не записывайте в файл указательные переменные, если вы не записываете адреса памяти. А адреса памяти вам вряд ли придётся записывать в файл, потому что работа с адресами памяти в полномочиях операционной системы. Вам нужно или сразу записывать значения не указательного характера, или переводить указательный вариант в вариант неуказательный. Будьте особенно осторожны, если вам надо записывать данные, связанные с указательными переменными.
Дальше совсем новичкам можно пока не читать, а просто принять к сведению. В бинарные файлы можно быстро и легко сохранять объекты тривиальных, т. е. простых по своему строению, классов и читать в объекты подходящих классов данные из таких бинарников.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//clang Сохранение в бинарник объекта класса Листинг #4
Здесь всё то же самое: приведение к байтовой строке, особое внимание на число байт. Если сохраняем объект в файл целиком, то и читать его лучше целиком, а не отдельные его части. Современные компьютеры выравнивают поля классов, чтобы работа со значениями, хранимыми в объектах, происходила быстрее. Эти выравнивания могут повлиять на выполнение поставленной вами задачи. Когда вы сохраняете объект полностью и потом читаете его полностью, то вам не надо задумываться о выравниваниях, а если пробовать выцепить отдельные части сохранённого объекта как одного целого, то не факт, что всё у вас выцепится правильно, где-нибудь когда-нибудь произойдёт несовпадение и сохранённые в файл данные не будут выглядеть практически полезными.
При сложном строении классов или активном участии указателей в классах объекты часто сериализуют. Я сериализовать ничего не умею, поэтому и показать, как такое вытворять — не могу.
Если эта статья оказалась для вас полезной и вы чему-то научились, то я рад.
12 комментариев на «“C++ для начинающих. Знакомство работы с бинарными файлами”»
Смущают не строчки, а текст после кода.
iostream, ifstream — это не команды, это конструкторы. А вот после того как с их помощью сконструировали файловые объекты, вот тогды используются методы — члены класса: для чтения read, для записи write
Да, классы, но в таком написании вызываются конструкторы файловых объектов (неявно). И уж точно никакие н команды. Для С++ это вообще какой-то странный термин.
я уже исправил.
надеюсь лучше стало.
и я не знаю чем вам не нравится термин: «команда».
В конкретном месте, я действительно неправильно его применил и, благодаря вам, исправил, но Команда — это элементарное действие, операция, выполняемые вычислительным устройством
т.е. написали a+b, написали команду сложения, использовали оператор сложения.
написали ifstream — использовали команду создания класса для описания объекта.
русский язык велик и могуч и то, что другие языки не могут так широко использоваться означает, что совсем не обязательно кто-то там ошибается, если использует фразеологию не по книжному варианту.
не работает код в Visual studio. начиная с объявления заголовочных файлов
Для Visual Studio первые три строчки такие
=========
Чушь! Не работает. Записывается не строка в файл а указатель.
Переписал. Спасибо, что сказали.
Вот пример http://ci-plus-plus-snachala.ru/?p=2177
Смущают не строчки, а текст после кода.
iostream, ifstream — это не команды, это конструкторы. А вот после того как с их помощью сконструировали файловые объекты, вот тогды используются методы — члены класса: для чтения read, для записи write
Правильнее сказать: «классы».(не конструкторы)
вы же не можете вызвать конструктор класса в самостоятельном виде.
Я исправлю. Спасибо, что показали.
Да, классы, но в таком написании вызываются конструкторы файловых объектов (неявно). И уж точно никакие н команды. Для С++ это вообще какой-то странный термин.
я уже исправил.
надеюсь лучше стало.
и я не знаю чем вам не нравится термин: «команда».
В конкретном месте, я действительно неправильно его применил и, благодаря вам, исправил, но
Команда — это элементарное действие, операция, выполняемые вычислительным устройством
т.е. написали a+b, написали команду сложения, использовали оператор сложения.
написали ifstream — использовали команду создания класса для описания объекта.
русский язык велик и могуч и то, что другие языки не могут так широко использоваться означает, что совсем не обязательно кто-то там ошибается, если использует фразеологию не по книжному варианту.
ifstream in(«C://1.txt»,ios::binary|ios::in); //Открыли для только для чтения в бинарном режиме
Два слова «для», исправьте.
Спасибі величезне!
термин: «команда» це дійсно негарно. Він плутає і ріже слух (зір0!
Конструктор відразу пояснює суть справи — і це саме те що треба
Тобто ж клас — рівно зрозуміло!
Не знаю как в c++, а в русском нет слова "нету"