Если записать в файл число миллиард в текстовом виде, оно займет 10 байт:
| '1' | '0' | '0' | '0' | '0' | '0' | '0' | '0' | '0' |
|---|---|---|---|---|---|---|---|---|
В шестнадцатиричной системе 31 30 30 30 30 30 30 30 30 30 …
Еще нужно добавить какой-то знак разделения, чтобы отличить одно число от другого. Всего 11 байт.
Миллиард можно записать намного короче: в шестнадцатеричной миллиард это 3B9ACA00 и это можно записать всего в 4 байта. И если все числа хранить в четырех байтах, то разделитель не требуется
В памяти компьютера это число хранится именно так. Тут есть тонкость, во многих системах эти байты расположены в обратном порядке: 00 CA 9A 9B …
Такой обратный порядок называется «little endian». Для работы нужно учитывать этот порядок хранения в программе и в файле. Далее будем считать, что он и там и там одинаковый.
Будем считать, что файл – это просто последовательность байтов.
В классаx istream и ostream есть методы istream& read (char* s, streamsize n) и ostream& write (const char* s, streamsize n), которые читают из потока и пишут в поток массив байтов s, размера n.
Например, прочитать весь файл можно таким кодом:
#include <iostream> // std::cout
#include <fstream> // std::ifstream
int main () {
std::ifstream is ("file.dat", std::ifstream::binary);
if (is) {
// get length of file:
is.seekg (0, is.end);
int length = is.tellg();
is.seekg (0, is.beg);
char * buffer = new char [length];
std::cout << "Reading " << length << " characters... ";
// read data as a block:
is.read (buffer,length);
if (is)
std::cout << "all characters read successfully.";
else
std::cout << "error: only " << is.gcount() << " could be read";
is.close();
// ...buffer contains the entire file...
delete[] buffer;
}
return 0;
}
В этом примере создается поток is для чтения бинарных данных. Сначала определяется длина файла, а потом читается весь файл целиком в переменную buffer.
Можно прочитать файл символ за символом
#include <iostream>
#include <fstream>
#include <iomanip>
int main() {
std::ifstream fin("file.dat", std::ios_base::binary);
char x;
while (source.read(&x, 1)){
std::cout << std::hex << std::uppercase <<
std::setfill('0') << std::setw(2)
<< int((unsigned char)x) << ' ';
}
return 0;
}
Для записи просто используем функцию write().
«Достать» записанное в бинарном файле число можно двумя способами.
Работая с байтами, «собрать» число из байтов, как записанное в 256-ричной системе счисления.
Но есть магический С++ способ. Сразу «превратить» область памяти из байтов в число. При помощи функции reinterpret_cast():
#include <iostream>
#include <fstream>
int main() {
std::ifstream fin("file.dat", std::ios_base::binary);
char x[4]={};
fin.read(x, 4);
int *k = reinterpret_cast<int*>(x);
std::cout << *k;
}
Здесь мы читаем начало файла в массив из 4 байтов и интерпретируем указатель на них как указатель на целое число.
На самом деле int в С++ не всегда 32 бита, то есть 4 байта. Для работы с числами разной байтности удобно пользоваться типами int8_t, int16_t, int 32_t. Они имеют фиксированный размер.
Записывать числа в файл можно так:
int x = 1234;
fout.write(reinterpret_cast<char*>(x), sizeof(x));
Тип бинарного файла обычно указывается специальной последовательностью в начале файла. Например архивы rar начинаются на 52 61 72 21 1A 07 01 00. Первые байты выглядят как Rar!, так что понять, что перед нами именно этот формат можно, загрузив файл в обычный Блокнот.
У одного типа файлов, например, GIF-картинок, могут быть разные сигнатуры, по которым моджно различить версию формата.
Формат картинок BMP, пожалуй, самый простой тип бинарного файла, который вполне можно и прочитать и записать своим кодом.
Самый простой 24-биный BMP файл устроен так:
В скобках указываются фиксированные значения.
Заголовок файла 14 байт.
| Параметр | Размер в байтах |
|---|---|
| Сигнатура BM | 2 |
| Размер файла | 4 |
| Зарезервировано | 2 |
| Зарезервировано | 2 |
| Смещение данных (14+40) | 4 |
Информационный заголовок 40 байт
| Параметр | Размер в байтах |
|---|---|
| Размер заголовка (40) | 4 |
| Ширина в пикселях | 4 |
| Высота в пикселях | 4 |
| Количество цветовых плоскостей (1) | 2 |
| Битность изображения (24) | 2 |
| Несущественные для нас параметры (0) | 24 |
Данные Дальше, со смещением 54 (14 + 40) следуют пиксели, на каждый по три байта (красный, синий, зеленый).
Строки расположены в обратном порядке, снизу вверх.
Количество байт в строке выравнивается до четырех. Например, если у изображения ширина 3 пикселя, количество байт на строку 3*3 = 9, поэтому каждая строка состоит из 12 байт, это ближайшее к девяти кратное четырем. А если изображение состоит всего из одного пикселя, к каждой строке добавляется один байт, таким образом кажждая строка получается по 4 байта. Дополнительные байты для успешного тестирования нужно сделать нулевыми.
Удобно сделать структуры заголовков со всеми полями и читать заголоки целиком при помощи reinterpret_cast().