Приклади збереження/читання об’єктів класу. Серіалізація
Дана тема містить приклади виконання серіалізації об’єктів класів. У найбільш загальному випадку, серіалізація це властивість об’єкту зберігати свій стан у файловому потоці та (за вимогою) відновлювати його.
Зміст
- 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++ для роботи з файлами
- Приклади роботи з текстовими файлами. Модифікація файлів. Сортування даних у файлах. Конвертування рядків файлу в масив. Заміна/видалення рядка у файлі. Вставка рядка у файлі
⇑