Разница между хранением двоичного представления фиксированного размера (например, в виде типа int
) и символьного представления переменного размера (например, в виде типа string
) проявляется и при работе с файлами. По умолчанию потоки iostream
работают с символьными представлениями; иначе говоря, поток istream
считывает последовательность символов и превращает их в объект заданного типа. Поток ostream
принимает объект заданного типа и преобразует их в последовательность записываемых символов. Однако можно потребовать, чтобы потоки istream
и ostream
просто копировали байты из файла в файл. Такой ввод-вывод называется ios_base::binary
. Рассмотрим пример, в котором считываются и записываются двоичные файлы, содержащие целые числа. Главные сроки, предназначенные для обработки двоичных файлов, объясняются ниже.
int main()
{
// открываем поток istream для двоичного ввода из файла:
cout << "Пожалуйста, введите имя файла для ввода \n";
string name;
cin >> name;
ifstream ifs(name.c_str(),ios_base::binary); // примечание: опция
// binary сообщает потоку, чтобы он ничего не делал
// с байтами
if (!ifs) error("Невозможно открыть файл для ввода ", name);
// открываем поток ostream для двоичного вывода в файл:
cout << "Пожалуйста, введите имя файла для вывода \n";
cin >> name;
ofstream ofs(name.c_str(),ios_base::binary); // примечание: опция
// binary сообщает потоку, чтобы он ничего не делал
// с байтами
if (!ofs) error("Невозможно открыть файл для ввода ",name);
vector
// чтение из бинарного файла:
int i;
while (ifs.read(as_bytes(i),sizeof(int))) // примечание:
// читаем байты
v.push_back(i);
// ...что-то делаем с вектором v...
// записываем в двоичный файл:
for(int i=0; i
ofs.write(as_bytes(v[i]),sizeof(int)); // примечание:
// запись байтов
return 0;
}
Мы открыли эти файлы с помощью опции ios_base::binary
.
ifstream ifs(name.c_str(), ios_base::binary);
ofstream ofs(name.c_str(), ios_base::binary);
В обоих вариантах мы выбрали более сложное, но часто более компактное двоичное представление. Если мы перейдем от символьно-ориентированного ввода-вывода к двоичному, то не сможем использовать обычные операторы ввода и вывода >>
и <<
. Эти операторы преобразуют значения в последовательности символов, руководствуясь установленными по умолчанию правилами (например, строка "asdf"
превращается в символы a
, s
, d
, f
, а число 123
превращается в символы 1
, 2
, 3
). Если вы не хотите работать с двоичным представлением чисел, достаточно ничего не делать и использовать режим, заданный по умолчанию. Мы рекомендуем применять опцию binary
, только если вы (или кто-нибудь еще) считаете, что так будет лучше. Например, с помощью опции binary
можно сообщить потоку, что он ничего не должен делать с байтами.
А что вообще мы могли бы сделать с типом int
? Очевидно, записать его в память размером четыре байта; иначе говоря, мы могли бы обратиться к представлению типа int в памяти (последовательность четырех байтов) и записать эти байты в файл. Позднее мы могли бы преобразовать эти байты обратно в целое число.
ifs.read(as_bytes(i),sizeof(int)) // чтение байтов
ofs.write(as_bytes(v[i]),sizeof(int)) // запись байтов
Функция write()
потока ostream
и функция read()
потока istream
принимают адрес (с помощью функции as_bytes()
) и количество байтов (символов), полученное с помощью оператора sizeof
. Этот адрес должен ссылаться на первый байт в памяти, хранящей значение, которое мы хотим прочитать или записать. Например, если у нас есть объект типа int
со значением 1234
, то мы могли бы получить четыре байта (используя шестнадцатеричную систему обозначений) — 00
, 00
, 04
, d2
: