Проблемы хранения объектов на диске — КиберПедия 

Адаптации растений и животных к жизни в горах: Большое значение для жизни организмов в горах имеют степень расчленения, крутизна и экспозиционные различия склонов...

Состав сооружений: решетки и песколовки: Решетки – это первое устройство в схеме очистных сооружений. Они представляют...

Проблемы хранения объектов на диске

2021-12-07 31
Проблемы хранения объектов на диске 0.00 из 5.00 0 оценок
Заказать работу

Вернемся к классу Men, с которого мы начали изучение ООП, и рассмотрим возможности работы с его данными:

class Men{

char* name;

int age;

};

Легко сообразить, что хотя с объектами этого класса можно выполнять любые требуемые преобразования в памяти, однако их нельзя записать на диск. Действительно, после создания объекта и его инициализации переменная name будет содержать адрес символьной строки с именем индивидуума; сама строка, введенная в программу, например, с клавиатуры, будет находится где-то в памяти. Если записать такой объект на диск, а затем прочитать его (другой программой), в переменную name поступит тот же адрес, но в памяти по этому адресу уже никакого имени не будет, и фактически переменная name будет указывать на случайное место в памяти.

Для того, чтобы символьная строка входила в состав объекта, переменную name можно объявить как символьный массив:

char name[20];

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

name=”Переплетчиков”;

(имени массива name нельзя присвоить никакого значения!). Строку придется копировать:

strcpy(name,”Переплетчиков”);

Таким образом, при необходимости сохранения на диске объектов, содержащих символьные строки, возникают определенные сложности. Эти сложности можно преодолеть, используя библиотечный класс string, который и предназначен специально для повышения мобильности объектов, в состав которых входят символьные строки.

Библиотечный класса string

Класс string (его определение содержится в заголовочном файле cstring.h) предназначен для хранения символьных строк и выполнения с ними разнообразных операций. В класс входит 15 конструкторов, около пяти десятков функций-членов и большое количество перегруженных операторов, обеспечивающих практически все действия, которые приходится выполнять над символьными строками. Приведем несколько примеров.

Конструкторы

string () – конструктор по умолчанию; создает объект со строкой нулевой длины (в некоторых случаях требуется сначала создать “пустой” объект, и лишь затем заполнить его данными, например, с помощью операций копирования или чтения с диска).

string (char * str) – создание объекта класса string из символьной строки str. Другими словами, этот конструктор преобразует обычную символьную строку в объект класса string.

string (char c) – создание объекта класса string, содержащего единственный символ c.

Функции-члены

char * c _ str () – функция возвращает символьную строку, содержащуюся в объекте класса string. Фактически эта функция выполняет операцию преобразования объекта класса string в обычную символьную строку.

unsigned int length () – функция возвращает длину строки, содержащейся в объекте класса string.

string & append (string & s) – функция сцепляет объект-аргумент класса string с объектом (тоже класса string), для которой вызывается эта функция.

Перегруженные операторы

Перегруженный оператор присваивания “ = ” позволяет назначить объекту класса string значение другого объекта того же класса.

Перегруженный оператор сложения “+” выполняет операцию сцепления (конкатенации) строк, хранящихся в виде объектов класса string.

Группа перегруженных операторов “ == ”, “ != ”, “ <= ”, “ >= ”, “ < ”, “ > ” позволяет выполнять операции сравнения символьных строк, хранящихся в виде объектов класса string.

Рассмотрим несколько примеров использования объектов класса string.

Вывод в окно приложения обычной символьной строки (типа char *) выполняется следующим образом:

char txt[]=”Программа №15”;

TextOut(hdc,10,10,txt,strlen(txt));

При использовании класса string та же операция будет выглядеть так:

string txt(”Программа №15”);// Создание объекта класса string

TextOut(hdc,10,10,txt.c_str(),txt.length());

Функция Windows TextOut () требует в качестве четвертого аргумента адрес символьной строки, а в качестве пятого – ее длину. Имея объект txt класса string (который мы в этом примере создали с помощью второго из приведенных выше конструкторов класса string), адрес символьной строки мы получаем с помощью функции-члена c _ str (), а длину строки с помощью функции-члена length ().

При использовании обычных символьных строк образование составной строки требует таких операций:

char s1[]=”Имя: ”;

char s2[]=”Петров”;

char s3[100];// Строка-приемник достаточной длины

strcpy(s3,s1);// Копируем в приемник строку s 1

strcat(s3,s2);// Подцепляем строку s2

При использовании класса string тот же пример будет выглядеть заметно изящнее:

string s1(”Имя: ”);

string s2(”Петров”);

s1.append(s2);//

В последнем предложении мы вызываем функцию append () для объекта s 1, указывая в качестве аргумента функции подцепляемую строку. Обратите внимание на эффективность класса string: объекты этого класса могут изменять свою длину, и отпадает необходимость предусматривать строки-приемники с длиной, заведомо большей, чем это будет нужно в действительности.

Вернемся к нашему классу Men. Заменим указатель на символьную строку объектом типа string:

#include <cstring>

class Men{

string name;// Данное - член – объект класса string

int age;

public:

Men(string n, int a){

name=n; age=a;

}

};

При таком варианте описания класса конструктор класса должен иметь аргумент типа string и это необходимо учитывать при создании экземпляров класса:

Men m1(string(”Курбатова”), 18);

Men* pm2=new(string(”Richter”),55);

Теперь уже нельзя создать объект, указав в качестве аргумента конструктора символьную строку:

Men m1(”Курбатова”), 18);

Необходимо преобразовать символьную строку в объект класса string, что выполняется вызовом конструктора класса string:

string(”Курбатова”)

Живучие объекты

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

Рассмотрим вопрос о придании объектам свойства живучести на основе нашего простого класса Men. Приведем сначала полный текст программы, в которой создаются и записываются на диск три объекта класса Men.

/*Файл PERSIST.H*/

#include <classlib\objstrm.h>// Для операций с потоками

class Men:public TStreamableBase{

string name;

int age;

public:

char txt[200];// Для формирования строки с содержимым объекта

Men(string,int);// Прототип конструктора-инициализитора

void Print();// Формирует строку с содержимым объектом

DECLARE_STREAMABLE(,Men,1);// Класс Men объявляется потоковым

};

/*Файл PERSIST.CPP*/

#include <windows.h>

#include "pers.h"

IMPLEMENT_STREAMABLE(Men);

Men::Men(string n,int a){// Конструктор - инициализатор

name=n; age=a;

}

void Men::Print(){// Формирование строки с объектом

wsprintf (txt,"Имя: %s\nВозраст: %#x\n\n",name.c_str(),age);

}

/*Функция-член Read из вложенного класса Streamer */

LPVOID Men::Streamer::Read(ipstream& val, uint32) const{

val >> GetObject()->name>>GetObject()->age;

return GetObject();

}

/*Функция-член Write из вложенного класса Streame r */.

void Men::Streamer::Write(opstream& val) const{

val << GetObject()->name<<GetObject()->age;

}

/*Глобальные переменные*/

char report[600];// Для формирования полного отчета

Men* pMen[3];// Массив из трех указателей на класс Men

/*Главная функция WinMain ()*/

int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int){

ZeroMemory(report,strlen(report));// Обнулим report

pMen[0]=new Men(string("White"),0x11);// Создадим и

pMen[1]=new Men(string("Gray"),0x22);// инциализируем

pMen[2]=new Men(string("Black"),0x33);// объекты

for(int i=0;i<3;i++)// В цикле

pMen[i]->Print();// формируем строки с данными объектов

strcat(report,pMen[i]->txt);// и сцепляем их в report

}

MessageBox(NULL,report,"Men",MB_OK);// Выводим в окно

ofpstream obj(”1.dat”,ios::binary);// Создаем объект obj

           // класса ofpstream, связанный с файлом 1. DAT

for(int i=0;i<3;i++)// В цикле отправляем в obj

obj<<pMen[i]; // последовательно объекты Men

obj.close();// Закрываем поток и файл

return 0;// Завершение WinMain ()

}

Содержательная часть класса Men состоит из двух данных-членов name и age, обычного инициализирующего конструктора Men () и функции-члена Print () для контрольного вывода содержимого создаваемых объектов в окно сообщения. Кроме этого, в состав класса включен пустой символьный массив txt для формирования строки сообщения и макрос DECLARE _ STREAMABLE, о котором речь будет идти позже.

Дисковые операции с живучими объектами осуществляются с помощью технологии потоковых (streamable) классов, реализуемой средствами библиотеки классов ввода-вывода C++. Для того, чтобы сделать прикладной класс потоковым, его следует объявить производным от базового потокового класса TStreamableBase:

class Men:public TStreamableBase{...}

Для упрощения процедуры создания потоковых объектов в библиотеке ввода-вывода С++ предусмотрен ряд макросов, из которых важнейшими являются два: DECLARE _ STREAMABLE и IMPLEMENT _ STREAMABLE. Включение их в соответствующие места программы добавляют к нашему прикладному классу все необходимые для функционирования потоковых объектов функции.

Макрос DECLARE _ STREAMABLE включается непосредственно в состав создаваемого потокового класса. Он принимает три аргумента; первый аргумент нужен при работе с DLL-библиотеками и в данном случае не используется, вторым является имя нашего класса, а третий представляет собой номер версии объектов. Внутри этого макроса содержатся спецификаторы доступа (public и private), поэтому его лучше размещать в самом конце определения класса.

Макрос IMPLEMENT _ STREAMABLE включается в текст программы (до функции WinMain ()). В качестве аргумента ему передается имя нашего класса.

Макрос DECLARE _ STREAMABLE добавляет в наш класс пару операторов вставки в поток << и пару операторов извлечения из потока >>. Из каждой пары один оператор предназначен для работы с именами объектов, а второй – с указателями на них. Как уже отмечалось выше, в языке С++ эти операторы обозначают побитовый сдвиг на заданное число битов; однако будучи перегружены в классах opstream (класс, организующий запись потоковых объектов) и ipstream (класс чтения потоковых объектов), они выполняют совсем другие действия – помещение в поток и извлечение из него объектов прикладных классов.

Классы opstream и ipstream входят в иерархию потоковых классов, обеспечивающих операции с файлами (рис. 7.1).

Рис. 7.1. Часть иерархии потоковых классов для работы в файлами

Для выполнения потоковых операций с некоторым файлом программа должна создать объекты потоков вывода и ввода, связанные с этим файлом:

ofpstream outstrm(”Myfile.dat,ios::binary”);// Объект вывода

                             // в файл в двоичном режиме

ifpstream instrm(”Myfile.dat,ios::binary ”);// Объект ввода

                              // из файла в двоичном режиме

Поскольку операторы вставки в поток и извлечения из потока перегружены для классов opstream и ipstream, они наследуются производными от них классами ofpstream и ifpstream и, таким образом, могут быть применены к нашим объектам outstrm и instrm:

Men m("Row",10);

outstrm << m;// В поток outstrm передается объект

        // нашего класса с указанием имени объекта

Men* pm = new Men("Dow",12);

outstrm << pm;// Тот же синтаксис (!), хотя теперь объект

         // передается в поток посредством указателя

То же для операторов извлечения из потока:

Men m;// Создается пустой объект нашего класса по имени

instrm >> m;// Из потока instrm объект передается в переменную m

Men* pm=new Men;// Создается пустой объект посредством указателя

instrm >> pm;// Из потока объект передается через указатель pm

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

Функции перегрузки операторов << и >>, определенные в классах opstream и ipstream, включают в себя вызов целого ряда служебных функций (которые, в частности, записывают в файл и читают из него, кроме собственно данных, еще и служебную информацию – префикс и суффикс записи), которые в итоге вызывают функции Write () и Read () класса Streamer. В этих функциях, содержимое которых в известной степени определяется программистом, как раз и определяется, какие именно данные из объекта нашего прикладного класса и в каком порядке будут записываться в файл и затем читаться из него. Каким образом функции Streamer:: Write () и Streamer:: Read () включаются в нашу программу?

Выше указывалось, что перегруженные операторы вставки и извлечения добавляются в наш прикладной класс макросом DECLARE _ STREAMABLE. Второе важное действие, выполняемое этим макросом, состоит в том, что он включает в наш класс вложенный класс с именем Streamer. Этот класс наследует от своего базового класса TStreamer две упомянутые выше чисто виртуальные функции Read () для чтения из потока и Write () для записи в поток с такими прототипами:

virtual void* Read(ipstream&, uint32) = 0;

virtual void Write(opstream&) = 0;

Поскольку эти функции объявлены в классе Streamer как чисто виртуальные, их необходимо определить в нашем классе, даже если мы, например, собираемся только записывать в файл, но не читать из него (или наоборот). Хотя функции Read () и Write () формально являются прикладными, их определение подчиняется жестким правилам. Функция записи в поток Write () определяется для рассматриваемого примера следующим образом:

void Men::Streamer::Write(opstream& outstrm) const{

outstrm << GetObject()->name << GetObject()->age;

}

Здесь outstrm – произвольное имя потока, который связан с конкретным файлом, и через который данные будут записываться в этот файл. GetObject () – функция класса Streamer, возвращающая адрес объекта нашего класса, который направляется в поток outstrm, а name и age – имена членов-данных этого объекта. Поскольку возвращаемым значением функции GetObject () является указатель на объект, конструкция

GetObject()->data

представляет конкретное данное-член data этого объекта.

Однако как действует в данном случае оператор <<? Выше было показано, что если в качестве аргумента этого оператора (т. е. переменной, стоящей справа от него) используется объект прикладного класса, то компилятор подставляет перегруженный оператор класса opstream. В функции же Write () аргументом оператора вставки выступают не объекты, а конкретные данные-члены самых разных типов (в нашем примере – string и int). Как известно, при наличии нескольких перегруженных функций компилятор определяет, какую именно функцию следует использовать, по списку аргументов ее вызова. Таким образом, операторная функция вставки с аргументом-объектом и такая же функция с аргументом-данным представляют собой совершенно различные функции, которые могут выполнять различающиеся действия.

В самом деле, в состав класса opstream включено большое количество перегруженных операторов вставки <<. Один из них, требующий в качестве аргумента имя объекта или указатель на объект, действует так, как было описано выше – вызывает целый ряд служебных функций и, в конце концов, функцию Streamer:: Write (). Остальные перегруженные операторы вставки, по одному на каждый тип данных (char, unsigned char, int, unsigned int, short, float, string и т. д.) просто записывают в поток соответствующее данное. В функции Write () используются как раз эти “элементарные” (хотя и перегруженные!) операторы вставки.

Необходимо иметь в виду, что порядок перечисления членов нашего класса (name и age в данном случае), в принципе произвольный, определяет порядок записи данных в файл. В дальнейшем при чтении из файла данные следует принимать в программу в том же порядке.

В приведенном примере в файл направляются два члена класса name и age. Если в классе данных-членов больше, то цепочка операторов вставки в поток << вместе с вызовами функции GetObject () становится длиннее:

outstrm<<GetObject()->d1<<GetObject()->d2<<GetObject()->d3...;

Здесь d 1, d 2, d 3 и т. д. – имена членов класса, выводимых в поток.

Функция чтения из потока Read () определяется чуть сложнее:

LPVOID Men::Streamer::Read(ipstream& instrm, uint32) const{

instrm >> GetObject()->name >> GetObject()->age;

return GetObject();

}

Здесь обозначения имеют тот же смысл, только поток instrm направляет данные из файла, с которым он связан, в указанные данные объекта класса. Все приведенные выше рассуждения относительно перегруженных операторов вставки справедливы и для перегруженных операторов извлечения >>. Если в качестве аргумента этого оператора выступает объект, автоматически вызывается функция Streamer:: Read (); в самой функции Read () используются другие варианты операторов извлечения с аргументами-данными, выполняющими просто перенос данных из потока, связанного с файлом, в данные-члены прикладного класса.

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

ZeroMemory(report,strlen(report));// Обнулим report

ifpstream instrm(”Myfile.dat,ios::binary”);// Создаем

               // объект instrm класса ifpstream для ввода

               // из файла MYFILE. DAT в двоичном режиме

Men* pMan=new Men;// Создаем один “пустой” объект для чтения

for(int i=0;i<3;i++){

instrm>>pMan;// Читаем из файла объект за объектом

pMan->Print();// Формируем строку вывода

strcat(report,pMan->txt);// и переносим ее в report

}

MessageBox(NULL,report,"Чтение",MB_OK);// Вывод в окно

instrm.close();// Закроем поток ввода и файл

Обратите внимание на важное обстоятельство: для того, чтобы можно было создать “пустой” объект класса для чтения в него данных с диска, в классе необходимо иметь конструктор по умолчанию. В определение класса Men в файле PERSIST.H такой конструктор включен не был. Таким образом, чтобы приведенный выше фрагмент мог выполняться, класс Men необходимо дополнить конструктором по умолчанию, т. е. конструктором без параметров, не выполняющим никаких действий:

Men(){}// Конструктор по умолчанию для создания ”пустого” объекта


Часть 2
Лабораторный практикум

Работы лабораторного практикума

Работа №1. Понятия класса и объекта
(индивидуальное задание А)

Опишите в заголовочном файле (с расширением.H) класс, состоящий из указанных в индивидуальном задании A закрытых данных-членов и соответствующего количества открытых функций-членов Set xxx () для инициализации данных. Кроме этого, включите в состав класса открытую функцию Print () для распечатки в окне сообщения значений объектов класса в наглядном формате с соответствующими комментариями (например: “ Возраст: 22 ” или “ Банк: Гута-банк ”). В описание класса включите не только прототипы функций Set xxx (), но и их реализацию, т. е. сделайте эти функции встроенными.

В невстроенной функции Print () объявите достаточно длинный пустой символьный массив txt для формирования выводимой строки и включите вызовы функций Windows wsprintf () для формирования строки и MessageBox () для ее вывода. Предусмотрите в функции Print () анализ значения булевой переменной-члена и вывод этого значения в виде разумной строки текста (например: “ Наличие: есть ” или “ Наличие: нет ”).

Составьте программу (файл.CPP), в которой создаются два объекта описанного класса, один – по имени, а другой – с помощью указателя. Вызовом функций Set xxx () инициализируйте данные обоих объектов конкретными значениями, а функцией Print () выведите в последовательные окна сообщений содержимое обоих объектов.


Поделиться с друзьями:

Индивидуальные и групповые автопоилки: для животных. Схемы и конструкции...

Общие условия выбора системы дренажа: Система дренажа выбирается в зависимости от характера защищаемого...

Типы оградительных сооружений в морском порту: По расположению оградительных сооружений в плане различают волноломы, обе оконечности...

Опора деревянной одностоечной и способы укрепление угловых опор: Опоры ВЛ - конструкции, предназначен­ные для поддерживания проводов на необходимой высоте над землей, водой...



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.084 с.