ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В 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;
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
Схожі підручники
- ЦЕНТРАЛЬНІ БАНКИ В СИСТЕМІ МОНЕТАРНОГО ТА БАНКІВСЬКОГО УПРАВЛІННЯ
- Загальні задачі з курсу Політекономія
- Продажи и управление продажами Учеб. пособие для вузов (часть 5) (онлайн)
- Продажи и управление продажами Учеб. пособие для вузов (часть 3) (онлайн)
- задачі з курсу ПСУ
- ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В C++ (4-Е ИЗДАНИЕ) (часть 5) онлайн