Практический курс по C/C++: https://stepik.org/course/193691
На данный момент
мы с вами научились открывать файловые потоки, сохранять и читать данные
базовых типов в текстовом режиме доступа. На этом занятии сделаем следующий шаг
и посмотрим, как используется бинарный режим и зачем он нужен.
Итак, что такое
бинарный режим? Мы с вами об этом подробно уже говорили на курсе по языку Си. Кратко
напомню. Бинарный режим позволяет сохранять данные в файл в том же виде, в
котором они представлены в ячейках памяти устройства. То есть, мы можем просто
прочитать данные из памяти и перенести их в файл. И, наоборот, прочитать данные
из файла и занести их в нужные ячейки памяти. В результате, сложные структуры
данных достаточно просто можно сохранять и загружать из файла.
Давайте
предположим, что нам в программе нужно сохранять массив из вещественных чисел в
файл. Такие «не простые» наборы данных удобнее всего сохранять и, затем, читать
в бинарном режиме доступа:
#include <iostream>
#include <fstream>
using std::ios;
int main()
{
double pow[] {4.3, -54.33, 0.01};
std::ofstream ofs("out_course.dat", ios::out | ios::binary);
if(ofs.is_open()) {
}
ofs.close();
return 0;
}
Если сейчас
попытаться сохранить массив pow, используя операцию <<:
if(ofs.is_open()) {
ofs << pow;
}
то в файл
запишется адрес массива в текстовом виде. Как же нам сохранить данные этого
массива, перенеся данные из ячеек памяти, которые он занимает, в файл? Для
этого следует воспользоваться методом write() следующим
образом:
if(ofs.is_open()) {
ofs.write((char *)pow, sizeof(pow));
}
Этот метод
работает по аналогии с функцией write() языка Си. Первым аргументом
передается адрес области памяти, которая переносится в файл, а вторым
аргументом – размер сохраняемого фрагмента. В нашем случае – это размер массива
pow в байтах.
После выполнения
программы в выходном файле появится набор нечитаемых символов, соответствующих
байтовому представлению массива pow в памяти устройства. Прочитать обратно
эти данные в массив очень просто. Для этого откроем файл на чтение также в
бинарном режиме и прочитаем байты с помощью метода read():
// ------------- чтение данных из файла --------------------------------
double read_pow[5] {0};
std::ifstream ifs("out_course.dat", ios::in | ios::binary);
if(ifs.is_open()) {
ifs.read((char *)read_pow, sizeof(pow));
}
for(double x : read_pow)
std::cout << x << " ";
ifs.close()
Метод read() работает по
аналогии с функцией read() языка Си и позволяет заносить в указанную область
памяти, прочитанные из файла данные. В данном случае, мы в массив read_pow заносим байты,
ранее сохраненного массива pow. Разумеется, массив read_pow должен иметь
тот же тип данных и размер не меньше, чем pow.
После запуска
программы, увидим значения:
4.3 -54.33 0.01
0 0
Как видим,
данные были успешно прочитаны.
Чтение и запись структур в файл
Давайте, для
лучшего понимания, приведу еще один практический пример использования бинарного
режима доступа. Пусть у нас объявлена следующая структура:
struct person {
char fio[100];
short old;
unsigned int salary;
double weight;
};
И, затем, в
программе выполняется сохранение массива таких структур и их последующее чтение
следующим образом:
int main()
{
struct person ps[] {{"Sergey Balakirev", 102, 1000001, 82.6},
{"Bjarne Stroustrup", 56, 100001, 78.2},
{"Dennis Ritchie", 62, 10001, 88.9},
{"Kenneth Thompson", 58, 10002, 75.3},
};
std::ofstream ofs("out_course.dat", ios::out | ios::binary);
if(ofs.is_open()) {
for(auto& p : ps)
ofs.write((char *)&p, sizeof(p));
}
ofs.close();
// ------------- чтение данных из файла --------------------------------
struct person ps_r[10];
int count = 0;
std::ifstream ifs("out_course.dat", ios::in | ios::binary);
if(ifs.is_open()) {
while(ifs.read((char *)&ps_r[count], sizeof(person))) {
count++;
}
}
ifs.close();
std::cout << count << std::endl;
for(int i = 0; i < count; ++i) {
std::cout << ps_r[i].fio << "\n";
std::cout << ps_r[i].old << " " << ps_r[i].salary << " " << ps_r[i].weight << std::endl;
}
return 0;
}
Вначале мы
перебираем поэлементно все элементы массива ps и с помощью
метода write() записываем
друг за другом в файл. После этого объявляем еще один массив таких же структур
и в цикле поэлементно читаем данные из файла в этот новый массив.
Обратите
внимание, как выполняется чтение данных. Условием окончания цикла while является возвращаемое
значение метода read(). Этот метод вернет 0 (false), когда данные
в файле закончатся. Таким образом, мы прочитаем ровно столько же порций данных,
сколько было записано. Вывод результата в консоль это подтверждает.
Вот основные
(базовые) возможности классов файловых потоков языка С++. Конечно, это далеко
не все, на что они способны. Но для начального их использования этой информации
вам будет вполне достаточно.
Практический курс по C/C++: https://stepik.org/course/193691