Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
РУКОВОДСТВО TURBO VISION ДЛЯ C++ TV Turbo Visio...doc
Скачиваний:
2
Добавлен:
01.04.2025
Размер:
5.2 Mб
Скачать

Глава 8. Объекты, хранимые с потоками

-----------------------------------------------------------------

В этой главе рассказывается о том, как при помощи потоков в

Turbo Vision осуществляется хранение объектов. Различные экземп-

ляры классов, созданные для прикладных программ, разработанных с

помощью Turbo Vision (окна, меню и т.д.), имеют очень короткую

жизнь. Они создаются, отображаются во время работы программы.

Объекты могут появляться и исчезать, подчиняясь при этом правилам

видимости, принятым в языке. При завершении программы все объекты

из памяти удаляются. Обработчик потоков предоставляет возможность

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

в определенном состоянии, отличном от обычного.

Существует множество прикладных программ использующих спо-

собные сохраняться объекты. При хранении в разделяемой памяти,

например, они могут поддерживать взаимосвязь между процессами.

Они могут передаваться через модемы в другие системы, и, что осо-

бенно важно, могут сохраняться на диске при помощи потоков. Затем

можно аналогичными прикладными программами прочитать файлы, где

сохранены объекты и восстановить их первоначальное содержание.

Такие возможности предоставляются всем объектам, созданным в

Turbo Vision - все основные классы (группы, видимые объекты,

TCollection и все полученные из него классы и ресурсы) имеют воз-

можность передаваться через потоки. При этом управление потоками,

осуществляется также просто, как и в случае работы с обыкновенным

файловым вводом/выводом обыкновенных файлов.

Создание своих собственных классов, обладающих такими свойс-

твами не представляет особого труда. При этом необходимо сделать

лишь небольшое добавление к определению класса. Ему необходимы

три дополнительные виртуальные функции - read, streamableName, и

write. Таким образом, все классы, нуждающиеся во взаимодействии с

потоками должны получаться из класса TStreamable (неважно прямо

или косвенно). Класс TView уже имеет в качестве одного из своих

"родителей" класс TStreamable. Аналогично можно сказать и о клас-

се TGroup и всех полученных из него классах.

Объекты потока можно легко создавать при помощи класса

pstream и его "наследников". Эти классы специально разработаны

для потоков, сохраняющих объекты, однако использование их абсо-

лютно аналогично использованию стандартных потоков С++ iostream.

Если вы знакомы с потоками С++, то многое из сказанного ниже вам

уже известно. Для тех, кто еще не знаком о потоками, проводится

короткое введение в терминологию и основные идеи ввода/вывода в

С++. Затем они рассматриваются более детально.

В С++, как и в обыкновенном языке Си не имеются ключевые

слова или предопределенные операторы для обеспечения ввода/выво-

да. Вместо этого реализованы функции стандартной библиотеки:

stdio для Си и iostream для С++. При этом, библиотека С++, ис-

пользуя преимущества ООП, более гибкая, расширяемая и надежная

благодаря переопределенным операторам и вводу/выводу, сохраняюще-

Turbo Vision для С++ = 200 =

му как стандартные, так и определенные пользователем типы данных.

Хотя функции библиотеки stdio, например printf, доступны и в С++,

большинство программистов предпочитают преимущества библиотеки

iostream, использующей потоки. Что же такое поток?

Поток - это абстрактный тип данных, представляющий собой

(конечно же в теории) последовательность элементов с различными

возможностями доступа. Потоки имеют длину (количество элементов),

текущую позицию (уникальную точку, которая доступна именно в дан-

ный момент) и режим доступа (только для чтения (read-only), толь-

ко для записи (write-only) и для чтения/записи (read/write). Чте-

ние (или извлечение) может осуществляться в любой позиции (в лю-

бом месте потока), тогда как запись (добавление) выполняется

посредством добавления элементов в конец потока.

Традиционный дисковый файл - один из известных примеров реа-

лизации потока, однако концепция потока была распространена и на

такие устройства как память, клавиатура (так называемый стандарт-

ный ввод cin), дисплей (стандартный вывод cout и cerr), коммуни-

кационные порты и т.д. Большинство этих идей заимствовано из опе-

рационной системы UNIX, где применяется принцип "все является

файлом". Фактически с помощью объектно-ориентированной технологии

любые источники данных или объектов могут обеспечиваться соот-

ветствующими функциями и рассматриваться как потоки ввода.

Аналогично, любых потребителей этих данных можно расширить

функциями вывода и рассматривать как потоки вывода.

Потоки, связанные с дисковыми файлами, обычно поддерживают

операции ввода и вывода. Для классов, взаимодействующих с потока-

ми, существует большое количество разновидностей ввода и вывода:

буферизованный или прямой, форматированный и безформатный, файло-

вый или в память, а также различные комбинации этих версий. Для

большей гибкости объектов в Turbo Vision используется библиотека

iostream, обеспечивающая все перечисленные выше преимущества.

Turbo Vision для С++ = 201 =

Переопределенные операции << и >>

-----------------------------------------------------------------

Одним из моментов, обеспечивающих успех потоков С++, явля-

ется удобство переопределенных операций: << для вывода и >> для

ввода. Таким образом, комплексный синтаксис printf и других функ-

ций stdio заменен простым и элегантным выражением, например, та-

ким:

cout << "Hello, World" << endl;

Обеспечивая возможность легко переопределять операции << и

>> для различных типов данных и классов, взаимодействующих с по-

токами, объекты этих классов можно записать в поток, а затем про-

читать оттуда баз каких-либо затруднений. Для стандартных типов

С++, таких как char, short, int, long, char*, float, double и

void *. Такое переопределение уже сделано в библиотеке iostream.

Для типов данных, определенных пользователем, это можно сделать

довольно легко. Последнее касается данных, не являющихся класса-

ми, так как для объектов классов это сделать труднее. Однако, в

этом случае Turbo Vision основную работу берет на себя, позволяя

писать и читать объекты при помощи выражений таких как, например,

следующие:

os << aTVObject << anotherTVObject;

is >> yetAnotherTVObject;

При этом программист не думает о реализации этих операций.

Ему необходимо, лишь, сделать некоторые подготовительные дейс-

твия, прежде чем создавать и регистрировать свои классы, взаимо-

действующие с потоками. Об этом будет рассказано ниже в данной

главе. В последнем примере os является объектом класса opstream.

Это же относится и к объекту is, который принадлежит классу

ipstream (или полученному из него классу). Оба этих класса полу-

чены из pstream - класса, являющегося основой всех остальных

классов, взаимодействующих с потоками. Они аналогичны классам

istream и ostream, полученным из ios, в иерархии классов С++.

Класс iopstream получен комбинированием классов ipstream и

opstream. Имеются, также, версии классов для работы с файлами.

Они называются ifstream, opstream и fpstream, и соответствуют

стандартным классам С++ ifstream, ofstream и fstream. Все соот-

ветствующие классы С++ и Turbo Vision имеют аналогичные поля дан-

ных и функции. На Рис. 8.1 показаны иерархия класса pstream. Как

принято в С++, стрелка указывает прямо на базовый класс.

Turbo Vision для С++ = 202 =

┌────────────────┐ (f) ┌─────────────────┐

│ pstream ├───────┤ Типы TSreamable │

└────────────────┘ └─────────────────┘

(v)^(v)^ ^(v)

┌────────┘ │ └────────────────────┐

┌───┐(f)┌─────┴────┐ ┌────┴─────┐(f)┌──┐ ┌──────┴───────┐

│>> ├───┤ ipstream │ │ opstream ├───┤<<│ │ fpbase │

└───┘ └──────────┘ └──────────┘ └──┘ └──────────────┘

^ ^ ^ ^ ^ ^ ^

│ └────┬───┘ └───────┬─────────┘ │ │

│ ┌─────┴─────┐ ┌─────┴─────┐ │ │

│ │ iopstream │ │ ofpstream │ │ │

│ └───────────┘ └───────────┘ │ │

│ ^ │ │

│ └───────────────┬──────────────┘ │

│ ┌─────┴─────┐ │

│ │ fpstream │ │

│ └───────────┘ │

└─────────────────────┬──────────────────────┘

┌─────┴─────┐

v - виртуальный │ ifpstream │

f - дружественный └───────────┘

Рис. 8.1. Иерархия класса pstream

Переопределенные операции <<, которые используются для запи-

си объектов TView и указателей на эти объекты, определены в файле

VIEWS.H следующим образом:

inline opstream& operator << (opstream& os, TView& cl);

inline opstream& operator << (opstream& os, TView* cl);

В первом случае объект cl класса TView будет записан в по-

ток, представленный объектам класса opstream os.

Аналогичные действия выполняет и операция <<, переопределен-

ная в стандартом классе С++ ostream. Во втором случае тоже самое

происходит с указателем на объект класса TView. Видно, что эта

операция поддерживает очень простую передачу объектов в качестве

параметров и вызывает операцию <<, определенную в базовом классе.

В этом случае класс TStreamable является базовым для класса

TView.

Похожие переопределения операций << и >> сделаны для всех

стандартных классов Turbo Vision. Обычно переопределение сопро-

вождается объявлением этих операций как inline. Иногда необходимо

написать небольшой текст функции переопределения для каких-либо

созданных пользователем классов, взаимодействующих с потоками.

Это может показаться повторяющейся терминологией! Так что же та-

кое классы, взаимодействующие с потоками? Об этом читайте ниже.

Turbo Vision для С++ = 203 =

Если объекты класса можно записывать в поток и читать из по-

тока, используя средства обработчика потоков Turbo Vision, то та-

кой класс взаимодействует с потоком. Кроме наличия удобных опера-

ций ввода/вывода (обычно это << и >>, но вы можете определить

свои собственные), этот класс должен иметь в качестве своего ба-

зового класса TStreamable. Диаграмма иерархии классов Turbo

Vision показывает, что класс TView получен одновременно из

TObject и TStreamable. Все видимые классы также имеют переопреде-

ленные операции << и >> и могут взаимодействовать с потоками.

А что собой представляют "невидимые" классы? В предыдущей

главе говорилось, что TCollection имеет в качестве своего базово-

го класса TStreamable. Другим базовым классом является

класс TNSCollection). Поэтому все классы, полученные из

TCollection имеют переопределенные операции << и >> и могут взаи-

модействовать с потоками.

Знакомство с обработчиком потоков

-----------------------------------------------------------------

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

требуется некоторая подготовительная работа. Ее выполняет обра-

ботчик потоков. Если необходимо записать в поток или восстановить

из потока простой объект данных, то это выполняется без каких-ли-

бо сложностей посредством манипулирования с двоичным представле-

нием этого объекта. Однако, объекты классов могут содержать любое

количество данных различных типов, включая указатели на другие

комплексные объекты и, возможно, указатель на таблицу виртуальных

методов.

Чтобы объекты эти и другие сложности, обработчик потока под-

держивает базу данных объектов, используя TStreamableTypes,

TStreamable и TStreamableClass. Описание класса (заметьте не эк-

земпляра класса, а всего класса) занимает 16 байт. Эти классы вы-

полняют базовую регистрацию определяют функции read, write и

build для отдельных объектов. Цель регистрации класса фактически

заключается в том, что обработчику потоков сообщается информация

о данном классе, которая вводится в базу данных.

Примечание. Более подробная информация о классах

TPWrittenObj и TPReadObj содержится в разделах в главе 13.

При проведении операций чтения и записи, обработчик потока

хранит в базе данных информацию о всех объектах, записанных в по-

ток или прочитанных из него. Не вдаваясь в подробности, рассмот-

рим проблему записи объекта в поток при помощи указателей. пред-

положим, что указатели ptr1 и ptr2 указывают на один и тот же

объект и их надо записать в поток.

Когда эти объекты будут последовательно считаны из потока,

необходимо иметь две совпадающие копии объектов с различными ука-

зателями на них. Обработчик потоков и его база данных обойдут эту

Turbo Vision для С++ = 204 =

проблему: только одна копия объекта будет записана в поток. А

когда потребуется восстановить объект из потока, будет создан

лишь один его экземпляр, при этом оба указателя будут указывать

на него. При проведении операций чтения и записи обработчик пото-

ков также работает очень аккуратно. Класс TStreamable имеет пус-

тые виртуальные функции read и write (известные как читатели и

писатели), которые должны быть переопределены во всех классах,

полученных из него:

classTStreamable

...

protected:

virtual void *read(ipstream&) = 0;

virtual void write(opstream&) = 0;

Работа функции write заключается в записи всех необходимых

данных объекта в поток. Каждый класс, взаимодействующий с потока-

ми, должен иметь свою собственную реализацию функции write. Это

можно легко сделать вызвав функцию базового класса. Ниже приведе-

ны отрывки из определений функции TView::write и TGroup::write:

void TView::write(opstream& os)

{

ushort saveState =

state & ~(sfActive | sfSelected | sfFocused | sfExposed);

os << origin << size << cursor

<< growMode << dragMode << helpCtx

<< saveState << options << eventMask;

}

...

void TGroup::write(opstream& os)

(

unshort index;

TView::write(os);

TGroup *ownerSave = owner;

owner = this;

int count = indexOf(last);

os << count;

forEach(doPut,&os);

if (current == 0)

index = 0;

else

index = indexOf(current);

os << index;

owner = ownerSave;

)

Действия функции read аналогичны функции write. Каждый

класс, взаимодействующий с потоками, должен переопределять пустую

виртуальную функцию read, наследуемую от TStreamable. Это часто

делается при помощи расширения этой же функции базового класса

для обеспечения обработки дополнительных данных.

Turbo Vision для С++ = 205 =

При определенных случаях чтения возникают некоторые вопросы,

которые обсуждены ниже. Если чтение объекта из потока выполняется

в уже имеющийся объект этого типа, то прочитанные данные просто

преобразуются к этому типу. Однако, если изначально не существует

такого "пустого" объекта, его необходимо создать, а только после

этого производить восстановление объекта из потока. Эту работу

выполняет так называемый "строитель", который выделяет необходи-

мое количество памяти и устанавливает указатели на таблицу вирту-

альных методов. Каждый класс, взаимодействующий с потоками, дол-

жен включать в себя определение "строителя" и конструктор build.

Все выше рассмотренные функции уже определены для стандартных

классов Turbo Vision. Если вы хотите создать свои собственные

классы, взаимодействующие с потоками, то вы должны определить их

заново.

Конструкторы классов, взаимодействующих с потоками

-----------------------------------------------------------------

Как сказано выше, классы, чьи объекты, независимо от того

статические они или автоматические, могут использоваться в опера-

циях ввода/вывода в потоки, и должны иметь специальные конструк-

торы, которые имеют только один аргумент: streamableInit. Обычно

такие конструкторы объявляются как protected:

class TMyStreamable : public virtual TBase, public TStreamable

{

...

protected:

TMyStreamable(StreamableInit s);

...

};

Тип данных StreamableInit является перечисляемым типом

(enum) с одним членом streamableInit. Когда происходит создание

объекта при помощи конструктора:

TMyStreamable str(streamableInit);

то конструкторы для любых содержащихся указателей не вызываются,

как было бы в случае вызова стандартного конструктора. Вместо

этого происходит простое выделение памяти. Когда необходимо про-

читать объект из потока в объект Str, сначала нужно создать

пустой объект, а только после этого выполнять чтение:

TMyStreamable str(streamableInit);

// создание "пустого" str

...

ifpstream ifps("str.sav"); //открытие потока

ifps >> str; //чтение объекта в str

...

Turbo Vision для С++ = 206 =

Таким образом, объекты, созданные при помощи streamableInit,

такие как str, не могут корректно использоваться до тех пор, пока

они не будут инициализированы с помощью операций чтения из пото-

ка. Между прочим, количество и имена членов класса не имеют зна-

чения, так как аргументы, передаваемые конструктору однозначно

различаются по типу.

Функция build может быть определена следующим образом:

TStreamable *TMyStreamableClass::build()

{

return new TMyStreamableClass(streamableInit);

}

TMyStreamableClass::TMyStreamable(StreamableInit s);

TBaseClass(streamableInit)

{

}

Другими словами, конструктор build просто вызывает конструк-

тор базового класса, который в свою очередь вызывает конструктор

своего базового класса и т.д.

Имена классов, взаимодействующих с потоками

-----------------------------------------------------------------

В каждом классе, взаимодействующем с потоками, необходимо

переопределить виртуальную функцию streamableName, объявленную

как private, и унаследованную от класса TStreamable. Она должна

возвращать уникальное имя класса как обыкновенную строку, которая

заканчивается символом '\0'.

class TMyStreamable : public virtual TBase, public TStreamable

{

...

private:

virtual const char *streamableName() const;

{ return "TMyStreamable"; }

Для стандартных классов TurboVision это уже сделано. Если же

вы определите свои собственные классы, которые будут работать с

потоками, то необходимо сделать это самостоятельно. Обычно, это

выполняется так, как показано в последнем примере, т.е. вы должны

возвратить streamableName. Обработчик потоков использует уникаль-

ное имя класса для распознавания их в своей базе данных.

Turbo Vision для С++ = 207 =

Использование обработчика потоков

-----------------------------------------------------------------

Имеются три операции для использования обработчика потоков:

1. Компоновка кода с обработчиком потока.

2. Создание объектов, которые могут взаимодействовать с по-

током.

3. Использование объекта.

Рассмотрим эти операции более подробно.

Компоновка кода с обработчиком потока

Каждый класс, использующий потоки, должен определить три

функции read, write и build, разработанные для обработки объектов

в потоках.

Эти функции должны быть известны обработчику потоков и ком-

поноваться с любой прикладной программой, которая использует воз-

можности обработчика потоков. Компоновка, известная еще как ре-

гистрация класса, выполняется однократно посредством вызова мак-

рокоманды __Link. При этом вызов происходит с параметром

RClassName, где ClassName - имя регистрируемого класса, исключая

первую букву T. Например, если компонуется класс TChDirDialog, то

необходимо написать следующее выражение:

__Link(RChDirDialog);

Создание и использование объектов потока

Создание объектов потока ipstream и opstream требует объяв-

ления их с подходящими аргументами, также как и создание объектов

iostream. Чтобы сохранить объект, обеспечивающий диалог с пользо-

вателем, в файле DLG.SAV, достаточно объявить объект ofpstream

следующим образом:

// Детальная информация о конструкторах потоков и их

// аргументах приведена в главе 13.

TChDirDialog cdlg;

...

// создание диалога

ofpstream of ("dlg.sav");

//открытие выходного потока файла

of << cdlg;

// запись диалога в файл потока

Turbo Vision для С++ = 208 =

Здесь используется конструктор, который был вызван с аргу-

ментами, принимаемыми по умолчанию:

ofpstream(const char *filename, int mode = ios::out, int

prot = filebuf::openprot)

Теперь рассмотрим типичную операцию чтения:

TChDirDialog cdlg (streamableInit);

// обращение к созданному конструктору;

// создание скелетона объекта

ifpstream ifps("dlv.sav");

// открытие входного потока файла

ifps >> cdlg;

// чтение диалога из потока в cdlg

Turbo Vision для С++ = 209 =

Коллекции в потоках

-----------------------------------------------------------------

В главе 7 "Коллекции" было рассказано, как коллекции могут

содержать различные, хотя и взаимосвязанные, объекты. Аналогичные

полиморфные возможности имеются у потоков. Они могут быть исполь-

зованы для записи коллекций на диск, сохранения их там, а затем

для восстановления в другое время и, даже, другой программой.

Вернитесь назад и посмотрите пример из файла TVGUID20.CPP. Что

еще необходимо сделать, чтобы программа могла записывать коллек-

ции в поток?

Ответ чрезвычайно прост. Во-первых, надо начать с базового

класса TGraphObject и "научить" его посылать свои данные (x и y)

в поток. Для этого и существует функция класса write. Затем опре-

делите новые функции write для каждого "потомка" класса

TGraphObject, если, конечно, они добавляют свои поля данных

(TGraphCircle добавляет радиус в виде поля radius; TGraphRec до-

бавляет ширину и длину - width и height).

Кроме того необходимо определить функции readitem и

writeitem, которые читают и возвращают элемент из ipstream, и пи-

шут элемент в opstream, соответственно.

Перед тем как взаимодействовать с потоками надо зарегистри-

ровать все классы. На этом подготовительный этап заканчивается.

Теперь с потоками можно манипулировать точно также, как и с обык-

новенными файлами: сначала объявляется переменная (объект) пото-

ка; создается новый поток, коллекция записывается в поток при по-

мощи короткого выражения и, наконец, поток закрывается.

Добавление функции write

Ниже приводятся объявления функций классов write. Заметьте,

что TGraphPoint не нуждается в такой функции, так как он не до-

бавляет никаких новых данных к унаследованным от TGraphObject.

class TGraphObject : public TObject

{

public:

...

virtual void write(opstream& os);

...

};

class TGraphCircle : public TGraphObject

{

public:

int radius;

...

virtual void write(opstream& os);

};

class TGraphRect : public TgraphObject

{

Turbo Vision для С++ = 210 =

public:

...

int width, height;

...

virtual void write(opstream& os);

};

Реализация функций write довольно проста. Каждый объект вы-

зывает наследуемую функцию write, которая сохраняет все наследуе-

мые данные. Затем вызывается write, принадлежащая потоку, которая

и записывает данные в поток.

Ниже приведен текст из файла TVGUID21.CPP.

void TGraphObject::write( opstream& os )

{

os << x << y;

}

void TGraphCircle::write( opstream& os )

{

TGraphObject::write( os );

os << radius;

}

void TGraphRect::write( opstream& os )

{

TGraphObject::write( os );

os << width << height;

}

В этом примере показывается как можно написать свои собс-

твенные функции read, write и build и регистрировать классы для

их взаимодействия с потоками.

Сохранение и восстановление рабочей области

-----------------------------------------------------------------

Если сохраняемым в потоке объектом является рабочая область,

то она будет сохранена вместе со всеми своими элементами: рабочей

средой, которая окружает программу, включая все текущие отобража-

емые объекты. Если необходима возможность сохранения пользовате-

лем рабочей области, то все возможные отображаемые объекты должны

иметь соответствующие функции read и write, а все отображаемые

объекты быть зарегистрированы, так как содержимое рабочей области

в любой момент может понадобиться пользователю.

Можно пойти еще дальше и сохранять и восстанавливать целые

прикладные программы. Объект TApplication обладает способностью

сохранять и восстанавливать самого себя.

Turbo Vision для С++ = 211 =