Примеры сохранения/чтения объектов класса. Сериализация
Данная тема содержит примеры сериализации объектов классов. В наиболее общем случае сериализация это свойство объекта сохранять свое состояние в файловом потоке и (по требованию) восстанавливать его.
Содержание
- 1. Пример разработки класса, содержащий средства записи в файл и чтение из файла. Класс реализует массив чисел типа double*
- 2. Пример записи/чтения объекта класса, содержащий поля типа string
- 3. Пример записи/чтения объекта класса, содержащий вложенные объекты другого класса
- Связанные темы
Поиск на других ресурсах:
1. Пример разработки класса, содержащий средства записи в файл и чтение из файла. Класс реализует массив чисел типа double*
В примере показано свойство экземпляра класса сохранять свое состояние в файле и восстанавливать его из файла. Под состоянием объекта понимается текущее значение внутренних данных (внутренних полей) объекта.
Разработан класс, содержащий следующие составляющие:
- A – массив чисел типа double;
- count – текущее количество элементов в массиве;
- конструктор ArrayNumbers(double*, int) – инициализирует внутренний массив внешними данными;
- конструктор ArrayNumbers() – создает массив нулевой длины;
- конструктор копирования ArrayNumbers(const ArrayNumbers&);
- операторная функция operator=(const ArrayNumbers&);
- деструктор ~ArrayNumbers();
- метод Print() – выводит содержимое текущего экземпляра;
- метод SaveToFile(char*) – записывает текущий экземпляр в файл;
- метод ReadFromFile(char*) – считывает текущий экземпляр из файла.
#include <iostream> #include <fstream> using namespace std; // Массив чисел class ArrayNumbers { private: // Внутренние поля double* A; int count; public: // Конструкторы ArrayNumbers(double* _A, int _count) { try { count = _count; A = new double[count]; for (int i = 0; i < _count; i++) A[i] = _A[i]; } catch (bad_alloc e) { cout << e.what() << endl; } } // Конструктор без параметров ArrayNumbers() { count = 0; } // Конструктор копирования ArrayNumbers(const ArrayNumbers& obj) { try { count = obj.count; A = new double[count]; for (int i = 0; i < count; i++) A[i] = obj.A[i]; } catch (bad_alloc e) { cout << e.what() << endl; } } // Оператор копирования ArrayNumbers& operator=(const ArrayNumbers& obj) { // освободить предварительно выделенную память if (count > 0) delete[] A; try { count = obj.count; A = new double[count]; for (int i = 0; i < count; i++) A[i] = obj.A[i]; } catch (bad_alloc e) { cout << e.what() << endl; } } // Деструктор ~ArrayNumbers() { if (count > 0) delete[] A; } // Функция вывода содержимого текущего экземпляра void Print(string msg) { cout << msg; cout << ": A => { "; for (int i = 0; i < count; i++) { cout << A[i] << " "; } cout << "}" << endl; } // Методы сериализации // Запись текущего экземпляра в файл bool SaveToFile(const char* filename) { // 1. Создать экземпляр класса ofstream ofstream outF(filename, ios::out | ios::binary); if (!outF) return false; // 2. Записать количество элементов в массиве outF.write((char*)&count, sizeof(count)); // 3. Записать непосредственно массив for (int i = 0; i < count; i++) { outF.write((char*)&A[i], sizeof(A[i])); } // 4. Закрыть файл outF.close(); // 5. Вернуть true return true; } // Чтение массива из файла и запись в текущий экземпляр bool ReadFromFile(const char* filename) { // 1. Создать экземпляр класса ifstream, попытка открытия файла ifstream inF(filename, ios::in | ios::binary); if (!inF) return false; // 2. Сначала освободить предварительно выделенную память для массива if (count > 0) delete[] A; // 3. Считать количество элементов в массиве inF.read((char*)&count, sizeof(int)); try { // 4. Попытка выделить память для элементов массива A = new double[count]; } catch (bad_alloc e) { // 5. Если попытка неудачная, то выйти из функции cout << e.what(); inF.close(); // закрыть файл return false; } // 6. Прочитать массив поэлементно double x; for (int i = 0; i < count; i++) { inF.read((char*)&x, sizeof(double)); // прочитать 1 элемент типа double A[i] = x; } // 7. Закрыть файловый поток inF.close(); // 8. Выход с кодом true - файл прочитан return true; } }; void main() { // 1. Создать тестовый массив double AD[] = { 2.8, 3.5, 1.4, 7.0, 2.5 }; // 2. Создать экземпляр A1 класса ArrayNumbers // и записать его в файл file1.bin ArrayNumbers A1(AD, 5); A1.Print("A1"); // A1 = { 2.8, 3.5, 1.4, 7.0, 2.5 } A1.SaveToFile("file1.bin"); // 3. Создать экземпляр A2 класса ArrayNumbers который содержит нулевой массив // и заполнить его данными из файла file1.bin ArrayNumbers A2; A2.Print("A2"); // A2 = { } A2.ReadFromFile("file1.bin"); A2.Print("A2"); // A2 = { 2.8, 3.5, 1.4, 7.0, 2.5 } }
Результат выполнения программы
A1: A => { 2.8 3.5 1.4 7 2.5 } A2: A => { } A2: A => { 2.8 3.5 1.4 7 2.5 }
⇑
2. Пример записи/чтения объекта класса, содержащий поля типа string
Чтобы записать строку типа string в бинарный файл, нужно выполнить следующую последовательность действий:
- получить длину строки (число символов). Это целое число типа int;
- записать длину строки в файл;
- с помощью цикла записать каждый символ строки в файл.
Чтобы считать строку из файла в объект типа string, нужно следовать следующей последовательности:
- считать длину строки из файла как тип int;
- в цикле посимвольно считать всю строку в некоторый участок памяти или использовать добавление символа в конец строки в каждой итерации цикла.
В примере реализуется сериализация объекта типа BOOK. Класс BOOK описывает книгу, содержащую следующие поля:
- author – название автора книги. Это поле типа string;
- title – заголовок книги, поле типа string;
- year – год издания;
- price – стоимость книги.
Класс также содержит ряд методов оперирования данными о книге и сохранения/чтения этих данных:
- BOOK() – параметризированный конструктор;
- Author() – метод, возвращающий имя автора книги;
- Title() – заголовок книги;
- Year() – год издания;
- Price() – стоимость книги;
- SaveToFile() – метод, записывающий текущее значение внутренних полей в файл;
- ReadFromFile() – метод, считывающий значение полей из файла.
Текст демонстрационной программы следующий.
#include <iostream> #include <fstream> using namespace std; // Клас, описывающий книгу class BOOK { private: // Внутренние поля string author; // автор string title; // название книги int year; // год издания float price; // стоимость public: // Конструктор BOOK(string _author, string _title, int _year, float _price) { author = _author; title = _title; year = _year; price = _price; } // Методы чтения данных string Author() { return author; } string Title() { return title; } int Year() { return year; } float Price() { return price; } // Метод, выводящий значения полей (внутреннее состояние объекта) void Print(string msg) { cout << msg << endl; cout << "Author = " << author << endl; cout << "Title = " << title << endl; cout << "Year = " << year << endl; cout << "Price = " << price << endl; cout << "-----------------------------" << endl; } // Методы записи/чтения текущих данных (сериализации) bool SaveToFile(const char* filename) { // 1. Создать экземпляр класса ofstream ofstream outF(filename, ios::out | ios::binary); // 2. Проверка, создан ли файл, // если не создан то выход с кодом false if (!outF.is_open()) return false; // 3. Записать данные полей // 3.1. Поле author типа string // 3.1.1. Определить длину строки типа string int len = author.length(); // 3.1.2. Записать длину строки author outF.write((char*)&len, sizeof(int)); // 3.1.3. Записать каждый символ строки в цикле for (int i = 0; i < author.length(); i++) outF.write((char*)&author[i], sizeof(author[i])); // 3.2. Поле title типа string // 3.2.1. Получить длину строки len = title.length(); // 3.2.2. Записать длину строки title outF.write((char*)&len, sizeof(int)); // 3.2.3. Записать посимвольно строку for (int i = 0; i < title.length(); i++) outF.write((char*)&title[i], sizeof(title[i])); // 3.3. Поле year типа int outF.write((char*)&year, sizeof(int)); // 3.4. Поле price типа float outF.write((char*)&price, sizeof(float)); // 4. Закрыть файл outF.close(); // 5. Код возврата return true; } // Чтение массива из файла и запись в текущий экземпляр bool ReadFromFile(const char* filename) { // 1. Создать экземпляр класса ifstream ifstream inF(filename, ios::in | ios::binary); // 2. Проверка, открыт ли файл успешно if (!inF.is_open()) return false; // 3. Чтение полей объекта // 3.1. Поле author // 3.1.1. Считать количество элементов в поле author int len; inF.read((char*)&len, sizeof(int)); // 3.1.2. Считать строку в цикле char c; author = ""; for (int i = 0; i < len; i++) { inF.read((char*)&c, sizeof(c)); author += c; } // 3.2. Поле title типа string // 3.2.1. Получить длину строки inF.read((char*)&len, sizeof(int)); // 3.2.2. Считать строку в цикле title = ""; for (int i = 0; i < len; i++) { inF.read((char*)&c, sizeof(c)); title += c; } // 3.3. Поле year типа int inF.read((char*)&year, sizeof(int)); // 3.4. Поле price типа float inF.read((char*)&price, sizeof(float)); // Закрыть файл inF.close(); // Код возврата return true; } }; void main() { // 1. Создать объект типа BOOK BOOK obj1("Author", "Title", 2000, 2.55); // 2. Сохранить объект obj1 obj1.Print("obj1"); // 3. Записать в файл obj1 => "obj1.bin" obj1.SaveToFile("obj1.bin"); // 4. Создать другой объект типа BOOK BOOK obj2("--", "--", 3000, 10.88); obj2.Print("obj2"); // 5. Считать данные из файла obj1.bin в объект obj2 // "obj1.bin" => obj2 obj2.ReadFromFile("obj1.bin"); // 6. Вывести прочитанный объект obj2.Print("obj2 <= obj1.bin"); }
Результат выполнения программы
obj1 Author = Author Title = Title Year = 2000 Price = 2.55 ----------------------------- obj2 Author = -- Title = -- Year = 3000 Price = 10.88 ----------------------------- obj2 <= obj1.bin Author = Author Title = Title Year = 2000 Price = 2.55 -----------------------------
⇑
3. Пример записи/чтения объекта класса, содержащий вложенные объекты другого класса
Данный пример рассматривает случай, когда в классе объявляются объекты (экземпляры) другого класса, и эти объекты нужно корректно сохранять и считывать. По приведенному примеру можно сохранять и считывать структурные переменные (структуры).
Чтобы сохранить вложенные объекты в классе, нужно получить непосредственно данные этих объектов в виде примитивных типов (int, doulbe, char и т.п.).
При записи и считывании данных (внутренних полей) объектов важно соблюдать следующее правило:
- последовательность записываемых данных должна совпадать с последовательностью считываемых данных. Так, например, если записываются данные x, y, z, то в том же порядке эти данные должны считываться.
В примере демонстрируется сохранение экземпляра класса Line, в котором есть два встроенных объекта типа Point. Для записи/чтения в классе Line соответственно реализовано два метода SaveToFile() и ReadFromFile().
Текст демонстрационной программы следующий
#include <iostream> #include <fstream> using namespace std; // Класс, описывающий точку на координатнойй плоскости class Point { private: // Внутренние поля double x, y; public: // Конструктор Point(double _x, double _y) : x(_x), y(_y) { } // Методы доступа void SetXY(double _x, double _y) { x = _x; y = _y; } double X() { return x; } double Y() { return y; } // Метод, выводящий значения x,y void Print(string msg) { cout << msg << " => x = " << x << ", y = " << y << endl; } }; // Класс, описывающий линию class Line { private: Point p1; // первая точка Point p2; // вторая точка public: // Конструктор Line(double _x1, double _y1, double _x2, double _y2) : p1(_x1, _y1), p2(_x2, _y2) { } // Методы доступа Point P1() { return p1; } Point P2() { return p2; } // Метод, выводящий координаты точек линии void Print(string msg) { cout << msg << endl; p1.Print("p1"); p2.Print("p2"); cout << "-----------------------" << endl; } // Методы, которые записывают/считывают данные bool SaveToFile(const char* filename) { // 1. Создать экземпляр outF, который связан с файлом filename, // здесь: // - ios::out - файл открывается для записи; // - ios::binary - файл открывается в двоичном режиме. ofstream outF(filename, ios::out | ios::binary); // 2. Проверка, создан ли файл if (!outF.is_open()) return false; // 3. Записать данные полей p1, p2 типа Point double x1 = p1.X(); double y1 = p1.Y(); double x2 = p2.X(); double y2 = p2.Y(); outF.write((char*)&x1, sizeof(x1)); outF.write((char*)&y1, sizeof(y1)); outF.write((char*)&x2, sizeof(x2)); outF.write((char*)&y2, sizeof(double)); // можно и так // 4. Закрыть файл outF.close(); } bool ReadFromFile(const char* filename) { // 1. Создать экземпляр inF, который связан с файлом filename ifstream inF(filename, ios::in | ios::binary); // 2. Проверка, открыт ли файл успешно if (!inF.is_open()) return false; // 3. Чтение координат линии во временные переменные double x1, y1, x2, y2; // порядок чтения полей должен совпадать с порядком записи inF.read((char*)&x1, sizeof(double)); inF.read((char*)&y1, sizeof(double)); inF.read((char*)&x2, sizeof(x2)); inF.read((char*)&y2, sizeof(y2)); // 4. Запись координат во внутренние поля текущего объекта p1.SetXY(x1, y1); p2.SetXY(x2, y2); // 5. Закрыть файл inF.close(); } }; void main() { // 1. Создать первую линию и вывести ее Line L1(2.5, 3.8, 4.1, 7.9); L1.Print("L1"); // 2. Создать вторую линию и вывести ее Line L2(1.0, 1.0, 2.0, 2.0); L2.Print("L2"); // 3. Записать первую линию в файл "line.bin" L1.SaveToFile("line.bin"); // 4. Считать данные из файла "line.bin" и записать их в объект L2 L2.ReadFromFile("line.bin"); // 5. Вывести значение объекта L2 L2.Print("L2 <= L1:"); }
Результат работы программы
L1 p1 => x = 2.5, y = 3.8 p2 => x = 4.1, y = 7.9 ----------------------- L2 p1 => x = 1, y = 1 p2 => x = 2, y = 2 ----------------------- L2 <= L1: p1 => x = 2.5, y = 3.8 p2 => x = 4.1, y = 7.9 -----------------------
⇑
Связанные темы
- Файловая система. Принципы работы файловой системы C++. Примеры. Открытие/закрытие файла
- Примеры использования средств C++ для работы с файлами
- Примеры работы с текстовыми файлами. Модификация файлов. Сортировка данных в файлах. Конвертирование строк файла в массив. Замена/удаление строки в файле. Вставка строки в файле
⇑