Главная->Інформатика та програмування->Содержание->Как классы записывают и читают сами себя

ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В C++ (4-Е ИЗДАНИЕ) (часть 12) онлайн

Как классы записывают и читают сами себя

Предположим, что в памяти хранится большое число объектов, и все их нужно записать в файлы. Недостаточно иметь для каждого из них метод, который от- кроет файл, запишет в него объект, потом закроет файл, как было в предыдущем

примере. Гораздо быстрее и логичнее в такой ситуации открыть файл, записать в него все объекты, которые этого хотят, потом закрыть файл.

Статические функции

Одним из способов записать за один сеанс множество объектов является упот- ребление статической функции, которая применяется ко всему классу в целом, а не к отдельным его объектам. Такая функция действительно может записать все объекты сразу. Но как она узнает, где находятся объекты? Она обратится к массиву указателей на объекты, который можно хранить в виде статической пе- ременной. При создании каждого объекта его указатель заносится в этот массив. Статический элемент данных также может свято хранить память о том, сколько всего объектов было создано. Статическая функция write() откроет файл, в цикле пройдет по всем ссылкам массива, записывая по очереди все объекты, и после окончания записи закроет файл.

Размеры порожденных объектов

Чтобы жизнь медом не казалась, давайте сделаем следующее предположение: объекты, хранящиеся в памяти, имеют разные размеры. Почему и когда такое бывает? Типичной предпосылкой этого является создание порожденных клас- сов. Например, рассмотрим программу EMPLOY из главы 9 «Наследование». Там у нас был класс employee, выступавший как базовый для классов manager, scientist и laborer. Объекты этих трех порожденных классов имеют разные размеры, так как содержат разные объемы данных. Например, в дополнение к имени и поряд- ковому номеру, которые присущи всем работникам, у менеджера есть еще такие поля данных, как должность и членские взносы гольф-клуба, а у ученого есть по- ле данных, содержащее количество его публикаций.

Нам хотелось бы записать данные из списка, содержащего все три типа по- рожденных объектов, используя простой цикл и метод write() класса ofstream. Но нам нужно знать размеры объектов, чтобы передать их в качестве второго аргумента этой функции.

Пусть имеется массив указателей arrар[], хранящий ссылки на объекты типа employee. Указатели могут ссылаться, таким образом, и на все три порожденных класса (см. программу VIRTPERS из главы 11, там тоже имеется массив указате- лей на объекты порожденных классов). При использовании виртуальных функ- ций можно использовать выражения типа

 

arrap[]->putdata();

 

Тогда будет поставлена на исполнение во время работы программы версия putdata(), соответствующая объекту, на который ссылается указатель, а не та, что соответствует базовому классу. Можно ли использовать sizeof() для вычис- ления размера ссылочного аргумента? То есть можно ли написать, например, такое выражение:

 

ouf.write( (char*)arrap[j], sizeof(*arrap[j]) ); //плохо это

 

Увы, нельзя, потому как sizeof() не является виртуальной функцией. Она не в курсе, что нужно обращаться к типу объекта, на который ссылается указатель,

а не на тип самого указателя. Эта функция всегда будет возвращать размер объ- екта базового класса.

Использование функции typeid()

И все-таки, как же нам найти размер объекта, если все, что мы имеем, это указа- тель на него? Одним из решений является, несомненно, функция typeid(), пред- ставленная в главе 11. Можно использовать ее для определения класса объекта, подставляя имя этого класса в качестве аргумента sizeof(). Чтобы использовать typeid(), надо включить параметр компилятора RTTI. (это существенно только для компилятора Microsoft Visual C++, см. приложение В «Microsoft Visual C++»),

Наш следующий пример показывает, как все вышеописанное работает. Узнав размер объекта, мы можем использовать его в функции write() для записи в файл.

К программе EMPLOY добавился несложный пользовательский интерфейс. Некоторые специфические методы сделаны виртуальными, чтобы можно было пользоваться массивом указателей на объекты. В программу также включены некоторые приемы обнаружения ошибок, описанные в предыдущем параграфе.

Программа получилась довольно претенциозной, но, тем не менее, она дей- ствительно демонстрирует многие приемы, которые могут быть использованы в настоящем приложении для работы с базами данных. Она также показывает реальные возможности ООП. Что бы мы делали без него? Как бы смогли с помо- щью одного выражения записывать в файл объекты разных размеров? Приводим листинг программы EMPL_IO и рекомендуем вникнуть в каждую строчку кода.

 

Листинг 12.17. Программа EMPL_IO

// empl_io.cpp

// Файловый ввод/вывод объектов employee

// Поддержка объектов неодинаковых размеров

#include <fstream>      // для потоковых файловых функций

#include <iostream>

#include <typeinfo>     // для typeid()

using namespace std;

#include <process.h>    // для exit()

 

const int LEN = 32;     // Максимальная длина фамилий

const int MAXEM = 100;  // максимальное число работников

 

enum employee_type {tmanager, tscientist, tlaborer};

///////////////////////////////////////////////////////////

class employee                // класс employee

{

private:

                char name[LEN];           // фамилия работника

                unsigned long number;     // номер работника

                static int n;             // текущее число работников

                static employee* arrap[]; //массив указателей на класс работников

public:

                virtual void getdata()

                {

                               cin.ignore(10, '\n');

                               cout << "  Введите фамилию: "; cin >> name;

                               cout << "  Введите номер: ";    cin >> number;

                }

                virtual void putdata()

                {

                               cout << "\n  Фамилия: " << name;

                               cout << "\n  Номер: " << number;

                }

                virtual employee_type get_type(); // получить тип

                static void add();                // добавить работника

                static void display();            // вывести данные обо всех

                static void read();               // чтение из файла

                static void write();              // запись в файл

};

//---------------------------------------------------------

//статические переменные

int employee::n;                  // текущее число работников

employee* employee::arrap[MAXEM]; // массив указателей на класс работников

///////////////////////////////////////////////////////////

//класс manager (менеджеры)

class manager : public employee

{

private:

                char title[LEN];  // титул ("вице-президент" и т. п.)

                double dues;      // Налоги гольф-клуба

public:

                void getdata()

                {

                               employee::getdata();

                               cout << "  Введите титул: ";       cin >> title;

                               cout << "  Введите налоги: "; cin >> dues;

                }

                void putdata()

                {

                               employee::putdata();

                               cout << "\n  Титул: " << title;

                               cout << "\n  Налоги гольф-клуба: " << dues;

                }

};

///////////////////////////////////////////////////////////

//класс scientist (ученые)

class scientist : public employee

{

private:

                int pubs;              // число публикаций

public:

                void getdata()

                {

                               employee::getdata();

                               cout << "  Введите число публикаций: "; cin >> pubs;

                }

                void putdata()

                {

                               employee::putdata();

                               cout << "\n  Число публикаций: " << pubs;

                }

};

Листинг 12.17 (продолжение)

///////////////////////////////////////////////////////////

//класс laborer (рабочие)

class laborer : public employee

{

};

///////////////////////////////////////////////////////////

//добавить работника в список (хранится в ОП)

void employee::add()

{

                char ch;

                cout << "'m' для добавления менеджера"

                               "\n's' для добавления ученого"

                                "\n'l' для добавления рабочего"

                               "\nВаш выбор: ";

                cin >> ch;

                switch(ch)

                {          //создать объект указанного типа

                case 'm': arrap[n] = new manager;  break;

                case 's': arrap[n] = new scientist; break;

                case 'l': arrap[n] = new laborer;  break;

                default: cout << "\nНеизвестный тип работника\n"; return;

                }

                arrap[n++]->getdata();  //Получить данные от пользователя

}

//---------------------------------------------------------

//Вывести данные обо всех работниках

void employee::display()

{

                for(int j=0; j<n; j++)

                {

                               cout  << (j+1);                // вывести номер

                               switch( arrap[j]->get_type() ) //вывести тип

                               {

                               case tmanager:  cout << ". Тип: Менеджер";  break;

                               case tscientist: cout << ". Тип: Ученый"; break;

                               case tlaborer:   cout << ". Тип: Рабочий";  break;

                               default: cout << ". Неизвестный тип";

                               }

                               arrap[j]->putdata();           // Вывод данных

                               cout << endl;

                }

}

//---------------------------------------------------------

//Возврат типа объекта

employee_type employee::get_type()

{

                if( typeid(*this) == typeid(manager) )

                               return tmanager;

                else if( typeid(*this)==typeid(scientist) )

                               return tscientist;

                else if( typeid(*this)==typeid(laborer) )

                               return tlaborer;

                else

                { cerr << "\nНеправильный тип работника"; exit(1); }

                return tmanager;

}

 

 

//---------------------------------------------------------

//Записать все объекты, хранящиеся в памяти, в файл

void employee::write()

{

                int size;

                cout << "Идет запись " << n << " работников.\n";

                ofstream ouf;           // открыть ofstream в двоичном виде

                employee_type etype;   // тип каждого объекта employee

 

                ouf.open("EMPLOY.DAT", ios::trunc | ios::binary);

                if(!ouf)

                { cout << "\nНевозможно открыть файл\n"; return; }

                for(int j=0; j<n; j++)  // Для каждого объекта

                {                     // получить его тип

                               etype = arrap[j]->get_type();

                               // записать данные в файл

                               ouf.write( (char*)&etype, sizeof(etype) );

                               switch(etype)         // find its size

                               {

                               case tmanager:  size=sizeof(manager); break;

                               case tscientist: size=sizeof(scientist); break;

                               case tlaborer:  size=sizeof(laborer); break;

                               }      //запись объекта employee в файл  

                               ouf.write( (char*)(arrap[j]), size );

                               if(!ouf)

                               { cout << "\nЗапись в файл невозможна\n"; return; }

                }

}

//---------------------------------------------------------

//чтение всех данных из файла в память

void employee::read()

{

                int size;               // размер объекта employee

                employee_type etype;    // тип работника

                ifstream inf;           // открыть ifstream в двоичном виде

                inf.open("EMPLOY.DAT", ios::binary);

                if(!inf)

                { cout << "\nНевозможно открыть файл\n"; return; }

                n = 0;                  // В памяти работников нет

                while(true)

                {                     // чтение типа следующего работника

                               inf.read( (char*)&etype, sizeof(etype) );

                               if( inf.eof() )       // выход из цикла по EOF

                                               break;

                               if(!inf)              // ошибка чтения типа

                               { cout << "\nНевозможно чтение типа\n"; return; }

                               switch(etype)

                               {                   // создать нового работника

                               case tmanager:      // корректного типа

                                               arrap[n] = new manager;

                                               size=sizeof(manager);

                                               break;

                               case tscientist:

                                               arrap[n] = new scientist;

                                               size=sizeof(scientist);

                                               break;

 

Листинг 12.17 (продолжение)

                               case tlaborer:

                                               arrap[n] = new laborer;

                                               size=sizeof(laborer);

                                               break;

                               default: cout << "\nНеизвестный тип в файле\n"; return;

                               }                   // чтение данных из файла

                               inf.read( (char*)arrap[n], size  );

                               if(!inf)              // ошибка, но не EOF

                               { cout << "\nЧтение данных из файла невозможно\n"; return; }

                               n++;                  // счетчик работников увеличить

                }  //end while

                cout << "Идет чтение " << n << " работников\n";

}

///////////////////////////////////////////////////////////

int main()

{

                system("chcp 1251 > nul");

 

                char ch;

                while(true)

                {

                               cout << "'a' – добавление сведений о работнике"

                                               "\n'd' - вывести сведения обо всех работниках"

                                               "\n'w' – записать все данные в файл"

                                               "\n'r' – прочитать все данные из файла"

                                               "\n'x' - выход"

                                               "\nВаш выбор: ";

                               cin >> ch;

                               switch(ch)

                               {

                               case 'a':           // добавить работника

                                               employee::add(); break;

                               case 'd':           // вывести все сведения

                                               employee::display(); break;

                               case 'w':           // запись в файл

                                               employee::write(); break;

                               case 'r':           // чтение всех данных из файла

                                               employee::read(); break;

                               case 'x': exit(0);  // выход

                               default: cout << "\nНеизвестная команда";

                               }  //end switch

                }  //end while

                return 0;

 

38