Разница между хранением двоичного представления фиксированного размера (например, в виде типа 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: