8. Объекты данных
-----------------

A. Обзор
--------

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

     Основной объем данной главы посвящен описанию модуля OPROOT.
OPROOT содержит порядка дюжины структур данных  общего назначения
- стеки,  списки,  очереди и т.д. В нем также реализован менеджер
потоков Object Professional,обеспечивающий объекто-ориентированные
средства файлового  ввода-вывода  для  всех  прочих  объектов.  И
поскольку OPROOT используется столь большим числом других модулей
Object Professional,  он  также  представляет собой удобное место
хранения многих констант - кодов ошибки,  кодов модулей,  строк с
сообщениями об  ошибке,  кодами  тиов  объектов  и  кодов  версий
объектов.

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

B. OPROOT: объекто-ориентированные структуры данных
---------------------------------------------------

     OPROOT обеспечивает   набор   объектов    низкого    уровня,
предназначенных для   организации  многих  классических  структур
данных. Сюда входят стеки,  списки,  очереди, битовые множества и
прочие. Вследствие  того,  что  они  разрабатывались  именно  как
объекто-ориентированные, эти    структуры     данных     являются
расширяемыми. Вы имеете возможность расширять их,  например,  для
организации связных списков своих собственных типов  данных.  Эти
объекты используются  и  самим  Object Professional:  WindowStack
является производным от объекта общего  назначения  PointerStack;
список заголовков   для   окна   является   специальным   случаем
DoubleList; множество   выбранных   элементов   в   PickList    с
множественными вариантами  выбора  -  это  случай BitSet,  и т.д.
Использование этих структур  данных  общего  назначения  в  ваших
собственных программах   сэкономит   время,  обеспечит  повторное
использование объектных  кодов,  и  возможно,   предоставит   вам
возможности, позволяющие  не  писать  собственных структур данных
специального назначения.

     Следующая иерархия представляет объекты  организации данных,
имеющиеся в OPROOT:

     [Root]
       PointerStack
       StaticQueue
       SingleListNode
       SingleList
         CircularList
       BitSet
         LargeBitSet
       StringDict
         StringSet
       StringArray

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

     Root представляет собой абстрактный родительский  объект для
всех объектов  Object  Professional,  поддерживающих  потоки.  Мы
вернемчся к нему в ращзделе "Потоки", который находится ниже.

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

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

     SingleListNode представляет    собой    абстрактный     тип,
формирующий базис для одно-связного списка. Его единственное поле
данных - это указатель Next.  Фактические  узлы  списка  являются
производными от этого типа.

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

     DoubleListNode и  DoubleList  реализуют  двусвязные  списки.
Каждый узел списка содержит на четыре байта  данных  больше,  чем
SingleListNode, в   которых   хранится  указатель  для  обратного
направения. Объекты DoubleList  предпочтительны  по  сравнению  с
SingleList, когда   по  списку  требуется  перемещаться  в  обоих
направлениях.

     CircularList - это специальный случай DoubleList.  Запрос на
следующий элементпосле   конца   CircularList  возвращает  первый
элемент, в противоположность  DoubleList,  для  которого  в  этом
случае возвращается пустой список (nil).

     BitSet -   это   структура  данных,  позволяющая  эффективно
хранить массивы  булевых  значений,  упаковывая  по   8   булевых
значений в   каждый   байт   памяти.   Она   предлагает  правила,
позволяющие устанавливать в единичное  значение  и  сбрасывать  в
нулевое, а также проверять значения конкретных битов, сканировать
биты, а также многое другое.

     В то время, как стандартный объект BitSet ограничен емкостью
в 65534  значения,  LargeBitSet  может  хранить  до  8  миллионов
значений, либо столько, сколько позволяет память.

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

     StringSet предлагает        ограниченное        подмножество
функциональных средств  StringDict.  В StringSet четырех-байтовое
значение данных опущено. Следовательно, он обеспечивает операции,
аналогичные обычным  множествам  Паскаля,  но  при  этом элементы
множества образованы строками.

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

Потоки
------

     OPROOT включает   еще   одну   категорию  объектов,  которая
является базисом для  очень  мощного  и  универсального  средства
Object Professional:  потоков.  Потоки подробно рассматриваются в
разделе 6 данной главы.  В кратком же изложении потоком  является
байтовая последовательность,  хранящая почти любой тип данных, во
многом аналогично нетипированному файлу.  В  Object  Professional
потоки используются в основном для хранения данных из экземпляров
объектов. Поток может  быть  общего  назначения  -  для  хранения
объектов произвольного  типа - поскольку только от самих объектов
зависит выполнение двух важных задач:  их запись и  чтение  самих
себя в  поток  и из потока.  Каждый объект,  обеспечивающий такие
средства, имеет  правила,  называемые  Store  и  Load.   Менеджер
потоков OPROOT  при  выполнении  объекто-ориентированного ввода и
вывода принимает эти правила во внимание.

     Сложность потоковых объектов в OPROOT существенно повышена с
тем, чтобы  дать  возможность  решать важный практический вопрос:
если бы OPROOT напрямую обращался к правилам Load и Store каждого
типа объекта,  для которго он может выполнять чтение и запись, то
интеллекуальный компоновщик   Turbo   Pascal   должен   был    бы
прикомпоновывать объектный   код  для  каждого  из  правил.  Это,
безусловно, нежелательно,  особенно  если  данный   поток   будет
содержать только  один  тип  объекта.  Для  решения этой проблемы
OPROOT обеспечивает несколько правил для  "регистрации" объектных
типов в  потоке.  Таким  образом,  менеджеру  потоков  становятся
известны типы,  которые он может читать и  писать,  и  тем  самым
предотвращается компоновка   избыточного   объектного  кода.  Это
означает также,  что менеджер потоков может читать  и  записывать
объекты, которые    даже    не   существовали   к   моменту   его
первоначальной разработки,  что важно  для  его  использования  с
вашими производными типами.

     До сих  пор  мы  рассматривали  потоки только  как  байтовые
последовательности.  Другое  важное  различие  между  потоками  и
нетипированными   файлами   состоит   в   том,  что  на  наиболее
абстрактном уровне  потоки  не  предполагают  свящи  с  дисковыми
файлами  DOS.  Сохраняя  абстрактный  подход  к объекту как можно
дольше,  OPROOT может генерировать несколько полезных вариаций на
базе  общей  концепции.  При использовании потоков OPROOT объекты
могут последовательно храниться в дисковом файле DOS,  они  могут
быть  скомпонованы непосредственно с программным .EXE-файлом, или
они могут быть  организованы  через  библиотеки  потоков  прямого
доступа   на   диске,  либо  включены  в  код.  Поскольку  Object
Professional обеспечивает поддержку потоков для всех  его  важных
объектов, вы можете компоновать с вашими программами образы окон,
организовывать библиотеки экранов ввода данных, либо использовать
множество ранее написанных драйверов принтеров и многое другое, и
все это через потоковые объекты OPROOT.

     Возможно, вам известно,  что Borland  обеспечивает  средство
работы с  потоками  в  качестве  примера модуля Turbo Pascal 5.5.
Потоки OPROOT,  которые аналогичны потокам Borland по концепции и
реализации, позволяют  пойти  дальше  и  могут  быть  полезны при
разработке производительного программного обеспечения. Конкретно,
потоки OPROOT обладают следующими преимуществами:

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

     Значение этих  терминов  станет  яснее,  когда  вы  прочтете
описание потоковых объектов в данной главе.

     В следующей  иерархии  показаны объекты организации потоков,
обеспечиваемые OPROOT.

     Root
       DirEntry
       IdStream
         DosIdStream
           BufIdStream
             Library
         MemIdStream
           MemLibrary

     Root -  это  абстрактный родительский объект практически для
всех типов объектов Object Professional.  Его основное назначение
состоит в  том,  чтобы  обеспечить  наличие  в первых двух байтах
каждого экземпляра объекта ссылки на виртуальную  таблицу  правил
(VMT). Это   важно  для  правильного  функционирования  менеджера
потоков. Порождение всех объектов от общего предка обеспечивает и
другие преимущества   (рассматриваемые   в  разделе,  посвященном
объекту Root).

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

     IdStream -  это  абстрактный  тип,  служащий для организации
всех прочих  потоковых  объектов.  Он  обеспечивает  правила  для
низкоуровневого чтения  и  записи,  а также для регистрации типов
данных в менеджере потоков.  Часть  его  имени  "Id"  служит  для
различения этих  потоков  от реализованных Borland и для указания
на то,  что каждый объект  в  потоке  идентифицирует  себя  кодом
идентификации ("Identification").

     DosIdStream связывает  абстрактный поток с файлом DOS.  Этот
объект обеспечивает правила для  открытия,  создания  и  закрытия
файлов потока.  Разумеется,  при  этом  он  наследует все правила
чтения, записи и регистрации из IdStream.

     BufIdStream добавляет к  DosIdStream  средства  буферизации.
Для временного  хранения информации по дороге на диск или с диска
в памяти  выделяется  буфер.  Это   повышает   производительность
потока, особенно  если  он  состоит  из  множества  малых  блоков
данных.

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

     MemIdStream и  MemLibrary похожи на DosIdStream и Library, с
одним существенным различием:  вместо дискового  файла  потоки  в
этом случае  хранятся  в памяти.  Программы могут воспользоваться
этим средством, применяя утилиту Borland BINOBJ для создания .OBJ
-файла из файла потока и затем компонуя этот файл непосредственно
с .EXE-файлом.  Тем самым  прикладная  программа  может  избежать
необходимости иметь отдельный файл данных.

Константы общего назначения, типы и переменные
----------------------------------------------

     OPROOT представляет  собой  удобное   место   для   хранения
некоторых категорий объявлений, к которым выполняют ссылки многие
другие модули Object  Professional.  Все  объекто-ориентированные
модули уже  имеют  оператор USE OPROOT для доступа к типам Root и
IdStream, поэтому  никаких  дополнительных   зависимостей   между
модулями не генерируется. Объявления общего назначения делятся на
следующие категории:

     - коды типа объекта
     - коды версии объекта
     - коды указателя
     - коды модуля
     - коды ошибки
     - строки сообщений об ошибке
     - тип процедуры обработки ошибки
     - переменная состояния конструктора

     Первые три   категории   близко   связаны  с  потоками;  эти
константы описаны в разделе 6  данной  главы.  Прочие  описаны  в
разделе 8, "Обработка ошибок".

1. Стеки: PointerStack

     Первичной реализацией  стека  в Object Professional является
объект PointerStack.  Как  вы  могли  предположить,  PointerStack
организует стек  указателей.  Однако,  типы  данных,  с  которыми
фактически работает PointerStack,  могут быть и более  общими,  и
более конкретными,  чем  следует из его имени.  Говоря обобщенно,
PointerStack (при   соответствующем   использовании    механизмов
приведения типа) может хранить любые типы данных,  имеющие размер
от одного до четырех байт.  Однако,  выгоднее всего  использовать
PointerStack для   хранения   именно  указателей  на  объекты:  в
частности, объекты,  являющиеся  производными  от   типа   Object
Professional Root.  Например,  WindowStack  (из  модуля OPWINDOW)
является производным  от  PointerStack  и  хранит  указатели   на
различные оконные типы объектов, производные от Root.

     Разумеется, стек    -    это   тип   данных,   как   минимум
поддерживающий операции помещения на  стек  и  снятия  со  стека.
(Эта структура данных называется LIFO:  последним пришел - первым
вышел). Стек можно реализовать как минимум двумя  способами:  как
простой массив  или как специальная форма связного списка. Массив
имеет то  преимущество,  что  в  этом  случае  индексация   стека
происходит более эффективно, а используемая память и фрагментация
меньше. Метод связного списка хорош тем,  что в этом случае  стек
может нарастать   неопределенно,  ограничиваясь  только  размером
доступной памяти.

     PointerStack реализован в качестве массива указателей,  но с
одной особенностью:  он  умеет  динамически  изменять собственный
размер, обеспечивая тем самым  достоинства  как  первого,  так  и
второго методов.  Если  не существует особых причин,  вам даже не
требуется знать,   сколько   элементов   будет    помещаться    в
PointerStack. Однако,  и  в  этом  случае  во  избежание  сильной
фрагментированности "кучи" желательно  инициализировать  стек  на
максимально ожидаемую фактическую емкость.

     PointerStack обеспечивает   все   правил,   которыми  должен
обладать стек,  включая Push,  Pop и Clear.  Он  также  позволяет
просматривать элементы стека, скрытые в его середине.

     Следующий пример  демонстрирует  основные  свойства  объекта
PointerStack.

program PointerStackExample;  {EXPSTACK.PAS}
uses
  OpRoot;
type
  {Объект для манипулирования именем и возрастом}
  NameDataPtr = ^NameData;
  NameData =
    object(Root)
      Name : String[20];
      Age : Byte;
      constructor Init;
      procedure Display;
    end;
var
  NamePtr : NameDataPtr;
  NamePtrStack : PointerStack;
  I : Integer;
  Status : Word;
  {----- Правила NameData  -------------------}
  constructor NameData.Init;
  begin
    WriteLn;
    Write('Name:  ');
    ReadLn(Name);
    Write('Age:   ');
    ReadLn(Age);
  end;
  procedure NameData.Display;
  begin
    WriteLn;
    Writeln(Name,' ',Age);
  end;
begin
  {Создание стека для хранения до 5 указателей}
  if not NamePtrStack.Init(5) then begin
   WriteLn('Не удалось инициализировать PointerStack,
            Состояние = ', InitStatus);
   Halt;
  end;
  {Создание и помещение в стек трех объектов NameData}
  for I := 1 to 3 do begin
    New(NamePtr, Init);
    NamePtrStack.Push(NamePtr);
    Status := NamePtrStack.GetStatus;
    if Status <> 0 then begin
      WriteLn('Не удалось поместить в стек NameData,
               Состояние = ', Status);
      Halt;
    end;
  end;
  {Просмотр первого помещенного на стек объекта}
  NamePtr := NameDataPtr(NamePtrStack.Peek(1));
  NamePtr^.Display;
  {Просмотр верхнего элемента, без его фактического снятия со стека}
  NamePtr := NameDataptr(NamePtrStack.PeekTop);
  NamePtr^.Display;
  {Теперь снимаем со стека и выводим на дисплей каждый NameDataPtr,
   4-й вызов завершится неудачно}
  for I := 1 to 4 do begin
    NamePtr := NameDataPtr(NamePtrStack.Pop);
    if NamePtr = nil then begin
      WriteLn('Стек пуст');
      Halt;
    end;
    NamePtr^.Display;
  end;
  NamePtrStack.Done;
end.

     Данный пример   инициализирует   PointerStack  емкостью  в  5
элементов. (Для этого выделяется соответствующая  область  "кучи".
Если стек  вырастет  за 5 элементов,  то PointerStack отменит этот
первоначально выделенный буфер и выделит новый,  по размеру в  два
раза превышающий исходный).  Если в "куче" недостаточно места,  то
вызов Init завершится неудачно.

     Трем экземплярам простого типа объекта NamaData, производного
от Root, выделяется память, и они помещаются на стек. Мы проверяем
состояние каждой операции помещения на стек (Push),вызывая правило
GetStatus, которое  при  нормальном  завершении  возвращает  ноль.
Вызовы Push всегда  завершаются  удачно,  за  исключением  случая,
когда требуется  расширение  стека,  а  места  в  "куче" для этого
недостаточно.

     Далее мы просматриваем первый элемент,  помещенный  на  стек.
Первый элемент  всегда  имеет  индекс  1.  Если  элементы  в стеке
отсутствуют, просмотр (Peek) возвращает значение nil. Отметим, что
для присвоения   результата   Peek  переменной  NamePtr  требуется
выполнить приведение типа.  Это свойство  объекто-ориентированного
программирования.

     Затем мы  просматривает  верхний  элемент стека,  вызывая для
этого PeekTop.

     Потом мы снимаем элементы  со  стека.  В  данном  примере  мы
пытаемся снять  со стека на один элемент больше,  чем их имеется в
стеке, сравниваем возвращенный указатель с nil,  чтобы  определить
момент, когда стек пуст.

     И наконец,  мы  вызываем  деструктор  PointerStack Done,  для
освобождения памяти в "куче",  использованной  PointerStack.  Этот
последний шаг   является  необязательным:  он  важен  только  если
требуется свободная память, когда программа продолжит работу после
того, как необходимость в стеке исчезла.

Константы
---------

     MaxStackSize = 65520 div SizeOf(RootPtr);

     Максимальное число указателей, которое вообще может храниться
в PointerStack. Это выражение дает значение в 16380 указателей.

     InitStackSize = 8;

     Размер создаваемого стека,  если сделан вызов Push,  а память
для стека перед этим вызовом выделена не была.

Типы
----

     PointerStackPtr = ^PointerStack;
     PointerStack =
       object(Root)
         ...
       end.

     Типы данных,  определяющие объект PointerStack и указатель на
PointerStack.

Clear
----------------------------------------------------------------
Объявление
----------

     procedure Clear;

Назначение
----------

     Удаление всех элементов из стека.

Описание
--------

     Данная подпрограмма    просто    устанавливает    внутренний
указатель стека  равным  нулю.  Ни  один  из  элементов  стека не
освобождается, и сам стек при этом не отменяется.

Пример
------

     NamePtrStack.Clear;

     Удаляет все указатели из NamePtrStack.

Done
----------------------------------------------------------------
Объявление
----------

     destructor Done; virtual;

Назначение
----------

     Отмена выделения памяти для стека.

Описание
--------

     Эта подпрограмма вызывается,  когда стек более не нужен. Она
отменяет  выделение  область  "кучи",   выполненное   ранее   для
организации  стека.  Освобождение  элементов  стека  при  этом не
происходит.

Пример
------

     NamePtrStack.Done;

     Отменяет выделение     области     "кучи",      используемую
NamePtrStack.

     Dispose(StackP, Done);

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

См. также
---------

     Init


Error
----------------------------------------------------------------
Объявление
----------

     Procedure Error(Code : Word); virtual;

Назначение
----------

     Сообщение об ошибке.

Описание
--------

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

См. также
---------

     GetStatus

GetStatus
----------------------------------------------------------------
Объявление
----------

     function GetStatus : Word;

Назначение
----------

     Возвращение последнего   кода   состояния   и   сброс   в  0
внутреннего состояния.

Описание
--------

     Каждый PointerStack    организует   внутреннюю   переменную,
которая хранит текущее состояние  стека.  GetStatus  обеспечивает
доступ к  этой  переменной.  Как  и  IoResult,  GetStatus очищает
внутреннюю переменную состояния после возврата текущего значения.

     Если ошибки не  произошло,  то  GetStatus  возвращает  0.  В
противном случае   возвращаемое   значение   равно  коду  ошибки,
описанному в Приложении D. Единственная ожидаемая ошибка в данном
случае это - epFatal + ecOutOfMemory.

Пример
------

     New(namePtr, Init);
     NamePtrStack.Push(NamePtr);
     Status := GetStatus;
     if Status <> 0 then begin
       WriteLn('Не удалось поместить на стек NameData.
                Состояние = ', Status); Halt;
     end;

     После помещения  на  стек  нового  указателя   мы   вызываем
GetStatus для  того,  чтобы  проверить,  не  произошла ли ошибка.
Поскольку вызов функции  GetStatus  приводит  к  сбросу  значения
ошибки, мы  сначала  помещаем  это  значение  в переменную Status
(имеющую тип Word).

     Если мы точно знаем,  что на стек будет помещено  не  больше
указателей, чем для этого было выделено памяти (при помощи Init),
то нам фактически не требуется проверять  здесь  наличие  ошибки.
Однако, при превышении запрошенного вначале числа указателей Push
сделает попытку  выделить  дополнительную  область  "кучи",   что
подразумевает возможность  ошибки  "out of memory" ("недостаточно
памяти"). Этот тип ошибки характерен только для правил Init, Push
и Resize.


Init
----------------------------------------------------------------
Объявление
----------

     constructor Init(Max : Word);

Назначение
----------

     Выделение стека указателей,  организующего до Max  элементов
стека.

Описание
--------

     Данный конструктор выделяет 4*Max байтов в области  "кучи" в
дополнение к тому,  что требуется, если сам PointerStack является
динамическим объектом.  Указатель стека инициализируется в  0  (в
стеке нет элементов).

     Если значение  Max равно 0,  то Init инициализирует значения
внутренних данных (нулями),  но  область  "кучи"  не  выделяется.
Иногда это   бывает   полезным   при   конструировании  объектов,
зависящих от PointerStack.  После данной формы вызова Init  вызов
PointerStack.Done вполне безопасен.

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

     При неудачном выполнении данного конструктора  он возвращает
булево значение  False  или результирующий указатель со значением
nil, в  зависимости  от  того,  определен  ли  PointerStack   как
статический, или динамический экземпляр объекта. Состояние ошибки
возвращается через глобальную переменную OPROOT - InitStatus.

Пример
------

     if not NamePtrStack.Init(5) then begin
       WriteLn('Не удалось инициализировать PointerStack.
                Состояние = ', InitStatus); Halt;
     end;

     Инициализирует стек   указателей,   содержащий    до    пяти
указателей, без повторного выделения памяти.

См. также
---------

     Done   Resize


Load
----------------------------------------------------------------
Объявление
----------

     constructor Load(var S : IdStream);

Назначение
----------

     Загрузка PointerStack из потока.

Описание
--------

     S представляет собой правильно  инициализированный потоковый
объект (например,  файл потока, открытый на чтение). Отметим, что
S может  быть  любым   потомком   от   типа   IdStream,   включая
DosIdStream, BufIdStream и MemIdStream.

     Load считывает  из  потока  S  следующую  последовательность
байтов. Эти  байты  должны  быть  записаны   предыдущим   вызовом
PointerStack.Store. Load  выделяет  область  "кучи"  для  стека и
помещает на стек любые элементы,  которые были активными во время
сохранения PointerStack.

     Load использует для чтения этих указателей полиморфный вызов
IdStream.GetPtr. Get.Ptr автоматически вызывает  конструктор Load
для объекта,  на  который  указывает  указатель,  но  только если
объект был   ранее   зарегистрирован    для    данного    потока.
Следовательно, Load  может  быть  использовано  только  для таких
PointerStack, которые содержат указатели на  объекты, производные
от Root, или те, которые в момент сохранения стека были пустыми.

     При неудачном завершении данный конструктор возвратит False,
а код ошибки будет записан в глобальную переменную InitStatus.

     Процедура регистрации  потока  для  PointerStack  называется
PointerStackStream.

Пример
------

uses
  OpRoot;
const
  otNameData = 1000;        {тип объекта}
  veNameData = 0;           {версия объекта}
type
  NameDataPtr = ^NameData;
  NameData =
    object(Root)
      Name : String[20];
      Age : Byte;
      procedure Display;
      procedure Store(var S : IdStream);
      constructor Load(var S : IdStream);
    end;
var
  NamePtr : NameDataPtr;
  NamePtrStack : PointerStack;
  NPS : BufIdStream;

  procedure NameData.Display;
  begin
    WriteLn(Name,' ',Age);
  end;

  procedure NameData.Store(var S : IdStream);
  begin
    S.WriteString(Name);
    S.Write(Age, SizeOf(Age));
  end;

  constructor NameData.Load(var S : IdStream);
  begin
    Root.Init;
    Name := S.ReadString;
    S.Read(Age, SizeOf(Age));
    if S.PeekStatus <> 0 then
      Fail;
  end;

begin
  {Открыть буферизованный файл потока}
  NPS.Init('PTRSTACK.STM', SOpen, 1024);

  {Зарегистрировать объектные типы, содержащиеся в стеке}
  NPS.RegisterType(otMameData, veNameData, TypeOf(NameData),
                   @NameData.Store, @NameData.Load);

  {Загрузка PointerStack}
  if not NamePtrStack.Load(NPS) then begin
    WriteLn('Ошибка загрузки Load ', InitStatus);
    Halt;
  end;

  {Закрытие потока}
  NPS.Done;

  {Теперь снять со стека каждый NameDataPtr и вывести его на
   дисплей}
  repeat
    NamePtr := NameDataPtr(NamePtrStack.Pop);
    if NamePtr <> nil then
      NamePtr^.Display;
  until NamePtr = nil;
end.

     Данный пример   загружает   стек   указателей   из   потока,
создаваемого в  примере  для Store.  В этом примере происходит не
только выделение стека,  но также чтение и выделение  памяти  для
всех объектов,  записанных в этот поток (в данном примере это три
экземпляра NameData).

     Важной частью   открытия   потока    является    регистрация
содержащихся в нем типов данных. Более подробную информацию см. в
разделе 6.

     Эффект, аналогичный вызову Load,  может быть  достигнут  при
помощи правила IdStream.Get.  IdStream.Get часто предпочтителен в
силу своей  полиморфности;  т.е.,  он  автоматически   определяет
подходящий для  вызова  конструктор  Load,  в зависимости от типа
данных потока.  Для использования Get поток должен  быть  сначала
создан при помощи IdStream.Put. Тогда вызов Load, показанные выше
в примере, можно переписать в виде:

     NPS.RegisterHier(PointerStackStream);
     NPS.Get(NamePtrStack);
     Status := NPS.GetStatus;
     if Status <> 0 then begin
       WriteLn('Ошибка загрузки Load, ', Status);
       Halt;
     end;

См. также
---------

     Store                   IdStream.RegisterType
     IdStream.GetPtr


Peek
----------------------------------------------------------------
Объявление
----------

     function Peek(Index : Word) : RootPtr;

Назначение
----------

     Возвращает элемент стека в позиции, определяемой Index.

Описание
--------

     Первый элемент,  помещенный на PointerStack, имеет индекс 1.
Дополнительные элементы    имеют   последовательно   возвращаемые
номера. Peek  обеспечивает  доступ  к  элементам  в  произвольных
позициях стека  по  их номерам индекса.  Peek не модифицирует сам
стек.

     Если индекс (Index),  переданный  в  Peek,  равен  нулю  или
превышает максимальный  индекс  (верхнего)  элемента стека,  Peek
возвращает значение nil.

Пример
------

     NamePtr := NameDataPtr(NamePtrStack.Peek(1));
     NamePtr^.Display;

     Принимает первый  указатель,  помещенный   на   стек.   Peek
возвращает указатель  типа  RootPtr,  тип  которого  должен  быть
приведен к NameDataPtr.

См. также
---------

     Pop

PeekTop
----------------------------------------------------------------
Объявление
----------

     function PeekTop : RootPtr;

Назначение
----------

     Возвращает указатель, находящийся в текущий момент в вершине
стека.

Описание
--------

     Такой вызов  эквивалентен  Peek(SP).  Он возвращает элемент,
находящийся в текущий момент в вершине стека.  Если в это время в
стеке нет элементов,  PeekTop возвращает значение nil. PeekTop не
модифицирует сам стек.


Пример
------

     NamePtr := NameDataPtr(NamePtrStack.PeekTop);
     NamePtr^.Display;

     Принимает верхний  указатель (указатель,  помещенный на стек
последим), не  модифицируя  стек.  Тип  возвращаемого   указателя
приводится к NameDataPtr.

См. также
---------

     Pop

Poke
----------------------------------------------------------------
Объявление
----------

     procedure Poke(P : RootPtr; Index : Word);

Назначение
----------

     Напрямую устанавливает элемент стека.

Описание
--------

     Данное правило  позволяет  замену  любого заданного элемента
стека новым значением.  Вызов Poke не изменяет  указателя  стека.
Если значение  Index равно нулю или больше текущей емкости стека,
Poke не выполняет никаких действий.  Poke позволяет изменять стек
для значений выше текущего указателя стека.

Пример
------

     New(NamePtr, Init);
     NamePtrStack.Poke(NamePtr, 1);

     Реализует новый  объект  Name,  затем  помещает указатель на
этот объектна самое  дно  стека  (как  если  бы  это  был  первый
помещенный на стек элемент).

См. также
---------

     Push


Pop
----------------------------------------------------------------
Объявление
----------

     function Pop : RootPtr;

Назначение
----------

     Снимает с вершины стека верхний указатель.

Описание
--------

     Возвращает указатель,  находящийся  в   вершине   стека,   и
декрементирует указатель стека на 1. Если на стеке ничего нет, то
возвращает значение nil.

Пример
------

     for I := 1 to 3 begin
       NamePtr := NameDataPtr(NamePtrStack.Pop);
       if NamePtr = nil then begin
         WriteLn('Стек пуст');
         Halt;
       end;
       NamePtr^.Display;
     end;

     Выполняет обращение к  трем  указателям  в  стеке  в  режиме
"последним пришел   -   первым   вышел"   (LIFO).  Поскольку  Pop
возвращает указатель типа RootPtr, мы должны выполнить приведение
его типа   к   NameDataPtr   при   присвоении  результата  другой
переменной.

См. также
---------

     PeekTop

Push
----------------------------------------------------------------
Объявление
----------

     procedure Push(P : RootPtr);

Назначение
----------

     Помещает P на стек.

Описание
--------

     Инкрементирует указатель стека на 1 и помещает в эту позицию
стека указатель P.

     Если превышена  текущая  емкость стека,  Push делает попытке
удвоить размер стека, выделив для него новый блок "кучи", копируя
в этот  блок  содержимое  существующего  стека  и  затем  отменяя
выделенную старому  блоку  память  "кучи".  Если  в  этом  случае
доступная область "кучи" недостаточна,  Push не выполняет никаких
действий. Такого рода ошибка может  быть  обнаружена  посредством
вызова GetStatus после обращения к Push.

Пример
------

     for I := 1 to 3 do begin
       New(NamePtr, Init);
       NamePtrStack.Push(NamePtr);
       Status := NamePtrStack.GetStatus;
       if Status <> 0 then begin
         WriteLn('Не удалось поместить на стек NameData,
                  Состояние = ', Status);
         Halt;
       end;
     end;

     Создает и  помещает  в  стек  Name  PtrStack  три экземпляра
NameData.

     Name.Init;
     NamePtrStack.Push(Name);

     Неверно !  Push  ожидает указателя на объект Root,  а не сам
объект. Для  статического  экземпляра  соответствующий   оператор
должен быть записан как:

     NamePtrStack.Push(@Name);

См. также
---------

     Pop                   Resize
     Poke                  Size


Resize
----------------------------------------------------------------
Объявление
----------

     function Resize(Max : Word) : Boolean;

Назначение
----------

     Расширяет или уменьшает стек до Max указателей.

Описание
--------

     Данная подпрограмма  сначала выделяет область "кучи" для Max
указателей (4*Max байтов).  Затем она копирует любые существующие
элементы стека,  в  пределах  емкости нового стека,  в этот новый
стек. Если новый стек  меньше  страрого,  и  старый  стек  хранит
больше указателей,  чем может поместиться в новом,  выршина стека
усекается. Resize может  быть  использована  для  увеличения  или
уменьшения размеров текущего стека.

     При успешном  завершении  Resize  возвращает  значение True.
Значение False будет возвращено только в случае,  если  в  "куче"
недостаточно места для нового размера стека.

Пример
------

     if not NamePtrStack.Init(5) then begin
       WriteLn('Не удалось инициализировать PointerStack.
                Состояние = ', InitStatus); Halt;
     end;
     {...помещение указателей на стек...}
     if not NamePtrStack.Resize(50) then begin
       WriteLn('Не удалось изменить размер PointerStack'); Halt;
     end;

     NamePtrStack инициализируется   только  на  пять  элементов.
Далее в программе делается попытка  изменить  его  размер  до  50
элементов.


Size
----------------------------------------------------------------
Объявление
----------

     function Size : Word;

Назначение
----------

     Возвращает размер стека.

Описание
--------

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

     Возвращенный размер является числом имеющих размер указателя
элементов, а не число байтов.

Пример
------

     WriteLn('NamePtrStack может хранить ', NamePtrStack.Size,
              ' элементов.');

     Сообщает максимально возможное число элементов NamePtrStack.


SP
----------------------------------------------------------------
Объявление
----------

     function SP : Word;

Назначение
----------

     Возвращает текущий индекс для вершины стека.

Описание
--------

     Если стек пуст,  то SP  возвращает  0.  В  противном  случае
возвращается индекс элесмента, помещенного на стек последним.

Пример
------

     WriteLn('Текущая вершина стека имеет индекс ',
              NamePtrStack.SP);

     Сообщает индекс  текущей вершины стека,  который также равен
общему числу элементов стека на текущий момент.


Store
----------------------------------------------------------------
Объявление
----------

     procedure Store(var S : IdStream);

Назначение
----------

     Запись стека в поток.

Описание
--------

     S представляет собой правильно  инициализированный потоковый
объект (обычно это - файл потока,  открытый на запись).  Отметим,
что S  может  быть  любым  потомком  от  типа  IdStream,  включая
DosIdStream,  BufIdStream  и  MemIdStream  (хотя  обычно запись в
MemIdStream не используется).

     Store записывает  в  поток  S  образ   PointerStack.   Образ
включает в  себя  емкость стека,  текущее число помещенных в него
элементов, а также образы,  связанные с каждым помещенным в  него
указателем.

     Store использует  полиморфный вызов IdStream.PutPtr. Put.Ptr
автоматически вызывает правило  Store  для  объекта,  на  который
указывает   указатель,   но   только   если   объект   был  ранее
зарегистрирован для данного потока.  Следовательно,  Store  может
быть использовано только для таких PointerStack, которые содержат
указатели на объекты,  производные от Root,  или  те,  которые  в
момент сохранения стека были пустыми.

     Для проверки ошибок,  которые могли произойти при выполнении
Store, следует вызвать функцию потока GetStatus,  как показано  в
нижеприведенном примере.

     Процедура регистрации  потока  для  PointerStack  называется
PointerStackStream.

Пример
------

uses
  OpRoot;
const
  otNameData = 1000;        {тип объекта}
  veNameData = 0;           {версия объекта}
type
  NameDataPtr = ^NameData;
  NameData =
    object(Root)
      Name : String[20];
      Age : Byte;
      procedure Display;
      procedure Store(var S : IdStream);
      constructor Load(var S : IdStream);
    end;
var
  NamePtr : NameDataPtr;
  NamePtrStack : PointerStack;
  NPS : BufIdStream;
  I : Integer;
  Status : Word;

  constructor NameData.Init;
  begin
    WriteLn;
    Write('Name:  ');
    ReadLn(Name);
    Write('Age:  ');
    ReadLn(Age);
  end;

  procedure NameData.Store(var S : IdStream);
  begin
    S.WriteString(Name);
    S.Write(Age, SizeOf(Age));
  end;

  constructor NameData.Load(var S : IdStream);
  begin
    Root.Init;
    Name := S.ReadString;
    S.Read(Age, SizeOf(Age));
    if S.PeekStatus <> 0 then
      Fail;
  end;


begin
  {Создание стека для хранения до 5 указателей}
  if not NamePtrStack.Init(5) then begin
    WriteLn('Не удалось инициализировать PointerStack,
             Состояние =  ', InitStatus);
    Halt;
  end;

  {Создать и поместить на стек три объекта NameData}
  for I := 1 to 3 do begin
    New(NamePtr, Init);
    NamePtrStack.Push(NamePtr);
  end;

  {Записать стек указателей в поток}
  NPS.Init('PTRSTACK.STM', SCreate, 1024);
  NPS.RegisterType(otMameData, veNameData, TypeOf(NameData),
                   @NameData.Store, @NameData.Load);
  NamePtrStack.Store(NPS);
  Status := NPS.GetStatus;
  if Status <> 0 then begin
    WriteLn('Ошибка записи Store ', Status);
    Halt;
  end;

  {Закрытие потока}
  NPS.Done;
end.


     Данный пример  создает  стек  указателей  из  трех  объектов
NameData и  записывает стек в поток.  Запись стека в поток влечет
запись значений размера и текущей вершины стека,  затем косвенный
вызов процедуры  Store для записи указываемых объектов. Для этого
мы регистрируем объект NameData.

     Эффект, аналогичный вызову Store,  может быть  достигнут  при
помощи правила IdStream.Put.  IdStream.Put часто предпочтителен в
силу своей  полиморфности;  т.е.,  он  автоматически   определяет
подходящий для  вызова  конструктор  Store,  в зависимости от типа
данных потока. При использовании для записи потока Put этот поток
должен затем   читаться   при   помощи   Get.   Get  затем  может
автоматически определить,  какой конструктор  Load  вызвать.  Для
использования Put вызов Store в примере можно переписать в виде:

     NPS.RegisterHier(PointerStackStream);
     NPS.Put(NamePtrStack);

См. также
---------

     Load                   IdStream.PutPtr


1. Очереди: StaticQueue

     Объект StaticQueue    реализует    следующую    классическую
структуру данных,  Queue  (очередь).  Очередь  -  это  структура,
обеспечивающая операции   добавления   нового  элемента  к  концу
очереди и  извлечения  элемента  из  начала  очереди,  аналогично
человеческой очереди   у   билетной   кассы.   Другим   термином,
применяемым к  такой  структуре  данных  является  FIFO  ("первым
пришел - первым вышел").

     StaticQueue представляет собой частную реализацию этой общей
идеи. Он  организует  кольцевую  очередь  заранее   определенного
размера. Если  все  места  в очереди заполнены,  StaticQueue либо
откажется принять вновь поступившего претендента,  либо вытолкнет
элемент из другого конца очереди,  в зависимости от установленной
опции.

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

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

     StaticQueue был  написан  сначала для поддержки стека "ранее
показанных пунктов" в объекте AbstractHelpWindow. В том случае он
использовался более    как    стек,   чем   как   очередь;   этим
подчеркивается гибкость StaticQueue.

     Следующий пример программы демонстрирует  основные  свойства
StaticQueue.


program StaticQueueExample;  {EXSQUEUE.PAS}
uses
  OpRoot;
var
  Name : String;
  NameQueue : StaticQueue;
  I : Integer;
begin
  {Создание StaticQueue емкость в 5 строк,  каждая длиной до  255
   символов}
  if not NameQueue.Init(5, SizeOf(Name), True) then begin
    WriteLn('Не удалось создание StaticQueue,
             Состояние = ', InitStatus);
    Halt;
  end;
  {Создание трех имен (Name), добавление их в хвост очереди}
  for I := 1 to 3 do begin
    Write('Введите имя: ');
    ReadLn(Name);
    NameQueue.PushTail(Name);
    if NameQueue.GetStatus <> 0 then begin
      WriteLn('Не удалось добавить имя');
      Halt;
    end;
  end;
  {Извлечение элементов из начала очереди}
  for I := 1 to 3 do begin
    NameQueue.PopHead(Name);
    if NameQueue.GetStatus <> 0 then
      WriteLn ('Не удалось извлечь имя')
    else
      WriteLn(Name);
  end;

  {Освобождение очереди}
  NameQueue.Done;
end.


     Данный пример  создает  очередь  на  5 элементов,  каждый из
которых может содержать полную символьную строку на 255 символов.
Для хранения  данных  очереди  конструктор  Init выделяет область
"кучи", равную 1280 байтов (=5*256).  Третий параметр для Init  -
это булево  значение,  называемое  RejectIfFull (Отказ постановки
при заполненной очереди). Поскольку в данном случае он установлен
в значение True,  очередь отвергнет дополнительные элементы, если
она уже заполнена,  и делается попытка поместить  туда  еще  один
элемент.

     Три элемента   добавляются   в   хвост  очереди  при  помощи
PushTail, а затем эти элементы извлекаются из начала  очереди при
помощи PopHead. Вызовы etStatus проверяют наличие ошибки.

     И наконец,   область   "кучи",   выделенная   для   очереди,
освобождается посредством вызова Done.

Типы
----

     StaticQueuePtr = ^StaticQueue;
     StaticQueue =
       object(Root)
       ...
       end;

     Типы данных,  определяющие объект StaticQueue и указатель на
StaticQueue.

Clear
----------------------------------------------------------------
Объявление
----------

     procedure Clear;

Назначение
----------

     Удаляет из очереди все элементы.

Описание
--------

     Указатели начала  и  конца  очереди устанавливаются в равные
значения; это означает, что очередь пуста. Clear также сбрасывает
внутреннюю переменную состояния очереди в 0.

Пример
------

     NameQueue.Clear;

     Удаляет все эементы очереди.

Done
----------------------------------------------------------------
Объявление
----------

     destructor Done; virtual;

Назначение
----------

     Отмена выделенной для очереди области памяти.

Описание
--------

     Эту подпрограмму  следует  вызывать  в  тех  случаях,  когда
очередь больше не нужна.  Она отменяет выделение области памяти в
"куче" для организации данной очереди.

Пример
------

     NameQueue.Done;

     Отменяет выделенную для NameQueue область "кучи".

Elements
----------------------------------------------------------------
Объявление
----------

     function Elements : Word;

Назначение
----------

     Возвращает текущее число элементов очереди.

Описание
--------

     Если очередь только что перед этим была создана или очищена,
то возвращаемое значение равно  нулю.  Максимальное  возвращаемое
число всегда   на   единицу   меньше   общего   числа  элементов,
выделенного при  вызове  конструктора  очереди.   (Одна   позиция
элемента используется  для  обозначения того,  что очередь пуста,
либо заполнена).

Пример
------

     Writeln('NameQueue в текущий момент имеет ',
              NameQueue.Elements, ' элементов');

     Сообщает о числе элементов,  поставленных в текущий момент в
очередь NameQueue.

См. также
---------

     Init


Error
----------------------------------------------------------------
Объявление
----------

     procedure Error(Code : Word); virtual;

Назначение
----------

     Присваивает код ошибки

Описание
--------

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


GetStatus
----------------------------------------------------------------
Объявление
----------

     function GetStatus : Word;

Назначение
----------

     Возвращение последнего   кода   состояния   и   сброс   в  0
внутреннего состояния.

Описание
--------

     Каждый StaticQueue организует внутреннюю переменную, которая
хранит текущее состояние очереди. GetStatus обеспечивает доступ к
этой  переменной.  Как  и IoResult,  GetStatus очищает внутреннюю
переменную состояния после возврата текущего значения.

     Если ошибки не  произошло,  то  GetStatus  возвращает  0.  В
противном случае   возвращаемое   значение   равно  коду  ошибки,
описанному в Приложении D. Возможны следующие коды ошибки:

epNonFatal + ecQueueFull    попытка добавить элемент в заполненную
                            очередь
epNonFatal + ecQueueEmpty   попытка прочитать элемент из пустой
                            очереди

Пример
------

     NameQueue.PopHead(Name);
     Status := NameQueue.GetStatus;
     if Status <> 0 then
       WriteLn('Не удалось извлечь из очереди Name,
                Состояние = ', Status);
     else
       WriteLn(Name);

     После извлечения из очереди головного элемента  мы  вызываем
GetStatus  для  того,  чтобы  проверить,  не произошла ли ошибка.
Поскольку вызов функции  GetStatus  приводит  к  сбросу  значения
ошибки, мы сначала помещаем это значение в какую-либо собственную
переменную, чтобы иметь возможность сообщить это значение.


Init
----------------------------------------------------------------
Объявление
----------

     constructor Init(NumElements, ElemenrSize : Word;
                      RejectIfFull : boolean);

Назначение
----------

     Выделение области памяти для статической очереди.

Описание
--------

     Данный конструктор выделяет NumElements*ElementSize байтов в
области "кучи" в дополнение  к  тому,  что  требуется,  если  сам
StaticQueue  является динамическим объектом.  Произведение первых
двух параметров должно быть по величине не более 65521  байта,  в
противном   случае  выполнение  данного  конструктора  завершится
неудачно.  Фактическая максимальная емкости  очереди  на  единицу
меньше   NumElements,  поскольку  одно  место  в  очереди  должно
резервироваться  для  обозначения  того,  что  данная   кольцевая
очередь заполнена или пуста.

     Если значение  NumElements  равно 0,  то Init инициализирует
значения  внутренних  данных  (нулями),  но  область  "кучи"   не
выделяется.   Иногда  это  бывает  полезным  при  конструировании
объектов,  зависящих от StaticQueue.  После данной  формы  вызова
Init вызов StaticQueue.Done вполне безопасен.

     Параметр RejectFull определяет поведение очереди,  когда она
заполнена, и делается поставить в конец очереди еще один элемент.
При значении RejectFull,  равном True,  очередь в таком случае не
выполнит никаких  действий,  кроме   возврата   состояния   ошбки
(epNonFatal +  ecQueueFull).  если же RejectFull равно False,  то
данный элемент будет добавлен,  но первый элемент  очереди  будет
"вытолкнут" из нее и следовательно, потерян.

     При неудачном выполнении данного конструктора  он возвращает
булево значение  False  или результирующий указатель со значением
nil, в  зависимости  от  того,  определен  ли  StaticQueue    как
статический, или динамический экземпляр объекта. Состояние ошибки
возвращается через глобальную переменную OPROOT - InitStatus.

Пример
------

     if not NameQueue.Init(5, SizeOf(Name), True) then begin
       WriteLn('Не удалось создать StaticQueue,
                Состояние = ', InitStatus);
       Halt;
     end;

     Инициализирует статическую очередь на пять элементов Name. В
данном примере  мы  запрашиваем,  чтобы  при  заполненной очереди
попытка постановки   в   очередь   нового   элемента   (PushTail)
отвергалась.

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

См. также
---------

     Done


Load
----------------------------------------------------------------
Объявление
----------

     constructor Load(var S : IdStream);

Назначение
----------

     Загрузка статической очереди из потока.

Описание
--------

     S представляет собой правильно  инициализированный потоковый
объект (например,  файл потока, открытый на чтение). Отметим, что
S может  быть  любым   потомком   от   типа   IdStream,   включая
DosIdStream, BufIdStream и MemIdStream.

     Load считывает  из  потока  S  следующую  последовательность
байтов. Эти  байты  должны  быть  записаны   предыдущим   вызовом
StaticQueue.Store.  Load  выделяет  область  "кучи" для очереди и
ставит в очередь любые элементы,  которые были активными во время
сохранения StaticQueue.

     Load использует  для  чтения  каждого  элемента   эквивалент
вызова BlockRead. В отличие от PointerStack, для чтения элементов
никаких форм полиморфного вызова не используется.

     При неудачном завершении данный конструктор возвратит False,
а код ошибки будет записан в глобальную переменную InitStatus.

     Процедура регистрации  потока  для  StaticQueue   называется
StaticQueueStream.

Пример
------

uses
  OpRoot;
var
  Name : String;
  NameQueue : StaticQueue;
  NQS : BufIdStream;
  I : integer;
begin
  {Инициализация NameQueue из потока}
  NQS.Init('NQUEUE.STM', SOpen, 1024);
  if not NameQueue.Load(NQS) then begin
    WriteLn('Ошибка загрузки Load, ', InitStatus);
    Halt;
  end;
  NQS.Done;

  {Обращение к элементам в очереди}
  for I := 1 to 3 do begin
    NameQueue.PopHead(Name);
    if NameQueue.GetStatus <> 0 then
      WriteLn('Не удалось извлечь из очереди Name');
    else
      WriteLn(Name);
  end;
end.

     Данный пример загружает NameQueue из потока,  создаваемого в
примере для Store. В этом примере происходит построение очереди и
загрузка всех существующих элементов в новую очередь.

     Эффект, аналогичный вызову Load,  может быть  достигнут  при
помощи правила IdStream.Get.  Более подробную  информацию  см.  в
описании PointerStack.Load.

См. также
---------

     Store                   IdStream.Get


PeekHead
----------------------------------------------------------------
Объявление
----------

     procedure PeekHead(var Element);

Назначение
----------

     Просмотр элемента, стоящего в начале очереди.

Описание
--------

     Читает элемент из начала  очереди  в  параметр  Element,  не
удаляя его из очереди. Если очередь пуста, то Element заполняется
нулями, и сообщается  код  ошибки  (epNonFatal  +  ecQueueEmpty).
Состояние можно проверить, вызвав GetStatus.

Пример
------

     NameQueue.PeekHead(Name);
     if NameQueue.GetStatus <> 0 then begin
       WriteLn('Очередь пуста');
     else
       WriteLn(Name);

     Возвращает самый  старый  элемент  (Name)  в   очереди,   не
продвигая при  этом  очередь.  Поскольку  данная  операция  может
возвратить ошибку типа "очередь пуста",  это условие должно  быть
проверено.

См. также
---------

     GetStatus            PopHead


PeekTail
----------------------------------------------------------------
Объявление
----------

     procedure PeekTail(var Element);

Назначение
----------

     Просмотр элемента, стоящего в хвосте очереди.

Описание
--------

     Читает элемент из хвоста  очереди  в  параметр  Element,  не
удаляя его из очереди. Если очередь пуста, то Element заполняется
нулями,  и сообщается код  ошибки  (epNonFatal  +  ecQueueEmpty).
Состояние можно проверить, вызвав GetStatus.

Пример
------

     NameQueue.PeekTail(Name);
     if NameQueue.GetStatus <> 0 then begin
       WriteLn('Очередь пуста');
     else
       WriteLn(Name);

     Возвращает самый  новый  элемент  (Name)  в   очереди,   не
продвигая при  этом  очередь.  Поскольку  данная  операция  может
возвратить ошибку типа "очередь пуста",  это условие должно  быть
проверено.

См. также
---------

     GetStatus            PopHead


PopHead
----------------------------------------------------------------
Объявление
----------

     procedure PopHead(var Element);

Назначение
----------

     Удаляет элемент, стоящий в начале очереди.

Описание
--------

     Читает элемент  из  начала  очереди  в  параметр  Element  и
удаляет   его   из   очереди.  Если  очередь  пуста,  то  Element
заполняется  нулями,  и  сообщается  код  ошибки  (epNonFatal   +
ecQueueEmpty). Состояние можно проверить, вызвав GetStatus.

     Классическое поведение    очереди   состоит   в   добавлении
элементов в хвост очереди при помощи PushTail и извлечения их  из
начала очереди при помощи PopHead.

     Следует убедиться,  что  PopHead  была  передана  переменная
подходящего типа. Если размер переменной меньше, чем ElementSize,
используемый при инициализации очереди, то PopHead может нечаянно
затереть память. Это правило не может само определить, достаточно
ли велика переданная ему переменная.

Пример
------

     for I := 1 to 3 do begin
       NameQueue.PopHead(Name);
       if NameQueue.GetStatus <> 0 then WriteLn('Очередь пуста');
       else WriteLn(Name);
     end;

     Извлекает из очереди три элемента (начиная  с  головного)  в
той  самой  последовательности,  в  которой они были поставлены в
очередь.  Поскольку данная операция может возвратить ошибку  типа
"очередь пуста", это условие должно быть проверено.

См. также
---------

     GetStatus            PeekHead


PopTail
----------------------------------------------------------------
Объявление
----------

     procedure PopTail(var Element);

Назначение
----------

     Удаление элемента, стоящего в хвосте очереди.

Описание
--------

     Читает элемент  из  хвоста  очереди  в  параметр  Element  и
удаляет   его   из   очереди.  Если  очередь  пуста,  то  Element
заполняется  нулями,  и  сообщается  код  ошибки  (epNonFatal   +
ecQueueEmpty). Состояние можно проверить, вызвав GetStatus.

     StaticQueue может  быть использовано в качестве статического
стека, если добавлять в  него  элементы  при  помощи  PushTail  и
удалять их при помощи PopTail.

Пример
------

     for I := 1 to 3 do begin
       NameQueue.PopTail(Name);
       if NameQueue.GetStatus <> 0 then WriteLn('Очередь пуста');
       else WriteLn(Name);
     end;

     Извлекает из хвоста очереди три элемента, поставленных в нее
последними. Элемент Name, поставленный в очередь последним, будет
извлечен первым.

См. также
---------

     GetStatus            PushTail
     PeekTail


PushTail
----------------------------------------------------------------
Объявление
----------

     procedure PushTail(var Element);

Назначение
----------

     Постановка элемента в хвост очереди.

Описание
--------

     PushTail обеспечивает единственное правило для  добавления в
очередь элемента.  Если  очередь  уже  полна,  то  элемент  будет
добавлен только  в  том   случае,   если   параметр   RejectFull,
переданный в Init, был равен False. В таком случае первый элемент
очереди будет "вытолкнут" из нее и  утерян.  В  противном  случае
будет сообщено об ошибке (epNonFatal + ecQueueFull).

     PushTail читает   ElementSize   байтов   из  нетипированного
параметра Element.

Пример
------

     NameQueue.Init(5, SizeOf(Name), True);
     for I := 1 to 3 do begin
       Write('Введите имя: ');
       Readln(Name);
       NameQueue.PushTail(Name);
       if NameQueue.GetStatus <> 0 then begin
         WriteLn('Очередь заполнена');
         Halt;
       end;
     end;

     Помещает в хвост очереди NameQueue три новых  элемента Name.
Здесь важно,   каким   образом   была  инициализирована  очередь.
NameQueue была  инициализирована  таким  образом,   что   попытки
добавить новые   элементы   в  заполненную  очередь  отвергаются.
Следовательно, после каждой попытки добавить в очередь элемент мы
должны проверять, удачно ли завершилась эта операция.

     Если очередь  была  инициализирована при RejectFull = False,
то очередь  всегда  сможет  принять  новые  элементы,  отбрасывая
элементы из  начала очереди,  если это необходимо.  В этом случае
вызывать GetStatus после каждого вызова PushTail не требуется.

См. также
---------

     Init


Store
----------------------------------------------------------------
Объявление
----------

     procedure Store(var S : IdStream);

Назначение
----------

     Запись статической очереди в поток.

Описание
--------

     S представляет собой правильно  инициализированный потоковый
объект (обычно это - файл потока,  открытый на запись).  Отметим,
что S  может  быть  любым  потомком  от  типа  IdStream,  включая
DosIdStream,  BufIdStream  и  MemIdStream  (хотя  обычно запись в
MemIdStream не используется).

     Store записывает в поток S образ StaticQueue. Образ включает
в  себя  емкость  очереди,  текущее  число  помещенных   в   него
элементов, а также байтовые образы, связанные с каждым помещенным
в него элементом очереди.

     Для проверки ошибок,  которые могли произойти при выполнении
Store, следует вызвать функцию потока GetStatus,  как показано  в
нижеприведенном примере.

     Процедура регистрации  потока  для  StaticQueue   называется
StaticQueueStream.

Пример
------

uses
  OpRoot;
var
  Name : String;
  NameQueue : StaticQueue;
  NQS : BufIdStream;
  I : integer;
  Status : Word;
begin
  {Создание StaticQueue}
  if not NameQueue.Init(5, SizeOf(Name), True) then begin
    WriteLn('Не удалось создать StaticQueue,
             Состояние =  ', InitStatus); Halt;
  end;
  {Создать и поместить в очередь StaticQueue три объекта Name}
  for I := 1 to 3 do begin
    Write('Введите имя: ');
    ReadLn(Name);
    NameQueue.PushTail(Name);
    if NameQueue.GetStatus <> 0 then begin
      WriteLn('Не удалось добавить в очередь имя Name'); Halt;
    end;
  end;
  {Записать элементы очереди в поток}
  NQS.Init('NQUEUE.STM', SCreate, 1024);
  NameQueue.Store(NQS);
  Status := NQS.GetStatus;
  if Status <> 0 then begin
    WriteLn('Ошибка записи Store ', Status); Halt;
  end;
  NQS.Done;
  NameQueue.Done;
end.


     Данный пример  создает  три  элемента  Name и добавляет их к
статической очереди.  Затем вся очередь в  целом  записывается  в
поток. Пример  восстановления  записанных  данных см.  в описании
Load.

     Эффект, аналогичный вызову Store,  может быть  достигнут  при
помощи правила  IdStream.Put.  Дополнительную  информацию  см.  в
описании PointerStack.Store.

См. также
---------

     IdStream.Put                    Load


3. Связные списки

     Возможно, связные   списки   являются  наиболее  вездесущими
структурами данных в  структурном  программировании  в  целом.  В
Object Professional   они  широчайшим  образом  используются  для
организации оконных заголовков в OPWINDOW, полей ввода в OPENTRY,
блоков печати  в  OPFORM  и  элементов меню в OPMENU,  а также во
многих других   случаях.    Чтобы    гарантировать    доступность
соответствующего списочного   типа   во   всех   случаях,  OPROOT
обеспечивает три  различных   реализации   списков:   односвязные
списки, двусвязные  списки  и кольцевые списки.  В данном разделе
рассматриваются соответствующие им объекты.

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

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

     Кольцевой список  представляет   собой   специальную   форму
двусвязного списка.   При  достижении  конца  двусвязного  списка
дальнейшее движение по списку в этом  направлении  невозможно.  В
случае же кольцевого списка конец списка граничит с его началом.

     Связные списка     полностью     соответствуют    концепциям
наследования и    полиморфизма    объектного    программирования.
Узлы представленных  здесь  связанных списков не содержат никаких
реальных данных.  Включаемые ими данные составляют самый минимум,
достаточный только   для   организации   самих  списков.  Выигрыш
достигается при   создании   вами   производных    объектов    от
представленных здесь  узловых объектов типа связных списков. Ваши
собственные узлы наследуют связи данных от родительского  типа и,
что более важно,  все правила организации списка от объектов типа
связных списков из OPROOT.  Если вы когда-либо писали  прикладные
программы, интенсивно  использующие списки,  и вам пришлось часто
переписывать из одной программы в другую  код,  ответственный  за
организацию этих списков,  то вы,  безусловно,  оцените удобства,
предоставляемые вам OOP с этой точки зрения.

     Связные списки в стиле OOP дают вам также  средство, которым
вы, скорее  всего,  ранее  не  располагали:  один и тот же список
может содержать узлы разных типов.  OPENTRY  и  OPFORM  полностью
используют преимущества   данной  концепции  при  хранении  полей
данных многих типов.

     Следующий пример программы демонстрирует  основные  свойства
односвязного списка.

program SingleListExample;  {EXSLIST.PAS}
uses
  OpRoot;
type
  {Объект для манипулирования Name и Age, производный от SingleListNode}
  NameDataPtr = ^NameData;
  NameData =
    object(SingleListNode)
      Name : String[20];
      Age  : Byte;
      constructor Init;
      procedure Display;
    end;
var
  NamePtr : NameDataPtr;
  NameList : SingleList;
  I : Integer;
  {----- Правила NameData  -------------------}
  constructor NameData.Init;
  begin
    if not SingleListNode.Init then
      Fail;
    WriteLn;
    Write('Name:  ');
    ReadLn(Name);
    Write('Age:   ');
    ReadLn(Age);
  end;
  procedure NameData.Display;
  begin
    WriteLn;
    Writeln(Name, ' ', Age);
  end;
begin
  {Create a SingleList}
  NameList.Init;
  {Создание трех обэектов NameData, добавление их в конец списка}
  for I := 1 to 3 do begin
    New(NamePtr, Init);
    NameList.Append(NamePtr);
  end;
  {Вставка объекта NameData в начало списка}
  New(NamePtr, Init);
  NameList.Insert(NamePtr);
  {Прием головного узла, затем траверсирование по всему списку}
  NamePtr := NameDataPtr(NameList.Head);
  while NamePtr <> nil do begin
    NamePtr^.Display;
    NamePtr := NameDataPtr(NameList.Next(NamePtr));
  end;
  {Снова траверсирование по всему списку,
   на этот раз - всякий раз удаляя И освобождая каждый объект}
  NamePtr := NameDataPtr(NameList.Head);
  while NamePtr <> nil do begin
    NameList.Delete(NamePtr);
    NamePtr := NameDataPtr(NameList.Head);
  end;
  {Done при заполненном списке}
  NameList.Done;
end.


     Поскольку объект    NameData    является    производным   от
SingleListNode, то он наследует связку со  следующим  полем,  что
существенно для  работы  менеджера  списков.  NameList  в  данном
примере является просто экземпляром SingleList.  Менеджер списков
не обращает  внимание  на то,  что он должен организовывать узлы,
состоящие из объектов именно NameData,  а не какого-либо  другого
типа. В других случаях может потребоваться новый тип, производный
от SingleList.   Это   обычно   выполняется,   когда    требуются
специальные правила для доступа к списку.

     Отметим, что NameData.Init начинает с вызова SingleListNode.
Init. Тем  самым  связка  для  следующего  узла  инициализируется
значением nil,  а  также  вызывается  конструктор  объекта  Root,
производным от   которого    является    SingleListNode.    Затем
NameData.Init инициализирует свои собственные поля данных.

     Главная программа   начинается  с  инициализации  экземпляра
связного списка NameList вызовом  конструктора  Init  SingleList.
Этот вызов  обозначает  список  как  полностью пустой.  Затем три
экземпляра типа    NameData    распределяются    в    "куче"    и
инициализируются. Эти три экземпляра добавляются к концу связного
списка. Четвертый  экземпляр  инициализируется  и  вставляется  в
начало связного списка.

     Данный пример   принимает   головной  узел  списка  и  затем
траверсирует по списку от узла к узлу,  отображая их на  дисплее,
до тех   пор,  пока  правило  Next  не  возвратит  значение  nil,
указывающее на то, что достигнут конец списка.

     Вы увидите,  что здесь требуется выполнить  приведение  типа
для преобразования  результата  Head  и  Next к типу NameDataPtr.
Такое приведение типов требует  некоторого  навыка.  Этот  вопрос
кратко обсуждается в поставляемом вместе с Turbo Pascal 5.5 файле
OOPDEMOS.DOC, а здесь мы изложим его своими словами.

     При выполнении  присвоения  из  экземпляра  одного  типа   в
экземпляр производного  от него типа требуется приведение типа. В
данном примере мы присваиваем экземпляр  SingleNodePtr экземпляру
NameDataPtr. Приведение  типа  фактически  сообщает  компьютеру о
том, что хотя переменная SingleNode и не содержит всю информацию,
которую содержит  NameData,  вы берете на себя ответственность за
корректность данного присвоения.  вы знаете,  что список содержит
узлы NameData, поэтому преобразование безопасно.

     Присвоение в   обратном   направлении  -  из  NameDataPtr  в
SingleNodePtr в  данном  примере  не  требует  приведения   типа.
Причиной этого    является    то,    что    переменная   NameData
гарантированно хранит всю информацию, что и SingleNode, поскольку
является производной от последней.  Мы фактически выполняем такое
присвоение, когда передаем в Next переменную NamePtr  в  качестве
параметра.

     Последний цикл   примера   показывает,   как   освобождаются
элементы связного списка.  Правило  Delete  отсоединяет  заданный
элемент от  списка,  выполняет полиморфный вызов деструктора Done
самого удаляемого  элемента,  после  чего   отменяет   выделенную
данному узлу область "кучи". Полиморфный вызов позволяет элементу
выполнить собственные операции очистки памяти. Объект NameData не
нуждается в выполнении каких-либо специальных действий, поэтому в
нем не реализовано его собственное правило Done.

     Другое правило,  Remove,  можно использовать в  том  случае,
если вы  хотите  отсоединить  элемент  от  списка,  но  не хотите
освобождать выделенную ему память.

     Вызов NameList.Done в данном примере избыточен,  но  показан
для полноты изложения материала. Правило Done делает то же самое,
что и предыдущий цикл - отсоединяет от списка и  освобождает  все
элементы списка.

     Пример программы  для  двусвязного  списка будет практически
идентичен описанному.  SingleListNode и SingleList будут заменены
DoubleListNode и    DoubleList,   и   на   этом   все   изменения
заканчиваются.

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

  {Прием хвостового узла, затем траверсирование по всему списку
   в обратном направлении}
  NamePtr := NameDataPtr(NameList.Tail);
  while NamePtr <> nil do begin
    NamePtr^.Display;
    NamePtr := NameDataPtr(NameList.Prev(NamePtr));
  end;

     Здесь выполняется  вызов правил Tail и Prev,  но в остальном
это идентично примеру для односвязного списка.

     пример для  кольцевого  списка  CircularList  будет   вполне
аналогичен. Поскольку  он является производным от DoubleList, тип
узла NameData  в  данном  примере  также  будет  производным   от
DoubleListNode. (Такого  понятия,  как  CircularListNode здесь не
имеется). Главное отличие кольцевого списка состоит в  подходе  к
траверсированию по  всему  списку  в  целом.  Для этого требуется
примерно следующий цикл:

     var
       SavePtr : NamaDataPtr;

     SavaPtr := NamePtr;
     repeat
       NamePtr^.Display;
       NamePtr := NameDataPtr(NameList.Next(NamePtr));
     until NamePtr = SavePtr;

     Траверсирование может   начинаться   в   произвольной  точке
списка; при этом всегда будет проходиться полный цикл.  Здесь  он
начинается с  элемента  NamePtr,  ранее  инициализированного  при
добавлении нового элемента.

     Объекты для связных списков описаны далее в седующем порядке:

     SingleListNode
     SingleList
     DoubleListNode
     DoubleList
     CircularList

Типы
----

     SingleNodePtr = ^SingleListNode;
     SingleListNode =
       object(Root)
         ...
       end;
     SingleListPtr = ^singleList;
     Singlelist =
       object(Root)
         ...
       end;

     Типы данных для организации односвязных списков.

     DoubleNodePtr = ^DoubleListNode;
     DoubleListNode =
       object(Root)
         ...
       end;
     DoubleListPtr = ^doubleList;
     Doublelist =
       object(Root)
         ...
       end;

     Типы данных для организации двусвязных списков.

     CircularListPtr = ^CircularList;
     CircularList =
       object(DoubleList)
         ...
       end;

     Типы данных для организации кольцевых списков.

     Примечание: CircularList    наследует   большинство   правил
DoubleList. Повторно документированы только  переопределяемые для
этого объекта правила. Остальные правила для него см. в описаниях
для DoubleList.


SingleListNode
----------------------------------------------------------------

Init
----------------------------------------------------------------
Объявление
----------

     constructor Init;

Назначение
----------

     Инициализирует узел односвязного списка.

Описание
--------

     Вызывает конструктор Init объекта Root и затем устанавливает
связку следующего  поля  для  данного  экземпляра в значение nil.
Объекты, производные   от   SingleListNode,    должны    вызывать
SingleListNode.Init, прежде   чем  перейти  к  своей  собственной
инициализации.

Пример
------

     constructor NameData.Init;
     begin
       if not SingleListNode.Init then
         Fail;
       WriteLn;
       Write('Name:  ');
       ReadLn(Name);
       Write('Age:   ');
       ReadLn(Age);
     end;

     Данный объект,  производный   от   SingleListNode,   сначала
вызывает конструктор    Init   родительского   объекта.   Текущая
реализация SingleListNode.Init  не   допускает   его   неудачного
завершения. Тем не менее, с учетом возможности будущих изменений,
рекомендуется, чтобы  в  NameData  проверялся  результат,   и   в
собственном конструкторе использовалась Fail.


См. также
---------

     Root.Init


SingleList
----------------------------------------------------------------

Append
----------------------------------------------------------------
Объявление
----------

     procedure Append(P : SingleNodePtr);

Назначение
----------

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

Описание
--------

     Элемент, на  который  указывает  P,  связывается  с   концом
списка. Если  список  пустой,  то  P  становится первым элементом
списка. Если P равно nil, Append не выполнит никаких действий.

Пример
------

     for I := 1 to 3 do begin
       New(NamePtr, Init);
       NameList.Append(NamePtr);
     end;

     Реализует три новых объекта NamePtr и добавляет каждый новый
указатель к связному списку NameList.

См. также
---------

     Insert               Place


Delete
----------------------------------------------------------------
Объявление
----------

     procedure Delete(P : SingleNodePtr);

Назначение
----------

     Удаляет существующий  элемент из списка и освобождает память
от его содержимого.

Описание
--------

     Сначала в списке отыскивается элемент P.  Если он не найден,
то Delete не  выполняет  никаких  действий.  В  противном  случае
элемент отсоединяется от списка, элементы вокруг него связываются
между собой, а затем вызывается правило

     Dispose(P, Done);

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

Пример
------

     NameList.Delete(NameList.Head);

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

     NameList.Delete(NamePtr);
     NamePtr^.Display;

     Неверно! Объект,  на   который   указывает   Name.Ptr,   был
освобожден и более не существует.

См. также
---------

     Remove


Done
----------------------------------------------------------------
Объявление
----------

     destructor Done; virtual;

Назначение
----------

     Разрушает список.

Описание
--------

     Освобождает все  элементы  списка  и  затем  разрушает   сам
список.

Пример
------

     NameList.Done;

     Done "гуляет"  по  списку,  вызывая  деструктор  (Done)  для
каждого указателя в списке.


Head
----------------------------------------------------------------
Объявление
----------

     function Head : SingleNodePtr;

Назначение
----------

     Возвращает указатель на головной элемент списка.

Описание
--------

     Если список пуст,  то возвращает указатель nil.  В противном
случае возвращает указатель на первый элемент в  списке. Отметим,
что для  результата  функции  должно  быть выполнено приведение к
типу узла производного списка.

Пример
------

     NamePtr := NameDataPtr(NameList.Head);
     if NamePtr <> nil then NamePtr^.Display;

     Принимает указатель  головного  элемента   связного   списка
NameList  и  вызывает  правило  Display  объекта.  Поскольку Head
возвращает SingleNodePtr,  мы выполняем приведение  типа  к  типу
NameDataPtr.


Init
----------------------------------------------------------------
Объявление
----------

     constructor Init;

Назначение
----------

     Инициализирует пустой список элементов.

Описание
--------

     Устанавливает указатель головного элемента списка в  nil,  а
счетчик элементов равным нулю.

Пример
------

     NameList.Init;

     Инициализирует односвязный   список   NameList.    Проверять
результат функции     конструктора    необязательно,    Поскольку
конструктор просто инициализирует некоторые поля данных.

     if not NameList.Init then {InitStatus содержит код ошибки};

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

     New(NameListP, Init);
     if NameListP = nil then {Ошибка, проверить InitStatus};

     Инициализирует указатель на связный список.  Если  NameListP
равен nil, это означает, что "куча" не имела достаточного размера
для выделения самому LingleList. При не-нулевом InitStatus ошибка
была обнаруджена конструктором SingleList.


Insert
----------------------------------------------------------------
Объявление
----------

     procedure Insert(P : SingleNodePtr);

Назначение
----------

     Вставить элемент в начало списка.

Описание
--------

     Элемент, на   который  указывает  P,  вставляется  в  начало
списка. Если список пуст,  P становится первым элементом  списка.
Если P имеет значение nil, Insert не выполняет никаких действий.

Пример
------

     New(NamePtr, Init);
     NameList.Insert(NamePtr);

     Инициализирует NamePtr   и   вставляет  указатель  в  начало
связного списка NameList.

См. также
---------

     Append             PlaceBefore


Load
----------------------------------------------------------------
Объявление
----------

     constructor Load(var S : IdStream);

Назначение
----------

     Загрузка списка из потока.

Описание
--------

     S представляет собой правильно  инициализированный потоковый
объект (например,  файл потока, открытый на чтение). Отметим, что
S может  быть  любым   потомком   от   типа   IdStream,   включая
DosIdStream, BufIdStream и MemIdStream.

     Load считывает  из  потока  S  следующую  последовательность
байтов.   Эти  байты  должны  быть  записаны  предыдущим  вызовом
SingleList.Store.  Load инициализирует список и добавляет к  нему
любые  элементы,  которые  были  активными  во  время  сохранения
SingleList.

     Load использует для чтения указателей на каждый  узел списка
полиморфный вызов IdStream.GetPtr. Get.Ptr автоматически вызывает
конструктор Load для объекта,  на который указывает указатель, но
только  если объект был ранее зарегистрирован для данного потока.

     При неудачном завершении данный конструктор возвратит False,
а код ошибки будет записан в глобальную переменную InitStatus.

     Процедура регистрации  потока  для   SingleList   называется
SingleListStream.

Пример
------

uses
  OpRoot;
const
  otNameData = 1000;        {тип объекта}
  veNameData = 0;           {версия объекта}
type
  {Объект для манипулирования Name и Data,
   производный от SingleListNode}
  NameDataPtr = ^NameData;
  NameData =
    object(SingleListNode)
      Name : String[20];
      Age : Byte;
      procedure Display;
      constructor Load(var S : IdStream);
      procedure Store(var S : IdStream);
    end;
var
  NamePtr : NameDataPtr;
  NameList : SingleList;
  NLS : BufIdStream;

  procedure NameData.Display;
  begin
    WriteLn;
    WriteLn(Name, ' ', Age);
  end;

  constructor NameData.Load(var S : IdStream);
  begin
    Root.Init;
    Name := S.ReadString;
    S.Read(Age, SizeOf(Age));
    if S.PeekStatus <> 0 then
      Fail;
  end;

  procedure NameData.Store(var S : IdStream);
  begin
    S.WriteString(Name);
    S.Write(Age, SizeOf(Age));
  end;


begin
  {Прочитать связный список из потока}
  NLS.Init('SGLLIST.STM', SOpen, 1024);
  NLS.RegisterType(otNameData, veNameData, TypeOf(NameData),
                   @NameData.Store, @NameData.Load);
  if not NameList.Load(NLS) then begin
    WriteLn('Ошибка загрузки Load ', InitStatus);
    Halt;
  end;
  NLS.Done;

  {Начиная с головного элемента траверсировать по всему списку,
   выводя на дисплей каждый элемент}
  NamePtr := NameDataPtr(NameList.Head);
  while NamePtr <> nil do begin
    NamePtr^.Display;
    NamePtr := NameDataPtr(NameList.Next(NamePtr));
  end;
end.

     Считывает односвязный список из потока. Инициализируется сам
NameList, и  для  каждого  узла  в  списке вызывается конструктор
Load, реализующий  эти  объекты.  Результатом  в  данном  примере
является связный   список   из  трех  узлов,  каждый  из  которых
указывает на объект NamePtr в "куче".

     Эффект, аналогичный вызову Load,  может быть  достигнут  при
помощи правила IdStream.Get.  IdStream.Get часто предпочтителен в
силу своей  полиморфности;  т.е.,  он  автоматически   определяет
подходящий для  вызова  конструктор  Load,  в зависимости от типа
данных потока.  Для использования Get поток должен  быть  сначала
создан при помощи IdStream.Put. Тогда вызов Load, показанные выше
в примере, можно переписать в виде:

     NLS.RegisterHier(SinglelistStream);
     NLS.Get(NameList);
     Status := NLS.GetStatus;
     if Status <> 0 then begin
       WriteLn('Ошибка загрузки Load, ', Status);
       Halt;
     end;

См. также
---------

     IdStream.Get                  Store
     IdStream.RegisterType


Next
----------------------------------------------------------------
Объявление
----------

     function Next(P : SingleNodePtr) : SingleNodePtr;

Назначение
----------

     Возвращает указатель на следующий узел.

Описание
--------

     Предполагает, что P это допустимый узел,  уже находящийся  в
списке. В противном случае Next вернет "мусор".

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

Пример
------

     NamePtr := NameDataPtr(NameList.Head);
     while NamePtr <> nil do begin
       NamePtr^.Display;
       NamePtr := NameDataPtr(NameList.Next(NamePtr));
     end;

     Сначала обращается  к  указателю на головной элемент списка.
Затем при  помощи  правила  Next  "гуляет"  по  связному  списку,
вызывая правило Display каждого объекта.

См. также
---------

     DoubleList.Prev


Place
----------------------------------------------------------------
Объявление
----------

     procedure Place(P, L : SingleNodePtr);

Назначение
----------

     Помещает элемент P в список после существующего элемента L.

Описание
--------

     L должен  являться  допустимым  узлом,  уже  находящимся   в
списке. Для   улучшения   быстродействия   Place   не  проверяет,
выполняется ли это условие.

     В качестве специального случая,  если L имеет значение  nil,
place вставляет новый узел P в начало списка.

     Если существующий  элемент L является хвостовым в списке, то
более эффективен вызов Append.

Пример
------

     New(NamePtr, Init);
     NameList.Place(NamePtr, OldNamePtr);

     Неверно! Если OldNamePtr не иницилизирован,  Place  запортит
память в произвольном месте.

См. также
---------

     PlaceBefore


PlaceBefore
----------------------------------------------------------------
Объявление
----------

     procedure PlaceBefore(P, L : SingleNodePtr);

Назначение
----------

     Помещает элемент P в список перед существующим элементом L.

Описание
--------

     L должен  являться  допустимым  узлом,  уже  находящимся   в
списке. Для  того,  чтобы  вставить  новый  элемент,  PlaceBefore
должна найти  существующй  элемент  L;  если  L  не  найден,   то
PlaceBefore не выполняет никаких действий.

     В качестве специального случая,  если L имеет значение  nil,
PlaceBefore вставляет новый узел P в начало списка.

     Добавить элемент  в  конец  списка  при  помощи  PlaceBefore
нельзя. Для этого следует воспользоваться правилом Append.

Пример
------

     New(NamePtr, Init);
     NameList.PlaceBefore(NamePtr, Namelist.Tail);

     реализует новый   объект  и  вставляет  новый  объект  перед
последним узлом  (Tail)  в  NameList.  Новый   указатель   теперь
является предпоследним в данном связном списке.

См. также
---------

     Place


Remove
----------------------------------------------------------------
Объявление
----------

     procedure Remove(P : SingleNodePtr);

Назначение
----------

     Удаляет из  списка  существующий  элемент  без  освобождения
памяти этого элемента.

Описание
--------

     Узел P  отсоединяется  от списка,  а соседние с ним элементы
связываются между собой. Объект, на который указывает P, остается
реализованным в  памяти,  и при необходимости может быть связан с
другим списком или временно использован, прежде чем занимаемая им
память будет освобождена.

     Если в текущий момент P не является членом списка, Remove не
выполнит никаких действий.

Пример
------

     NameList.Remove(NameList.Head);

     Отсоединяет текущий  головной элемент от связного списка, но
не освобождает его.

См. также
---------

     Delete


Size
----------------------------------------------------------------
Объявление
----------

     function Size : Word;

Назначение
----------

     Возвращает размер текущего списка.

Описание
--------

     Размером считается  число  элементов  в  списке.  SingleList
обновляет счетчик  элементов  всякий  раз  при   добавлении   или
удалении узла,  а  функция  Size просто возвращает значение этого
счетчика.

     Если каким-либо образом список получил более  64К элементов,
Size вернет   неверный  результат.  Однако,  в  противном  случае
менеджер списков всегда работает исправно.

Пример
------

     WriteLn('NameList имеет ', NameList.Size, ' элементов');

     Выводит на дисплей текущее число элементов в связном списке.


Store
----------------------------------------------------------------
Объявление
----------

     procedure Store(var S : IdStream);

Назначение
----------

     Запись списка в поток.

Описание
--------

     S представляет собой правильно  инициализированный потоковый
объект (обычно это - файл потока,  открытый на запись).  Отметим,
что S  может  быть  любым  потомком  от  типа  IdStream,  включая
DosIdStream,  BufIdStream  и  MemIdStream  (хотя  обычно запись в
MemIdStream не используется).

     Store записывает   в   поток   S   образ  SingleList.  Store
использует полиморфный вызов IdStream.PutPtr  для  записи  образа
объекта,   в   текущий   момент   входящего   в  список.  Put.Ptr
автоматически вызывает правило  Store  для  объекта,  на  который
указывает  каждый указатель узла,  но только если тип объекта был
ранее зарегистрирован для данного потока.

     Для проверки ошибок,  которые могли произойти при выполнении
Store, следует вызвать функцию потока GetStatus,  как показано  в
нижеприведенном примере.

     Процедура регистрации  потока  для   SingleList   называется
SingleListStream.

Пример
------

uses
  OpRoot;
const
  otNameData = 1000;        {тип объекта}
  veNameData = 0;           {версия объекта}
type
  {Объект для манипулирования Name и Age,
   являющийся потомком от SingleListNode}
  NameDataPtr = ^NameData;
  NameData =
    object(SingleListNode)
      Name : String[20];
      Age : Byte;
      constructor Init;
      constructor Load(var S : IdStream);
      procedure Store(var S : IdStream);
    end;
var
  NamePtr : NameDataPtr;
  NameList : SingleList;
  NLS : BufIdStream;
  I : Integer;
  Status : Word;

  constructor NameData.Init;
  begin
    WriteLn;
    Write('Name:  ');
    ReadLn(Name);
    Write('Age:   ');
    ReadLn(Age);
  end;

  constructor NameData.Load(var S : IdStream);
  begin
    Root.Init;
    Name := S.ReadString;
    S.Read(Age, SizeOf(Age));
    if S.PeekStatus <> 0 then Fail;
  end;

  procedure NameData.Store(var S : IdStream);
  begin
    S.WriteString(Name);
    S.Write(Age, SizeOf(Age));
  end;

begin
  {Создать пример списка}
  for I := 1 to 3 do begin
    New(NamePtr, Init);
    NameList.Append(NamePtr);
  end;

  {Записать текущий связный список в поток}
  NLS.Init('SGLLIST.STM', SCreate, 1024);
  NLS.RegisterType(otNameData, veNameData, TypeOf(NameData),
                   @NameData.Store, @NameData.Load);
  NameList.Store(NLS);
  Status := NLS.GetStatus;
  if Status <> 0 then begin
    WriteLn('Ошибка запис Store, ', Status);
    Halt;
  end;
  NLS.Done;
  NameList.Done;
end.

     Данный пример  реализует  односвязный   список   (NameList),
добавляет в  него  три  элемента  и  записывает  список  в поток.
Результирующий поток содержит, в последовательности той же, что в
связном списке, каждый объект, на который указывает этот список.

     Эффект, аналогичный вызову Store,  может быть  достигнут  при
помощи правила IdStream.Put.  IdStream.Put часто предпочтителен в
силу своей  полиморфности;  т.е.,  он  автоматически   определяет
подходящий для  вызова  конструктор  Store,  в зависимости от типа
данных потока. При использовании для записи потока Put этот поток
должен затем   читаться   при   помощи   Get.   Get  затем  может
автоматически определить,  какой конструктор  Load  вызвать.  Для
использования Put вызов Store в примере можно переписать в виде:

     NLS.RegisterHier(SingleListStream);
     NLS.Put(NameList);

См. также
---------

     IdStream.PutPtr             Load
     IdStream.Register.Type


Tail
----------------------------------------------------------------
Объявление
----------

     function Tail : SingleNodePtr;

Назначение
----------

     Возвращает указатель на хвостовой элемент списка.

Описание
--------

     Если список пуст, Tail возвращает указатель nil. В противном
случае функция сканирует список от головного элемента  в  поисках
последнего элемента и возвращает указатель на него.

Пример
------

     NamePtr := NameDataPtr(NameList.Tail);
     NamePtr^.Display;

     Принимает указатель   хвостового  элемента  связного  списка
NameList (это будет последний указатель,  добавленный  с  помощью
Append) и вызывает правило Display объекта.

См. также
---------

     DoubleList.Tail                Head


DoubleListNode
----------------------------------------------------------------

Init
----------------------------------------------------------------
Объявление
----------

     constructor Init;

Назначение
----------

     Инициализирует узел двусвязного списка.

Описание
--------

     Вызывает конструктор Init объекта Root и затем устанавливает
связки следующего и предыдущего полей для  данного  экземпляра  в
значение  nil.  Объекты,  производные  от DoubleListNode,  должны
вызывать  DoubleListNode.Init,  прежде  чем   перейти   к   своей
собственной инициализации.

См. также
---------

    SingleListNode.Init


Append
----------------------------------------------------------------
Объявление
----------

     procedure Append(P : DoubleNodePtr);

Назначение
----------

     Добавление элемента в конец двусвязного списка.

Описание
--------

     Используется аналогично  SingleList.Append.  Для  двусвязных
списков прямая  и  обратная  связки  устанавливаются  для каждого
элемента. DoubleList не  требуется  выполнять  специальный  поиск
конца списка, поскольку поле Tail поддерживается внутренне.

См. также
---------

     Insert                SingleList.Append
     Place


Delete
----------------------------------------------------------------
Объявление
----------

     procedure Delete(P : DoubleNodePtr);

Назначение
----------

     Удаляет существующий  элемент из списка и освобождает память
от его содержимого.

Описание
--------

     Используется аналогично SingleList.Delete. Однако, в отличие
от SingleList,  DoubleList  не ищет заданный элемент P (поскольку
узел уже  задает  и  прямую,  и  обратную   связки).   Вызывающая
программа должна,  следовательно,  обеспечить,  чтобы элемент уже
был частью списка;  иначе Delete при попытке отсоединить  элемент
испортит память.

См. также
---------

     Remove             SingleList.Delete


Done
----------------------------------------------------------------
Объявление
----------

     destructor Done; virtual;

Назначение
----------

     Разрушает список.

Описание
--------

     Освобождает все  элементы  списка  и  затем  разрушает   сам
список.

См. также
---------

     SingleList.Done

Head
----------------------------------------------------------------
Объявление
----------

     function Head : DoubleNodePtr;

Назначение
----------

     Возвращает указатель на головной элемент списка.

Описание
--------

     Работает идентично SingleList.Head.

См. также
---------

     SingleList.Head            Tail

Init
----------------------------------------------------------------
Объявление
----------

     constructor Init;

Назначение
----------

     Инициализирует пустой список элементов.

Описание
--------

     Устанавливает указатель  головного  и  хвостового  элементов
списка в nil, а счетчик элементов равным нулю.

См. также
---------

     Done               SingleList.Init


Insert
----------------------------------------------------------------
Объявление
----------

     procedure Insert(P : DoubleNodePtr);

Назначение
----------

     Вставить элемент в начало списка.

Описание
--------

     Используется как и SingleList.Insert.

См. также
---------

     Append                    SingleList.Insert
     PlaceBefore


Load
----------------------------------------------------------------
Объявление
----------

     constructor Load(var S : IdStream);

Назначение
----------

     Загрузка списка из потока.

Описание
--------

     Используется аналогично  SingleList.Load.  При использовании
правил IdStream.Get или IdStream.GetPtr для  полиморфной загрузки
отметим,  что  DoubleList имеет свой собственный код типа объекта
(otDoubleList) и код версии (veDoubleList). Процедура регистрации
в потоке для DoubleList называется DoubleListStream.

См. также
---------

     SingleList.Load                  Store


Next
----------------------------------------------------------------
Объявление
----------

     function Next(P : DoubleNodePtr) : DoubleNodePtr; virtual;

Назначение
----------

     Возвращает указатель на следующий узел.

Описание
--------

     Используется аналогично   SingleList.Next.   DoubleList.Next
является виртуальным   правилом,   поскольку   оно   должно  быть
переопределено при использовании в типе объекта CircularList.

См. также
---------

     Prev              SingleList.Next


Place
----------------------------------------------------------------
Объявление
----------

     procedure Place(P, L : DoubleNodePtr);

Назначение
----------

     Помещает элемент P в список после существующего элемента L.

Описание
--------

     Используется аналогично SingleList.Place.

См. также
---------

     PlaceBefore              Singlelist.Place


PlaceBefore
----------------------------------------------------------------
Объявление
----------

     procedure PlaceBefore(P, L : DoubleNodePtr);

Назначение
----------

     Помещает элемент P в список перед существующим элементом L.

Описание
--------

     Используется аналогично  SingleList.PlaceBefore.  Однако,  в
отличие   от   SingleList,   DoubleList   не   требуется   искать
существующий элемент L (поскольку связка с  предыдущим  элементом
хранится    в   самом   узле).   Вызывающая   программа   должна,
следовательно,  обеспечить,  чтобы элемент уже был частью списка;
иначе PlaceBefore при попытке присоединить новый элемент испортит
память.


См. также
---------

     Place               SingleList.PlaceBefore


Prev
----------------------------------------------------------------
Объявление
----------

     function Prev(P : DoubleNodePtr) : DoubleNodePtr; virtual;

Назначение
----------

     Возвращает указатель на предыдущий узел.

Описание
--------

     Предполагает, что P - это допустимый узел, уже находящийся в
списке. В  противном  случае  Prev вернет "мусор".  Prev является
виртуальным правилом,  поскольку оно должно  быть  переопределено
при использовании с объектом типа CircularList.

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

Пример
------

     NamePtr := NameDataPtr(NameList.Tail);
     while NamePtr <> nil do begin
       NamePtr^.Display;
       NamePtr := NameDataPtr(NameList.Prev(NamePtr));
     end;

     Берет указатель  на  хвостовой  элемент  списка.  Затем  при
помощи правила  Prev  "гуляет"  по  связному  списку  в  обратном
направлении, вызывая правило Display каждого объекта.

См. также
---------

     Next


Remove
----------------------------------------------------------------
Объявление
----------

     procedure Remove(P : DoubleNodePtr);

Назначение
----------

     Удаляет из  списка  существующий  элемент  без  освобождения
памяти этого элемента.


Описание
--------

     Используется аналогично SingleList.Remove. Однако, в отличие
от  SingleList,  DoubleList  не  требуется   искать   элемент   P
Вызывающая  программа должна,  следовательно,  обеспечить,  чтобы
элемент  уже  был  частью  списка;  иначе  Remove   при   попытке
отсоединить элемент испортит память.

См. также
---------

     Delete             SingleList.Remove


Size
----------------------------------------------------------------
Объявление
----------

     function Size : Word;

Назначение
----------

     Возвращает размер текущего списка.

Описание
--------

     Размером считается число элементов в списке.  Данное правило
работает аналогично SingleList.Size.

См. также
---------

     SingleList.Size


Store
----------------------------------------------------------------
Объявление
----------

     procedure Store(var S : IdStream);

Назначение
----------

     Запись списка в поток.

Описание
--------

     Используется аналогично SingleList.Store.  При использовании
правил IdStream.Put или IdStream.PutPtr  для  полиморфной  записи
отметим,  что  DoubleList имеет свой собственный код типа объекта
(otDoubleList) и код версии (veDoubleList). Процедура регистрации
в потоке для DoubleList называется DoubleListStream.

См. также
---------

     Load               SingleList.Store


Tail
----------------------------------------------------------------
Объявление
----------

     function Tail : DoubleNodePtr;

Назначение
----------

     Возвращает указатель на хвостовой элемент списка.

Описание
--------

     Считывает поле данных объекта,  которе содержит указатель на
последний  элемент  списка.  Если  список  пуст,  Tail возвращает
указатель nil.

Пример
------

     NamePtr := NameDataPtr(NameList.Tail);
     NamePtr^.Display;

     Принимает указатель   хвостового  элемента  связного  списка
NameList (это будет последний указатель,  добавленный  с  помощью
Append) и  вызывает  правило  Display  объекта.  Поскольку   Tail
возвращает указатель  типа  DoubleNodePtr,  мы  должны  выполнить
выполнить его приведение к типу NameDataPtr.

См. также
---------

     Head


CircularList
----------------------------------------------------------------

Next
----------------------------------------------------------------
Объявление
----------

     function Next(P : DoubleNodePtr) : DoubleNodePtr; virtual;

Назначение
----------

     Возвращает указатель   на   следующий  узел  (с  циклическим
переходом в конце списка).

Описание
--------

     Используется аналогично   DoubleList.Next.   DoubleList.Next
возвращает nil,  когда его параметр является  последним  узлом  в
списке; CircularList.Next возвращает в этом случае первый элемент
в списке.

Примеры
-------

     SavePtr := NamePtr;
     repeat
       NamePtr^.Display;
       NamePtr := NameDataPtr(NameList.Next(NamePtr));
     until NamePtr = nil;

     Неверно! Так работает двусвязный список. Кольцевые списки не
имеют указателей со  значением  nil.  Такой  цикл  будет  длиться
вечно.



Prev
----------------------------------------------------------------
Объявление
----------

     function Prev(P : DoubleNodePtr) : DoubleNodePtr; virtual;

Назначение
----------

     Возвращает указатель   на  предыдущий  узел  (с  циклическим
переходом в конце списка).

Описание
--------

     Работает аналогично  DoubleList.Prev  с  одним  исключением.
DoubleList.Prev возвращает nil,  когда  его  параметром  является
первый узел  списка;  CircularList.Prev  в этом случае возвращает
последний узел в списке.


Store
----------------------------------------------------------------
Объявление
----------

     procedure Store(var S : IdStream);

Назначение
----------

     Запись списка в поток.

Описание
--------

     Используется аналогично  DoubleList.Store,  за   исключением
того, что  здесь  используется правило траверсирования по списку,
описанное для   CircularList.Next,   что    позволяет    избежать
зацикливания.

     CircularList использует   то   же   правило   Load,   что  и
DoubleList, поскольку  эти  два  списка   создаются   аналогичным
образом.

     Процедура регистрации  в  потоке для CircularList называется
CircularListStream.

См. также
---------

     DoubleList.Store               DoubleList.Load


4. Битовые множества

     BitSet представляет    собой   простую   структуру   данных,
позволяющую за счет  некоторой  потери  быстродействия  уменьшить
объем используемой  памяти.  Она выполняет те же действия,  что и
массив булевых значений:  каждый  элемент  такого  массива  может
принимать значения    либо    True    ("истина"),    либо   False
("ложь"). Object  Professional  использует  объект   BitSet   для
маркировки выбираемых пунктов объектов PickList (списки выбора) с
множественными вариантами решения.  Локальные  экземпляры  BitSet
используются и в других случаях: для нахождения номера следующего
доступного окна в OpWindow и для  индикации  активных  шрифтов  в
OPFORM.

     BitSet использует  каждый  из  8  битов  байта  для хранения
состояния Вкл/Выкл  одного  элемента.  Объект  включает  в   себя
соответствующие операции  поразрядного сдвига и маскирования, так
что прикладной программе об этом заботиться  не  нужно.  Правила,
определенные для    BitSet,    позволяют    установку,   очистку,
переключение и  проверку  состояния  конкретных  битов,   подсчет
количества установленных  битов  и быстрое сканирование от одного
установленного бита к другому.

     Хотя BitSet  работает  во   многом   аналогично   встроенным
множествам Turbo   Pascal,  BitSet  имеют  то  преимущество,  что
позволяют хранить более 256 элементов.  Основной же их недостаток
заключается в  отсутствии встроенной поддержки компилятором таких
операций, как  инициализация  множества,   создание   объединений
множеств и т.д.

     В данном  разделе  описаны  две различные реализации битовых
множеств. Первый объект,  который называется BitSet, ограничивает
общее число  элементов  множества числом 65534.  Второй,  который
называется LargeBitSet,  позволяет  иметь  в   множестве   до   8
миллионов битов.  Вы  можете понять,  насколько вторая реализация
сложнее первой,  и что использовать ее стоит только  при  наличии
веских причин.  Однако,  с точки зрения записи использование этих
объектов почти идентично,  поэтому менять в  программах  одни  на
другие очень легко.

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

     Объект LargeBitSet имеет  одно  дополнительное  преимущество
над BitSet:  LargeBitSet  позволяет  динамическое переопределение
размера. Это  делает  данные  объекты  предпочтительными  в   тех
случаях, когда   число   элементов  потенциально  велико,  но  не
определено заранее.

     Следующая программа демонстрирует некоторые свойства битовых
множеств.

program BitSetExample;  {EXBITSET.PAS}
uses
  OpRoot;
var
  DivisibleBy9 : BitSet;
  I, Cnt : Integer;
begin
  {Создание BitSet на 1000 флаговых битов
   (все биты сначала очищаются)}
  if not DivisibleBy9.Init(1000) then begin
    Writeln('не удалось инициализировать BitSet,
             Состояние = ', InitStatus);
    Halt;
  end;
  {Цикл по первым 1000 целым, бит устанавливается, если это целое
   делится на 9}
  for I := 1 to 1000 do
    if (I mod 9) = 0 then
      DivisibleBy9.SetBit(I);
  {Сколько всего нацело делится на 9?}
  Writeln(DivisibleBy9.BitsSet, ' делится на 9');
  {Сколько целых от 500 до 600 делится на 9?}
  Cnt := 0;
  for I := 500 to 600 do
    if DivisibleBy9.BitIsSet(I) then
      Inc(Cnt);
  WriteLn(Cnt, ' между 500 и 600 делится на 9');
  DivisibleBy9.Done;
end.

     Инициализация BitSet выделяет достаточную область "кучи" для
хранения запрошенного числа битов.  В данном примере  1000  битов
требуют  126  байтов  "кучи".  Вызов  BitSet.Init  также  очищает
(сбрасывает в 0) эти биты.  Пример программы устанавливает только
те  из них,  номера которых нацело делятся на 9.  Имеется еще ряд
операций,  аналогичных SetBit (установить бит)  по  назначению  -
ClearBit, ToggleBit, ControlBit, TestBit и т.д.

     Правило BitsSet  возвращает  общее число установленных битов
(в диапазоне 1...MaxBit,  0 бит в специальном  случае).  BitIsSet
проверяет состояние одного конкретного бита.

     И наконец,   вызов   DivisibleBy9.Done   отменяет  выделение
области "кучи" для BitSet и закрывает объект.

Константы
---------

     MaxBitCount = 65534;

     Максимальное число битов в BitSet.

     MaxLargeBitCount = 1024*1024*8;

     Максимальное число битов в LargeBitSet.

     NoMoreBits = $FFFFFFFF;

     Константа, возвращаемая   правилами   FirstSet,   LastSet  и
прочими методами,  когда в множестве больше  не  осталось  битов,
отвечающих заданному критерию.

Типы
----

     BitSetPtr = ^BitSet;
     BitSet =
       object(Root)
         ...
       end;

     LargeBitSetPtr = ^LargeBitSet;
     LargeBitSet =
       object(BitSet)
         ...
       end;

     Типы данных для организации битовых множеств.  отметим,  что
LargeBitSet является производным от BitSet.

BitSet
----------------------------------------------------------------


BitIsSet
----------------------------------------------------------------
Объявление
----------

     function BitIsSet(Element : LongInt) : Boolean; virtual;

Назначение
----------

     Возвращает значение True, если бит установлен.

Описание
--------

     Допустимые значения для Element лежат в диапазоне  от  0  до
Maxbits (максимум,  задаваемый  при конструировании BitSet).  Для
любого другого значения BitSet всегда возвращает False.

     Вы также при помощи BitIsSet  можете  проверить,  очищен  ли
бит, взяв для этого булево дополнение к результату.

     Данное правило    является    виртуальным,   поскольку   оно
переопределяется в объекте LargeBitSet.

Пример
------

     Cnt := 0;
     for I := 500 to 600 do
       if DivisibleBy9.BitIsSet(I) then Inc(Cnt);
     WriteLn(Cnt, ' делится на 9');

     Предполагая, что каждый  установленный  бит  в  DivisibleBy9
представляет   целое,   нацело   делимое   на  9,  данный  пример
подсчитывает количество целых между 500 и 600,  нацело делимых на
9.

См. также
---------

     FirstSet


BitsSet
----------------------------------------------------------------
Объявление
----------

     function BitsSet : LongInt; virtual;

Назначение
----------

     Возвращает общее число установленных битов.

Описание
--------

     Сканирует все   битовое   множество  (от  1  до  MaxBits)  и
подсчитывает в нем общее число установленных битов.  для  больших
битовых множеств  эта операция может занимать много времени.  Для
минимизации времени выполнения BitsSet вызывает  NextSet, которая
использует для   максимально   быстрого  сканирования  от  одного
установленного бита к другому встроенную макрокоманду.

     Нулевой бит не учитывается правилом  BitsSet  независимо  от
его состояния.    Число    очищенных   битов   определяется   как
MaxBits-BitsSet, и опять игнорируя состояние 0-го бита.

Пример
------

     WriteLn(DivisibleBy9.BitsSet, 'делится на 9');

     Предполагая, что DivisibleBy9 имеет  установленный  бит  для
каждого целого  между  1  и  1000,  нацело делимого на 9,  данный
пример сообщает общее число таких целых.


ClearAll
----------------------------------------------------------------
Объявление
----------

     procedure ClearAll; virtual;

Назначение
----------

     Очищает все биты множества.

Описание
--------

     Сбрасывает все биты  в  состояние,  которое  они  имели  при
создании множества (ни один бит в этот момент не был установлен).

Пример
------

     DivisibleBy9.ClearAll;

     Очищает все биты в битовом множестве DivisibleBy9, сбрасывая
их в ноль.

См. также
---------

     SetAll


ClearBit
----------------------------------------------------------------
Объявление
----------

     procedure ClearBit(Element : LongInt); virtual;

Назначение
----------

     Очищает бит.

Описание
--------

     Очищает заданный  бит.  Допустимый  диапазон  значений   для
Element от 0 до MaxBits (максимум, задаваемый при конструировании
BitSet). Для любых любых значений ClearBit не  выполняет  никаких
действий.

Пример
------

     DivisibleBy9.SetAll;
     for I := 1 to 1000 do
       if (I mod 9) = 0 then
         DivisibleBy9.ClearBit(I);

     Сначала устанавливает все биты,  а затем очищает каждый бит,
представляющий целое, нацело делимое на 9.

См. также
---------

     SetBit               ToggleBit


ControlBit
----------------------------------------------------------------
Объявление
----------

     procedure ControlBit(Element : LongInt; State : Boolean);

Назначение
----------

     Устанавливает или очищает бит,  в зависимости  от  параметра
State.

Описание
--------

     Данное правило  содержит   внутренний   вызов   SetBit   или
ClearBit, в зависимости от значения,  переданного ему в State.  В
случае неверного номера Element никаких действий не выполняется.

Пример
------

     OptionBit.ControlBit(ThisOption, ThisOptionDefault);

     Предполагая, что  OptionBit  содержит множество флагов опций
программы, данный   пример   модифицирует   бит,   представляющий
ThisOption. Если ThisOptionDafault равно True,  то бит ThisOption
будет установлен; в противном случае он будет очищен.

См. также
---------

     ToggleBit


Done
----------------------------------------------------------------
Объявление
----------

     destructor Done; virtual;

Назначение
----------

     Отменяет выделение для BitSet области памяти.

Описание
--------

     Отменяет выделение для BitSet области памяти "кучи".


Пример
------

     DivisibleBy9.Done;

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

См. также
---------

     Init


FirstClear
----------------------------------------------------------------
Объявление
----------

     function FirstClear : LongInt;

Назначение
----------

     Возвращает индекс первого очищенного бита,  либо NoMoreBits,
если таковой не найден.

Описание
--------

     Внутренне вызывает   правило    NextClear,    которое    для
максимально   быстрого   нахождения   первого   очищенного   бита
использует встроенную макрокоманду.  FirstClear всегда игнорирует
0-й бит, младшее возвращаемое им значение всегда не меньше 1.

     NoMoreBits - это константа, равная $FFFFFFFF.

См. также
---------

     FirstSet                LastClear



FirstSet
----------------------------------------------------------------
Объявление
----------

     function FirstSet : LongInt;

Назначение
----------

     Возвращает индекс первого установленного бита,  либо NoMoreBits,
если таковой не найден.

Описание
--------

     Внутренне вызывает правило NextSet,  которое для максимально
быстрого   нахождения   первого  установленного  бита  использует
встроенную макрокоманду.  FirstSet  всегда  игнорирует  0-й  бит,
младшее возвращаемое им значение всегда не меньше 1.

     NoMoreBits - это константа, равная $FFFFFFFF.

См. также
---------

     FirstClear                LastSet


Init
----------------------------------------------------------------
Объявление
----------

     constructor Init(Max : LongInt);

Назначение
----------

     Выделяет область для элементов.

Описание
--------

     Init выделяет ((Max+8) div 8)  байтов  области  "кучи"  (для
малого BitSet).  При  недостаточной памяти конструктор завершится
неудачно, и в глобальную переменную InitStatus будет  записан код
ошибки. Если   Max  больше  MaxBitCount,  Init  также  завершится
неудачно.

     Доаустимые индексы элементов BitSet лежат в диапазоне  от  0
до Max.  Однако,  в  качестве  сторожевого  элемента для операций
сканирования множества,  таких как  NextSet  и  FirstSet,  всегда
используется бит  с  номером  0.  Следовательно,  когда требуется
выполнение данных   операций,    использование    бита    0    не
рекомендуется. В  случае  же  вызова  правил  SetBit,  ClearBit и
BitIsSet бит 0 можно  использовать  так  же,  как  и  все  прочие
элементы битового множества.

     Если значение  Max  равно  0,  то  Init  инициализирует свои
значения внутренних данных (все нулями), но не выплняет выделение
области "кучи".  Это  иногда  бывает полезным при конструировании
объектов, зависимых от BitSet.  После  такой  формы  вызова  Init
вызов BitSet безопасен.

Пример
------

     if not DivisibleBy9.Init(1000) then begin
       WriteLn('Не удалось инициализировать BitSet,
                Состояние = ', InitStatus);
       Halt;
     end;

     Создает BitSet,  называющийся  DivisibleBy9,  который  может
хранить до 1000 битов.

     if not DivisibleBy9.Init(100000) then begin
       WriteLn('Не удалось инициализировать BitSet,
                Состояние = ', InitStatus);
       Halt;
     end;

     Неверно! Граничным числом элементов для объектов типа BitSet
является 65534.  Для битовых множеств, превышающих данный размер,
должны использоваться большие битовые множества (LargeBitSet).

См. также
---------

     Done                   LargeBitSet.Init


LastClear
----------------------------------------------------------------
Объявление
----------

     function FirstClear : LongInt;

Назначение
----------

     Возвращает индекс    последнего    очищенного   бита,   либо
NoMoreBits, если таковой не найден.

Описание
--------

     Внутренне вызывает    правило    PrevClear,    которое   для
максимально  быстрого  нахождения  предыдущего  очищенного   бита
использует встроенную макрокоманду. LastClear всегда игнорирует 0
-й бит, младшее возвращаемое им значение всегда не меньше 1.

     NoMoreBits - это константа, равная $FFFFFFFF.

См. также
---------

     FirstClear



LastSet
----------------------------------------------------------------
Объявление
----------

     function LastSet : LongInt;

Назначение
----------

     Возвращает индекс   последнего   установленного  бита,  либо
NoMoreBits, если таковой не найден.

Описание
--------

     Внутренне вызывает правило PrevSet,  которое для максимально
быстрого нахождения предыдущего  установленного  бита  использует
встроенную  макрокоманду.  LastSet  всегда  игнорирует  0-й  бит,
младшее возвращаемое им значение всегда не меньше 1.

     NoMoreBits - это константа, равная $FFFFFFFF.

См. также
---------

     FirstSet


Load
----------------------------------------------------------------
Объявление
----------

     constructor Load(var S : IdStream);

Назначение
----------

     Загрузка BitSet из потока.

Описание
--------

     S представляет собой правильно  инициализированный потоковый
объект (например,  файл потока, открытый на чтение). Отметим, что
S может  быть  любым   потомком   от   типа   IdStream,   включая
DosIdStream, BufIdStream и MemIdStream.

     Load считывает  из  потока  S  следующую  последовательность
байтов.   Эти  байты  должны  быть  записаны  предыдущим  вызовом
BitSet.Store. Load выделяет для BitSet  область  "кучи"  и  затем
считывает туда из потока образ BitSet.

     При неудачном завершении данный конструктор возвратит False,
а код ошибки будет записан в глобальную переменную InitStatus.

     Процедура регистрации   потока   для    BitSet    называется
BitSetStream.

Пример
------

uses
  OpRoot;
var
  DivisibleBy9 : BitSet;
  S : BufIdStream;
  I, Cnt : Integer;
begin
  {Чтение BitSet из потока}
  S.Init('BITSET.STM', SOpen, 1024);
  if not DivisibleBy9.Load(S) then begin
    WriteLn('Ошибка загрузки Load ', InitStatus);
    Halt;
  end;
  S.Done;

  {Сколько чисел от 500 до 600 нацело делится на 9?}
  Cnt := 0;
  for I := 500 to 600 do
    if DivisibleBy9.BitIsSet(I) then Inc(Cnt);
  WriteLn(Cnt, ' делится на 9');

  {Освободить память BitSet}
  DivisibleBy9.Done;
end.

     Данный пример  программы  загружает объект DivisibleBy9 типа
BitSet из потока, создаваемого в примере Store.

     Эффект, аналогичный вызову Load,  может быть  достигнут  при
помощи правила  IdStream.Get.  Более  подробную информацию см.  в
описании PointerStack.Load.

См. также
---------

     IdStream.Get                Store


MaxBits
----------------------------------------------------------------
Объявление
----------

     function MaxBits : LongInt;

Назначение
----------

     Возвращает емкость BitSet.

Описание
--------

     Возвращает то  же  максимальное значение,  что было передано
конструктору BitSet.  Допустимое  значение  в   битах   лежит   в
диапазоне 0...MaxBits.  Однако  позиция  бита 0 резервируется как
сторожевая позиция операций поразрядного  сканирования  (NextSet,
PrevSet и  т.д.).  В большинстве случаев использования этого бита
следует избегать.

Пример
------

     WriteLn('Емкость BitSet равна ', DivisibleBy9.MaxBits);

     Сообщает емкость в битах DivisibleBy9.

См. также
---------

     BitSet               Init

NextClear
----------------------------------------------------------------
Объявление
----------

     function NextClear(Element : LongInt) : LongInt; virtual;

Назначение
----------

     Возвращает индекс следующего очищенного бита множества, либо
NoMoreBits, если таковой не найден.

Описание
--------

     Для максимально быстрого сканирования  следующего очищенного
бита использует встроенную макрокоманду.

См. также
---------

     NextSet             PrevClear


NextSet
----------------------------------------------------------------
Объявление
----------

     function NextSet(Element : LongInt) : LongInt; virtual;

Назначение
----------

     Возвращает индекс  следующего установленного бита множества,
либо NoMoreBits, если таковой не найден.

Описание
--------

     Для максимально     быстрого     сканирования     следующего
установленного бита использует встроенную  макрокоманду.  Element
должен являться  допустимым  индексом в BitSet;  NextSet начинает
поиск с позиции, на 1 старше, чем Element.

     Будьте внимательны,  проверяя  возврат  NextSet в   качестве
результата значения  NoMoreBits.  NoMoreBits  является константой
типа LongInt;  если результат NextSet присваивается переменной  с
размером Word, то необходимо выполнить следующее приведение типа:

     WordVar := NextSet(Element);
     if WordVar = Word(NoMoreBits) then
       {в множестве больше нет установленных битов} ;


Пример
------

     I := DivisibleBy9.NextSet(500);  {I - переменная типа LongInt}
     if I = NoMoreBits then
       WriteLn('после 500-го в этом множестве нет
                установленных битов');
     else
       WriteLn(I, ' это следующий установленный бит после 500-го' );

     Сообщает номер следующего после 500-го  установленного бита.
Если там  нет  ни  одного  установленного  бита,  то возвращаемое
значение равно NoMoreBits ($FFFFFFFF).

     I := 0;  {I это переменная типа LongInt}
     repeat
       I := DivisibleBy9.NextSet(I);
       if I <> NoMoreBits then
         WriteLn(I, ' установлен');
     until I = NoMoreBits;

     Сканирует все установленные биты в DivisibleBy9.

См. также
---------

     NextClear            PrevSet


PrevClear
----------------------------------------------------------------
Объявление
----------

     function PrevClear(Element : LongInt) : LongInt; virtual;

Назначение
----------

     Возвращает индекс  предыдущего  очищенного  бита  множества,
либо NoMoreBits, если таковой не найден.

Описание
--------

     Для максимально быстрого сканирования предыдущего очищенного
бита использует встроенную макрокоманду.

См. также
---------

     PrevSet             NextClear


PrevSet
----------------------------------------------------------------
Объявление
----------

     function PrevSet(Element : LongInt) : LongInt; virtual;

Назначение
----------

     Возвращает индекс предыдущего установленного бита множества,
либо NoMoreBits, если таковой не найден.

Описание
--------

     Для максимально     быстрого     сканирования     следующего
установленного бита использует встроенную макрокоманду.  За одним
исключением, Element всегда должен являться допустимым индексом в
BitSet; PrevSet начинает поиск с позиции, на 1 ниже, чем Element.
Для того,  чтобы найти  послелний  установленный  бит  множества,
этому  правилу  допустимо  передать в качестве аргумента значение
MaxBits+1.

     Будьте внимательны,  проверяя  возврат  PrevSet в   качестве
результата значения  NoMoreBits.  NoMoreBits  является константой
типа LongInt;  если результат PrevSet присваивается переменной  с
размером Word, то необходимо выполнить следующее приведение типа:

     WordVar := PrevSet(Element);
     if WordVar = Word(NoMoreBits) then
       {в множестве больше нет установленных битов} ;


Пример
------

     I := DivisibleBy9.PrevSet(500);  {I - переменная типа LongInt}
     if I = NoMoreBits then
       WriteLn('до 500-го в этом множестве нет
                установленных битов');
     else
       WriteLn(I, ' это следующий установленный бит до 500-го' );

     Сообщает номер следующего перед 500-м  установленного бита.
Если там  нет  ни  одного  установленного  бита,  то возвращаемое
значение равно NoMoreBits ($FFFFFFFF).

     I := DivisibleBy9.MaxBits+1;  {I это переменная типа LongInt}
     repeat
       I := DivisibleBy9.PrevSet(I);
       if I <> NoMoreBits then
         WriteLn(I, ' установлен');
     until I = NoMoreBits;

     Сканирует все  установленные  биты в DivisibleBy9 в обратной
последовательности.

См. также
---------

     PrevClear            NextSet


SetAll
----------------------------------------------------------------
Объявление
----------

     procedure SetAll; virtual;

Назначение
----------

     Устанавливает все биты.

Описание
--------

     Заполняет всю  область BitSet байтами со значением $FF,  тем
самым устанавливая все биты множества в 1.

Пример
------

     DivisibleBy9.Init(1000);
     DivisibleBy9.SetAll;

     После создания множества устанавливает все его  биты (делает
равными 1).

См. также
---------

     ClearAll


SetBit
----------------------------------------------------------------
Объявление
----------

     procedure SetBit(Element : LongInt); virtual;

Назначение
----------

     Устанавливает бит.

Описание
--------

     Устанавливает заданный бит множества. Допустимым для Element
является диапазон  значений  от 0 до MaxBits (максимум,  заданный
при конструировании  BitSet).  В  слючае  любых  других  значений
SetBit не выполняет никаких действий.

Пример
------

     for I := 1 to 1000 do
       if (I mod 9) = 0 then
         DivisibleBy9.SetBit(I);

     Цикл по первой 1000  целых  чисел,  устанавливающий  битовый
флаг для тех из них, которые нацело делятся на 9.

См. также
---------

     ClearBit               ToggleBit
     ControlBit


Store
----------------------------------------------------------------
Объявление
----------

     procedure Store(var S : IdStream);

Назначение
----------

     Запись BitSet в поток.

Описание
--------

     S представляет собой правильно  инициализированный потоковый
объект (обычно это - файл потока,  открытый на запись).  Отметим,
что S  может  быть  любым  потомком  от  типа  IdStream,  включая
DosIdStream,  BufIdStream  и  MemIdStream  (хотя  обычно запись в
MemIdStream не используется).

     Store записывает в поток S образ BitSet,  который может быть
прочитан последующим вызовом Load.

     Для проверки ошибок,  которые могли произойти при выполнении
Store, следует вызвать функцию потока GetStatus,  как показано  в
нижеприведенном примере.

     Процедура регистрации   потока   для    BitSet    называется
BitSetStream.

Пример
------

uses
  OpRoot;
var
  DivisibleBy9 : BitSet;
  S : BufIdStream;
  I, Cnt : Integer;
  Status : Word;
begin
  {Создание BitSet с флагами на 1000 элементов
   (при создании все флаги будут очищены)}
  if not DivisibleBy9.Init(1000) then begin
    WriteLn('Не удалось инициализировать BitSet,
             Состояние = ', InitStatus);
    Halt;
  end;
  {Цикл по первой 1000 целых чисел, установка бита,
   если соответствующее целое нацело делится на 9}
  for I := 1 to 1000 do
    if (I mod 9) = 0 then
      DivisibleBy9.SetBit(I);
  {Запись этого BitSet в поток}
  S.Init('BITSET.STM', SCreate, 1024);
  DivisibleBy9.Store(S);
  Status := S.GetStatus;
  if Status <> 0 then begin
    WriteLn('Ошибка запис Store, ', Status);
    Halt;
  end;
  S.Done;
  DivisibleBy9.Done;
end.

     Данный пример программы  создает  объект  DivisibleBy9  типа
BitSet, устанавливает  некоторые  биты и записывает этот BitSet в
поток. Пример программы,  загружающей и использующей этот  поток,
см. в писании Load.

     Эффект, аналогичный вызову Store,  может быть  достигнут  при
помощи правила IdStream.Put.  IdStream.Put часто предпочтителен в
силу своей  полиморфности;  т.е.,  он  автоматически   определяет
подходящий для  вызова  конструктор  Store,  в зависимости от типа
данных потока. При использовании для записи потока Put этот поток
должен затем   читаться   при   помощи   Get.   Get  затем  может
автоматически определить,  какой конструктор  Load  вызвать.  Для
использования Put вызов Store в примере можно переписать в виде:

     S.RegisterHier(BitSetStream);
     S.Put(DivisibleBy9);

См. также
---------

     IdStream.Put             Load
     IdStream.Register.Type


TestClearBit
----------------------------------------------------------------
Объявление
----------

     function TestClearBit(Element : LongInt) : Boolean;

Назначение
----------

     Возвращает текущее состояние бита и очищает этот бит.

Описание
--------

     Данное правило   объединяет   действие   правил  BitIsSet  и
ClearBit, обеспечивая некоторый выигрыш в  быстродействии,  когда
обе эти операции требуются одновременно.

Пример
------

     if Option.Bits.TestClearBit(ThisOption) then
       WriteLn('Опция ThisOption отменена');
     else
       WriteLn('Опция ThisOption уже была ранее отменена');

     Предполагая, что   OptionBits   содержит   множество  флагов
управления программой,  данный пример отменяет опцию ThisOption и
сообщает ее предыдущее состояние.


TestSetBit
----------------------------------------------------------------
Объявление
----------

     function TestSetBit(Element : LongInt) : Boolean;

Назначение
----------

     Возвращает текущее состояние бита и устанавливает этот бит.

Описание
--------

     Данное правило объединяет действие правил BitIsSet и SetBit,
обеспечивая некоторый выигрыш в  быстродействии,  когда  обе  эти
операции требуются одновременно.

Пример
------

     if Option.Bits.TestSetBit(ThisOption) then
       WriteLn('Опция ThisOption уже была ранее отменена');
     else
       WriteLn('Опция ThisOption установлена');

     Предполагая, что   OptionBits   содержит   множество  флагов
управления  программой,   данный   пример   устанавливает   опцию
ThisOption и сообщает ее предыдущее состояние.


ToggleBit
----------------------------------------------------------------
Объявление
----------

     procedure ToggleBit(Element : LongInt);

Назначение
----------

     Переключает состояние бита на альтернативное.

Описание
--------

     Реверсирует состояние заданного элемента.

Пример
------

     OptionBits.ToggleBit(ThisOption);

     Предполагая, что   OptionBits   содержит   множество  флагов
управления программой,  данный пример изменяет на противоположное
состояние бита, заданного как ThisOption. Если бит ThisOption был
установлен, то  теперь  он очищается.  Если же он был очищен,  то
теперь он устанавливается.


См. также
---------

     ClearBit                  SetBit


LargeBitSet
----------------------------------------------------------------

Error
----------------------------------------------------------------
Объявление
----------

     procedure Error(Code : Word); virtual;

Назначение
----------

     Сообщает об ошибке.

Описание
--------

     В отличие  от  BitSet,  LargeBitSet требуется внутренний код
состояния,  поскольку он может попытаться выделить  область"кучи"
во  время  операций  SetBit.  В случае ошибки правила LargeBitSet
могут вызвать подпрограмму Error,  чтобы  сообщить  обошибке.  По
умолчанию   подпрограмма  Error  просто  хранит  код  (Code)  как
внутреннюю   переменную   состояния.   Типы,    производные    от
LargeBitSet,   могут   переопределять   правило  Error,  реализуя
какую-либо иную стратегию сообщения об ошибке.


GetStatus
----------------------------------------------------------------
Объявление
----------

     function GetStatus : Word;

Назначение
----------

     Возвращает последний  код  состояния и сбрасывает внутреннее
состояние в 0.

Описание
--------

     В отличие  от  BitSet,  LargeBitSet требуется внутренний код
состояния, поскольку он может попытаться  выделить  область"кучи"
во время  операций SetBit.  Когда вызывающая программа или SetBit
задает номер  элемента,  превышающий   ранее   заданную   границу
MaxBits, SetBit   вызывает  LargeBitSet.Resize  для  того,  чтобы
расширить битовое множество и поместить  в  него  новый  элемент.
Resize может  сообщить  одну  из  двух  ошибок,  либо "недостаток
памяти", либо "неверный параметр".  После такого вызова программа
может вызвать GetStatus, чтобы определить причину ошибки.

     Если вызовы  SetBit  никогда не влекут расширения BitSet,  в
вызове GetStatus нет необходимости.

Пример
------

     {...SetBit или Resize...}
     Status := DivisibleBy9.GetStatus;
     if Status <> 0 then begin
       WriteLn('Неудачное завершение операции,
                Состояние = ', Status);
       Halt;
     end;

     Вызов GetStatus  для  проверки  состояния последней операции
SetBit или Resize.  Поскольку  ссылка  к  GetStatus  очищает  код
состояния, для  многократного  использования код состояния должен
быть помещен в некоторую временную переменную.

См. также
---------

     Resize


Init
----------------------------------------------------------------
Объявление
----------

     constructor Init(Max : LongInt);

Назначение
----------

     Выделяет область памяти для элементов и очищает все биты.

Описание
--------

     Init выделяет  область  "кучи" в страницах по 512 байтов,  в
каждой из которых может храниться 4096 битов.  Она также выделяет
память массиву   указателей,   предназначенному  для  организации
страниц. Например, при инициализации битового множества на 100000
элементов Init должна выделить

     25 страниц, каждый по 512 байтов
     1 страницу для массива из 4*25 байтов

для 12000 байтов.  Всего же потребуется 12501 байт,  что означает
затраты памяти  собственно на организацию LargeBitSet всего в 3%.
Максимально допустимое значение для Max  равно  MaxLargeBitCount,
что составляет около 8 миллионов битов.

     LargeBitSet позволяет  динамическое изменение размеров,  что
дает возможность  его   использования   в   задачах   с   заранее
неизвестным верхним пределом количества элементов. Для увеличения
верхнего предела после инициализации можно  использовать  правила
SetBit или Resize.

     Если значение  Max  равно  0,  то  Init  инициализирует свои
внутренние значения данных (все нулями),  но не  выделит  область
"кучи" для множества. Это может быть полезным при конструировании
объектов, зависящих от LargeBitSet. После такой формы вызова Init
вызов LargeBitSet.Done безопасен.

Пример
------

     if not DivisibleBy9.Init(100000) then begin
       WriteLn('Не удалось создать битовое множество,
                Состояние = ', InitStatus);
       Halt;
     end;

     Создает битовое   множество   из   100000битов   (абсолютный
максимум составляет 8388608 битов).

См. также
---------

     Resize


Load
----------------------------------------------------------------
Объявление
----------

     constructor Load(var S : IdStream);

Назначение
----------

     Загрузка большого битового множества из потока.

Описание
--------

     Используется аналогично   BitSet.Load.   При   использовании
методологии IdStream.Get  отметим,  что  LargeBitSet  имеет  свои
собственные тип   объекта   (otLargeBitSet)    и    код    версии
(veLargeBitSet), которые  и  следует использовать для регистрации
типа в  потоке.  Процедура  регистрации  LergeBitSet   в   потоке
называется LargeBitSetStream.

См. также
---------

     IdStream.Get              IdStream.RegisterType


Resize
----------------------------------------------------------------
Объявление
----------

     function Resize(Max : LongInt) : Boolean;

Назначение
----------

     Расширяет или  уменьшает  большое  битовое  множество до Max
элементов.

Описание
--------

     LargeBitSet могут динамически изменять размер с очень малыми
затратами. Это  делает  их  идеальным  средством  для  прикладных
задач, где   максимальное  число  элементов  велико,  но  заранее
неизвестно.

     Если новое максимальное число элементов превышает старое, то
Resize  выделяет  для  битового  множества дополнительно страницы
размером по 512 байтов.  Новые  страницы  заполняются  нулями,  а
старые сохраняют  свои  прежние  значения.  Если  новое число Max
меньше старого, то лишние страницы освобождаются.

     Resize возвращает False при недостаточном  размере доступной
памяти для  выделения  новых страниц,  либо при неверном значении
Max (0 или больше, чем MaxLargeBitCount). Точный код ошибки можно
выяснить, обратившись к GetStatus.

Пример
------

     if not DivisibleBy9.Init(100000) then begin
       WriteLn('Не удалось создание битового множества,
                Состояние = ', InitStatus);
       Halt;
     end;

     if not DivisibleBy9.Resize(200000) then begin
       WriteLn('Не удалось изменение размера битового множества,
                Состояние = ', DivisibleBy9.GetStatus);
       Halt;
     end;

     if not DivisibleBy9.Resize(50000) then begin
       WriteLn('Не удалось изменение размера битового множества,
                Состояние = ', DivisibleBy9.GetStatus);
       Halt;
     end;

     Первая инициализация большого битового множества выполняется
на 100000  битов.  Затем  это  битовое  множество  расширяется до
200000 битов. Булева функция Resize возвращает False, если она не
смогла изменить  размер  массива.  В  данном  примере  это  могло
явиться следствием ошибки типа "не хватает  памяти".  И  наконец,
битовое множество сокращается до 50000 элементов. Эта операция не
может завершиться неудачно.

См. также
---------

     GetStatus


Store
----------------------------------------------------------------
Объявление
----------

     procedure Store(var S : IdStream);

Назначение
----------

     Запись большого битового множества в поток.

Описание
--------

     Используется аналогично   BitSet.Store.  При   использовании
методологии IdStream.Put  отметим,  что  LargeBitSet  имеет  свои
собственные тип   объекта   (otLargeBitSet)    и    код    версии
(veLargeBitSet), которые  и  следует использовать для регистрации
типа в  потоке.  Процедура  регистрации  LergeBitSet   в   потоке
называется LargeBitSetStream.

См. также
---------

     IdStream.Put


5. Организация строковых данных

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

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

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

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

     StringSet представляет    собой   объект,   производный   от
StringDict. Ограниченное   в   StringSet   подмножество   средств
StringDict позволяет  только  определить,  имеется  ли конкретная
строка в словаре,  или нет;  никакой дополнительной информации не
возвращается. В  результате StringSet несколько эффективнее,  чем
StringDict. Object Professional использует  StrinsSet  в  объекте
DirList для  принятия  решения  относительно  того,  принять  или
отвергнуть файлы с конкретными расширениями.

     StringArray обеспечивает другого рода средства,  нежели  два
первых объекта.  Часто  бывает  удобно  использовать  в программе
обычный массив  строк  Паскаля,  возможно  для  хранения   списка
справочных сообщений  или для создания элементов в списке выбора.
Недостаток обычного массива Паскаля состоит  в  том,  что  каждый
элемент такого  массива  должен по длине быть равен самой длинной
строке. Это потенциально ведет к напрасной трате памяти.

     StringArray упаковывает  строки  "край  к  краю",  и   таким
образом длина массива не превышает фактически требуемую. Он также
организует индексирование  этого  массива  для  быстрого   поиска
каждой строки.  В большинстве случаев затраты памяти на индекс (2
байта на строку)  значительно  меньше,  чем  экономится  за  счет
упаковки строк.

     StringArray обеспечивает   операции   для  чтения  и  записи
текстовых файлов из строк,  добавления  новых  строк  и  возврата
существующих строк.  Удаление  или модификация существующих строк
недопустимы.

     Следующий пример   демонстрирует   типичное    использование
StringDict:


program StringDictionaryExample;  {EXSDICT.PAS}
uses
  OpRoot;
type
  NameDataPtr = ^NameData;
  NameData = record
    Age : Byte;
    Addr : String;
  end;
var
  NamePtr : NameDataPtr;
  NameDict : StringDict;
  Status : Word;
begin
  {Инициализация StringDict}
  if not NameDict.Init then begin
    Writeln('Не удалось создать StringDict,
             Состояние = ', InitStatus);
    Halt;
  end;
  {Создание и добавление в StringDict нескольких имен}
  New(NamePtr);
  with NamePtr^ do begin
    Age := 20;
    Addr := '123 Vintage Lane';
  end;
  NameDict.Add('Nessus', LongInt(NamePtr));
  New(NamePtr);
  with NamePtr^ do begin
    Age := 46;
    Addr := 'Long House';
  end;
  NameDict.Add('Beowulf', LongInt(NamePtr));
  {Проверка состояния после добавления}
  Status := NameDict.GetStatus;
  if Status <> 0 then begin
    WriteLn('Не удалось добавть имена, Состояние = ', Status);
    Halt;
  end;
  {Поиск конкретного имени}
  if NameDict.Member('Nessus', LongInt(NamePtr)) then begin
    WriteLn('Найдено');
    with NamePtr^ do begin
      WriteLn('Возраст ', Age);
      WriteLn('Адрес   ', Addr);
    end;
  end else
    Writeln('Не найдено');
  {Обращение к указателям NamePtr и их освобождение}
  if NameDict.Member('Nessus', LongInt(NamePtr)) then
    Dispose(NamePtr);
  if NameDict.Member('Beowulf', LongInt(NamePtr)) then
    Dispose(NamePtr);
  NameDict.Done;
end.


     При инициализации   NameDict   создает   небольшую  исходную
хещ-таблицу максимум на 16 элементов.  Когда возникает  опасность
переполнения, объект автоматически расширит эту хеш-таблицу.

     Данный пример словаря использует в качестве ключа поиска имя
и сохраняет  указатель  на  более  общирную  информацию  об  этом
человеке. Мы  выделяем  и  инициализируем  две таких описательных
записи и добавляют в словарь два имени.  Отметим,  что указатель,
переданный правилу  Add,  должен быть приведен по типу к LongInt,
что делает его приемлемым для этой подпрограммы.

     Каждый вызов Add требует,  чтобы StringDict выделил  область
"кучи" для хранения имени.  Кроме того, если добавлено достаточно
строк, хеш-таблица  должна  быть  расширена,  что  потребует  еще
большей области  "кучи".  Следовательно,  желательно сопровождать
вызовы Add вызовами GetStatus.

     Функция Member  возвращает  True,   если   заданная   строка
находится в   словаре.  Она  также  возвращает  значение  данных,
исходно хранимое в строке.  В этом случае  значение  данных  было
указателем на  более  подробное  описание,  по  которому мы можем
обратиться к данным для вывода их на дисплей.

     Последующие вызовы Member в данном примере  программы служат
для получения указателей NameData, чтобы освободить занимаемую им
область "кучи".  И наконец,  вызов NameDict.Done  освобождает  из
памяти весь словарь в целом.

     StringSet является  аналогичным  StringDict,  поэтому пример
для этого объекта здесь не  приводится.  Основные  отличия  между
объектами состоят  в  том,  что  функции Add и Member в последнем
случае не принимают второго параметра.

     StringArray заслуживает  того,  чтобы  привести   для   него
отдельный пример.


program StringArrayExample;  {EXSARRAY.PAS}
uses
  OpRoot;
var
  Messages : StringArray;
  Failed : Boolean;
begin
  {Создание StringArray   (ориентировочно   10  сообщений  по  20
   символов каждое)}
  if not Messages.Init(10, 200) then begin
    WriteLn('Не удалось создать StringArray,
             Состояние = ', InitStatus);
    Halt;
  end;
  {Добавить несколько сообщений}
  Failed := False;
  if Messages.AddString('Не хватает памяти') = 0 then
    Failed := True;
  if Messages.AddString('Параметр вне диапазона') = 0 then
    Failed := True;
  if Messages.AddString('Занят') = 0 then
    Failed := True;
  {Успешно ли завершились все эти добавления (Add)?}
  if Failed then begin
    Writeln('Не хватает памяти');
    Halt;
  end;
  {Вывод сообщения}
  WriteLn(Messages.GetString(2));
  {Освбождение StringArray}
  Messages.Done;
end.


     Вызов конструктора для StringArray  требует  от  нас  оценку
примерного числа  строк  в  массиве  и  числа  байтов  в  строке.
Альтернативный конструктор позволяет чтение строк  из  текстового
файла без каких-либо предварительных оценок, что дает для больших
прикладных задач автоматический способ определения этих значений.
AddString также автоматически расширит таблицы при необходимости,
так что исходные границы не вызывают каких-либо настоящих проблем.

     Функция AddString добавляет строку к упакованному  массиву и
возвращает  номер индекса новой строки.  Если AddString вернет 0,
то StringArray заполнен и не может быть расширен. Часто вам может
потребоваться  сохранить  номер индекса,  возвращаемый AddString,
для  последующих  ссылок  в  программе.   Здесь   мы   можем   не
беспокоиться.

     Вызов GetString  возвращает  строку  с заданным индексом.  И
разумеется, Done освобождает область памяти,  выделенную в "куче"
StringArray и разрушает сам объект.

Константы
---------

     IniPoolSize = 16;

     Размер исходной хеш-таблицы для  StringDict.  всегда  должен
быть степенью двух.

     paGrowIndex : Word = 100;

     Когда StringArray вынужден расширить свою индексную таблицу,
он увеличивает число  доступных  элементов  на  paGrowIndex.  Это
значение можно  изменить  в любой момент до того,  как произойдет
расширение StringArray.

     paGrowInfo : Word = 1000;

     Число байтов,  на   которое   StringArray   расширяет   свой
упакованный массив,  когда  вынужден расти.  Индекс и упакованный
массив при необходимости расширяются независимо.

Типы
----

     StringDictPtr = ^StringDict;
     StringDict =
       object(Root)
         ...
       end;

     StringSetPtr = ^StringSet;
     StringSet =
       object(StringDict)
         ...
       end;

     StringArrayPtr = ^StringArray;
     StringArray =
       object(Root)
         ...
       end;

     Типы данных,   определяющие  объекты  организации  строковых
данных, описаны в данном разделе.


StringDict
----------------------------------------------------------------

Add
----------------------------------------------------------------
Объявления
----------

     procedure Add(S : String; Value : LongInt);

Назначение
----------

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

Описание
--------

     Если строка уже имеется в словаре (сравнение с существующими
элементами выполняется  вызовом  функций  Hash  и   Equal),   Add
генерирует предупреждение (epWarning + ecDupString) и выходит без
изменения словаря.  В  противном  случае  Add  выделяет   область
"кучи", достаточную  для  хранения  строки  и его значения данных
LongInt, и добавляет элемент в его внутреннюю хеш-таблицу.

     StringDict резервирует 4 байта каждого элемента  словаря для
хранения данных пользователя. Он объявляет эту область данных как
LongInt, чтобы  хранение  данных  было   облегчено,   не   требуя
приведения типа.  Другим  общим  типом данных является указатель,
который по месту занимает столько  же,  сколько  и  LongInt.  При
передаче указателя  в Add его тип должен быть приведен к LongInt,
чтобы пройти жесткий контроль типов,  принятый  в  Паскале.  Если
словарь должен хранить более 4 байтов информации о каждой строке,
он должен вместо нее хранить указатель на запись,  где фактически
находится действительная информация.

     Если после    добавления    очередной   строки   хеш-таблица
оказывается заполненной  более  чем   на   75%,   Add   расширяет
хеш-таблицу вдвое.   Сюда  входит  выделение  новой  хеш-таблицы,
передача всех  элементов  старой  таблицы   в   новую   и   затем
освобождение памяти от старой таблицы.

     Поскольку Add может генерировать предупреждения и ошибки, за
ней обычно должно следовать правило GetStatus.  Если  при  вызове
Add уже   имеется  подвешенная  ошибка  или  предупреждение,  Add
немедленно выполняет    выход,    без    каких-либо     действий.
Следовательно, достаточно  иметь  одну проверку GetStatus в конце
серии Add.

     Отметим, что по  умолчанию  StringDict  игнорирует  регистр,
которыми набраны  две  строки,  решая,  равны  ли они.  Вы можете
изменить такое поведение процедуры,  переопределив правила Hash и
Equal.

Примеры
-------

     var
       ParseDict : StringDict;
     begin
       ParseDict.Init;
       ParseDict.Add('BEGIN', 1);
       ParseDict.Add('END',   2);
       ParseDict.Add('IF',    3);
       ParseDict.Add('THEN',  4);
       if ParseDict.GetStatus <> 0 then begin
         WriteLn('Ошибка при добавлении строк');
         Halt;
       end;
     end.

     Инициализирует словарь,  который  может  быть  полезен   для
небольшого анализатора компьютерного языка.

     type
       NameDataPtr = ^NameData;
       NameData = record
         Age : Byte;
         Addr : String;
       end;
     var
       NamePtr : NameDataPtr;
       NameDict : StringDict;
     ...
       New(NamePtr);
       NameDict.Add('Nessus', LongInt(NamePtr));
       New(NamePtr);
       NameDict.Add('Beowulf', LongInt(NamePtr));
     Status := NameDict.GetStatus;
     if Status <> 0 then begin
       WriteLn('Не удалось добавить имена, Состояние = ', Status);
       Halt;
     end;

     Выделяет в  "куче"  записи NameData (хотя и не помещает туда
никакой информации).  Затем связывает каждую из этих  записей  со
строкой и  добавляет  строку  и  указатель  в  строковый  словарь
NameDict.

     Проверка ошибок  выполняется  только  после  того,  как  обе
строки будут  добавлены.  Проверять  ошибки можно и после каждого
добавления. Правило  Add  может  возвращать  ошибки  "не  хватает
памяти" и   "дублирующиеся   строки".   В  данном  примере,  если
GetStatus сообщает о дублировании строк, мы не можем знать, какая
из наших двух строк была дублирующейся.

См. также
---------

     Equal             Remove
     Hash              Update


Clear
----------------------------------------------------------------
Объявление
----------

     procedure Clear;

Назначение
----------

     Удаление всех строк из словаря.

Описание
--------

     Освобождает выделенную   строковую   область    и    очищает
внутренние хеш-таблицы. Если элементы словаря связаны с отдельной
динимической памятью (как в примере с NameDict),  перед  очисткой
словаря не забудьте освободить эту отдельную память.

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

Пример
------

     NameDict.Clear;

     Удаляет все строки из словаря NameDict.

     if NameDict.Member('ABC", LongInt(NamePtr)) then
       Dispose(NamePtr);
     NameDict.Clear;

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


Done
----------------------------------------------------------------
Объявление
----------

     destructor done; virtual;

Назначение
----------

     Отменяет выделение памяти строковому словарю.

Описание
--------

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

Пример
------

     NameDict.Done;

     Отменяет выделение  памяти  "кучи"  строковому словарю.  (Не
отменяет выделение специфической для прикладной программы области
"кучи", используемой каждым элементом.)


Equal
----------------------------------------------------------------
Объявление
----------

     function Equal(var S, T : String) : Boolean; virtual;

Назначение
----------

     Возвращает True, если две строки считаются равными.

Описание
--------

     Данное правило   вызывается  другими  правилами  StringDict,
когда бывает необходимо сравнить две  строки  на  равенство.  Оно
игнорирует регистр, в котором набраны символы сравниваемых строк.
Если ваша прикладная  программа  требует  учитывать  регистр,  вы
обязаны переопределить как это правило, так и функцию Hash.

Пример
------

function CaseStringDict.Equal(var S, T : String) : Boolean; virtual;
begin
  Equal := (S = T);

     Показывает правило Equal,  требуемое для  чувствительного  к
регистру строкового словаря, производного от StringDict.

См. также
---------

     Hash


Error
----------------------------------------------------------------
Объявление
----------

     procedure Error(Code : Word); virtual;

Назначение
----------

     Сообщает об ошибке.

Описание
--------

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


GetStatus
----------------------------------------------------------------
Объявление
----------

     function GetStatus : Word;

Назначение
----------

     Возвращение последнего   кода   состояния   и   сброс   в  0
внутреннего состояния.

Описание
--------

     Каждый StringDict  организует внутреннюю переменную, которая
хранит текущее состояние очереди. GetStatus обеспечивает доступ к
этой  переменной.  Как  и IoResult,  GetStatus очищает внутреннюю
переменную состояния после возврата текущего значения.

     Если ошибки не  произошло,  то  GetStatus  возвращает  0.  В
противном случае   возвращаемое   значение   равно  коду  ошибки,
описанному в Приложении D. Возможны следующие коды ошибки:

epFatal + ecOutOfMemory

epWarning + ecDupString

Пример
------

     ParseDict.Add('BEGIN', 1);
     Status := ParseDict.GetStatus;
     if Status <> 0 then
       WriteLn('Не удалось добавление Add,
                Состояние = ', Status);
       Halt;
     end;

     Add -  это  единственное  правили StringDict,  которое может
завершиться  неудачно  (кроме  Init).  Поскольку  вызов   функции
GetStatus приводит к сбросу значения ошибки,  мы сначала помещаем
это значение в какую-либо  собственную  переменную,  чтобы  иметь
возможность сообщить это значение.



Hash
----------------------------------------------------------------
Объявление
----------

     function Hash(Var S : String) : Word; virtual;

Назначение
----------

     Возвращает хеш-код заданной строки.

Описание
--------

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

     Хеширование должно  возвращать  значение от 0 до (sdSize-1),
включительно, где  sdSize   -   это   текущее   число   элементов
хеш-таблицы.

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

Пример
------

     function CaseStringDict.Hash(var S : String) : Word;
     var I, Sum : Word;
     begin
       Sum := 0;
       for I := 0 to Length(S) do
         Sum := (Sum shl 1)+Byte(S[I]);
       Hash := Sum and (sdSize-1);
     end;

     Показывает правило   Hash  для  чувствительного  к  регистру
строкового словаря, производного от StringDict.

См. также
---------

     Equal


Init
----------------------------------------------------------------
Объявление
----------

     constructor Init;

Назначение
----------

     Выделяет хеш-таблицу для строкового словаря.

Описание
--------

     Выделяет исходную хеш-таблицу,  достаточную для InitPoolSize
элементов. (InitPoolSize это  константа,  имеющая  значение  16).
Каждый элемент содержит 4 байта,  поэтому Init обычно выделяет 64
байта области "кучи". Хеш-таблица очищается, поэтому StringDict в
исходном состоянии пуста.

     При неудачном  завершении  данного конструктора возвращается
булево значение  False  или  указатель  со   значением   nil,   в
зависимости от того, определен ли данный экземпляр StringDict как
статический, или как динамический.  Состояние ошибки возвращается
посредством глобальной переменной OPROOT - InitStatus.

     Объект StringDict   автоматически  расширяется,  как  только
бывает заполнен на более чем 75%.  Вследствие мер,  принятых  при
реализации с точки зрения эффективности,  абсолютное максимальное
число строк в одном строковом словаре ограничено 8000 элементов.

Пример
------

     if not NameDict.Init then begin
       WriteLn('Не удалось создать StringDict.
                Состояние = ', InitStatus); Halt;
     end;

     Инициализирует строковый словарь NameDict.

См. также
---------

     Done


Load
----------------------------------------------------------------
Объявление
----------

     constructor Load(var S : IdStream);

Назначение
----------

     Загрузка строкового словаря из потока.

Описание
--------

     S представляет собой правильно  инициализированный потоковый
объект (например,  файл потока, открытый на чтение). Отметим, что
S может  быть  любым   потомком   от   типа   IdStream,   включая
DosIdStream, BufIdStream и MemIdStream.

     Load считывает  из  потока  S  следующую  последовательность
байтов. Эти  байты  должны  быть  записаны   предыдущим   вызовом
StringDict.Store. Load выделяет область "кучи" для хеш-таблицы, а
затем считывает  последовательность  строк  и  значений данных из
потока и добавляет их в словарь.

     При неудачном завершении данный конструктор возвратит False,
а код ошибки будет записан в глобальную переменную InitStatus.

     Процедура регистрации   потока   для  StringDict  называется
StringDictStream.

Пример
------

uses
  OpRoot;
var
  NameDict : StringDict;
  NDS : BufIdStream;
  Data : LongInt;
begin
  {Чтение StringDict из потока}
  NDS.Init('STRDICT.STM', SOpen, 1024);
  if not NameDict.Load(NDS) then begin
    WriteLn('Ошибка загрузки Load, ', InitStatus);
    Halt;
  end;
  NDS.Done;

  {Просмотр строк и вывод на дисплей хранимых в них данных}
  if NameDict.Member('Nessus', Data) then
    WriteLn(Data);
  if NameDict.Member('Beowulf Shaeffer', Data) then
    WriteLn(Data);
  NameDict.Done;
end.


     Данный пример   загружает   строковый   словарь  из  потока,
создаваемого в примере для Store.  Этот поток содержит две строки
и  их  "данные"  (в  данном  случае  типа LongInt).  Мы реализуем
NameDict из потока,  обращаемся к двум его строкам и  выводим  их
данные на экран.

     Эффект, аналогичный вызову Load,  может быть  достигнут  при
помощи правила IdStream.Get.  Более подробную  информацию  см.  в
описании PointerStack.Load.

См. также
---------

     IdStream.Get                  Store


Member
----------------------------------------------------------------
Объявление
----------

     function Member(S : String; var Value : Longint) : Boolean;

Назначение
----------

     Возвращает True и связанное значение данных, если S является
элементом словаря.

Описание
--------

     Функция Member  вычисляет  хеш-значение  для строки S и ищет
строку по внутренней  хеш-таблице.  Обычно  для  этого  требуется
всего несколько сравнений, даже в случае очень больших словарей.

     Если соответственная   строка   найдена   (с  игнорированием
регистра), Member  возвращает  значение  True   и   инициализиует
результирующую переменную     Value    значением    пользователя,
записанным при первоначальном добавлении данной строки в словарь.
Отметим, что  Value  имеет  тип  LongInt:  даже  если фактический
результат имеет размер байт, переданный параметр должен быть типа
LongInt. Переменная типа указателя также должна быть использована
с соответствующим приведением типа.

Пример
------

     if NameDict.Member('abc', NameValue) then
       WriteLn('Найдена с NameValue = ', NameValue)
     else
       WriteLn('Не найдено');

     Проверяет, является ли 'abc' членом словаря  NameDict.  Если
да, то Member имеет значение True,  и возвращает данные, хранимые
в этой строке.


Remove
----------------------------------------------------------------
Объявление
----------

     procedure Remove(S : String);

Назначение
----------

     Удаляет строку из словаря.

Описание
--------

     Если строка S найдена в словаре,  Remove отменяет  выделение
ей памяти и удаляет ее.

     Хотя хеш-таблца  StringDict  автоматически  расширяется  при
необходимости, при удалении строк автоматического  сжатия словаря
не происходит.

Пример
------

     NameDict.Add('Nessus', NameValue);
     ...
     NameDict.Remove('Nessus');

     Добавляет строку 'Nessus' и указатель  в  словарь  NameDict.
Затем в  программе  мы  удаляем  строку  из словаря.  Если строка
'Nessus' не была ранее добавлена в словарь, то Remove не выполнит
никаких действий.  Если необходимо наверняка знать,  находится ли
строка в словаре, то программа может сначала вызвать Member.

См. также
---------

     Clear


Store
----------------------------------------------------------------
Объявление
----------

     procedure Store(var S : IdStream);

Назначение
----------

     Запись строкового словаря в поток.

Описание
--------

     S представляет собой правильно  инициализированный потоковый
объект (обычно это - файл потока,  открытый на запись).  Отметим,
что S  может  быть  любым  потомком  от  типа  IdStream,  включая
DosIdStream,  BufIdStream  и  MemIdStream  (хотя  обычно запись в
MemIdStream не используется).

     Store записывает  в  поток  S текущий размер хеш-таблицы,  а
затем упакованный образ всех строк и значений данных для словаря.
раз StaticQueue. Образ может быть затем прочитан вызовом Load.

     Для проверки ошибок,  которые могли произойти при выполнении
Store, следует вызвать функцию потока GetStatus,  как показано  в
нижеприведенном примере.

     Процедура регистрации   потока   для  StringDict  называется
StringDictStream.

Пример
------

uses OpRoot;
var
  NameDict : StringDict;
  NDS : BufIdStream;
  Status : Word;
begin
  if not NameDict.Init then begin
    WriteLn('Не удалось создать StringDict.
             Состояние =  ', InitStatus); Halt;
  end;
  {Добавить в StringDict несколько имен}
  NameDict.Add('Nessus', 1000);
  NameDict.Add('Beowulf', 1001);
  Status := NameDict.GetStatus;
  if Status <> 0 then begin
    WriteLn('Не удалось добавить имена.
             Состояние =  ', Status); Halt;
  end;
  {Запись словаря в поток}
  NDS.Init('STRDICT.STM', SCreate, 1024);
  NameDict.Store(NDS);
  Status := NDS.GetStatus;
  if Status <> 0 then begin
    WriteLn('Ошибка записи Store ', Status); Halt;
  end;
  NDS.Done;
  NameDict.Done;
end.

     Данный пример реализует NameDict,  добавляет туда две строки
не некоторые  "данные" (в данногм случае - несколько LongInt),  а
затем записывает NameDict в поток.  Пример программы, считывающей
этот поток, см. в описании Load.

     Эффект, аналогичный вызову Store,  может быть  достигнут  при
помощи правила IdStream.Put.  IdStream.Put часто предпочтителен в
силу своей  полиморфности;  т.е.,  он  автоматически   определяет
подходящий для  вызова  конструктор  Store,  в зависимости от типа
данных потока. При использовании для записи потока Put этот поток
должен затем   читаться   при   помощи   Get.   Get  затем  может
автоматически определить,  какой конструктор  Load  вызвать.  Для
использования Put вызов Store в примере можно переписать в виде:

     NDS.RegisterHier(StringDictStream);
     NDS.Put(NameDict);

См. также
---------

     IdStream.Put                    Load
     IdStream.RegisterType


Update
----------------------------------------------------------------
Объявление
----------

     procedure Update(S : String; Value ; LongInt);

Назначение
----------

     Изменение значения данных, связанного со строкой.

Описание
--------

     Ищет строку  в  словаре  и  изменяет  ее  значение на новое,
заданное здесь.  Если S не найдено,  Update не выполняет  никаких
действий.

Пример
------

     NameDict.Add('Nessus', 1000);
     ...
     NameDict.Update('Nessus', 2000);

     Изменяет значение данных для 'Nessus' с 1000 на 2000.


См. также
---------

     Add


StringSet
----------------------------------------------------------------

Add
----------------------------------------------------------------
Объявление
----------

     procedure Add(S : String);

Назначение
----------

     Добавляет строку в множество.

Описание
--------

     Используется аналогично StringDict.Add, за исключением того,
что здесь не может быть задано значение данных.

См. также
---------

     StringDict.Add


Load
----------------------------------------------------------------
Объявление
----------

     constructor Load(var S : IdStream);

Назначение
----------

     Загружает строковое множество из потока.

Описание
--------

     Используется аналогично  StringDict.Load.  При использовании
методологии  IdStream.Get  отметим,  что  StringSet  имеет   свой
собственные тип объекта (otStringSet) и код версии (veStringSet),
которые и следует использовать для  регистрации  типа  в  потоке.
Процедура    регистрации    StringSet    в    потоке   называется
StringSetStream.

См. также
---------

     Store                StringDict.Load


Member
----------------------------------------------------------------
Объявление
----------

     function Member(S : String) : Boolean;

Назначение
----------

     Возвращает True, если S это элемент множества.

Описание
--------

     Используется аналогично  StringDict.Member,  за  исключением
того, что здесь не может быть задано значение данных.

См. также
---------

     StringDict.Member


Store
----------------------------------------------------------------
Объявление
----------

     procedure Store(var S : IdStream);

Назначение
----------

     Записывает список в поток.

Описание
--------

     Используется аналогично StringDict.Store.  При использовании
методологии  IdStream.Get  отметим,  что  StringSet  имеет   свой
собственные тип объекта (otStringSet) и код версии (veStringSet),
которые и следует использовать для  регистрации  типа  в  потоке.
Процедура    регистрации    StringSet    в    потоке   называется
StringSetStream.

См. также
---------

     Load                StringDict.Store



StringArray
----------------------------------------------------------------

AddString
----------------------------------------------------------------
Объявление
----------

     function Addstring(St : String) : Word;

Назначение
----------

     Добавляет новую строку, возвращая ее индекс, либо 0 в случае
ошибки.

Описание
--------

     Добавляет строку  St  во  внутренний   строковый   буфер   и
присваивает ему   следующий   свободный  индекс,  возвращаемый  в
качестве результата функции.  Нижнее значение этого индекса равно
1.

     Если строковый  буфер  или  массив  индексов  заполнены,  то
AddString делает попытку расширить  его,  выделяя  дополнительную
область "кучи".   Если   это  не  удается,  AddString  возвращает
значение 0.

     AddString может добавлять строки длиной от 0 до  255 байтов.
Никаких требований  к  уникальности  строки  не предъявляется,  и
никаких проверок ее содержимого не выполняется.

Пример
------

     var
       S : String;
       I : Word;
     ...
     Write('Введите сообщение: ');
     ReadLn(S);
     if S <> '' then begin
       I := Messages.AddString(S);
       if I = 0 then
         WriteLn('Не удалось добавить сообщение')
       else
         WriteLn('Сообщение добавлено с индексом ', I);
     end;

     После считывания  S  с клавиатуры мы добавляем S в строковый
массив Messages,  сохраняем  значение  его  индекса  и  проверяем
успешное завершение операции.

См. также
---------

     GetString


Done
----------------------------------------------------------------
Объявление
----------

     destructor Done; virtual;

Назначение
----------

     Отмена выделенной упакованному массиву памяти.

Описание
--------

     Память, выделенная   внутреннему    буферу    и    массивам,
отменяется, а объект StringArray разрушается.

Пример
------

     Messages.Done;

     Отменяет выделение Messages области "кучи".

См. также
---------

     Init


GetString
----------------------------------------------------------------
Объявление
----------

     function GetString(Which : Word) : String; virtual;

Назначение
----------

     Возвращает строку из упакованного массива.

Описание
--------

     Which задает    номер   индекса   возвращаемой   строки.   0
представляет собой особый случай:  он  всегда  ведет  к  возврату
пустой строки.   Если   Which   превышает   максимальный  индекс,
GetString тоже  возвращает  пустую  строку.  В  противном  случае
GetString возвращает строку, первоначально добавленную при помощи
AddString.

     GetString является  виртуальной,  что  позволяет   создавать
производный тип, не только упаковывающий строки, но и выполняющий
их сжатие. Правило WriteText вызывает GetString для записи каждой
строки в текстовый файл.

Пример
------

     WriteLn(Messages.GetString(2));

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

     WriteLn(Messages.GetString(0));

     Выводит пустую строку.


GetStringPtr
----------------------------------------------------------------
Объявление
----------

     function GetStringPtr(Which : Word) : StringPtr;

Назначение
----------

     Возвращает указатель на строку из упакованного массива.

Описание
--------

     Вызов этой  подпрограммы иногда бывает более эффективен, чем
вызов GetString. Эта подпрограмма избегает создания промежуточной
копии строки, непосредственно возвращая указатель на копию строки
в упакованном массиве.

     Если Which  равно  0  или  превышает  общее   число   строк,
GetStringPtr возвращает указатель со значением nil.

     Не пытайтесь модифицировать указываемую GetStringPtr строку.
Тем самым вы можете испортить буфер упакованных строк.

Пример
------

     S := Messages.GetStringPtr(2)^;

     Присваивает вторую  строку из упакованного массива строковой
переменной.


Init
----------------------------------------------------------------
Объявление
----------

     constructor Init(StrMax, Amount : Word);

Назначение
----------

     Выделяет память на StrMax строк в области Amount.

Описание
--------

     StringArray работает,  упаковывая строки "край - к -краю"  в
байтовом буфере.  Таким образом, потери памяти при хранении строк
переменной длины,  как в случае обычных строковых массивов, здесь
не происходит. Для обеспечения непосредственного доступа к каждой
строке StringArray также организует индекс,  представляющий собой
смещение этой строки в упакованном строковом буфере.

     Вызов Init  требует  от  вас предварительной оценки размеров
этих двух  буферов.  StrMax  задает  максимальное  число   строк,
которое может  быть  индексировано.  Amount  задает  максимальное
число байтов (включая байт длины), используемое всеми строками.

     Если вы добавляете строки за  этими  границами,  StringArray
попытается расшириться соответственным образом. Однако, это может
оказаться нежелательным,  поскольку это приведет  к  фрагментации
"кучи" и  поскольку  свободного  пространства  в  "куче" может не
оказаться.

     Init требует   (Amount+2*StrMax)    байтов    "кучи".    При
недостаточном количестве памяти завершение будет неудачным, а код
состояния помещается в глобальную переменную OPROOT InitStatus.

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

Пример
------

     const
        MsgCount = 10; {Общее число сообщений}
        MsgSize = 20;  {Средний размер одной строки}
     ...
     if not Messages.Init(MsgCount, MsgCount*MsgSize) then begin
       WriteLn('Не удалось создать StringArray,
                Состояние = ', InitStatus);
       Halt;
     end;

     Создает упакованный   строковый   массив,   который    может
содержать максимум 10 строк,  длиной всего до 200 символов. Длина
отдельных строк  может  изменяться,  но  итог  должен  оставаться
равным 200 символов.

     if not Messages.Init(MsgCount, MsgSize) then begin
       WriteLn('Не удалось создать StringArray,
                Состояние = ', InitStatus);
       Halt;
     end;

     Неверно! Это  будет работать,  но это не то,  чего мы хотим.
Второй параметр должен  быть  равным  общему  размеру  строкового
пространства, а не размеру одного сообщения.


См. также
---------

     ReadText                 Load



Load
----------------------------------------------------------------
Объявление
----------

     constructor Load(var S : IdStream);

Назначение
----------

     Загрузка строкового словаря из потока.

Описание
--------

     S представляет собой правильно  инициализированный потоковый
объект (например,  файл потока, открытый на чтение). Отметим, что
S может  быть  любым   потомком   от   типа   IdStream,   включая
DosIdStream, BufIdStream и MemIdStream.

     Load считывает  из  потока  S  следующую  последовательность
байтов.  Эти  байты  должны  быть  записаны  предыдущим   вызовом
StringArray.Store.  Load  выделяет  область  "кучи"  для массивов
индекса и буфера,  а  затем  блочно  считывает  ранее  записанные
образы этих массивов непосредственно в память.

     При неудачном завершении данный конструктор возвратит False,
а код ошибки будет записан в глобальную переменную InitStatus.

     Процедура регистрации   потока   для StringArray  называется
StringArrayStream.

Пример
------

uses
  OpRoot;
var Messages : StringArray;
    MS : BufIdStream;
begin
  {Загрузка StringArray из потока}
  MS.Init('MSG.STM', SOpen, 1024);
  if not Messages.Load(MS) then begin
    WriteLn('Ошибка загрузки Load, ', InitStatus); Halt;
  end;
  MS.Done;
  {Вывод на дисплей сообщения}
    WriteLn(Messages.GetString(2));
  Messages.Done;
end.


     Данный пример  создает  StringArray  с  именем  Messagaes из
потока, создаваемого в примере для Store. Этот поток содержит три
сообщения.  После  загрузки  Messages  мы выводим на экран вторую
хранимую там строку.

     Эффект, аналогичный вызову Load,  может быть  достигнут  при
помощи правила IdStream.Get.  Более подробную  информацию  см.  в
описании PointerStack.Load.

См. также
---------

     Store


NumStrings
----------------------------------------------------------------
Объявление
----------

     function NumStrings : word;

Назначение
----------

     Возвращает число строк в упакованном массиве.

Описание
--------

     Для только  чн созданного StringArray это правило возвращает
0. В противном случае возвращает число строк, успешно добавленных
в массив.

Пример
------

     WriteLn('Messages содержит ', Messages.NumStrings, ' строк');

     Выводит на дисплей число строк строкового массива Messages.


ReadText
----------------------------------------------------------------
Объявление
----------

     constructor ReadText(FileName : PathStr);

Назначение
----------

     Инициализирует строковый массив из текстового файла.

Описание
--------

     Открывает файл, заданный FileName, на чтение, а затем дважды
читает файл.  На  первом проходе подсчитывает число строк и объем
требуемой памяти.  Затем   выделяется   StringArray   правильного
размера. На втором проходе инициализирует StringArray. И наконец,
закрывает файл.

     Поскольку ReadText  работает  с   файловым   вводом/выводом,
существует множество  возможных механизмов отказа.  При неудачном
завершении ReadText  вследствие  файловых  ошибок  в   глобальной
переменной InitStatus возвращается значение (epFatal + IoResult).

Пример
------

     if not Messages.ReadText('SAMPLE.TXT') then begin
       WriteLn('Не удалось создать StringArray,
                Состояние = ', InitStatus);
       Halt;
     end;

     Создает упакованный строковый массив, заполненный текстовыми
строками из файла SAMPLE.TXT.  SAMPLE.TXT - это обычный текстовый
ASCII-файл, который может иметь следующее содержимое:

     Не хватает памяти<CR><LF>
     Параметр вне диапазона<CR><LF>
     Занят<CR><LF>

См. также
---------

     Init                     WriteText
     Load


Shrink
----------------------------------------------------------------

     function Shrink : Boolean;

Объявление
----------

     Удаляет лишнюю область массива.

Назначение
----------

     Это правило сжимает буферы индексов и строк до того размера,
который требуется  для хранения строк,  находящихся там в текущий
момент. Хотя это правило  уменьшает  область  "кучи",  занимаемой
StringArray, оно обычно увеличивает фрагментированность "кучи".

     При успешном завершении Shrink возвращает значение True.

Описание
--------

     Messages.Init(100,2000);
     for I := 1 to 3 do begin
       Write('Введите сообщение: ');
       ReadLn(S);
       if S <> '' then
         if Messages.AddString(S) = 0 then
           WriteLn('Не удалось добавить сообщение')
         else
           WriteLn('Сообщение добавлено');
     end;
     if not Messages.Shrink then
       WriteLn('Для сжатия не хватает памяти');

     Упакованному строковому  массиву Messsages при инициализации
было выделено значительно больше места,  чем нужно. Поэтому после
добавления необходимых  строк  мы  вызываем  Messages.Shrink  для
сжатия размеров Messages до минимально требуемого объема.

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


См. также
---------

     Init


Size
----------------------------------------------------------------
Объявление
----------

     function Size ; Word;

Назначение
----------

     Возвращает число байтов, использованное строками.

Описание
--------

     Возвращает степень заполнения строкового  буфера.  Например,
если в  StringArray  было  добавлено  10  строк  по 20 символов в
каждой, Size вернет 210 (включая все байты длины).

Пример
------

     WriteLn('Существующие сообщения используют ', Messages.Size,
              ' байтов');

     Выводит на дисплей число байтов,  используемое для  хранения
строк в текущий момент.

См. также
---------

     NumStrings



Store
----------------------------------------------------------------
Объявление
----------

     procedure Store(var S : IdStream);

Назначение
----------

     Запись строкового массива в поток.

Описание
--------

     S представляет собой правильно  инициализированный потоковый
объект (обычно это - файл потока,  открытый на запись).  Отметим,
что S  может  быть  любым  потомком  от  типа  IdStream,  включая
DosIdStream,  BufIdStream  и  MemIdStream  (хотя  обычно запись в
MemIdStream не используется).

     Store записывает  в  поток S текущие размеры буферов строк и
индексов,  а затем битовые образы буферов. Эта последовательность
может быть затем прочитана вызовом Load.

     Для проверки ошибок,  которые могли произойти при выполнении
Store, следует вызвать функцию потока GetStatus,  как показано  в
нижеприведенном примере.

     Процедура регистрации   потока   для StringArray  называется
StringArrayStream.

Пример
------

uses
  OpRoot;
var
  Messages : StringArray;
  MS : BufIdStream;
  Failed : Boolean;
  Status : Word;
begin
  {Создание StringArray   (ориентировочно   10  сообщений  по  20
   символов каждое)}
  if not Messages.Init(10, 200) then begin
    WriteLn('Не удалось создать StringArray,
             Состояние = ', InitStatus);
    Halt;
  end;
  {Добавить несколько сообщений}
  Failed := False;
  if Messages.AddString('Не хватает памяти') = 0 then
    Failed := True;
  if Messages.AddString('Параметр вне диапазона') = 0 then
    Failed := True;
  if Messages.AddString('Занят') = 0 then
    Failed := True;
  if Failed then begin
    Writeln('Не хватает памяти');
    Halt;
  end;
  {Запись упакованного массива в поток}
  MS.Init('MSG.STM', SCreate, 1024);
  Messages.Store(MS);
  Status := MS.GetStatus;
  if Status <> 0 then begin
    WriteLn('Ошибка записи Store ', Status); Halt;
  end;
  MS.Done;
  Messages.Done;
end.

     Создает StringArray  с  именем Messages,  добавляет туда три
строки  и  затем  записывает  его  в  поток.  Пример   программы,
считывающей этот поток, см. в описании Load.

     Эффект, аналогичный вызову Store,  может быть  достигнут  при
помощи правила IdStream.Put.  Более подробную  информацию  см.  в
описании PointerStack.Store.

См. также
---------

     IdStream.Put                    Load
     IdStream.RegisterType


WriteText
----------------------------------------------------------------
Объявление
----------

     procedure WriteText(FileName : PathStr);

Назначение
----------

     Записывает упакованный массив в текстовый файл.

Описание
--------

     Сканирует StringArray   в   порядке   возрастания   индексов
(начиная с индекса 1) и  записывает  каждую  строку  в  текстовый
файл, отделяя ее от других последовательностью <CR><LF>.

     Данное правило  сначало было разработано в качестве утилиты,
т.е. вспомогательного   средства    написания    программы,    не
включаемого в итоговую прикладную программу. В такос качестве оно
не обеспечивает тот же уровень контроля ошибок, что и большинство
других правил.   Проверка   ошибок   ввода/вывода   после  вызова
WriteText должна  выполняться  путем  считывания  функции   Turbo
Pascal IoResult.

Пример
------

     if not Messages.ReadText('SAMPLE.TXT') then begin
       WriteLn('Не удалось создать StringArray,
                Состояние = ', InitStatus);
       Halt;
     end;

     for I := 1 to 3 do begin
       Write('Введите сообщение: ');
       ReadLn(S);
       if S <> '' then
         if Messages.AddString(S) = 0 then
           WriteLn('Не удалось добавить сообщение')
         else
           WriteLn('Сообщение добавлено');
     end;
     Messages.WriteText('SAMPLE.TXT');
     Status := IoResult;
     if Status <> 0 then
       WriteLn('Не удалось записать файл сообщений,
                IoResult = ', Status);


     Создает упакованный строковый массив на три  строки.  Строки
считываются в   клавиатуры.   Затем  эти  сообщения  выводятся  в
текстовый файл  (предположительно  для  считывания   при   помощи
readText или некоторой другой программы).

     OPROOT компилируется  при  выключенной  (off) опции контроля
ввода/вывода ($I-}.  Это позволяет выполнять собственный контроль
ввода/вывода посредством ссылки к IoResult.

См. также
---------

     IdStrIdStream.registerType            ReadText


6. Потоки

     Термин "потоки"  привносит  некоторый  мистический оттенок в
целом простую концепцию.  В  текущей  интерпретации  этот  термин
возник   в  операционной  системе  Unix,  использовался  в  языке
программирования C++  и  с  дальнейшими  модификациями  вошела  в
модуль  примеров  Turbo  Pascal  5.5  OBJECTS.PAS.  Теперь Object
Professional вносит  в  этот  термин  свои  собственные  оттенки.
Однако,   независимо   от   этимологии   этого   термина,  потоки
обеспечивают исключительно полезные средства в контексте объекто-
ориентированного программирования.

     Первое, что  необходимо  знать  о  потоках,  это  что  поток
представляет собой последовательность байтов,  которые могут быть
прочитаны или  записаны.  За  интерпретацию  этих байтов отвечает
программа, читающая  или  записывающая  их.   Байты   эти   часто
сохраняются в   файле,  поэтому  поток  можно  считать  некоторым
специальным видом файла. Однако, поток никоим образом не сводится
к ограниченному  понятию  файла.  Фактически  Object Professional
поддерживает потоки,  которые могут храниться как  в  оперативной
памяти, так   и  на  диске,  что  дает  прикладным  программистам
некоторые удобные средства.

     При желании вы сожете использовать поток  как нетипированный
файл в  Turbo  Pascal.  Поток  можно  создавать,  выполнять в нем
запись, поиск,  чтение и закрывать  его,  как  любой  файл.  Это,
однако, не  заслуживает  особого внимания,  так как обычные файлы
достаточно хорошо обеспечивают эти действия.

     Специфика потоков относится к их связью с OOP.  Поток делает
возможной всю концепцию "объекто-ориентированного ввода/вывода" в
целом. Типовой единицей хранения в потоке является  один  объект.
Таким образом,    поток    содержит   данные,   необходимые   для
инициализации экземпляра объекта.  Разумеется,  он может  хранить
более одного экземпляра одного и того же типа объекта. Фактически
поток может содержать  экземпляры  различных  типов  объектов,  а
программа имеет возможность считывать эти объекты из потока, даже
не зная,  какие именно объекты  находятся  в  потоке  и  в  каком
порядке расположены.

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

     Зачем же иметь дело с потоками ? Возможно, объяснением может
послужить пример из нашего опыта. Поставляемые утилиты MAKEMENU и
MAKESCRN считывают   и  записывают  библиотеки  объектов  меню  и
экранов ввода.  Это упрощает разработку объекта, сохранение их на
диске и  последующее  обращение  к  ним  для  внесения изменений.
Пользуясь преимуществами    потоков,    мы    освобождаемся    от
необходимости самим  писать  какие-либо коды для организации этих
библиотек. Фактически такая библиотека позволяет хранить оба типа
бъектов и сама различает их между собой.

     Если вы  пишете  прикладную программу,  зависящую от сложной
системы меню,  вы обнаружите,  что загрузка  определения  системы
меню из  потока  делает  вашу  программу  меньше,  чем если бы вы
строили ее из  исходных  операторов.  Каждый  Menu.AddItem  может
генерировать код,  но загрузка меню из потока занимает всего один
вызов. Аналогичные аргументы относятся и к  упакованным текстовым
экранам, а также всем разновидностям окон.

     Мы ожидаем,  что  программисты,  использующие  Turbo Pascal,
создадут целые базы данных из объектов.  Уже сейчас  можно  найти
рекламу продуктов  "объекто-ориентированных  баз  данных"  самого
совершенного уровня.  Такого рода СУБД отличаются от традиционных
тем, что  им  не  требуется  знать,  какого  рода данные содержит
каждая запись;  правила  каждого  типа  объекта   сами   способны
классифицировать и  декодировать каждую запись при необходимости.
Потоки составляют основу такой футуристической концепции.

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


program StreamExample;  {EXSTREAM.PAS}
uses
  OpRoot;
const
  otNameData = 1000;         {Name object types}
  otCustData = 1001;
  veNameData = 0;            {Name object versions}
  veCustData = 0;
type
  {Объект для манипулирования именем Name и возрастом Age}
  NameDataPtr = ^NameData;
  NameData =
    object(Root)
      Name : String[20];
      Age  : Byte;
      constructor Init;
      procedure Display;
      constructor Load(var S : IdStream);
      procedure Store(var S : IdStream);
    end;
  {Потомок от NameData, добавлено имя компании Company}
  CustData =
    object(NameData)
      CompanyName : String[20];
      constructor Init;
      procedure Display;
      constructor Load(var S : IdStream);
      procedure Store(var S : IdStream);
    end;
var
  Customer : CustData;
  S : BufIdStream;
  I, Status : Word;
  {----- правила NameData  -------------------}
  constructor NameData.Init;
  begin
    WriteLn;
    Write('Имя:  ');
    ReadLn(Name);
    Write('Возрас:   ');
    ReadLn(Age);
  end;
  procedure NameData.Display;
  begin
    WriteLn;
    Writeln(Name,' ',Age);
  end;
  constructor NameData.Load(var S : IdStream);
  begin
    Root.Init;
    Name := S.ReadString;
    S.Read(Age, SizeOf(Age));
    if S.PeekStatus <> 0 then
      Fail;
  end;
  procedure NameData.Store(var S : IdStream);
  begin
    S.WriteString(Name);
    S.Write(Age, SizeOf(Age));
  end;
  {$F+}
  procedure NameDataStream(SPtr : IdStreamPtr);
  begin
    with SPtr^ do begin
      RegisterType(otNameData, veNameData,   {код объекта, номер версии}
                   TypeOf(NameData),         {указатель VMT объекта}
                   @NameData.Store,          {процедура записи в поток}
                   @NameData.Load);          {процедура чтения из потока}
    end;
  end;
  {$F-}
  {---- правила CustData  -----------------}
  constructor CustData.Init;
  begin
    NameData.Init;
    Write('Имя компании: ');
    ReadLn(CompanyName);
  end;
  procedure CustData.Display;
  begin
    NameData.Display;
    WriteLn(CompanyName);
  end;
  constructor CustData.Load(var S : IdStream);
  begin
    if not NameData.Load(S) then
      Fail;
    CompanyName := S.ReadString;
    if S.PeekStatus <> 0 then
      Fail;
  end;
  procedure CustData.Store(var S : IdStream);
  begin
    NameData.Store(S);
    S.WriteString(CompanyName);
  end;
  {$F+}
  procedure CustDataStream(SPtr : IdStreamPtr);
  begin
    NameDataStream(SPtr);
    with SPtr^ do
      RegisterType(otCustData, veCustData, TypeOf(CustData),
                   @CustData.Store, @CustData.Load);
  end;
  {$F-}
begin
  {Создание потока, проверка успешного завершения}
  if not S.Init('CUSTOMER.STM', SCreate, 1024) then begin
    Writeln('Не удалось создать поток,  Состояние = ', InitStatus);
    Halt;
  end;
  {Регистрация иерархии объекта}
  S.RegisterHier(CustDataStream);
  Status := S.GetStatus;
  if Status <> 0 then begin
    Writeln('Не удалось зарегистрировать объект,  Состояние = ', Status);
    Halt;
  end;
  {создание и сохранение трех объектов Customer,
   затем закрытие потока}
  for I := 1 to 3 do begin
    Customer.Init;
    S.Put(Customer);
    Status := S.GetStatus;
    if Status <> 0 then begin
      WriteLn('Не удалось записать,   Состояние = ', Status);
      Halt;
    end;
    Customer.Done;
  end;
  S.Done;
  {Повторное открытие существующего потока}
  if not S.Init('CUSTOMER.STM', SOpen, 1024) then begin
    Writeln('Не удалось открыть поток,  Состояние = ', InitStatus);
    Halt;
  end;
  {Повторная регистрация иерархии объекта}
  S.RegisterHier(CustDataStream);
  Status := S.GetStatus;
  if Status <> 0 then begin
    Writeln('Не удалось зарегистрировать объекты. Состояние = ', Status);
    Halt;
  end;
  {Получение Customers из потока, вывод на дисплей,
   затем закрытие потока}
  for I := 1 to 3 do begin
    S.Get(Customer);
    Status := S.GetStatus;
    if Status <> 0 then begin
      WriteLn ('Не удалось прочитать Customer. Состояние = ', Status);
      Halt;
    end;
    Customer.Display;
    Customer.Done;
  end;
  S.Done;
end.


     Данный пример создает  простую  иерархию  из  двух  объектов:
NameData, в котором хранится имя и возраст человека, и CustData, в
котором к объекту NameData добавляется имя  компании  CompanyName.
Каждый из  этих  объектов включает в себя четыре правила:  обычные
конструктор и деструктор,  плюс еще два правила -  Load  и  Store,
специально для работы с потоком.

     Load и   Store  это  ключевые  правила,  деляющие  возможными
объектоориентированные потоки.   Вместо   того,   чтобы   пытаться
встраивать в  менеджер  потоков  специальные коды - коды,  которые
позволили бы  читать  и  записывать  конкретные   объекты   -   мы
перекладываем эту   задачу   на  сами  эти  объекты.  Это  способ,
благодаря которому объект может сам  инициалиизировать  свои  поля
данных, выделить   область  "кучи"  и  выполнить  все  необходимые
для реализации объекта действия.

     Правила Load и Store обеспечиваются для всех  основных типов,
входящих в  Object  Professional.  В  данном  же  примере написаны
новые, чтобы показать,  насколько просто можно использовать потоки
в ваших собственных объектах.

     И Load,  и Store принимают один параметр-переменную: читаемый
или записываемый поток.  Этот параметр  имеет  тип  IdStream.  Это
наиболее примитивный  потоковый  объект  Object  Professional,  из
которого производится   множество    специализированных    версий.
Фактически переданный   параметр   может  являться  буферизованным
файловым потоком  (как   в   данном   случае),   потоком   памяти,
библиотекой и  т.д.Для Load и Store это безразлично,  поскольку на
данном уровне все эти различные виды потоков совместимы.

     Работа правила Store состоит в записи данных, необходимых для
описания объекта.  Оно имеет  доступ  к  некоторым вспомогательным
правилам потока:  WriteString  записывает  в  поток  строку  Turbo
Pascal, Write  записывает  в  поток заданное число байтов,  и т.д.
Правило NameData.Store записывает таким способом свои поля  Name и
Age.правило CustData.Store    сначало   вызывает   правило   Store
родительского объекта для записи унаследованной  части,  и  только
потом оно записывает свои собственные данные.  Такая иерархическая
методика записи производных объектов  настоянельно  рекомендуется.
Она не  только  экономит  ващу работу,  но и подразделяет задачу в
целом в соответствии с границами, используемыми самими объектами.

     Load выполняет  процесс   чтения   из   потока   в   обратной
последовательности.  Load является конструктором,  предоставляющим
специальные средства для  выделения  области  "кучи"  динамическим
экземплярам объектов,  а  также  для  инициализациисвязи  связи  с
таблицей виртуальных правил (VMT).  И опять, CustData.Load сначала
вызывает родительское правило Load для чтения унаследованной части
данных. При неудачном завершении NameData.Load CustData.Load также
имеет неудачное  завершение.  В  противном же случае CustData.Load
считывает остальную  часть  данных.  И  наконец,   оно   проверяет
переменную состояния потока с точки зрения ошибок ввода/вывода.

     Каждый объект,  имеющий  правила  Load и Store,  должен также
иметь процедуру "регистрации в потоке".  Здесь такими  процедурами
являются NameDataStream  и  CustDataStream.  Они информируют поток
обо всех объектных типах,  связанных с данным объектом.  Вы можете
спросить, как менеджер потока определяет, какие объекты хранятся в
потоке, и каким образом он декодирует последовательность  байтов в
осмысленную последовательность    объектов.   Эти   сведения   ему
обеспечивает процедура регистрации.

     Объект NameData не  зависит  от  каких-либо  других  объектов
(кроме  объекта  Root,  который представляет собой особый случай и
будет рассмотрен ниже). Следовательно, NameDataStream регистрирует
только  один  тип,  а  именно  сам объект NameData.  Для этого она
вызывает правило RegisterType менеджера потока,  которое принимает
несколько  параметров.  Первым  параметром является имеющая размер
Word константа,  задающая уникальный  код,  идентифицирующая  этот
объект.   Object  Professional  резервирует  первую  тысячу  кодов
(0...999) для своих  собственных  объектов.  В  примере  константа
otNameData  (которая равна 1000) уникальным образом идентифицирует
объект NameData.  Менеджер  потока  при  записи  объекта  в  поток
помещает   туда  и  этот  код;  впоследствии  этот  код  позволяет
идентифицировать соответствующее правило Load.

     Второй параметр,  передаваемый RegisterType, это номер версии
объекта. Это  позволит  вам прочесть старый файл потока даже после
того, как  вы  изменили   определение   вашего   объекта,   просто
регистрируя альтернативное  правило  Load с другим номером версии.
NameData это не имеющий  предыстории,  новый  объект,  поэтому  он
получит номер версии 0.

     Третий параметр  всегда читает TypeOf(ObjectBeingRegistered).
Он вызывает системную функцию Turbo Pascal, возвращающую указатель
на VMT данного объектного типа. Этот указатель позволяет менеджеру
потока определять  тип  записываемого  объекта,  и  следовательно,
вызывать соответственное  правило Store (что опять поясняет, каким
образом потоки могут хранить множество различных  объектных типов,
не имея  специальных  сведений о самих объектах).  Менеджер потока
делает важное допущение,  к  которому  мы  вернемся  позднее:  все
объекты, записываемые  в  потоки,  обязаны  быть  производными  от
объекта Object Professional Root.

     Последний параметр задает адрес правил Load и Store. Менеджер
потока предполагает,   что  эти  подпрограммы  являются  правилами
описанного типа  с  заданным  параметром.  Любая  другая  ситуация
приведет к нарушению работы программы.  К сожалению, эта ошибка во
время компиляции не обнаруживается.

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

     Одно дополнительное    замечание    относительно    процедуры
регистрации в  потоке:  эти  процедуры  должны  компилироваться  с
моделью памяти FAR (директива компилятора {$F+}),  они  не  должны
быть вложенными в какие-либо другие процедуры,  и они должны иметь
в точности один указанный параметр.  Причиной является то, что эти
процедуры иногда косвенно вызываются менеджером потока,  как будет
показано на примере.

     Пример программы  объявляет  глобальную  переменную  S   типа
BufIdStream. Этот  производный  потоковый  тип  связывает  поток с
файлом DOS,  а также обеспечивает  наличие  в  оперативной  памяти
буфера для   улучшения  скорости  операций  чтения  и  записи.  Мы
открываем файл потока, вызывая конструктор Init, который принимает
имя файла  ('CUSTOMER.STM'),  режим,  в  котором  открывается файл
(SCreate), а также размер буфера в байтах (1024).

     Следующий шаг состоит в том, чтобы сообщить менеджеру потока,
какие типы  данных  будут  храниться в данном потоке.  Мы вызываем
правило потока  RegisterHier  и   передаем   ему   имя   процедуры
регистрации. Затем  менеджер  потока  вызовет  заданную процедуру.
Этот же  эффект  может  быть  достигнут  при   помощи   следующего
оператора:

     CustDataStream(@S);

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

     Неудачное завершение  вызова RegisterHier маловероятно.  Если
же это произойдет, то скорее всего - это программная ошибка, такая
как присвоение  одного и того же номера кода двум разным объектам.
Кроме того,  менеджер потока выделяет небольшую область "кучи" для
хранения списка зарегистрированных объектов,  поэтому ошибка может
произойти при недостаточном размере доступной памяти. В примере мы
проверяем состояние потока именно на этот случай.

     При вызове  Customer.Init  создается  три  объекта  CustData.
Затем мы вызываем правило Put объекта для записи каждого объекта в
поток. Put  определяет  тип  объекта  Customer  в  соответствии  с
уже заданной  регистрационной   информацией,   записывает   номера
объекта и   версии  CustData  в  поток  и  передает  управление  в
CustData.Store, которая   завершает    запись    объекта.    Вызов
S.GetStatus служит для проверки ошибки ввода/вывода.

     После записи трех объектов мы закрываем поток вызовом S.Done.
Тем самым буфер оперативной памяти сбрасывается на диск,  файл DOS
закрывается, отменяется выделение буферу память, а регистрационная
информация о его типе освобождается.

     Пример продолжается повторным открытием потока,  на этот  раз
на чтение.  Здесь  используется тот же размер памяти буфера,  хотя
это и не требуется.  Типы данных объекта повторно  регистрируются,
поскольку выше  эта информация была освобождена.  Затем происходит
вызов S.Get для чтения каждого customer (клиент) из  потока. S.Get
читает из   потока   номера   объекта   и   версии  и,  на  основе
регистрационной информации, вызывает соответствующее правило Load.
После успешного    вызова    Get   экземпляр   объекта   полностью
инициализирован, и нам остается только вывести его  на  дисплей  и
затем разрушить.

     Интересно отметить, что поток, используемый в данном примере,
мог бы также содержать экземпляры типа объекта NameData.  Этот тип
был косвенным   образом   зарегистрирован   через  описанный  выше
иерархический процесс.  Менеджер потока может различатьмежду собой
объекты NameData  и  CustData  и правильно выполнять их чтение или
запись в поток.

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

     Root
       IdStream
         DosIdStream
           BufIdStream
         MemIdStream

     Рассмотрение категории   потоков   Library  откладывается  до
следующего раздела,   где   также   будет   приведен   и   пример,
использующий Library.


Константы
---------

     SCreate     = $3C00;    {Создание нового файла}
     SOpenRead   = $3D00;    {Доступ только на чтение}
     SOpenWrite  = $3D01;    {Доступ только на запись}
     SOpen       = $3D02;    {Доступ и на запись, и на чтение}

     Значения, передаваемые    методам    Init    DosIdStream    и
BufIdStream, указывающие  на  то,  в каком режиме открывается файл
потока. Каждая константа задает значение регистра AX, используемое
при вызове DOS, открывающем файл.

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

     otPointerStack   = 001;
       ...
     otShadedPField   = 169;
       ...
     otReservedForTP  = 999;

     Уникальные номера кода для всех объектов, поддерживающих ввод
/вывод потоком.  Обычно  ссылаться  на  эти  коды   напрямую   нет
необходимости, потому  что  это  выполняют процедуры регистрации в
потоке. До сих пор последний использованный  код  был  равен  169.
Значения до  999 резервированы для Turbo Power.  Вашим собственным
объектам вы должны присваивать более высокие номера.


     vePointerStack   = 00;
       ...
     veShadedPField   = 00;

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

     Подробности см.          в          описании          правила
IdStream.RegisterOldVersion.


     ptNil                = 0000;  {указатель nil}
     ptEmptyOpenCloseProc = 0001;  {по умолчанию не-разрывные окна}
       ...
     ptReservedForTP      = 0999;
     ptEntryUserRec       = $FFFF; {используется в OPENTRY}
     ptFormUserRec        = $FFFF; {используется в OPFORM}


     Коды указателей для использования при потоковом вводе/выводе.
Подобно тому,  как  объектным   типам   присваиваются   в   потоке
уникальные коды, то же происходит и с указателями для статического
кода или данных.  Например,  Object Professional широко использует
указатели процедур,  позволяющие  динамическую настройку поведения
программы.  ( Такие   указатели    обычно   называются  "ловушкой"
пользователя"). Простой образец такого поведения показывает объект
RawWindow, который связывает каждое окно с  процедурой, вызываемой
при открытии  окна.  Настроенная  версия процедуры открытия делает
окна "разрывными" на экране. Когда такое окно записывается в поток
из затем  перезагружается,  необходим  способ  связать настроенную
процедуру с объектом.  При этом требуется избежать компоновки этой
настроенной процедуры    открытия,   когда   она   фактически   не
используется программой.  Для решения этой проблемы  метод  потока
обеспечивает правило,  которое  вы  можете вызвать для регистрации
адреса настроенной процедуры открытия.  При записи потока в  поток
выводится зарегистрированный  код указателя.  Далее,  при загрузке
потока, код указателя обозначает используемую  процедуру.  Процесс
регистрации настроенной процедуры открытия обеспечивает компоновку
ее кода даже если на него нигде нет прямых ссылок.

     Object Professional резервирует  коды  0...999  и  $FFFF  для
собственного использования. Вам, вероятно, потребуются обращения к
некоторым из этих кодов при использовании потоков с объектами типа
StackWindow, PickList,  DirList, EntryScreen и др. Соответствующие
коды упоминаются в документации по  правилам  load  и  Store  этих
объектов.

     Дальнейшая информация  по  этому вопросу находится в описании
правила IdStream.RegisterPointer.

Типы
----

     RootPtr = ^Root;
     Root =
       object
         ...
       end;

     Объект Root  должен  являться предком любого другого объекта,
поддерживающего потоки Object Professional.  Root сам по  себе  ни
для чего  не  нужен - вам никогда не понадобится создать экземпляр
объекта Root.  Его  объявление  гарантирует,  что  ссылка  на  VMT
(таблица виртуальных  правил)  для  каждого  производного  объекта
будет занимать  первые  два  байта  каждого  экземпляра   объекта.
Менеджер потока зависит от этого, когда записывает объекты в поток.

     Объект Root   обеспечивает   дополнительные   практические  и
теоретические преимущества.  Подробности см. в описаниях Root.Init
и Root.Done.

     StreamRegisterProc = procedure (SPtr : IdStreamPtr);

     Тип процедуры,   используемый  менеджером  потока,  когда  он
регистрирует типы  данных,  использованные  в  конкретном  потоке.
Процедура этого  типа  передается  правилу  IdStream.registerHier.
Каждый "потокизируемый" объект  Object  Professional  обеспечивает
одну или  более  процедуру  этого типа.  Соответствующая процедура
упоминается в описаниях правил  Load  и  Store  для  каждого  типа
объекта.

     IdStreamPtr = ^IdStream;
     IdStream =
       object
         ...
       end;

     Наиболее примитивное   объявление   потока.   Поскольку   это
абстрактный тип, вы не должны объявлять экземпляры этого типа. Его
свойства наследуются    DosIdStream,   BufIdStream,   MemIdStream,
library и MemLibrary. В IdStream реализованы многие важные правила
регистрации типов,   поэтому   вам   следует   ознакомиться  с  их
описаниями в следующих разделах.

     Вы могли заметить,  что IdStream не является  производным  от
Root. Это  позволяет  обойти  многие  проблемы  с замкнутым кругом
ссылок, которые в противном случае могли бы возникнуть в отношении
определения типа IdStream.  Единственное неудобство состоит в том,
что вы не имеете возможности создатьтакой поток,  который сохранял
экземпляры других потоков.  Однако,  это такая головоломная штука,
что она вам - мы уверены - никогда не потребуется.

     DosIdStreamPtr = ^DosIdStream;
     DosIdStream =
       object(IdStream)
         ...
       end;

     Связывает поток  с  файлом  DOS.  Обеспечивает  правила   для
создания нового   файла  потока,  а  также  для  чтения  и  записи
существующего потока.

     BufIdStreamPtr = ^BufIdStream;
     BufIdStream =
       object(DosIdStream)
         ...
       end;

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

     MemIdStreamPtr = ^MemIdStream;
     MemIdStream =
       object(IdStream)
         ...
       end;

     Связывает поток с областью оперативной памяти.  Это полезно в
том случае, если вы хотите связать поток непосредственно с вашим .
EXE-файлом. После инициализации MemIdStream используется точно так
же, как любой другой поток.

Процедуры
---------

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

     procedure PointerStackStream(SPtr : IdStreamPtr);
       {Регистрация всех типов, необходимых для потоков,
        содержащих объекты типа PointerStack}

     procedure StaticQueueStream(SPtr : IdStreamPtr);
       {Регистрация всех типов, необходимых для потоков,
        содержащих объекты типа StaticQueue}

     procedure SingleListStream(SPtr : IdStreamPtr);
       {Регистрация всех типов, необходимых для потоков,
        содержащих объекты типа SingleList}

     procedure DoubleListStream(SPtr : IdStreamPtr);
       {Регистрация всех типов, необходимых для потоков,
        содержащих объекты типа DoubleList}

     procedure CircularListStream(SPtr : IdStreamPtr);
       {Регистрация всех типов, необходимых для потоков,
        содержащих объекты типа CircularList}

     procedure BitSetStream(SPtr : IdStreamPtr);
       {Регистрация всех типов, необходимых для потоков,
        содержащих объекты типа BitSet}

     procedure LargeBitSetStream(SPtr : IdStreamPtr);
       {Регистрация всех типов, необходимых для потоков,
        содержащих объекты типа LargeBitSet}

     procedure StringDictStream(SPtr : IdStreamPtr);
       {Регистрация всех типов, необходимых для потоков,
        содержащих объекты типа StringDict}

     procedure StringSetStream(SPtr : IdStreamPtr);
       {Регистрация всех типов, необходимых для потоков,
        содержащих объекты типа StringSet}

     procedure StringArrayStream(SPtr : IdStreamPtr);
       {Регистрация всех типов, необходимых для потоков,
        содержащих объекты типа StringArray}



Root
----------------------------------------------------------------

Done
----------------------------------------------------------------
Объявление
----------

     destructor Done; virtual;

Назначение
----------

     Разрушает объект Root.

Описание
--------

     Фактически правило  Root.Done  не выполняет никаких действий.
Оно используется  как  абстрактная  ловушка,  гарантирующая,   что
каждый объект, производный от Root, будет иметь правило Done, даже
если это правило не будет выполнять никаких действий. В результате
вы можете  безопасно  вызывать Что_угодно.Done,  закончив работу с
этим объектом Object Professional.  Почти все правила, производные
от Root,  переопределяют  правило  Done  для  отмены выделенной им
области "кучи" или  любых  других  действий  очистки  памяти,  как
только они больше не нужны.

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


Init
----------------------------------------------------------------
Объявление
----------

     constructor Init;

Назначение
----------

     Инициализирует объект Root.

Описание
--------

     Как и в случае Root.Done,  действие Init более теоретическое,
нежели практическое.  Однако,  Init  служит одной конкретной цели.
Оно сбрасывает в  0  глобальную  переменную  InitStatus,  так  что
каждому конкретному конструктору это делать не требуется.

     Правила-конструкторы каждого  объекта,  производного от Root,
должны вызывать Root.Init,  прежде чем перейти к своей собственной
инициализации. Правило  не  только  сбрасывает  InitStatus,  но  и
гарантирует любую  глобальную  инициализацию  в   случае   будущих
расширений Object Professional. Лучше, если такие вызовы Root.Init
будут принимать  во  внимание  возможность  неудачного  завершения
вызова, используя следующую форму:

     if not Root.Init then
       Fail;


IdStream
----------------------------------------------------------------

CodeVerRegistered
----------------------------------------------------------------
Объявление
----------

     function CodeVerRegistered(Code, Version : Word) : Boolean;

Назначение
----------

     Возвращает True,  если   заданные   код   и   номер    версии
зарегистрированы.

Описание
--------

     Данное правило особенно полезно в  потоках  Library,  которые
хранят код  типа  и  версию в каждом элементе каталога библиотеки.
Проверяя, зарегистрирован ли тип конкретного  элемента, библиотека
может заранее   определить,   была  ли  выполнена  соответственная
инициализация.

См. также
---------

     Library.GetCodeAndVersion           TypeRegistered


Error
----------------------------------------------------------------
Объявление
----------

     procedure Error(Code : Word); virtual;

Назначение
----------

     Сообщает об ошибке.

Описание
--------

     Прикладные программы не вызывают эту  подпрограмму напрямую.
Когда правило StringDict обнаруживает ошибку, для сообщения о ней
оно  вызывает  правило  Error.  Реализация  данного  правила   по
умолчанию  просто  присваивает  код  ошибки внутренней переменной
состояния.    Объект,    производный    от    IdStream,     может
переопределять   правило   Error,   устанавливая   другие  методы
обработки ошибочной ситуации.


См. также
---------

     GetStatus               PeekStatus


Get
----------------------------------------------------------------
Объявление
----------

     procedure Get(var R : Root);

Назначение
----------

     Вызывает полиморфный конструктор Load для чтения статического
объекта R.

Описание
--------

     Сначала Get считывает из потока четыре  байта  для  получения
кода типа  объекта  и  номер его версии.  Затем оно классифицирует
объект, сканируя  список  зарегистрированных  типов  потока.  Если
соответствующий тип  найден,  Get передает управление конструктору
Load, зарегистрированному для этого типа.  Затем конструктор  Load
считает из   потока   столько   байтов,   сколько  необходимо  для
реализации объекта.

     Get предполагает,  что  переданный  ему   параметр   является
статической реализацией  объекта.  Он  вызывает  конструктор  Load
таким образом,  чтобы предупредить выделение  области  "кучи"  для
данной реализации  объекта.  Объект  R  может  быть  любого  типа,
производного от Root и зарегистрированного  в  потоке.  Убедитесь,
что размер   передаваемой   переменной   достаточен  для  хранения
следующего объекта в потоке. В противном случае Get затрет память;
здесь компилятор не может вам ничем помочь.

     Для определения    успешного   завершения   Get   используйте
GetStatus или   PeekStatus.   Ошибки   могут   быть   связаны   со
вводом/выводом, если тип не был зарегистрирован,  либо с неудачным
выполнением самого конструктора load.

     Данные, читаемые Get,  должны быть сначала записаны  Put  или
PutPtr.

Пример
------

     ...
     S.RegisterHier(CustDataStream);
     ...
     S.Get(Customer);
     Status := S.GetStatus;
     if Status <> 0 then begin
       WriteLn('Не удалось прочитать Customer,
                Состояние = ', Status);
       Halt;
     end;
     Customer.Display;

     S.Get вызывает  конструктор   Load   объекта   Customer   для
инициализации объекта.  GetStatus принимает код состояния. Если он
равен 0, объект готов для использования.

См. также
---------

     GetPtr                 Put


GetPos
----------------------------------------------------------------
Объявление
----------

     function GetPos : LongInt; virtual;

Назначение
----------

     Возвращает текущую позицию потока.

Описание
--------

     Данное правило   аналогично  системной  функции  FilePos  для
обычных файлов.  Оно возвращает текущую позицию в  потоке.  первая
позиция в потоке имеет значение 0.

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

Пример
------

     S.Init('TEST.STM', SCreate);
     WriteLn(S.GetPos);

     запишет 0.

См. также
---------

     Seek

GetPtr
----------------------------------------------------------------
Объявление
----------

     function GetPtr : RootPtr;

Назначение
----------

     Вызывает зарегистрированный конструктор load  для  выделения
памяти и чтения объекта.

Описание
--------

     Данная функция  работает  аналогично   Get,   но   с   одним
существенным исключением:    GetPtr    вызывает   соответствующий
конструктор Load таким образом,  чтобы выделить  объекту  область
"кучи", создав его динамический экземпляр. Такой подход важен для
чтения из потока переменных типов объектов,  когда  эти  типы  не
могут быть   известны   заранее.  GetPtr  автоматически  выделяет
необходимый размер памяти каждому экземпляру объекта.

     В случае какой-либо ошибки GetPtr возвращает  значение  nil.
Для определения   причины  ошибки  следует  использовать  правила
потока GetStatus или PeekStatus.  Данные,  считываемые при помощи
GetPtr, должны быть ранее записаны правилами Put или PutPtr.

Пример
------

     S.RegisterHier(CustDataStream);
     ...
     CustomerPtr := S.GetPtr;
     if Customer = nil then begin
       Status := S.GetStatus;
       if Status <> 0 then begin
         WriteLn ('Не удалось прочитать Customer. Состояние = ', Status);
         Halt;
       end;
     end;
     CustomerPtr^.Display;

     S.GetPtr вызывает конструктор Load CustomerPtr  и  проверяет
результат этого вызова. В "куче" создается объект, а указатель на
этот объект помещается в CustomerPtr.

См. также
---------

     Get                PutPtr


GetSize
----------------------------------------------------------------
Объявление
----------

     function GetSize : LongInt;

Назначение
----------

     Определяет размер потока.

Описание
--------

     Эта подпрограмма вызывает два внутренних виртуальных правила
IdStream и возвращает размер потока.  Учитывая, каким образом эти
правила реализованы   для  объектов  DosIdStream  и  BufIdStream,
результат GetSize  совпадает  с  размером  файла  в  байтах.  Для
MemIdStream GetSize   возвращает  число  байтов  области  памяти,
выделенной для данного потока.

Пример
------

     WriteLn('Размер потока: ', S.GetSize);

     Выводит на   дисплей   текущий   размер   файла,  в  байтах,
соответствующего потоку S, предполагая, что S связан с файлом.

См. также
---------

     MemIdStream.Init


GetStatus
----------------------------------------------------------------
Объявление
----------

     function GetStatus : Word;

Назначение
----------

     Возвращает состояние потока.

Описание
--------

     Каждый объект   IdStream  организует  внутреннюю  переменную
состояния, в которой хранится текущее состояние потока. GetStatus
обеспечивает доступ к этой переменной.  Как и IoResult, GetStatus
очищает внутреннюю  переменную  состояния  после  того,  как  она
возвратила текущее значение.

     Если ошибок   не   произошло,   GetStatus  возвращает  0.  В
противном случае,  она  возвращает  код  ошибки,  определенный  в
приложении D.

Пример
------

    S.Get(Customer);
    Status := S.GetStatus;
    if Status <> 0 then begin
      WriteLn ('Не удалось прочитать Customer. Состояние = ', Status);
      Halt;
    end;

     Проверяет состояние последней операции (операций).  Ссылка к
функции GetStatus также приводит к очистке состояния.

    if S.GetStatus <> 0 then begin
      WriteLn ('Не удалось прочитать Customer. Состояние = ', Status);

     Неверно !  Чтение  состояния очищает это значение.  Если вам
требуется обратиться к нему более одного раза, поместите значение
состояния в переменную или вызовите PeekStatus.

См. также
---------

     Error


PeekStatus
----------------------------------------------------------------
Объявление
----------

     function PeekStatus : Word;

Назначение
----------

     Возвращает состояние, не изменяя внутреннюю переменную.

Описание
--------

     Работает аналогично  GetStatus,  за  исключением  того,  что
данное правило  не  очищает внутреннюю переменную состояния после
обращения к ней.

Пример
------

     constructor NameData.Load(var S : IdStream);
     begin
       if not Root.Init then
         Fail;
       Name := S.ReadString;
       S.read(Age, SizeOf(Age));
       if S.PeekStatus <> 0 then
         Fail;
     end;

     Принимает состояние    последней    выполненной    потоковой
операции, не  очищая  значение  состояния  из   переменной.   Это
особенно полезно в конструкторах, для которых вы хотите выполнить
Fail, но при этом чтобы переменная состояния  осталась  доступной
для вызывающей программы.

См. также
---------

     Error


PointerRegistered
----------------------------------------------------------------
Объявление
----------

     function PointerRegistered(Ptr : Pointer) : Boolean;

Назначение
----------

     Возвращает True,  если  заданный указатель зарегистрирован в
потоке.

Описание
--------

     Концепция регистрации  указателя  рассматривается в описании
правила RegisterPointer  и  объявлениях  Констант   для   данного
раздела. Pointerregistered используется,  чтобы определить,  было
ли конкретное значение указателя зарегистрировано в потоке.

Пример
------

     if not S.PointerRegistered(@VenetianOpenProc) then
       S.RegisterPointer(1000, @VenetianOpenproc)

     Проверяет, зарегистрирован  ли  указатель  на   подпрограмму
VenetianOpenProc; если нет,  то он регистрируется.  Отметим,  что
ссылка на  @VenetianOpenProc  гарантирует,  что  интеллектуальный
компоновщик компилятора  сможет  подключить  код  именно заданной
подпрограммы. Будьте      осторожны       при       использовании
PointerRegistered в  отношении  подпрограмм,  которые  нужны лишь
опционально.

См. также
---------

     RegisterPointer


Put
----------------------------------------------------------------
Объявление
----------

     procedure Put(var R : Root);

Назначение
----------

     Полиморфно вызывает правило Store для записи объекта R.

Описание
--------

     Put анализирует  первые  два  байта экземпляра переменной R.
Для объектов,  производных от Root,  эти байты содержат  смещение
относительно DS (сегмента данных) таблицы виртуалных правил (VMT)
объекта. Put    классифицирует    объект,     сканируя     список
зарегистрированных в данном потоке объектов. Предполагая, что тип
найден, Put записывает номера  типа  и  версии  объекта  в  поток
(всего 4    байта)   и   передает   управление   правилу   Store,
зарегистрированному для этого типа.  Правило Store  записывает  в
поток столько  байтов,  сколько  необходимо  для полного описания
объекта.

     Переданный Put параметр может быть как  статическим,  так  и
динамическим экземпляром  объекта.  Тип  объекта  должен  иметь в
качестве прямого или косвенного предка тип  Root.  Put  и  PutPtr
практически являются одинаковыми, с различием только в синтаксисе
их вызова.

     Для определения  успешности   завершения   Put   используйте
GetStatus или PeekStatus. Ошибки могут происходить либо при вводе
/выводе, если  данный  тип  окажется  незарегистрированным,  либо
из-за неудачного завершения самого правила Store.

     Данные, записанные Put, могут быть затем прочитаны из потока
при помощи Get или GetPtr.

Пример
------

     S.RegisterHier(CustDataStream);
     ...
     S.Put(Customer);
     Status := S.GetStatus;
     if Status <> 0 then begin
       WriteLn ('Не удалось записать Customer. Состояние = ', Status);
       Halt;
     end;

     S.Put вызывает правило Store для Customer (типа CustData).

См. также
---------

     Get                     PutPtr


PutPtr
----------------------------------------------------------------
Объявление
----------

     procedure PutPtr(RPtr : RootPtr);

Назначение
----------

     Вызывает полиморфное  правило  Store  для   записи   объекта
согласно RPtr^.

Описание
--------

     Работает подобно  Put.  Единственное  различие   состоит   в
синтаксисе вызова.  Для  статического  экземпляра  следующие  два
оператора эквивалентны:

     S.Put(Customer);
     S.PutPtr(@Customer);

     Для динамического   экземпляра   следующие   два   оператора
эквивалентны:

     S.Put(CustomerPtr^);
     S.PutPtr(CustomerPtr);

См. также
---------

     GetPtr               Put


ReadPointer
----------------------------------------------------------------
Объявление
----------

     function ReadPointer : Pointer;

Назначение
----------

     Считывает указатель из потока.

Описание
--------

     ReadPointer считывает из потока следующие 4 байта.  Первые 2
байта содержат зарегистрированный код указателя, а вторые 2 байта
- специальный код версии ($FFFF),  обозначающий,  что первые  два
байта относятся к указателю,  а не к объектному типу. ReadPointer
сканирует список  зарегистрированных  типов,  чтобы   найти   там
физический указатель,   соответствующий  этим  типам.  Если  этот
указатель найден,  то он и будет возвращен.  В  противном  случае
возвращаемое значение равно nil.

     Концепция регистрации  указателя  рассматривается в описании
правила RegisterPointer и объявлениях Констант в начале раздела.

Пример
------

type
  NameData =
    object(Root)
      Name : String[20];
      BufPtr : Pointer;
      constructor Init;
      procedure Display;
      constructor Load(var S : IdStream);
      procedure Store(var S : IdStream);
    end;
  ...
  constructor NameData.Load(var S : IdStream);
  begin
    if not Root.Init then
      Fail;
    Name := S.ReadString;
    BufPtr := S.ReadPointer;
    if S.PeekStatus <> 0 then
      Fail;
    if BufPtr = nil then
      Fail;
  end;

  var
    Name : NameData;
  ...
  S.RegisterHier(NameDataStream);
  GetMem(TempPtr, 100);
  S.RegisterPointer(1000, TempPtr);
  ...
  Name.Load(S);

     Объект NameData включает указатель BufPtr на область данных,
обычно выделяемую вне его собственного конструктора. При создании
потока объектов   NameData   необходимо   зарегистрировать  адрес
области, на которую установлен  BufPtr.  Данный  пример  выделяет
область правильного размера (TempPtr^) и регистрирует указатель с
кодом 1000. Вызов из конструктора Load правила ReadPointer свяжет
значение TempPtr с полем данных BufPtr объекта.

См. также
---------

     ReadUserPointer               WritePointer
     RegisterPointer


ReadRange
----------------------------------------------------------------
Объявление
----------

     procedure ReadRange(var BufStart, BufBeyond);

Назначение
----------

     Считывает Ofs(BufBeyond) - Ofs(BufStart) байтов в BufStart.

Описание
--------

     Данное правило полезно для инициализации заданного диапазона
полей данных объекта в конструкторе Load.  Первое  читаемое  поле
передается как  параметр  BufStart,  а  последнее  как BufBeyond.
Число считываемых   байтов   будет   равно    Ofs(BufBeyond)    -
Ofs(BufStart).

     Параметры, переданные в readRange,  всегда должны относиться
к одному сегменту (т.е.  оба являться локальными переменными, или
оба - глобальными переменными,  или оба должны быть частью одного
и того же блока "кучи").  Это условие выполняется  автоматически,
если эти  два  параметра  являются полями данных одного и того же
экземпляра объекта.

     Компилятор не  может  выполнить   никаких   видов   проверки
типа переданных в ReadRange параметров. Будьте внимательны!

Пример
------

  NameData =
    object(Root)
      Name : String[20];
      Age : Byte;
      Other : array[1..10] of Word;
      Junk : Byte;
      constructor Init;
      procedure Display;
      constructor Load(var S : IdStream);
      procedure Store(var S : IdStream);
    end;
  ...
  constructor NameData.Load(var S : IdStream);
  begin
    if not Root.Init then
      Fail;
    Name := S.ReadString;
    S.ReadRange(Age, Junk);
    if S.PeekStatus <> 0 then
      Fail;
    if BufPtr = nil then
      Fail;
  end;

     Показывает определение объекта и  конструктор  для  создания
экземпляра этого объекта из потока. ReadRange считывает данные из
потока непосредственно в объект,  начиная с поля Age, и продолжая
до поля  Junk  (но  не включая это поле).  Для инициализации поля
Junk понадобится другой вызов, возможно правила Read.

См. также
---------

     DosIdStream.Read                 WriteRange


ReadString
----------------------------------------------------------------
Объявление
----------

     function ReadString : String;

Назначение
----------

     Считывает из потока следующую строку переменной длины.

Описание
--------

     Считывает в  точности  столько  байтов,  сколько  нужно  для
инициализации фактической  длины  строки.   Для   этого   сначалп
считывается байт длины,  а затем - число байлов,  заданное в этом
байте длины.  Эта  строка  должна  была  быть  записана  в  поток
правилом WriteString.    Размер   данного   потока   может   быть
минимизирован, если  использовать  именно  эти  правила,   а   не
записывать в  поток полный   объявленный  размер  каждой  строки,
включая ее неиспользуемую часть.

     Это правило  корректно  считывает  пустые  (нулевой   длины)
строки.

Пример
------

type
  NameData =
    object(Root)
      Name : String[20];
      constructor Init;
      procedure Display;
      constructor Load(var S : IdStream);
      procedure Store(var S : IdStream);
    end;
  ...
  constructor NameData.Load(var S : IdStream);
  begin
    if not Root.Init then
      Fail;
    Name := S.ReadString;
    if S.PeekStatus <> 0 then
      Fail;
  end;

     Показывает определение   объекта   и   конструктор  для  его
реализации из потока.  ReadString считывает сначала байт длины, а
затем фактическое число символов для реализации имени (Name).

См. также
---------

     WriteString


ReadUserPointer
----------------------------------------------------------------
Объявление
----------

     function ReadUserPointer(DefaultPtr : Pointer) : Pointer;

Назначение
----------

     Считывает указатель и присваивает ему DefaultPtr, если он не
зарегистрирован.

Описание
--------

     Работает подобно ReadPointer,  но с одним исключением.  В то
время, как  ReadPointer,  обнаружив,  что  коду  из   потока   не
соответствует ни  один  зарегистрированный указатель,  возвращает
значение nil,  то  ReadUserPointer  в  этом   случае   возвращает
DefaultPtr. Это правило полезно использовать с конструктором Load
объектов, которые могут иметь опциональные  ловушки  пользователя
(hook). Например,  правило  Loa  объекта  CommandWindow  вызывает
ReadUserPointer для  чтения  адреса  процедуры  обработки  ошибки
окна. Если  вы  зарегистрировали  обработчик ошибки пользователя,
ReadUserPointer вернет адрес  этого  обработчика.  Если  нет,  то
ReadUserPointer вернет    адрес    обработчика    по   умолчанию,
DefaultErrorProc, который  мы  передадим  в  ReadUserPointer  как
параметр.

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

     Концепция регистрации  указателя  рассматривается в описании
правила RegisterPointer и объявлениях Констант в начале раздела.

Пример
------

     @CustomerProc := S.ReadUserPointer(@DefaultProc);

     Считывает из   потока   код   указателя.   Если   этот   код
зарегистрирован, значение   указателя   помещается  в  переменную
CustomerProc, имеющую тип  процедуры  или  функции.  В  противном
случае используется адрес DefaultProc.

См. также
---------

     RegisterPointer             WriteUserPointer


RegisterHier
----------------------------------------------------------------
Объявление
----------

     procedure RegisterHier(RProc : StreamRegisterProc);

Назначение
----------

     Вызывает указанную  подпрограмму  для  регистрации  иерархии
объектных типов.

Описание
--------

     Объекто-ориентированный ввод/вывод    потоком   существенным
образм зависит  от  регистрации  пользователем  потока  различных
объектных типов  и  указателей,  которые  будут  являться  частью
потока. Для упрощения такой регистрации  автор  "потокизируемого"
объекта должен  написать процедуру,  которая будет регистрировать
все типы,  входящие в объект,  включая себя саму. Такие процедуры
обеспечиваются для   всех   потокизируемых   объектов   в  Object
Professional.

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

     {$F+}
     procedure XxxStream(SPtr : IdStreamPtr);

     Для вызова  RegisterHier  с  данной  процедурой  понадобится
следующий оператор:

     S.RegisterHier(XxxStream);

     Object Professional следует одинаковому соглашению об именах
всех регистрационных процедур.  Каждое из этих имен  представляет
собой просто  имя  объекта,  за  которым  следует слово "Stream".
Отсюда имена         PointerStackStream,         RawWindowStream,
EntryScreenStreem, MenuStream, и т.д.

     Если объект содержит более одного объектного типа,  то может
потребоваться несколько  вызовов  RegisterHier.  Если  объекты  в
потоке используют    указатели    процедур    или    определяемые
пользователем типы указателей, то могут также понадобиться вызовы
RegisterPointer или RegisterUserPointer.

     RegisterHier обычно  вызывается  сразу  после  инициализации
потока успешным вызовом Init.

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

Пример
------

  {Создание потока, проверка успешного завершения}
  if not S.Init('CUSTOMER.STM', SCreate, 1024) then begin
    Writeln('Не удалось создать поток,  Состояние = ', InitStatus);
    Halt;
  end;

  {Регистрация иерархии объекта}
  S.RegisterHier(CustDataStream);
  Status := S.GetStatus;
  if Status <> 0 then begin
    Writeln('Не удалось зарегистрировать объект,  Состояние = ', Status);
    Halt;
  end;

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

См. также
---------

     RegisRegisterPointer              RegisterType


RegisterOldVersion
----------------------------------------------------------------
Объявление
----------

     procedure RegisterOldVersion(Code, Version : Word;
                                  TypePtr, LoadPtr : Pointer);

Назначение
----------

     Регистрирует альтернативный   загрузчик  для  старой  версии
объектного типа.

Описание
--------

     Данная процедура,  в  сочетании  с  реализацией  IdStream  в
целом,    решает    потенциальную    проблему,    связанную     с
объекто-ориентированным вводом/выводом в целом.  Предположим, что
сегодня мы записываем  поток,  содержащий  экземпляр  объекта  А.
Завтра мы модифицируем объект А, и он станет содержать новое поле
данных,  которое  должно  записываться  в  поток.  Сможем  ли  мы
прочесть старый поток?  Вероятно,  нет. Если бы мы модифицировали
старую подпрограмму Load  таким  образом,  чтобы  она  каким-либо
способом  различала  новый  и  старый  форматы,  то  мы смогли бы
прочесть старый поток,  но тогда Load разрослась бы  до  огромных
размеров  из-за  необходимости  отображать  всю  историю развития
объекта.

     IdStream позволяет нам определять для данного  объекта более
одного конструктора Load.  Каждый экземпляр объекта, записанный в
поток при помощи Put,  включает в себя не  только  код  типа,  но
также и  номер  версии  объекта.  При чтении объекта Get менеджер
потока сначала смотрит на тип кода,  а затем на номер  версии,  и
определяет на  основании  этого  подходящее  правило Load.  Таким
образом, каждая подпрограмма Load может быть  оптимизирована  для
обработки только   конкретного  формата.  Если  некоторые  номера
версий признаны устаревшими,  то их не следует  регистрировать  в
менеджере потока,  что сэкономит программную память. Отметим, что
старые подпрограммы  Load  должны  обеспечивать   соответственные
значения для полей данных, добавленных со времени записи потока.

     Также отметим,   что   RegisterOldVersion   не  принимает  в
качестве параметра  адреса  альтернативной  подпрограммы   Store.
Экземпляры, записываемые  в  поток,  всегда должны записываться в
текущем формате,  с использованием одного правила Store на каждый
объект.

     Здесь номер  Code  должен быть таким же,  что и переданный в
RegisterType для того же объекта.  По соглашению, номер старейшей
версии (Version) равен 0,  а более поздние версии получают номера
с приращениями в 1.  Значение,  переданное  для  TypePtr,  всегда
является TypeOf(ObjectBeingRegistered).    LoadPtr    это   адрес
конструктора Load для старой версии.

     RegisterOldVersion генерирует     ошибку,     если     задан
противоречивый номер   кода,   либо  при  недостатке  памяти  для
добавления новой связки в регистрационный список.

Пример
------

  {$F+}
  procedure NameDataStream(SPtr : IdStreamPtr);
  begin
    with SPtr^ do begin
      RegisterType(otNameData, 1,            {код объекта, номер версии}
                   TypeOf(NameData),         {размер объекта}
                   @NameData.Store,          {процедура записи в поток}
                   @NameData.Load);          {процедура чтения из потока}
      RegisterType(otNameData, 0,            {код объекта, номер версии}
                   TypeOf(NameData),         {размер объекта}
                   @NameData.Load0);         {процедура чтения из потока}
    end;
  end;
  {$F-}

     Данная процедура  регистрации  в  потоке  предполагает,  что
иногда мы   будем   читать   старую   версию   объекта  NameData.
Следовательно, она регистрирует объект того же типа,  но с другим
номером версии,   и   предоставляет   для  чтения  этого  объекта
конструктор Load0. Если в потоке встретятся объектные типы старой
версии, для  их  чтения  будет вызвана правильная процедура Load.
NameData.Load0 должно быть объявлено,  как любое  другое  правило
Load, например:

     constructor Load0(var S : IdStream);

См. также
---------

     RegisterType                 TypeVerRegistered



RegisterPointer
----------------------------------------------------------------
Объявление
----------

     procedure RegisterPointer(Code : Word; Ptr : Pointer);

Назначение
----------

     Регистрирует один указатель для ввода/вывода IdStream.

Описание
--------

     Объектные типы   Object   Professional   широко   используют
указатели. Во  многих  случаях использование указателей не влияет
на ввод/вывод потока.  Например,  если для буфера  окна  выделена
область "кучи",  в  экземпляре объекта хранится указатель на этот
буфер. Потоку нет необходимости хранить этот указатель,  так  как
при повторной  загрузке  из  потока  произойдет  выделение нового
буфера. Вообще говоря,  когда объектное правило выделяет  область
"кучи" с  использованием  New или GetMem,  записывать указатель в
поток нет необхдимости,  и  следовательно,  нет  необходимости  в
регистрации указателя в менеджере потока.

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

     Следовательно, необходима  регистрация   указателя.   Вместо
физического адреса   мы   будем  хранить  в  потоке  номер  кода.
Номер кода связан с  физическим  адресом  процессом  регистрации.
Поскольку процедура,  записывающая поток,  и процедура,  читающая
поток, выполняют   регистрацию   независимо   друг   от    друга,
автоматически учитываются  различия в адресе загрузки программы и
выравнивание кода в памяти.

     Более тонкое преимущество регистрации  указателя  становится
очевидным, если     рассмотреть     действие    интеллектуального
компоновщика Turbo  Pascal.  Предположим,  что  некоторый  объект
использует указатель    процедуры,   позволяющий   выбирать   три
различные формы поведения, в зависимости от процедуры, на которую
установлен указатель.   (Именно   это   позволяет  делать  объект
PickList со своими возможностями  вертикальной,  горизнтальной  и
ломаной ориентации).

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

     Благодаря регистрации  указателя  компоновщик  добавит в код
программы только те подпрограммы,  которые были явно указаны  при
регистрации. И   что  еще  более  важно,  регистрация  указателей
позволяет использовать указателей  на  подпрограммы,  которые  не
были исходно поставлены с объектом. Метод же расширения оператора
case потребовал  бы  для  добавления  новой  метки   всякий   раз
редактировать исходный код объекта.

     С учетом   данного  вступления,  использование  подпрограммы
RegisterPointer является  прямолинейным.  Code  -  это  значение,
фактически записываемое  в  поток  - оно символизирует указатель,
переданный в параметре Ptr.

     Turbo Power   резервирует   коды   0...999   и   $FFFF   для
собственного использования.   Скорее   всего,   ими   вы   будете
пользоваться чаще,  чем  другими,  поскольку  мы   присвоили   их
именованным константам  для  указателей  встроенной  процедуры  в
Object Professional.  Каждый  нуждающийся  в  них  объект  должен
просто содержать описание соответствующих констант.

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

     Каки RegisterType,  RegisterPointer генерирует ошибку,  если
заданный код  конфликтует  с ранее зарегистрированным указателем,
ли если для расширения регистрационного списка не  хватает памяти
в "куче".

Пример
------

     type
       WriteProc = procedure (S : String);
       StringWriter =
         object(Root)
           WrProc : WriteProc;
           St : String;
           constructor Init(S : String; W : WriteProc);
           procedure WriteIt;
           procedure Store(var S : IdStream);
           constructor Load(var S : IdStream);
         end;

     {$F+}
     procedure TextWrite(S : String);
     begin
       Write(S);
     end;
     {$F-}

     constructor StringWriter.Init(S : String; W : WriteProc);
     begin
       if not Root.Init then
         Fail;
       WrProc := W;
       St := S;
     end;

     procedure StringWriter.WriteIt;
     begin
       WrProc(St);
     end;

     procedure StringWriter.Store(var S : IdStream);
     begin
       S.WriteString(St);
       S.WritePointer(@WrProc);
     end;

     constructor StringWriter.Load(var S : IdStream);
     begin
       if not Root.Init then
         Fail;
       St := S.ReadString;
       @WrProc := S.ReadPointer;
       if PeekStatus <> 0 then
         Fail;
       if @WrProc = nil then begin
         WriteLn('Процедуры записи не зарегистрированы');
         Fail;
       end;
     end;

     var
       S : DosIdStream;
       W : StringWriter;
     begin
       {Создание StringWriter}
       W.Init('hello world', TextWrite);

       {Запись потока}
       S.Init('TEST.STM', SCreate);
       S.RegisterType(1000, 0, TypeOf(StringWriter),
                      @StringWriter.Store, @StringWriter.load);
       S.RegisterPointer(1000, @TextWrite);
       S.Put(W);
       S.Done;

       {Повторное чтение потока}
       S.Init('TEST.STM', SOpen);
       S.RegisterType(1000, 0, TypeOf(StringWriter),
                      @StringWriter.Store, @StringWriter.load);
       S.RegisterPointer(1000, @TextWrite);
       S.Get(W);
       S.Done;

       W.WriteIt;
     end.

     Определяет объект,  зависящий от переменной процедуры записи
на экран.  Альтернативные  процедуры  могут  выполнять  запись  в
графическом режиме и т.д.  Адрес желаемой процедуры перед записью
потока и  его чтением регистрируется с кодом 1000.  Отметим,  что
конструктор Load проверяет,  не вернула ли  ReadPointer  значение
nil. Это может случиться,  если указатель не был зарегистрирован.
Прочие же потенциальные ошибки в данном примере игнорируются.

См. также
---------

     PointerRegistered             RegisterType


RegisterType
----------------------------------------------------------------
Объявление
----------

     procedure RegisterType(Code, Version : Word;
                            TypePtr, StorePtr, LoadPtr : Pointer);

Назначение
----------

     Регистрирует для  ввода/вывода  в  IdStream один тип объекта
заданной версии.

Описание
--------

     Причины регистрации  типов описаны в начале этого раздела. В
кратком изложении:  зная номер кода,  представляющий  каждый  тип
объекта, менеджер потока может направлять запись и чтение каждого
экземпляра объекта   согласно   правилам   Store   и   Load   для
соответствующего типа объекта. Таким образом, менеджеру потока не
требуется знать подробности об управляемом им объекте.

     Правило RegisterType обеспечивает связь между  номерами кода
и версии (Code и Version), типом объекта и правилами Store и Load
для данного объекта.  Менеджер  потока  предполагает,  что  любой
зарегистрированный тип  объекта  является  потомком  (прямым  или
косвенным) от объекта Root.  RegisterType не может обеспечить это
требование самостоятельно; это лежит на вашей ответственности.

     Номер Code  должен  быть  уникален  для  данного  объекта  и
данного потока. Во избежание противоречий Turbo Power резервирует
коды 0...999 и назначает код каждому объекту Object Professional.
Именованные константы для каждого объекта находятся  в OPROOT.PAS
и описаны  в руководствах к правилам регистрации в потоке каждого
объекта. Каждая  такая  константа  начинается  буквами   'ot'   и
заканчивается именем типа объекта.

     Каждый тип  объекта может иметь одну или более версий (более
подробно это свойство описано в  RegisterOldType).  Номер  версии
Version, передаваемый в RegisterType, должен быть самым последним
(по  соглашению  это  означает  -  самым  большим).  Turbo  Power
резервирует  номер  версии  $FFFF  для внутреннего использования.
Любые  другие  значения  размером  Word  приемлемы.  Turbo  Power
определяет  константы для текущей версии каждого объекта.  Каждая
такая константа начинается буквами 've'  и  заканчивается  именем
типа объекта.

     TypePtr должен  являться  указателем  на таблицу виртуальных
правил (VMT) объекта. Каждый объект, который может быть записан в
поток, должен  иметь  VMT,  поскольку  от  является  потомком  от
объекта Root,  а последний имеет  такую  таблицу.  Функция  Turbo
Pascal TypeOf(ObjectType) используется для получения указателя на
VMT объекта ObjectType.

     StorePtr - это адрес правила Store, которое вызовет менеджер
потока. Store  должно  являться  правилом регистрируемого объекта
(или его предка), и должно иметь следующее объявление:

     procedure Store(var S : IdStream);

     Менеждер потока не может  сам  обеспечить  выполнение  этого
требования. Для  получения  адреса  правила  Store для ObjectType
служит следующий синтаксис:

     @ObjectType.Store

     LoadPtr это адрес конструктора Load,  вызываемого менеджером
потока. Правило Load должно иметь следующее объявление:

     constructor load(var S : IdStream);

     RegisterType генерирует    ошибку,    если    заданный   код
противоречит уже  зарегистрированному  объекту,  либо   если   не
хватает памяти "кучи" для дальнейшего расширения регистрационного
списка. Для  проверки   наличия   ошибки   используется   правило
GetStatus.

Пример
------

     См. пример в  начале  этого  раздела,  а  также  пример  для
RegisterPointer.

См. также
---------

     RegisterOldType              TypeIsRegistered
     RegisterPointer


Seek
----------------------------------------------------------------
Объявление
----------

     procedure Seek(Pos : LongInt);

Назначение
----------

     Устанавливает текущую  позицию  потока  в  Pos  относительно
начала потока.

Описание
--------

     Работает во многом аналогично процедуре  Seek,  определенной
для обычных  файлов.  После  вызова  Seek  следующие читаемые или
записываемые байты  будут  браться  из,   либо   направляться   в
указанную Pos позицию. первый байт потока имеет позицию 0.

     Когда поток   используется   для   хранения   объекта,   эта
подпрограмма обычно бесполезна,  поскольку размеры образа объекта
могут изменяться.  Однако, объекты Library хорошо используют Seek
для произвольного доступа к отдельным объектам потока.

Пример
------

     S.Seek(100);

     Перемещает указатель файла на 100-1 байт в потоке.

См. также
---------

     GetPos


TypeRegistered
----------------------------------------------------------------
Объявление
----------

     function TypeRegistered(TypePtr : Pointer) : Boolean;

Назначение
----------

     Возвращает True,   если  заланный  тип  зарегистрирован  для
записи и чтения.

Описание
--------

     TypePtr это  указатель  на  таблицу виртуальных правил (VMT)
объекта. Указатель VMT получают при помощи функции  Turbo  Pascal
TypeOf.

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

Пример
------

     if not TypeRegistered(TypeOf(NameData)) then
       RegisterType(otNameData, veNameData,  {код объекта, номер версии}
                   TypeOf(NameData),         {размер объекта}
                   @NameData.Store,          {процедура записи в поток}
                   @NameData.Load);          {процедура чтения из потока}

     Если NameData  еще  не  зарегистрирован,  то  в этом примере
произойдет его регистрация.  Фактически необходима проверка,  так
как  RegisterType  ничего  не  сообщая  игнорирует  дублирующиеся
запросы на  регистрацию  объектов.  В  реальном  программировании
применение TypeRegistered происходит в более сложных ситуациях.

См. также
---------

     RegisterType


TypeVerRegistered
----------------------------------------------------------------
Объявление
----------

     function TypeVerRegistered(TypePtr: Pointer;
                                Version : Word) : Boolean;

Назначение
----------

     Возвращает True, если заданные тип и версия зарегистрированы
для загрузки.

Описание
--------

     TypePtr это  указатель  на  таблицу виртуальных правил (VMT)
объекта. Указатель VMT получают при помощи функции  Turbo  Pascal
TypeOf. Version - это проверяемый код версии.


Пример
------

if not TypeVerRegistered(TypeOf(NameData), vePrevVer) then
  RegisterOldVersion(otNameData, vePrevVer,  {код объекта, номер версии}
                   TypeOf(NameData),         {размер объекта}
                   @NameData.Load);          {процедура чтения из потока}

     Если заданная версия NameData еще не зарегистрирована,  то в
этом примере произойдет е регистрация.

См. также
---------

     TypeRegistered


WritePointer
----------------------------------------------------------------
Объявление
----------

     procedure WritePointer(Ptr : Pointer);

Назначение
----------

     Записывает идентификатор указателя в поток.

Описание
--------

     Записывает в  поток  четыре  байта.  Эти  байты  состоят  из
зарегистрированного кода указателя (2 байта) и  затем  байтов  со
значением $FFFF,   которые   представляют   собой  номер  версии,
зарезервированный для указателей.

     WritePointer генерирует ошибку,  если указанный указатель не
зарегистрирован. Ошибки    могут    быть    связаны    также    с
вводом/выводом. Проверить наличие ошибки можно, вызвав GetStatus.

     Более подробную информацию о регистрации  указателей  см.  в
описании подпрограммы RegisterPointer.

Пример
------

     Пример для   RegisterPointer   показывает   правило   Store,
вызывающее WritePointer  для  записи  переменной  -  процедуры  в
поток.

См. также
---------

     RegisterPointer


WriteRange
----------------------------------------------------------------
Объявление
----------

     procedure WriteRange(var BufStart, BufBeyond);

Назначение
----------

     Записывает Ofs(BufBeyond) - Ofs(BufStart) байтов в поток.

Описание
--------

     Данное правило   полезно   для   записи  в  поток  заданного
диапазона  полей  данных  объекта   в   правиле   Store.   Первое
записываемое  поле передается как параметр BufStart,  а последнее
как   BufBeyond.   Число   записываемых   байтов   будет    равно
Ofs(BufBeyond) - Ofs(BufStart).

     Параметры, переданные в WriteRange, всегда должны относиться
к одному сегменту (т.е.  оба являться локальными переменными, или
оба - глобальными переменными,  или оба должны быть частью одного
и того же блока "кучи").  Это условие выполняется  автоматически,
если  эти  два  параметра являются полями данных одного и того же
экземпляра объекта.

     Компилятор не  может  выполнить   никаких   видов   проверки
типа переданных в WriteRange параметров. Будьте внимательны!

Пример
------

  NameData =
    object(Root)
      Name : String[20];
      Age : Byte;
      Other : array[1..10] of Word;
      Junk : Byte;
      constructor Init;
      procedure Display;
      constructor Load(var S : IdStream);
      procedure Store(var S : IdStream);
    end;
  ...
  constructor NameData.Store(var S : IdStream);
  begin
    S.WriteString(Name);
    S.WriteRange(Age, Junk);
  end;

     S.WriteRange записывает данные из объекта в поток, начиная с
первого  байта  поля Age,  и продолжая до первого байта поля Junk
(но не включая этот байт).

См. также
---------

     ReadRange


WriteString
----------------------------------------------------------------
Объявление
----------

     procedure WriteString(S : String);

Назначение
----------

     Записывает в поток строку переменной длины.

Описание
--------

     Записывает число    байтов,    достаточное    для   хранения
фактической длины строки.  Всего в поток выводится  Length(S)+  1
байт.  Размер данного потока оптимизируется за счет использования
WriteString вместо  записи  в  в  поток  полного  размера  каждой
строки, включая неиспользуемые части строк.

Пример
------

     S.WriteString(Name);
     S.WriteString('проверка');

     Записывает фактическое число байтов,  занимаемое этими двумя
строками, включая байт длины (0-й байт) каждой из них.

См. также
---------

     ReadString


WriteUserPointer
----------------------------------------------------------------
Объявление
----------

     procedure WriteUserPointer(Ptr : Pointer;
                                DefaultCode : Word);

Назначение
----------

     Записывает код   Ptr,   если   он   зарегистрирован,   иначе
DefaultCode.

Описание
--------

     Работает подобно WritePointer,  но с одним исключением. Если
указатель Ptr не зарегистрирован,  WriteUserPointer вместо  того,
чтобы сгенерировать ошибку,  запмсывает в поток DefaultCode.  Это
правило полезно использовать с  объектами,  которые  поддерживают
опциональные ловушки пользователя (hook),  например, подпрограмму
предварительного редактирования в EntryScreen.  В этом случае при
отсутствии  процедуры  пользователя  вместо нее будет записан код
процедуры по умолчанию, не выполняющей никаких действий.

     Концепция регистрации  указателя  рассматривается в описании
правила RegisterPointer.

Пример
------

     procedure StringWriter.Store(var S : IdStream);
     begin
       S.WriteString(St);
       S.WriteUserPointer(@WrProc, ptDefaultWrite);
     end;

     Записывает код  (ptDefaultWrite)  в  качестве   подпрограммы
записи по   умолчанию,   если   действительная   WrProc  не  была
зарегистрирована.

См. также
---------

     RegisterPointer             WritePointer




DosIdStream
----------------------------------------------------------------

Done
----------------------------------------------------------------
Объявление
----------

     destructor Done; virtual;

Назначение
----------

     Закрывает файл и освобождает поток.

Описание
--------

     Использует обращение   к   DOS  для  закрытия  файла,  ранее
связанного с данным потоком.  Затем  вызывает  IdStream.Done  для
освобождения регистрационного списка данного потока.

Пример
------

     S.Init('TEST.STM', SCreate);
     ...
     S.Done;

     Закрывает файл TEST.STV и освобождает поток.


Init
----------------------------------------------------------------
Объявление
----------

     constructor Init(FileName : String; Mode : Word);

Назначение
----------

     Открывает файл  DOS  в заданном Mode режиме и инициализирует
поток.

Описание
--------

     FileName -  это  любой  допустимое в DOS имя маршрута.  Файл
может быть открыт в любом допустимом режиме(Mode) DOS.  Mode  это
значение, помещаемое  в  регистр  AX  перед  вызовом функции DOS.
Предопределенные константы, доступные для Mode, включают в себя:

     SCreate    = $3C00;   {Создание нового файла}
     SOpenRead  = $3D00;   {Доступ только на чтение}
     $OpenWrite = $3D01;   {Доступ только на запись}
     $Open      = $3D02;   {Доступ на чтение и запись}

     При создании  файла  потока  ему  присваивается "нормальный"
атрибут доступа (т.е.  он не помечен как "только для чтения"  или
"скрытый").

     Режимы "Open" требуют, чтобы данный файл к этому моменту уже
существовал. Могут быть  заданы  дополнительные  режимы  открытия
файлов, которые позволят разделенный доступ файла потока в сети.

     При неудачном завершении конструктора он вернет False, а код
ошибки может   быть   доступен   через   глобальную    переменную
InitStatus.

Пример
------

     if not S.Init('NAME.STM', SCreate) then begin
       WriteLn ('Не удалось создать поток,
                 Состояние = ', InitStatus);
       Halt;
     end;

     Инициализирует DosIdStream и создает файл NAME.STM.

См. также
---------

     IdStream.Init


Read
----------------------------------------------------------------
Объявление
----------

     procedure Read(var Buf; Count : Word); virtual;

Назначение
----------

     Читает байты из файла DOS.

Описание
--------

     Работает во   многом   похоже   на   функцию  BlockRead  для
переменных нетипированных файлов.  Из  потока  считывается  Count
байтов данных  в  память,  начиная  с  адреса,  заданного  в Buf.
Поскольку Buf это нетипированный параметр,  вы сами отвечаете  за
то, чтобы  адресуемая им область была достаточно велика для Count
байтов. При возможности используйте операцию SizeOf Turbo Pascal,
чтобы определить  число  байтов.  Если  Count  равен  0,  Read не
выполняет никаких действий.

     Прочие правила  объекта  IdStream  (ReadRange,   ReadString,
ReadPointer) вызывают   это   виртуальное   правило   для  чтения
необработанных байтов.

     Если в момент вызова Read имеется подвешенная  ошибка,  Read
не выполнит  никаких  действий.  Это  означает,  что часто бывает
безопасно выполнение последовательности чтений и  вызов GetStatus
только в конце этой последовательности.

     Если при  вызове Read происходит ошибка,  Read заполняет Buf
нулями в количестве Count и затем генерирует ошибку.

     Коды ошибки,  генерируемые Read,  совместимы с кодами ошибок
ввода/вывода  DOS  (и Turbo Pascal),  с добавлением кода префикса
ошибки. Подробности см. в Приложении D.

Пример
------

     S.Read(W, SizeOf(Word));  {W это переменная размером в слово}

     Инициализирует переменную размером в слово W.

     S.Read(St, SizeOf(St));

     Инициализирует строковую переменную St.

См. также
---------

     ReadRange              Write
     ReadString


Truncate
----------------------------------------------------------------
Объявление
----------

     procedure Truncate; virtual;

Назначение
----------

     Усекает файл в текущей позиции.

Описание
--------

     Работает, как  обычная  процедура  Truncate  для нормального
файла. Использует функцию DOS $40 с CX=0  для  усечения  файла  с
указанной позиции.

     В случае  подвешенной  ошибки  потока  никаких  действий  не
выполняет.

Пример
------

     S.Seek(100);
     S.Truncate;

     Усекает поток до 100 байтов.


Write
----------------------------------------------------------------
Объявление
----------

     procedure Write(var Buf; Count : Word); virtual;

Назначение
----------

     Записывает байты в файл DOS.

Описание
--------

     Работает во  многом  похоже  на   функцию   BlockWrite   для
переменных  нетипированных  файлов.  В  поток  записывается Count
байтов данных из памяти,  начиная  с  адреса,  заданного  в  Buf.
Поскольку  Buf это нетипированный параметр,  вы сами отвечаете за
то,  чтобы адресуемая им  область  содержала  Count  байтов.  При
возможности  используйте  операцию  SizeOf  Turbo  Pascal,  чтобы
определить число байтов.  Если Count равен 0,  Write не выполняет
никаких действий.

     Прочие правила  объекта  IdStream (WriteRange,  WriteString,
WritePointer)  вызывают  это  виртуальное  правило   для   записи
необработанных байтов.

     Если в момент вызова Write имеется подвешенная ошибка, Write
не выполнит никаких действий.  Это  означает,  что  часто  бывает
безопасно выполнение последовательности записей и вызов GetStatus
только в конце этой последовательности.

     Коды ошибки,  генерируемые Write, совместимы с кодами ошибок
ввода/вывода  DOS  (и Turbo Pascal),  с добавлением кода префикса
ошибки. Подробности см. в Приложении D.

Пример
------

     S.Write(W, SizeOf(W));

     Записывает переменную размером в слово.

     S.Write(St, SizeOf(St));

     Записывает в поток строковую переменную St. Заметим, что при
этом в  поток  будет  записана  вся  строка  целиком,  а  не   ее
используемая часть.  Для записи в поток только используемой части
строки используйте WriteString.

См. также
---------

     Read              WriteString
     WriteRange


BufIdStream
----------------------------------------------------------------

Done
----------------------------------------------------------------
Объявление
----------

     destructor Done; virtual;

Назначение
----------

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

Описание
--------

     Если файл потока был открыт на запись,  и  в  буфере  памяти
потока остались  какие-либо  байты,  то они сбрасываются на диск.
Затем файл DOS  закрывается.  Память,  выделенная  буферу  памяти
потока, отменяется, и сам IdStream освобождается.

Пример
------

     S.Init('TEST.STM', SOpen, 1024);
     ...
     S.Done;

     Сбрасывает на диск буфер и закрывает  файл  потока,  который
был открыт на чтение и запись при размере буфера 1024 байта.

См. также
---------

     Init


Flush
----------------------------------------------------------------
Объявление
----------

     procedure Flush; virtual;

Назначение
----------

     Сбрасывает на диск и очищает буфер.

Описание
--------

     Если в памяти буфера  нет  ожидающих  байтов,  то  указатели
буфера сбрасываются, и происходит выход из Flush.

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

     Если байты,  оставшиеся  в  буфере,  были  помещены туда при
записи в поток,  то эти байты записываются на диск,  а  указатели
буфера сбрасываются, указывая, что буфер пуст.

     При подвешенной ошибке Flush не выполняет никаких действий.

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

Пример
------

     S.Flush;

     Сбрасывает буфер   ввода/вывода  на  диск  или  с  диска,  в
зависимости от того, что требуется.


Init
----------------------------------------------------------------
Объявление
----------

     constructor Init(FileName : String; Mode, Size : Word);

Назначение
----------

     Открывает файл  в  заданном  режиме  Mode  и  выделяет буфер
размером в Size байтов.

Описание
--------

     Работает подобно DosIdStream.Init, но добавлен буфер памяти,
который должен  улучшить  быстродействие  чтения  и  записи.  Для
буфера выделяется Size байтов "кучи".

     При записи  в  BufIdStream  байты обычно переносятся прямо в
буфер. Физическая  запись  на  диск   производится   только   при
заполнении буфера.

     Аналогичным образом,  при чтении из BufIdStream, за один раз
считывается полный  буфер  информации,  а  затем  по   конкретным
запросам возвращаются части этого буфера.

     Отдельные операции  чтения  и  записи могут запросить больше
байтов, чем размер буфера.

     Обычно размер буфера от 1024 до  4096  байтовдостаточен  для
оптимизации быстродейтвия без слишком больших затрат памяти.

     При неудачном  завершении  конструктора он возвращает Fail и
записывает код ошибки в глобальную переменную InitStatus.

Пример
------

     if not S.Init('NAME.STM', SCreate, 1024) then begin
       WriteLn ('Не удалось создать поток,
                 Состояние = ', InitStatus); Halt;
     end;

     Инициализирует BufIdStream и создает файл NAME.STM. При этом
также выделяется буфер размером в 1024 байта.

См. также
---------

     Done


Read
----------------------------------------------------------------
Объявление
----------

     procedure Read(var Buf; Count : Word); virtual;

Назначение
----------

     Чтение с использованием буфера.

Описание
--------

     Работает подобно DosIdStream.Read,  за исключением того, что
при возможности  запросы удовлетворяются из буфера памяти.  Когда
требуется физическое  чтение  с  диска,  буфер   заполняется   по
возможности.

Пример
------

     DosIdStream.Read


Write
----------------------------------------------------------------
Объявление
----------

     procedure Write(var Buf; Count : Word); virtual;

Назначение
----------

     Запись с использованием буфера.

Описание
--------

     Работает подобно DosIdStream.Write, за исключением того, что
при  возможности  запись  выполняется  в  буфер,  пока  буфер  не
заполнится и не будет  сброшен  на  диск.  Параметр  Count  может
превысить размер буфера без вредных последствий.

См. также
---------

     DosIdStream.Write


MemIdStream
----------------------------------------------------------------

Init
----------------------------------------------------------------
Объявление
----------

     constructor Init(BasePtr : Pointer; Size : Word);

Назначение
----------

     Инициализирует поток в оперативной памяти.

Описание
--------

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

     Наиболее обычное   использование   MemIdStream   состоит   в
компоновке файлового потока к программе с  тем,  чтобы  избегнуть
распределения данному  потоку  отдельного файла.  Это выполняется
следующим образом при помощи утилиты Borland BINOBJ. Предположим,
что у  вас имеется файл потока STUFF.STM,  а размер (возвращаемый
DIR) файла равен 2000 байтов.  Сначала  вы  должны  преобразовать
файл потока в файл OBJ, запустив BINOBJ в командной строке DOS:

     BINOBJ STUFF.STM STUFF.OBJ STUFF

     Эта командная  строка  говорит  о  том,  что  нужно прочесть
STUFF.STM, создать STUFF.OBJ и использовать имя STUFF  как  общее
символическое имя этого блока данных.

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

     {$L STUFF.OBJ}
     procedure Stuff; external;

     Они сообщат    компоновщику    Turbo   Pascal,   что   нужно
прикомпоновать STUFF.OBJ   к   программе   и   использовать   при
компоновке внешнее имя Stuff.

     И наконец,  можно вызвать правило MemIdStream Init следующим
образом:

     M.Init(@Stuff, 2000);

     Это присвоение  адреса  процедуры  Stuff   базовому   адресу
области IdStream,  и  задание  исходного размера файла потока как
размера этого потока.

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

     Потоки памяти позволяют и запись в них, хотя необходимость в
этом менее очевидна.

     Отметим, что  MemIdStream   не   переопределяет   деструктор
IdStream.Done, поскольку  тут  нет  требующего закрытия файла,  и
обычно нет буфера памяти, который нужно освобождать.


Read
----------------------------------------------------------------
Объявление
----------

     procedure Read(var Buf; Count : Word); virtual;

Назначение
----------

     Читает байты из потока памяти.

Описание
--------

     Копирует Count байтов из текущей позиции памяти в переменную
Buf. Если  Count байтов превышают исходно заданный размер потока,
Read заполняет  Buf  числом  Count  нулей  и  генерирует   ошибку
(epNonFatal+ecDiskRead) - частичный буфер не возвращается.

См. также
---------

     DosIdStream.Read


Truncate
----------------------------------------------------------------
Объявление
----------

     procedure Truncate; virtual;

Назначение
----------

     Усечение в текущей позиции.

Описание
--------

     Уменьшает фактический размер потока памяти.  Действие  этого
правила в  MemIdStream  может быть отменено,  за исключением того
случая, когда было выполнено правило Done, а затем снова Init.


Write
----------------------------------------------------------------
Объявление
----------

     procedure Write(var Buf; Count : Word); virtual;

Назначение
----------

     Записывает байты в поток памяти.

Описание
--------

     Копирует число Count байтов из Buf в текущую  позицию потока
памяти. Если  в  конца  потока памяти осталось меньше,  чем Count
байтов, Write генерирует ошибку (epNonFatal+ecDiskFull)  и ничего
не записывает - частичная запись не выполняется.

Cм. также
---------

     DosIdStream.Write


7. Библиотеки

     Библиотека (Library)представляет   собой  специализированную
форму потока: она предоставляет возможность произвольного доступа
к  отдельным  объектам  по  имени,  не  читая  или  записывая  их
последовательно.  Library очень эффективно используется утилитами
Object  Professional  MAKEMENU  и  MAKESCRN.  Библиотечные  файлы
хранят системы меню и/или экраны ввода данных,  каждый из которых
может быть выбран для последующей модификации из списка выбора.

     Физический формат хранения библиотеки аналогичен формату LU,
первоначально разработанному   для   CP/M.   Библиотечный    файл
начинается с   каталожной  структуры  предопределенного  размера.
Каждый элемент  каталога  содержит  имя  ASCII  (до  12  символов
длиной, используемое для идентификации элемента,  позицию в файле
и длину образа объекта,  код типа и номер  версии  Load  объекта,
флаги, обозначающие состояние элемента, а также, возможно, другую
информацию, определяемую    пользователем.    Объект     DirEntry
организует каждый   элемент  каталога.  Объект  Library  содержит
указатель на  массив  указателей  объектов  DirEntry.   Указатели
используются в  связи  с  тем,  что каждый элемент может изменять
размер, а также может изменяться число  элементов.  Когда  объект
Library открыт, каталог поддерживается одновременно в памяти и на
диске.

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

     Отметим, что    объект    Library    не    создает   файлов,
непосредственно совместимых с форматом LU.  Доступ к библиотечным
файлам возможен только посредством правил самого объекта Library.

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

     Любой объект, который может быть записан в поток, может быть
записан и  в  библиотеку.  Как  и  поток,  одна  библиотека может
хранить множество типов объектов.  Например,  в одной  библиотеке
могут храниться  и  экраны ввода данных,  и системы меню.  Методы
регистрации типов данных в библиотеке те же,  что и для  потоков.
Объект Library  является  производным  от BufIdStream и наследует
все свойства потокового типа.

     Следующая короткая программа демонстрирует основные свойства
типа Library.


program LibraryExample;  {EXLIB.PAS}
uses OpRoot;
const
  otNameData = 1000;         {тип объекта Name}
  veNameData = 0;            {версия объекта Name}
type
  {Объект для манипулирования именем Name и возрастом Age}
  NameDataPtr = ^NameData;
  NameData =
    object(Root)
      Name : String[20];
      Age : Byte;
      constructor Init;
      procedure Display;
      constructor Load(var S : IdStream);
      procedure Store(var S : IdStream);
    end;
var
  Name : NameData;
  Lib : Library;
  I : Integer;
  S : String[3];
  Status : Word;
  {----- Правила NameData  -------------------}
  constructor NameData.Init;
  begin
    WriteLn;
    Write('Имя:  ');
    ReadLn(Name);
    Write('Возраст:   ');
    ReadLn(Age);
  end;
  procedure NameData.Display;
  begin
    WriteLn;
    Writeln(Name, ' ', Age);
  end;
  constructor NameData.Load(var S : IdStream);
  begin
    if not Root.Init then Fail;
    Name := S.ReadString;
    S.Read(Age, SizeOf(Age));
    if S.PeekStatus <> 0 then Fail;
  end;
  procedure NameData.Store(var S : IdStream);
  begin
    S.WriteString(Name);
    S.Write(Age, SizeOf(Age));
  end;
  {$F+}
  procedure NameDataStream(SPtr : IdStreamPtr);
  begin
    SPtr^.RegisterType(otNameData, veNameData, TypeOf(NameData),
                       @NameData.Store, @NameData.Load);
  end;
  {$F-}
begin
  {Создание библиотеки Library и регистрация объекта NameData}
  if not Lib.Create('NAMEDATA.OPL', 1024, 'NAMEDATA', 25) then begin
    WriteLn('Не удалось создать библиотеку. Состояние = ', InitStatus); Halt;
  end;
  Lib.RegisterHier(NameDataStream);
  {Создание и запись трех объектов NameData}
  for I := 1 to 3 do begin
    Name.Init;
    Str(I, S);
    Lib.PutEntry('NAME'+S, Name);
    Status := Lib.GetStatus;
    if Status <> 0 then
      WriteLn('Не удалось добавить NAME', S, ', Состояние = ', Status)
    else
      WriteLn('NAME', S, ' добавлено в библиотеку');
    Name.Done;
  end;
  Lib.Done;
  {Повторно открыть существующую библиотеку,
   Повторно зарегистрировать объекты}
  if not Lib.Init('NAMEDATA.OPL', SOpen, 1024, 'NAMEDATA') then begin
    WriteLn('Не удалось открыть библиотеку,  Состояние = ', InitStatus);
    Halt;
  end;
  Lib.RegisterHier(NameDataStream);
  {Обращение к третьему элементу по имени,
   вывод его на дисплей, затем закрытие библиотеки}
  Lib.GetEntry('NAME3', Name);
  if Lib.GetStatus <> 0 then begin
    WriteLn('Ошибка чтения NAME3');
    Halt;
  end;
  Name.Display;
  Lib.Done;
  Name.Done;
end.


     Здесь объект    NameData    идентичен    используемому   для
демонстрации потоковых  объектов.  Библиотеки  используют  те  же
самые концепции  регистрации  типа  и те же правила Load и Store,
что и потоки.

     Create - это один из  двух  конструкторов  объекта  Library.
Create создает  новый  пустой библиотечный файл.  Первый параметр
этого конструктора  это  имя  файла  DOS  для   библиотеки.   (По
соглашению мы  используем  для  обозначения  библиотечного  файла
расширение OPL - Object Professional Library).  Вторым параметром
является размер  буфера,  используемый  BufIdStream для улучшения
быстродействия операций чтения и записи  объектов  в  библиотеку.
Третьим параметром является строка, до 8 символов длиной, которая
хранится в начале  библиотечного  файла,  идентифицируя  его  как
достоверную библиотеку.  Последний  параметр  это число элементов
каталога, резервируемых библиотекой.  В данном примере библиотека
может одновременно хранить до 25 уникальных объектов.

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

     Типы объектов,    записываемые    в    библиотеку,    должны
регистрироваться правилами    RegisterHier    или   RegisterType.
Библиотека из  примера  хранит  только  один  тип  объекта,   тип
NameData, но в целом,  библиотеки могут хранить любое число типов
объектов. Как и в  случае  потоков,  типы  объектов,  хранимые  в
Library, всегда должны быть производными от объектов Root.

     Правило PutEntry  либо добавляет новый элемент в библиотеку,
либо заменяет  там  существующий.  В  примере  мы  добавляем  три
экземпляра объекта NameData в библиотеку, называя их NAME1, NAME2
и NAME3.  Если один из этих экземпляров будет замещен,  то старый
тем не  менее  останется  в  библиотеке,  а новый будет дописан к
концу библиотечного файла.  Старый образ станет недоступен,  т.к.
элемент каталога  будет  обновлен  и  станет  указывать только на
новый образ. Объект Library имеет правило Pack, который позволяет
очищать файл от недоступных участков.

     Правило Done  сбрасывает  на  диск  и очищает буфер Library,
закрывает файл  DOS   и   отменяет   выделенную   память   списка
зарегистрированных типов потока.

     Конструктор Init   объекта  Library  открывает  существующий
библиотечный файл. Его параметры включают в себя имя файла, режим
открытия (только чтение,  только запись,  чтение/запись),  размер
буфера памяти и ожидаемую сигнатуру файла. Этот конструктор может
завершиться неудачно,  если  файл  не  найден,  если недостаточно
места в "куче", либо если не найдена ожидаемая сигнатура.

     После успешного вызова Init типы данных потока  снова должны
быть зарегистрированы у менеджера потока.

     Данный пример    демонстрирует    произвольный    доступ   к
библиотеке, вызывая  GetEntry  для  чтения  последнего   элемента
библиотеки, NAME3.  GetEntry инициализирует статический экземпляр
объекта NAME.  Аналогичное  правило,  GetEntryPtr,  имеется   для
выделения и  инициализации динамического экземпляра объекта. Если
соответствующий элемент каталога не будет найден,  или  в  случае
дисковй ошибки,  либо при неудачном завершении конструктора Load,
вызванного для  чтения  образа   объекта,   GetEntry   закончится
неудачно.

     Объект MemLibrary имеет такое же отношение к Library,  как и
MemIdStream к BufIdStream.  MemLibrary связывается  с  программой
таким   образом,  что  для  доступа  к  библиотеке  не  требуется
каких-либо  дополнительных  файлов.  Сточки   зрения   прикладной
программы  большинство  правил  MemLibrary  работают так же,  как
соответствующие   правила   в   Library.   Эти   правила    будут
документированы вкратце.

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

     [Root]
       DirEntry
       [IdStream]
         [DosIdStream]
           [BufIdStream]
             Library
         [MemIdStream]
           MemLibrary

Константы
---------

     DirectorySig = Ord('O') + (Ord('P') shl 8); {'OP' => $504F}

     Двухбайтовая сигнатура,  предшествующая каждому достоверному
элементу в библиотеке.

     TempLibExit : string[3] = '$$$';

     Расширение имени    файла,    используемое    при   упаковке
библиотечного файла.  Более подробную информацию см.  в  описании
Library.Pack.

     deUnused   = $0001;
     deDeleted  = $0002;

     Битовые маски,  используемые для обозначения опцй в  объекте
DirEntry. Если   бит  deUnused  установлен,  элемент  каталога  в
текущий момент не используется. Если установлен бит deDeleted, то
данный элемент  описывает удаленный объект.  В этом случае объект
тем не     менее     может     быть     восстановлен     правилом
Library.UndeleteEntry.

Типы
----

     DirEntryName = string[12];

     Строка этого типа  используется  как  имя  каждого  элемента
библиотеки. Ее  размер совместим с форматом традиционного формата
"восемь-точка-три", принятого  в  именах   DOS.   Однако,   имена
элементов Library не ограничиваются форматом имени файла DOS.

     DirEntry = ^DirEntry;
     DirEntry =
       object(Root)
         ...
         deUser     : Pointer;
         deCustom   : record end;
         ...
       end;

     Тип объекта,  используемый для организации каждого  элемента
каталога в    Library.    Тип   специально   разрабатывался   как
расширяемый. В   частности,   поле   данных   deUser   специально
предназначено для   вашего  использования  во  время  выполнения.
(Правила SetUserPointer и GetUserPointer используются для доступа
к этому полю).  Если производный тип добавляет новые поля данных,
то имеется  любопытное  поле  deCustom,   позволяющее   менеджеру
каталога читать и записывать эти новые поля,  не зная, что именно
они содержат.

     DirEntries = array[1..512] of DirEntryPtr;
     DirPtr = ^DirEntries;

     Типы, определяющие  массив  указателей на элементы каталога.
Верхняя граница в 512 элементов произвольна.  Абсолютная  верхняя
граница числа элементов библиотеки равна 16380.

     LibrarySyg = string[8];

     Строка этого    типа    записывается    в   начало   каждого
библиотечного файла.   Она   идентифицирует   файл   в   качестве
достоверной библиотеки.  Независимо  от  фактической длины строки
записывается всегда девять байтов.

     LibraryPtr = ^Library;
     Library =
       object(BufIdStream)
         ...
       end;

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

     MemLibraryPtr = ^MemLibrary;
     MemLibrary =
       object(MemIdStream)
         ...
       end;

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


DirEntry
----------------------------------------------------------------

deOptionsAreOn
----------------------------------------------------------------
Объявление
----------

     function deOptionsAreOn(Options : Word) : Boolean;

Назначение
----------

     Возвращает On, если заданные флаги установлены.

Описание
--------

     Единственными двумя  флагами объекта DirEntry являются флаги
deUnused и deDeleted.  Когда флаг deUnused установлен,  то данный
элемент каталога   в   текущий   момент  не  используется.  Когда
deDeleted установлен, это означает, что данный элемент помечен на
удаление. Восстановить  помеченный  на  удаление  элемент можно с
помощью правила Library.UndeleteEntry.

     Данное правило предназначенио  в  основном  для  внутреннего
использования объектом Library.

Пример
------

     var
       Lib : Library;
     DEP : DirEntryPtr;
     begin
       ...
       DEP := Lib.FindAnyDirectoryEntry('NAME1');
       Write('Элемент NAME1 ');
       if DEP = nil then
         WriteLn('не найден')
       else if DEP^.deOptionsAreOn(deDeleted) then
         WriteLn('удален');
       else
         WriteLn('не удален');
     end.

     Сообщает состояние элемента NAME1.


deOptionsOff
----------------------------------------------------------------
Объявление
----------

     procedure deOptionsOff(Options : Word);

Назначение
----------

     Очищает заданный(е) флаг(и).

Описание
--------

     Может использоваться  для  очистки  флагов объекта DirEntry:
deUnused и deDeleted. Данное правило в основном предназначено для
внутреннего использования объектом Library.


deOptionsOn
----------------------------------------------------------------
Объявление
----------

     procedure deOptionsOn(Options : Word);

Назначение
----------

     Устанавливает заданный(е) флаг(и).

Описание
--------

     Может использоваться  для установки флагов объекта DirEntry:
deUnused и deDeleted. Данное правило в основном предназначено для
внутреннего использования объектом Library.


GetEntryLength
----------------------------------------------------------------
Объявление
----------

     function GetEntryLength : LongInt;

Назначение
----------

     Возвращает длину элемента.

Описание
--------

     Возвращаемое значение   представляет   собой   число  байтов
объекта, принадлежащее  элементу   каталога.   В   комбинации   с
GetEntryStart данное  правило полностью определяет позицю и длину
объекта.

     Если элемент   в    текущий    момент    не    используется,
GetEntryLength возвращает значение 0.

Пример
------

     var
       Lib : Library;
       DEP : DirEntryPtr;
     begin
       ...
       DEP := Lib.FindAnyDirectoryEntry('NAME1');
       Write('Длина элемента ', DEP^.GetEntryLength);
     end;


     Сообщает длину элемента каталога NAME1.

См. также
---------

     GetEntryStart


GetEntryName
----------------------------------------------------------------
Объявление
----------

     function GetEntryName : DirEntryName;

Назначение
----------

     Возвращает имя элемента.

Описание
--------

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

Пример
------

WriteLn('Первое имя в коталоге ', Lib.Directory^.GetEntryName);

     Данный пример прямо ссылается на мпссив  указателей каталога
в объекте  Library  и  сообщает  имя  первого элемента каталога в
данном массиве.

См. также
---------

     Rename


GetEntryStart
----------------------------------------------------------------
Объявление
----------

     function GetEntryStart : LongInt;

Назначение
----------

     Возвращает начальную точку данного элемента.

Описание
--------

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

     Если элемент   в    текущий    момент    не    используется,
GetEntryStart возвращает значение 0.

Пример
------

     var
       Lib : Library;
       DEP : DirEntryPtr;
     begin
       ...
       DEP := Lib.FindAnyDirectoryEntry('NAME1');
       Write('Позиция начала элемента ', DEP^.GetEntryStart);
     end;

     Сообщает начальную позицию элемента каталога NAME1.

См. также
---------

     GetEntryLength


GetUserPointer
----------------------------------------------------------------
Объявление
----------

     function GetUserPointer : Pointer;

Назначение
----------

     Возвращает указатель пользователя.

Описание
--------

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

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

Пример
------

     var
       Lib : Library;
       MyPtr : Pointer;
     begin
       ...
       MyPtr := Lib.FindDirectoryEntry('NAME1')^.GetUserPointer;
     end.

     Возвращает указатель   пользователя  для  элемента  каталога
NAME1.

См. также
---------

     SetUserPointer


Init
----------------------------------------------------------------
Объявление
----------

     constructor Init;

Назначение
----------

     Инициализирует пустой элемент каталога.

Описание
--------

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

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

См. также
---------

     Update


Rename
----------------------------------------------------------------
Объявление
----------

     procedure Rename(Name : DirEntryName);

Назначение
----------

     Изменяет имя элемента каталога.

Описание
--------

     Может служить  либо для изменения имени существующего имени,
либо для  того,  чтобы  задать  имя  ранее  не  использовавшемуся
элементу.

См. также
---------

     Update


SetUserPointer
----------------------------------------------------------------
Объявление
----------

     procedure SetUserPointer(P : Pointer);

Назначение
----------

     Устанавливает указатель пользователя равным P.

Описание
--------

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

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

Пример
------

     var
       Lib : Library;
       MyPtr : MyObjectPtr;
     begin
       ...
       {Выделение памяти и загрузка NAME1 с диска}
       MyPtr := Lib.GetEntryPtr('NAME1');
       {Сохранение адреса объекта в каталоге библиотеки}
       Lib.FindDirectoryEntry('NAME1')^.SetUserPointer(MyPtr);
     end.

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

См. также
---------

     GetUserPointer


Update
----------------------------------------------------------------
Объявление
----------

procedure Update(Start, Len : LongInt; Code, Ver : Word); virtual;

Назначение
----------

     Обновляет описание объекта.

Описание
--------

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

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


Пример
------

     var
       Lib : Library;
       DEP : DirEntryPtr;
       NewStart, NewLen : LongInt;
     begin
       ...
       DEP := Lib.DirectoryEntry('NAME1');
       DEP^.Update(NewStart, NewLenn, DEP^.deCode, DEP^.deVer);
     end;

     Присваивает элементу  библиотеки  новые  значения  начальной
позиции и длины.

См. также
---------

     Rename


Library
----------------------------------------------------------------

AvailableEntries
----------------------------------------------------------------
Объявление
----------

     function AvailableEntries : Word;

Назначение
----------

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

Описание
--------

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

Пример
------

     WriteLn('Состояние библиотеки');
     WriteLn('Максимальное число элементов: ', Lib.MaxEntries);
     WriteLn('Текущее число элементов:      ', Lib.CurrentEntries);
     WriteLn('Удаленных элементов :         ', Lib.DeletedEntries);
     WriteLn('Доступных элементов :         ', Lib.AvailableEntries);

     Вывод на дисплей текущего состояния библиотеки.

См. также
---------

     CurrentEntries                 MaxEntries
     DeletedEntries


Create
----------------------------------------------------------------
Объявление
----------

     constructor Create(FileName : PathStr; BufferSize : Word;
                        Sig : LibrarySig; Entries : Word);

Назначение
----------

     Создание новой библиотеки.

Описание
--------

     FileName -  это  допустимое  в  DOS имя маршрута создаваемой
библиотеки. BufferSize  -  это  число  байтов   области   "кучи",
выделяемой для  буферизации  операций  ввода и вывода библиотеки.
Sig -  это  строка  до  8  байтов  длиной,  которая  служит   для
идентификации данного файла как допустимой библиотеки.  Entries -
это число уникальных элементов каталога, резервируемое для данной
библиотеки. В  библиотеке  может  храниться  не более чем Entries
объектов.

     Create выделяет   BufferSize    +    Entries    *    (4    +
SizeOf(DirEntry))   байтов  области  "кучи".  Стандартный  размер
DirEntry составляет 35 байтов.  Отметим,  что каталог  библиотеки
организуется именно в оперативной памяти.  Следовательно, поиск в
библиотеке происходит быстро,  поскольку не требует  обращений  к
диску.

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

     После вызова Create библиотека остается открытой и доступной
для использования.  Перед  записью  в библиотеку объектов их типы
должны быть зарегистрированы при помощи правил  RegisterHier  или
RegisterType.

     Для открытия существующей библиотеки используйте конструктор
Init.

Пример
------

     if not Lib.Create('D:\NAMEDATA.OPL', 1024, 'NAMEDATA', 25)
     then begin
       WriteLn('Не удалось создать библиотеку.
                Состояние = ', InitStatus); Halt;
     end;
     LibRegisterHier(NameDataStream);

     Создает библиотеку с  именем  D:\NAMEDATA.OPL  и  сигнатурой
NAMEDATA. Эта  библиотека  будет  иметь  размер  в 25 элементов и
использовать буфер емкостью в 1К для ввода/вывода на диск.

См. также
---------

     IdStream.RegisterHier                  Init


CurrentEntries
----------------------------------------------------------------
Объявление
----------

     function CurrentEntries : Word;

Назначение
----------

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

Описание
--------

     Число элементов библиотеки описывается следующим уравнением:

     MaxEntries := CurrentEntries+DeletedEntries+AvailableEntries

     MaxEntries - это  число  элементов,  заданное  при  создании
библиотеки.

См. также
---------

     AvailableEntries                     MaxEntries


DeletedEntries
----------------------------------------------------------------
Объявление
----------

     function DeletedEntries : Word;

Назначение
----------

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

Описание
--------

     Отметим, что удаление элемента каталога может  быть отменено
при помощи   правила  UndeleteEntry.  После  упаковки  библиотеки
элементы, помеченные  как  удаленные,   будут   преобразованы   в
неиспользуемые.

См. также
---------

     AvailableEntries             MaxEntries


DeleteEntry
----------------------------------------------------------------
Объявление
----------

     procedure DeleteEntry(Name : DirEntryName); virtual;

Назначение
----------

     Помечает элемент с заданным именем как удаленный.

Описание
--------

     Если имя Name не найдено  в  каталоге,  генерируется  ошибка
(epNonFatal+ecEntryNotFound). В   противном   случае  в  заданном
элементе каталога устанавливается флаг deDeleted,  и библиотечный
файл обновляется.  Удаление  уже  ранее  удаленного  элемента  не
является ошибкой.

     Отмена удаления элемента может  быть  выполнена  при  помощи
правила UndeleteEntry.

Пример
------

     Lib.DeleteEntry('NAME1');
     Status := Lib.GetStatus;
     if Status <> 0 then
       WriteLn('Элемент не найден, Состояние = ', Status);

     Удаляет элемент каталога с именем NAME1.

См. также
---------

     Pack                    UndeleteEntry


Done
----------------------------------------------------------------
Объявление
----------

     destructor Done; virtual;

Назначение
----------

     Сбрасывает и очищает буфер и закрывает библиотечный  файл, а
затем освобождает каталог и поток из памяти.

Описание
--------

     Done отменяет   выделение   области   "кучи",   используемой
Library, и закрывает дисковый файл.

     Перед остановом  прикладной  программы  убедитесь,  что было
выполнено правило Done для всех использованных библиотек Library.
В противном  случае  не  все  их  содержимое может сохраниться на
диске.

Пример
------

     Lib.Done;

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

См. также
---------

     Create        Init


FileNeedsPacking
----------------------------------------------------------------
Объявление
----------

     function FileNeedsPacking : Boolean;

Назначение
----------

     Возвращает True, если требуется упаковка библиотеки.

Описание
--------

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

     FileNeedsPacking сравнивает  физический размер файла Library
с размером,  вычисляемым  суммированием  используемых   элементов
библиотеки. В результате эта функция выполняется очень быстро.

     Правило Pack  вызывает  FileNeedsPacking,  прежде чем начать
остальные действия,  так что явно вызывать данное  правило  перед
упаковкой не   требуется.  Основное  назначение  FileNeedsPacking
состоит в использовании ее при выводе экрана состояния,  как  это
делается, например, в MAKESCRN или MAKEMENU.

Пример
------

     WriteLn('Нужна упаковка? ', Lib.FileNeedsPacking);

См. также
---------

     Pack         PackedFileSize


FindAnyDirectoryEntry
----------------------------------------------------------------
Объявление
----------

     function FindAnyDirectoryEntry(Name : DirEntryName) :
              DirEntryPtr;

Назначение
----------

     Возвращает указатель  на  элемент  каталога,  или nil,  если
таковой не найден.

Описание
--------

     FindAnyDirectoryEntry просматривает    все   элементы,   как
активные, так и  удаленные.  Сравнение  заданного  имени  Name  с
каждым элементом  каталога  выполняется  без  учета  регистра,  в
котором набрано имя.  Отметим,  что если Name это пустая  строка,
FindAnyDirectoryEntry вернет   указатель   на   первый  доступный
элемент.

     Если возвращенный указатель не  равен  nil,  он  может  быть
использован для   доступа  к  правилам  и  полям  данных  объекта
DirEntry.

Пример
------

     var
       Lib : Library;
       DEP : DirEntryPtr;
     begin
       ...
       Lib.DeleteEntry('NAME1');
       DEP := Lib.FindAnyDirectoryEntry('NAME1');
       if DEP = nil then
         WriteLn('Элемент не найден');
       else
         {...работаем с DEP...}
     end.

     DEP присваивается указатель на элемент каталога  NAME1, даже
если этот элемент был удален.

См. также
---------

     FindEntryByIndex


FindDirectoryEntry
----------------------------------------------------------------
Объявление
----------

function FindDirectoryEntry(Name : DirEntryName) : DirEntryPtr;

Назначение
----------

     Возвращает указатель  на  элемент каталога,  либо nil,  если
этот элемент не найден или удален.

Описание
--------

     В отличие   от   FindAnyDirectoryEntry,  данное  правило  не
просматривает удаленные элементы  каталога.  Сравнение  заданного
имени Name  с  каждым  элементом  каталога  выполняется без учета
регистра, в котором набрано имя.

     Если возвращаемый указатель не  равен  nil,  он  может  быть
использован для  доступа  к правилам и полям данных этого объекта
DirEntry.

Пример
------

     См. пример для FindAnyDirectoryEntry.


FindEntryByIndex
----------------------------------------------------------------
Объявление
----------

     function FindEntryByIndex(N : Word) : DirEntryPtr;

Назначение
----------

     Возвращает указатель на N-й элемент каталога.

Описание
--------

     Позволяет выполнять  прямой  доступ  к  элементам  каталога.
Допустимые значения  N  должны  лежать  в  диапазоне  от   1   до
MaxEntries. Если  N  лежит вне этого диапазона,  FindEntryByIndex
возвращает nil.  В противном случае возвращаемый указатель  может
быть использован  для  доступа  к правилам и полям данных объекта
DirEntry.

Пример
------

     var
       Lib : Library;
       DEP : DirEntryPtr;
     begin
       ...
       WriteLn('Используемые элементы: ');
       for I := 1 to Lib.MaxEntries do
         with Lib.FindEntryByIndex(I)^ do
           if not deOptionsAreOn(deUnused) and not
                  deOptionsAreOn(deDeleted) then
             WriteLn(I, ' ', GetEntryName);
     end.

     Выводит список активных элементов библиотеки.

См. также
---------

     FindAnyDirectoryEntry


GetCodeAndVersion
----------------------------------------------------------------
Объявление
----------

     procedure GetCodeAndVersion(Name : DirEntryName;
                                 var Code, Ver : Word);

Назначение
----------

     Возвращает код и версию заданного элемента.

Описание
--------

     Если имя  Name  не  найдено,  GetCodeAndVersion   генерирует
ошибку (epNonFatal+ecEntryNotFound) и оставляет переменные Code и
Ver неинициализированными.  В  противном  случае   Code   и   Ver
устанавливаются в значения кода типа и номера версии для объекта,
заданного как Name.

     Данная информация может быть  использована  для  определения
того, были  ли  правила  Load  и  Store  для данного типа объекта
зарегистрированы в менеджере потока. Она может быть использована,
чтобы убедиться,  что  элемент  библиотеки совместим с переменной
экземпляра объекта, передаваемой правилу GetEntry.

     Отметим, что GetCodeAndVersion работает как с активными, так
и с удаленными элементами.

Пример
------

     var
       Code, Var : Word;
     ...
       Lib.GetCodeAndVersion('NAME1', Code, Ver);

     Просматривает код  и  версию  для элемента каталога с именем
NAME1.

См. также
---------

     IdStream.CodeVerRegistered


GetEntry
----------------------------------------------------------------
Объявление
----------

     procedure GetEntry(Name : DirEntryName; var R : Root);

Назначение
----------

     Вызывает полиморфный    конструктор    Load    для    чтения
статического объекта R.

Описание
--------

     Сначала GetEntry пытается найти названный элемент в каталоге
библиотеки. Если  поиск  окончился  неудачно,  либо  если элемент
помечен на         удаление,         генерируется          ошибка
(epNonFatal+ecEntryNotFound). В    противном    случае   GetEntry
использует элемент каталога для поиска соответствующего  адреса в
библиотечном файле  и  затем  вызывает  IdStream.Get  для  чтения
объекта в параметр R.

     GetEntry также завершается  неудачно  при  дисковой  ошибке,
если тип   данных  не  зарегистрирован,  либо  при  генерировании
конструктором Load своей собственной ошибки. Для проверки наличия
ошибки после вызова GetEntry используйте правило GetStatus.

     Убедитесь, что  R  это  переменная  типа,  достаточного  для
хранения экземпляра,  считанного при помощи GetEntry.  Компилятор
об ошибках  такого  рода не сообщает,  что может привести к тому,
что будет  испорчена  память.  Правило  GetCodeAndVersion   может
использоваться для проверкок времени выполнения, когда библиотека
хранит более одного типа данных. В качестве альтернативы, правило
GetEntryPtr позволяет    автоматически    выделять   динамический
экземпляр правильного размера, независимо от типа данных.

Пример
------

     Lib.GetEntry('NAME3', Name);
     Status := Lib.GetStatus;
     if Status <> 0 then
     WriteLn('Не удалось загрузить NAME3, Состояние = ', Status);

     GetEntry ищет  в  каталоге  элемент  с  именем  NAME3.  Если
элемент найден,  вызывается  конструктор  Load  этого объекта для
чтения данных объекта с диска.

См. также
---------

     GetCodeAndVersion       PutEntry
     IdStream.GetStatus


GetEntryPtr
----------------------------------------------------------------
Объявление
----------

     function GetEntryPtr(Name : DirEntryName) : RootPtr;

Назначение
----------

     Вызывает полиморфный конструктор Load для выделения памяти и
чтения объекта.

Описание
--------

     Работает аналогично   GetEntry,   за  тем  исключением,  что
создает не статический,  а динамический  экземпляр  объекта.  Это
означает, что  GetEntryPtr  выделяет  область "кучи" для хранения
самого объекта и возвращает  указатель  на  эту  область  "кучи".
Правило GetEntryPtr  является  предпочстительным,  когда во время
компиляции тип  объекта  неизвестен,  либо   когда   одновременно
требуется манипулировать   в   памяти   несколькими  экземплярами
объектов.

     GetEntryPtr возвращает nil, если при вызове произошла какая-
либо ошибка.  Для  определения  точного  кода  ошибки используйте
правило GetStatus.

Пример
------

   NamePtr := Lib.GetEntryPtr('NAME3');
   if NamePtr = nil then
     WriteLn('Не удалось загрузить NAME3,
              Состояние = ', Lib.GetStatus);

     GetEntry ищет в каталоге библиотеки элемент с  именем NAME3.
Если он  найден,  то для чтения данных объекта с диска вызывается
его конструктор Load.  Конструктор  сам  выделит  нужную  область
памяти "кучи" для хранения объекта.



Init
----------------------------------------------------------------
Объявление
----------

     constructor Init(FileName : PathStr; Mode,
                      BufferSize : Word; Sig : LibrarySig);

Назначение
----------

     Открывает существующую библиотеку.

Описание
--------

     FileName это имя маршрута доступа к существующей открываемой
библиотеке. Mode задает режим,  в котором  открывается  файл  (на
чтение, на  запись,  или на чтение/запись).  В этих целях следует
использовать константы,  определенные в начале  раздела  "Потоки"
данной главы  (SOpenRead,  SOpenWrite,  SOpen).  BufferSize - это
число байтов области "кучи",  выделяемой для буферизации операций
чтения и  записи  данной  библиотеки.  Значение 1024 байта обычно
обеспечивает хороший баланс быстродействия и затрат памяти. Sig -
это строка длиной до 8 байтов, которая идентифицирует данный файл
как допустимую библиотеку. Заданная строка должна соответствовать
той, что была использована при создании библиотеки.

     Init выделяет  BufferSize + Entries * (4 + SizeOf(DirEntry))
байтов  области  "кучи",  где  Entries  -  это  число   элементов
каталога,  заданное  при создании библиотеки.  Стандартный размер
DirEntry составляет 35 байтов.  Отметим,  что каталог  библиотеки
организуется при открытой библиотеке именно в оперативной памяти.
Следовательно, поиск в библиотеке происходит быстро, поскольку не
требует обращений к диску.

     Init может  завершиться  неудачно в случае задании неверного
имени файла,  при отсутствии доступных логических номеров файлов,
если  имеющейся  области  "кучи" недостаточно для организации там
структур данных буфера и каталога, либо при ошибке записи на диск
или несоответствии сигнатуры файла значению, заданному в Sig. При
неудачном завершении  Init  для  определения  причины,  вызвавшей
ошибку, проверьте значение InitStatus.

     После вызова  Init  библиотека остается открытой и доступной
для использования.  Перед записью или чтением  объектов  их  типы
должны  быть  зарегистрированы при помощи правил RegisterHier или
RegisterType.

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

Пример
------

     if not Lib.Init('NAMEDATA.OPL', SOpen, 1024, 'NAMEDATA')
     then begin
       WriteLn('Не удалось открыть библиотеку.
                Состояние = ', InitStatus);
       Halt;
     end;
     LibRegisterHier(NameDataStream);

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

     if not Lib.Init('NAMEDATA.OPL', SCreate, 1024, 'NAMEDATA')
     then begin
     ...

     Неверно! Создать новую библиотеку при  помощи  Init  нельзя.
Вместо этого используйте Create.

См. также
---------

     Create                   IdStream.RegisterHier


KillEntry
----------------------------------------------------------------
Объявление
----------

     procedure KillEntry(Name : DirEntryName);

Назначение
----------

     Необратимо удаляет элемент с заданным именем.

Описание
--------

     Элемент, удаленный  при  помощи  KillEntry,  не  может  быть
восстановлен. Соответствующий элемент каталога реинициализируется
как новый.  Отметим,  что данные, связанные с этим элементом, при
этом продолжают  оставаться  в библиотечном файле (занимая лишнее
место), пока не будет выполнена упаковка библиотеки.

Пример
------

     Lib.KillEntry('NAME1');
     Status := LibGetStatus;
     if Status <> 0 then
       WriteLn('Элемент не найден, Состояние = ', Status);

     Необратимо удаляет элемент каталога NAME1.

     Lib.KillEntry('NAME1');
     Lib.UndeleteEntry('NAME1');

     Неверно ! KillEntry реинициализирует элемент каталога; NAME1
потерян навсегда.

См. также
---------

     DeleteEntry         Pack


MaxEntries
----------------------------------------------------------------
Объявление
----------

     function MaxEntries : Word;

Назначение
----------

     Возвращает максимальное   число   элементов    в    каталоге
библиотеки.

Описание
--------

     MaxEntries имеет то же самое значение,  что  было  задано  в
качестве числа элементов при создании библиотеки.

Пример
------

     WriteLn('Состояние библиотеки');
     WriteLn('Максимальное число элементов: ', Lib.MaxEntries);
     WriteLn('Текущее число элементов:      ', Lib.CurrentEntries);
     WriteLn('Удаленных элементов :         ', Lib.DeletedEntries);
     WriteLn('Доступных элементов :         ', Lib.AvailableEntries);

     Вывод на дисплей текущего состояния библиотеки.

См. также
---------

     AvailableEntries              DeletedEntries
     CurrentEntries


Pack
----------------------------------------------------------------
Объявление
----------

     procedure Pack;

Назначение
----------

     Упаковывает библиотеку.

Описание
--------

     Pack удаляет из библиотеки лишнюю область данных. Эта лишняя
область образуется   при   удалении   или   обновлении  элементов
библиотеки, при  которых  старые  данные  физически  остаются   в
библиотеке до  упаковки.  Pack  сначала  проверяет,  имеется ли в
библиотеке такая лишняя  область.  Если  ее  нет,  то  немедленно
происходит выход из процедуры.

     Упаковываемая библиотека    должна    быть    предварительно
инициализирована конструкторами  Create  или   Init.   Если   бил
использован конструктор  Init,  библиотека  должна быть открыта в
режиме чтения/записи (SOpen); в противном случае произойдет выход
из Pack с состоянием ошибки.

     Pack создает   временный  рабочий  файл,  добавляя  к  имени
упаковываемой библиотеки расширение TempLibExt (по  умолчанию это
'$$$'). Сначала  в  этот  рабочий  файл копируется каталог старой
библиотеки. Однако,   удаленные   элементы   перед   копированием
реинициализируются, т.е.  после  упаковки  их  восстановление уже
невозможно. Затем из старого  файла  в  новый  копируется  каждый
достоверный объект   данных,   причем   в   качестве  буфера  при
копировании используется буфер библиотеки в  оперативной  памяти.
(Правила Load  и  Store  при  копировании  не  привлекаются;  все
происходит строго на байтовом  уровне).  При  удачном  завершении
исходная библиотека  закрывается  и  стирается,  а  рабочий  файл
переименовывается в   исходное   имя   библиотеки.   И   наконец,
библиотека снова открывается в ее исходном режиме доступа.

     Для проверки  ошибок  используйте  правило  GetStatus.  Если
обнаружена ошибка,  следует  немедленно  вызвать   правило   Done
библиотеки. В   большинстве   случаев  исходный  файл  библиотеки
останется неповрежденным.   В   одном   случае   (если   неудачно
завершилась только  операция  переименования),  вы  будете  иметь
достоверный упакованный  файл   с   расширением   TempLibExt,   а
исходный библиотечный файл уже перестанет существовать.

Пример
------

     Lib.DeleteEntry('NAME3');
     Lib.Pack;
     Status := LibGetStatus;
     if Status <> 0 then begin
       WriteLn('Не удалась упаковка, Состояние = ', Status);
       Lib.Done;
       Halt;
     end;

     Запрос на   удаление  NAME3  просто  помечает  этот  элемент
каталога как  удаленный.  Вызов  Pack  физически  удаляет  NAME3,
копируя в новый файл все, за исключением NAME3.

См. также
---------

     DeleteEntry                PutEntry


PackedFileSize
----------------------------------------------------------------
Объявление
----------

     function PackedFileSize : LongInt;

Назначение
----------

     Возвращает размер библиотеки после упаковки.

Описание
--------

     Правилу Pack  требуется  для  хранения  временного  рабочего
файла иметь  к моменту начала упаковки как минимум PackedFileSize
байтов дисковой  памяти.  Правило   FileNeedsPacking   сравнивает
PackedFileSize с  фактическим  размером файла,  чтобы определить,
нужна ли упаковка.

Пример
------

     if Lib.FileNeedsPacking then
       if DiskFree(0) < Lib.PackedFileSize then
         WriteLn('Для упаковки не хватит дисковой памяти')
       else
         Lib.Pack;

     Выполнит упаковку библиотеки,  если упаковка нужна, и нужный
размер дисковой памяти в текущий момент доступен.  В этом примере
предполагается, что  файл  находится на текущем дисководе,  а для
определения текущего   доступного   размера    дисковой    памяти
используется правило DiskFree модуля DOS.

См. также
---------

     Pack


PutEntry
----------------------------------------------------------------
Объявление
----------

     procedure PutEntry(Name : DirEntryName; var R : Root);

Назначение
----------

     Вызывает полиморфное правило Store для записи объекта R.

Описание
--------

     PutEntry создает новый элемент каталога  с  заданным  именем
или обновляет  существующий  элемент с этим именем.  Заданное имя
Name не может быть пустой строкой.  Просмотр каталога  происходит
без учета регистра; соответственно с этим, имя должно быть задано
уникальным образом.

     PutEntry сначала  ищет  доступный  элемент  каталога.   Если
элемент, соответствующий  Name,  не  найден,  а  в  каталоге  нет
неиспользованных позиций, PutEntry генерирует ошибку (epNonFatal+
ecDirectoryFull) и   выполняет   выход.   Затем  она  ищет  конец
библиотечного файла и вызывает правило IdStream.Put для  записи в
конец файла    образа    объекта   R.   Если   тип   объекта   не
зарегистрирован, либо   при   ошибке   дисковой   операции,   Put
завершается неудачно.   И  наконец,  PutEntry  обновляет  элемент
каталога именем, типом, позицией и длиной дописанного объекта.

     Если PutEntry используется для обновления  уже существующего
элемента, она   оставляет  в  библиотечном  файле  неиспользуемую
область, от которой можно избавиться при помощи Pack.

Пример
------

     Init(Name);
     Lib.PutEntry('NAME1', Name);
     Status := Lib.GetStatus;
     if Status <> 0 then
       WriteLn('Не удалось добавить имя NAME1,
                Состояние = ', Status);

     Создает экземпляр объекта Name и записывает  этот  экземпляр
(вызывая с  NAME1)  в  библиотеку.  Тем  самым  создается элемент
библиотеки, и объект записывается на диск. Как и для любых других
потоков, записываемый  объект  (Name) должен являться потомком от
общего предка Root.

     New(NamePtr, Init);
     Lib.PutEntry('NAME1', NamePtr^);

     Записывает в библиотеку динамический экземпляр объекта.

См. также
---------

     GetEntry                   IdStream.Put


RenameEntry
----------------------------------------------------------------
Объявление
----------

procedure RenameEntry(oldName, NewName : DirEntryName); virtual;

Назначение
----------

     Переименование заданного элемента.

Описание
--------

     Элемент каталога  с  именем  OldName   переименовывается   в
NewName. Если  NewName совпадает с другим именем,  существующим в
каталоге, либо если OldName не найдено, генерируется ошибка.

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

Пример
------

     Lib.RenameEntry('NAME1', 'NAME99');

     Переименовывает элемент NAME1 в NAME99.


UndeleteEntry
----------------------------------------------------------------
Объявление
----------

     procedure UndeleteEntry(Name : DirEntryName); virtual;

Назначение
----------

     Убирает из заданного элементу отметку его как удаленного.

Описание
--------

     UndeleteEntry используется,    чтобы    отменить    действие
DeleteEntry. Однако,  это  возможно только если после DeleteEntry
не вызывалось правило DeleteEntry.  Закрытие и повторное открытие
библиотеки (без  выполнения  упаковки)  не  влияет на возможность
восстановления удаленного элемента.

     Отмена удаления   для   элемента,   удаления   которого   не
происходило, не является ошибкой.

     Для необратимого удаления элемента следует использовать либо
KillEntry, либо вызвать DeleteEntry и  затем  Pack  для  упаковки
библиотеки.

Пример
------

     Lib.UndeleteEntry('NAME1');
     Status := Lib.GetStatus;
     if Status <> 0 then
       WriteLn(;Элемент не найден, Состояние = ', Status);

     Отменяет удаление элемента NAME1.

См. также
---------

     DeleteEntry


MemLibrary
----------------------------------------------------------------

GetEntry
----------------------------------------------------------------
Объявление
----------

     procedure GetEntry(Name : DirEntryName; var R : Root);

Назначение
----------

     Вызывает полиморфный    конструктор    Load    для    чтения
статического объекта R.

Описание
--------

     Работает аналогично Library.GetEntry,  за исключением  того,
что доступ к диску здесь не происходит:  объект просто передается
из одного адреса оперативной памяти в другой.

     Отметим, что объект MemLibrary не  имеет  правила  PutEntry.
Предполагается, что  сначала  MemLibrary  создается  как дисковый
файл, а затем преобразуется в компонуемый  образ  памяти  "только
для чтения".  Более  подробную информацию см.  в описании правила
Init.


GetEntryPtr
----------------------------------------------------------------
Объявление
----------

     function GetEntryPtr(Name : DirEntryName) : RootPtr;

Назначение
----------

     Вызывает полиморфный  конструктор  Load для выделения памяти
"кучи" и чтения объекта.

Описание
--------

     Работает аналогично   Library.GetEntryPtr,   за  исключением
того,  что доступ к диску здесь не происходит: для нового объекта
выделяется   область   памяти   "кучи",  инициализируемая  прямым
копированием из адреса памяти библиотеки.



Init
----------------------------------------------------------------
Объявление
----------

     constructor Init(BasePtr : Pointer; Size : Word);

Назначение
----------

     Открывает существующую библиотеку в памяти.

Описание
--------

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

     Затем утилита Borland BINOBJ преобразует библиотечный файл в
компонуемый файл .OBJ.  Если предположить,  что библиотечный файл
называется NAMEDATA.OPL,  то  по  приглашению  DOS следует ввести
команду:

     BINOBJ NAMEDATA.OPL NAMEDATA.OBJ NAMELIB

     Последний параметр задает символическое  имя  (NAMELIB),  по
которому прикладная  программа  сможет  обращаться  к содержимому
.OBJ-файла.

     .OBJ-файл компонуется  с  прикладной  программой   следующим
образом:

     const
       NameLibSize = 5000;   {д.б. >= фактического размера}
     var
       Lib :MenLibrary;

       {$L NAMEDATA.OBJ}
       procedure NameLib; external;

     begin
       {Открыть прикомпонованную библиотеку}
       if not Lib.Init(@NameLib, NameLibSize) then begin
         WriteLn('Не удалось открыть библиотеку,
                  Состояние = ', InitStatus);
         Halt;
       end;
       ...
     end.

     Переданный в  Init  параметр  Size  должен  быть  не  меньше
фактического размера исходного .OPL-файла библиотеки.  Если  этот
параметр слишком  мал,  операции чтения MemLibrary при превышении
заданного размера завершатся неудачно.

     Процедура NameLib в действительности не содержит выполняемый
код; она  просто  обеспечивает  способ  обращения к адресу данных
NAMEDATA.OBJ, прикомпонованных к программе  посредством директивы
компилятора $L.

     Мы передаем  адрес  NameLib  конструктору  Init  при  помощи
операции Turbo Pascal @.

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

     Когда прикладная программа заканчивает работу  с MemLibrary,
она должна вызвать правило Done, чтобы обеспечить освобождение из
памяти потока IdStream.

См. также
---------

     IdStream.RegisterHier


8.  Обработка ошибок

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

     Коды ошибок хранятся в  переменных  типа  Word.  Каждый  код
вычисляется как  сумма  двух факторов:  префикса ошибки,  который
всегда кратен 10000,  и  номера  ошибки,  который  всегда  меньше
10000. "Тип" ошибки определяется как префикс ошибки,  деленный на
10000. Следовательно,  по коду ошибки можно определить его тип  и
номер, применив следующие операторы:

     ErrorType    :=  Code div 10000;
     ErrorNumber  :=  Code mod 10000;

     либо, поместив Code обратно,

     Code  :=  (10000 * ErrorType) + ErrorNumber;

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

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

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

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

     И наконец,  категория "не-ошибки" резервируется и  означает,
что никакой   ошибки   не   произошло.  Object  Professional  эту
категорию не использует.

     Вторая часть кода ошибки,  а именно номер  ошибки,  содержит
более детальную информацию об ошибке. Младшие номера ошибки, от 2
(файл не найден) до 162 (общий сбой),  совпадают с кодами времени
выполнения Turbo Pascal. Остальные сгруппированы блоками по 1000,
где каждый блок относится к своей категории ошибки.

     0000-0999   Критические ошибки DOS, Turbo Pascal.
     1000-1999   Прочие ошибки ввода/вывода;
                 включая потоко-ориентированные ошибки.
     2000-2999   Ошибки емкости памяти или операционной среды.
     7000-7999   Предупреждения, ошибки пользователя.
     8000-8999   Программные ошибки.
     9000-9999   Сообщения

     Подробный листинг  всех  этих  кодов  ошибок  приводится   в
Приложении D.

     Для наиболее  распространенных  ошибок  и  сообщений  OPROOT
обеспечивает описательную строку,  которая может быть выведена на
дисплей соответствующим   обработчиком  ошибки.  Все  эти  строки
объявлены в  файле  OPROOT.IN1.  Если  вам  требуется   перевести
прикладную программу   на  другой  язык,  почти  все  нужные  вам
изменения находятся в этом файле.  Вы увидите,  что каждой строке
предшествует свое   собственное   ключевое   слово   CONST.   Для
компоновшика Turbo Pascal это означает сигнал удалить все строки,
на которые фактически не ссылается программа.

     Существует три   способа  информировать  программу  о  кодах
ошибок, которые  могут  встречаться   в   объекто-ориентированных
прикладных программах:

     - Ошибки конструктора, сообщаемые через InitStatus.
     - Ошибки,   возвращаемые    правилами-функциями,    например
       CommandWindow.GetLastError.
     - Ошибки,  о  которых   сообщают   обработчики,   написанные
       пользователем.

     OPROOT объявляет     глобальную    типированную    константу
InitStatus типа Word.  При неудачном завершении  конструктора  он
возвращает False  или  указатель  nil,  в  зависимости  от  того,
является ли  создаваемый  экземпляр   объекта   статическим   или
динамическим. Если    ваша    программа    обнаружила   неудачное
завершение, она немедленно должна проверить  значение InitStatus,
куда конструктор поместит стандартный код ошибки,  указывающий на
причину неудачного завершения.  Если никаких ошибок не произошло,
InitStatus будет содержать ноль.

     Большинство объектов  Object Professional имеет поле данных,
в котором хранится последний код  ошибки,  а  также  правило  для
доступа к   этому  коду.  Конкретная  их  реализация  зависит  от
объекта. Во всех случаях вы  можете  проверить  код  ошибки,  что
обеспечит успешный  вызов  остальных  правил.  Подробности см.  в
описаниях следующих правил, связанных с кодами ошибки:

     OPROOT
       IdStream.GetStatus
       PointerStack.GetStatus
       StaticQueue.GetStatus
       LargeBitSet.GetStatus
       StringDict.GetStatus

     OPLARRAY
       AbstractArray.ErrorA

     OPCLONE
       Cloner.GetLastError

     OPCMD
       CommandProcessor.GetLastError

     OPEDIT
       LineEditor.GetLastError

     OPFRAME
       Frame.GetLastError

     OPWINDOW
       RawWindow.GetLastError
       CommandWindow.GetLastError

     OPRNLOW
       BasePrinter.PrnError

     OPFORM
       PrintedForm.fGetLastError
       Report.rGetLastError

     Отметим, что  все  объекты  от  CommandWindow  наследуют его
правило GetLastError.

     И наконец,  многие объекты позволяют установку  обработчиков
ошибок, написанных  пользователем:  это подпрограммы,  вызываемые
объектами при обнаружении ошибок.  При установке такой  программы
вы избегаете   необходимости   вызывать   функцию   GetLastError,
поскольку все ее функции сохраняются.

     OPROOT объявляет   процедуру   типа    ErrorProc,    которая
определяет параметры,    переданные    подпрограмме,   написанной
пользователем. Эта  особенность  более  подробно  рассмотрена   в
разделе описания   OPWINDOW  CommandWindow.  Процедура  обработки
ошибки по  умолчанию,  DefaultErrorProc,  определяется  в  модуле
OPCMD. Также см.  следующие правила для обработки ошибок в других
модулях:

     OPLARRAY
       AbstractArray.SetErrorProc

     OPEDIT
       LineEditor.SetErrorProc

     OPWINDOW
       CommandWindow.SetErrorProc

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

     Многие из объектов  включают  в  себя  виртуальное  правило,
которое они  могут  вызывать  для  сообщения  об  ошибке  при  ее
обнаружении. Из каждого такого объекта вы можете произвести новый
объектный тип, переопределяющий данное виртуальное правило, чтобы
обеспечить другое  поведение  объекта   при   обработке   ошибки.
Объекты, показанные  выше  с  правилами GetStatus и GetLastError,
имеют такие виртуальные  правила,  обычно  называемые  Error  или
GotError:

Константы
---------

     ucNone           = 00; {без модуля}
     ucEdit           = 01; {OPEDIT}
     ucPick           = 02; {OPPICK}
     ucDir            = 03; {OPDIR}
     ucMenu           = 04; {OPMENU}
     ucSelect         = 05; {OPSELECT}
     ucEntry          = 06; {OPENTRY}
     ucMemo           = 07; {OPMEMO}
     ucEditor         = 08; {OPEDITOR}
     ucBrowse         = 09; {OPBROWSE}
     ucLArray         = 10; {OPLARRAY}
     ucMacEd          = 11; {OPMACED}
     ucHelp           = 12; {OPHELP}
     ucSEdit          = 13; {OPSEDIT}
     ucCalc           = 25; {OPCALC}
     ucQkRef          = 26; {OPQKREF}
     ucCal            = 27; {OPCAL}
     ucReservedForTp  = 99;

     Уникальные номера   кодов   для   каждого  модуля  в  Object
Professional, поддерживающего стандартный обработчик  ошибки  или
справочного запроса. Такой код передается в обработчик в качестве
параметра, что позволяет программе обнаруживать,  в  какой  точке
произошла ошибка или откуда пришел справочный запрос. Turbo Power
резервирует коды 0...99 для  своего  собственного  использования.
Для программ   доступен   диапазон   0...255.   Более   подробную
информацию см. в описании типа ErrorProc в данном разделе.

     etFatal     = 0; {фатальные ошибки}
     etNonFatal  = 1; {не-фатальные ошибки ввода/вывода}
     etWarning   = 2; {предупреждения, ошибки ввода пользователя и
                       т.д.}
     etMessage   = 3; {простые сообщения}
     etNoError   = 4; {нет ошибки}

     Коды типов ошибки для классификации ошибок.

     epFatal     = etFatal * 10000;
     epNonFatal  = etNonFatal * 10000;
     epWarning   = etWarning * 10000;
     epMessage   = etMessage * 10000;

     Префиксы ошибок, прибавляемые к номерам ошибок перед выводом
сообщений о них.

     ecFileNotFound  = 00002; {Файл не найден}
     ...
     mcReplaceYesNo  = 09012; {Приглашение подтвердить замену}

     Номера ошибок. Полный листинг см. в приложении D.

     emNoMoreFiles : string[17] = 'No matching files'
                                  ("Нет соответствующих файлов")
     ...
     emDiagNotAllowed : string[26] = 'Diagonal lies not allowed';
                              ("диагональные линии недопустимы")

     Строки с  сообщениями  об  ошибке,  передаваемые  процедурам
обработки ошибок,  написанным пользователем,  типа ErrorProc.  Не
все номера  ошибок  имеют  связанные с ними строки сообщений,  но
большинство все же имеет такие строки.  Полный список  всех  этих
строк см. в OPROOT.IN1.

     InitStatus : Word = 0;

     Глобальная переменная,   используемая  всеми  конструкторами
Object Professional для хранения  кода  состояния  при  неудачном
завершении конструктора.   Сам  конструктор  возвращает  значение
False или   указатель   nil   (для   динамических   экземпляров),
обозначающие неудачное завершение.  Если это произошло, программа
может проверить InitStatus для получение подробной  информации  о
причине неудачного завершения.  Значения,  хранимые в InitStatus,
используют описанные  здесь  стандартные   коды   ошибок   Object
Professional.

Типы
----

     ErrorProc = procedure (UnitCode : Byte; var ErrorCode : Word;
                            ErrorMsg : string);

     Тип процедуры,  определяющий  стандартный мехпнизм сообщения
об ошибке для многих объектов Object Professional. Список модулей
см. выше,  где приведены коды модулей, а полный список ошибок см.
в Приложении D.

C. OPLARRAY: Объекто-ориентированные большие массивы

     Хотя Turbo  Pascal  и  обеспечивает  возможность  работы   с
моделями памяти large,  при которых через "кучу" доступно до 640К
оперативной памяти DOS, отдельные элементы данных тем не менее не
могут превышать  по величине 64Кбайт.  Object Professional решает
эту проблему при помощи модуля OPLARRAY.  Большие массивы  Object
Professional могут   быть  одно-  или  двумерными,  а  элементами
массива могут являться любые типы данных Turbo Pascal.

     Модуль больших  массивов  позволяет   создание   виртуальных
массивов любого   размера.  Массивы  могут  храниться  в  обычной
оперативной памяти,  расширенной   памяти   Lotus/Intel/Microsoft
(EMS), либо  на  диске,  с виртуальным постраничным отображением.
Большие массивы нормальной оперативной памяти хранятся  в  "куче"
Паскаля, и  в  результате  ограничены размерами доступной области
"кучи". Большие массивы,  хранимые в  EMS,  ограничены  размерами
доступной расширенной  Большие массивы,  постранично отображаемые
на диске,  ограничены  в  основном  размером  доступной  дисковой
памяти и   максивальным   размером   файла,   принятым  в  данной
операционной системе (для дос это обычно 32 мегабайта).

     OPLARRAY определяет   для   организации   больших   массивов
несколько типов объектов, организованных в следующей иерархии:

     AbstractArray
       RamArray
         RamRCArray
       EmsArray
         EmsRcArray
       VirtualArray
         PrimitiveArray
         VirtualRCArray
       OpArray

     AbstractArray служит  для определения правил, обеспечиваемых
затем для всех типов  больших  массивов:  как  записать  элемент,
обратиться к  нему,  и  т.д.  RamArray,  EmsArray  и VirtualArray
реализуют механизм хранения массивов в "куче",  EMS-памяти  и  на
диске, соответственно.  RamRCArray,  EmsRCArray  и VirtualRCArray
добавляют сюда возможности контроля диапазона,  что  полезно  при
разработке прокладных  программ  с большими массивами.  Благодаря
созданию отдельных    объектов     для     контроля     диапазона
предназначенные для   промышленной   эксплуатации   версии   этих
объектов позволяют   избежать   любых   потерь    быстродействия.
PrimitiveArray представляет   собой  разновидность  VirtualArray,
позволяющуя задавать конкретные свойства  подкачки  страниц,  что
позволяет существенно    улучшить    быстродействие    прикладных
программ, имеющих хорошо определенные шаблоны доступа к массивам.
OpArray (сокращение от Optimized Array - оптимизированный массив)
может хранить массив с использованием  любого  механизма  памяти:
RAM ("куча"),  EMS  (расширенная  память)  или  дисковая память с
подкачкой страниц.  Этот объект выбирает лучший способ  во  время
выполнения, в  зависимости от задаваемого вами приоритета поиска.
Таким образом,  программа  может  оптимизировать   быстродействие
доступа к большим массивам,  не зная заранее,  какой из системных
ресурсов будет доступен в момент выполнения.

     За исключением конструкторов,  все объекты больших  массивов
реализуют одинаковые   правила.   В   результате   раздел  правил
настоящей главы описывает все объекты сразу, указывая на различия
только при необходимости.

     Большие массивы,  реализованные в модуле OPLARRAY, полностью
являются динамическими - т.е.,  размеры  и  типы  данных  массива
могут быть заданы не в момент компиляции,  а во время выполнения.
В результате одна  копия  кода  для  большого  массива  позволяет
организовать множество  массивов  данных  с  различными  типами в
одной и той же программе.  Недостатком данного  подхода  является
то, что  модуль  большого  массива  не  может определять функцию,
возвращающую значение элемента массива, поскольку тип элемента до
момента выполнения   неизвестен.   Вместо   этого  каждый  объект
обеспечивает правило,  возвращающее  элементы  массива   в   виде
нетипированных параметров-переменных.  Ваша  прикладная программа
легко может определить функцию-оболочку, вызывающую это правило и
возвращающую уже результат правильного типа.  Фактически это дает
идеальную возможность   воспользоваться    преимуществами    OOP:
произвести новый  тип объекта с конструктором, специализированным
для вашего конкретного типа  массива,  своим  правилом-процедурой
для присвоения значений элементам массива и правилом-функцией для
доступа к ним.

     Массивы, организуемые  модулями  больших   массивов   Object
Professional, индексированы от нуля: минимальный индекс по каждой
оси массива всегда равен нулю.  При выделении нового  массива  вы
должны задать общее число строк и столбцов массива. При доступе к
массиву старший  индекс  массива  на  единицу  меньшефактического
числа строк или столбцов.

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


program OpArrayExample;  {EXLARRAY.PAS}
uses
  OpRoot, OpLarray;
type
  Str20 = String[20];
  Str20Array =
    object(OpArray)
      constructor Init(Rows : Word; FileName : String; HeapToUse : LongInt);
      procedure SetVal(Row : Word; Val : Str20);
      function GetVal(Row : Word) : Str20;
    end;
constructor Str20Array.Init(Rows : Word; FileName : String;
                            HeapToUse : LongInt);
const
  Priority : AutoPriority = (lRamArray, lEmsArray, lVirtualArray);
var
  ClearEl : Str20;
begin
  if not OpArray.Init(Rows, 1,          {Строки, столбцы}
                      SizeOf(Str20),    {Размер одного элемента}
                      FileName,         {Файл виртуального массива}
                      HeapToUse,        {Используемая область "кучи"}
                      0,                {Опции массива}
                      Priority)         {Приоритет выделения памяти}
  then
     Fail;
  {Быстрая очистка массива}
  ClearEl := '';
  ClearA(ClearEl, FastInit);
end;
procedure Str20Array.SetVal(Row : Word; Val : Str20);
begin
  SetA(Row, 0, Val);
end;
function Str20Array.GetVal(Row : Word) : Str20;
var
  S : Str20;
begin
  RetA(Row, 0, S);
  GetVal := S;
end;
var
  OA : Str20Array;
begin
  if not OA.Init(1000,               {Строки}
                 'TEMP.ARR',         {Файл виртуального массива}
                 MaxAvail div 2)     {Используемая область "кучи"}
  then begin
    WriteLn('Не удалось инициализировать Str20Array.
             Состояние = ', InitStatus);
    Halt;
  end;
  case OA.TypeOfArray of
    lRamArray : WriteLn('Массив в RAM');
    lEmsArray : WriteLn('Массив в EMS');
    lVirtualArray : WriteLn('Виртуальный массив');
  end;
  {Установить первый элемент массива}
  OA.SetVal(0, 'Object');
  {Вывести элемент}
  Writeln('OA[0] = ', OA.GetVal(0));
  {Освободить массив}
  OA.Done;
end.

     Данный пример демонстрирует объекто-ориентированный подход к
созданию больших массивов конкретного типа.  В нем объявлен новый
тип объекта,  Str20Array,  производный  от  OpArray.   Str20Array
наследует все   поведение  OpArray,  включая  возможность  выбора
размещения массива в памяти в зависимости от  списка приоритетов.
Для Str20Array   требуется  реализация  всего  трех  правил.  Его
конструктор скрывает   некоторые   частности   работы    OpArray,
поскольку уже  известно,  что  массив одномерный,  имеет элементы
типа Str20 и использует заданный список  приоритетов. Конструктор
также очищает   массив   -  шаг,  не  выполняемый  конструкторами
OPLARRAY, так как для многих  прикладных  программ  это  было  бы
потерей времени. Правило Str20Array SetVal использует только один
индекс массива Row (строка)  и  значение  параметра  типа  Str20.
Метод же  GetVal  может  являться  функцией,  поскольку он всегда
возвращает значение типа Str20.

     В главном  блоке  реалзуется  массив  Str20Array   из   1000
элементов, массив   записывается  в  действительный  тип  памяти,
выбранный для массива,  далее в  его  первый  элемент  помещается
значение, и   потом   снова   извлекается   оттуда.   Вы   можете
поэкспериментировать с   этим   массивом,   по-разному    задавая
приоритеты в константе Priority.

Быстродействие при работе с большими массивами
----------------------------------------------

     При использовании архитектуры  семейства  Intel  8086  типам
данных, превышающим  64Кбайт,  присуще  большее  время доступа по
сравнению с  небольшими  объектами.   Кроме   того,   динамически
размещаемые структуры  также  имеют  большее  время доступа,  чем
статические адресуемые структуры.  Для каждого доступа к элементу
массива подпрограммы   Object   Professional   должны   вычислить
страницу (сегмент) и смещение в странице,  по которому фактически
расположены данные.   Для   максимально  быстрого  доступа  такие
вычисления пишутся на языке ассемблера. Виртуальные и EMS-массивы
требуют дополнительных  операций,  которые  должны  гарантировать
загрузку желаемой страницы в обычную оперативную память.

     В следующей  таблице   приводятся   контрольные   результаты
тестирования быстродействия  операций  чтения  и записи элементов
для каждой модели массива. Для сравнения в таблицу включены также
результаты для  небольшого  массива  Turbo Pascal 5.5.  Измерения
выполнялись в системе 80286 с тактовой частотой 8МГц. Контрольная
программа использовала   статический   экземпляр   объекта   типа
OpArray, аналогичный   показанному    в    примере.    (Благодаря
эффективному механизму   Turbo   Pascal   5.5   -   VMT  (таблицы
виртуальных правил) для организации виртуальных вызовов  правил -
объект OpArray   имеет   потери  быстродействия,  не  превышающие
аналогичных величин для других объектов типа  большого  массива).
Результаты приводятся  в  микросекундах,  идущих  на  чтение  или
запись одного элемента массива заданного типа.


     Элемент        Turbo     RAM     Виртуальный   EMS
     --------------------------------------------------
     Byte             8        86          123      133
     Word             8        85          123      133
     Real            16        91          127      140
     String[80]      96       150          186      229

     В результаты для виртуальной модели не включают в себя время
на подкачку  с  диска,  а результаты для EMS не включают время на
подкачку страничных окон памяти.  Аремя обращения к  диску  очень
существенно зависит  от  аппаратного  обеспечения  и  организации
страниц массива.  Подкачка страниц из EMS-памяти обычно  занимает
100-150 ьикросекунд.

     Тот, кто   знаком   с   модулями   больших   массивов  Turbo
Professional, может заметить,  что характеристики модуля OPLARRAY
немного лучше,  чем  характеристики модулей TPRARRAY,  TPVARRAY и
TPEARRAY.

     Turbo Pascal  позволяет  объявить   объект   либо   в   виде
динамического экземпляра  (данные  объекта  хранятся  в  "куче" и
доступны через указатель),  либо в виде  статического  экземпляра
(данные объекта   находятся   в   сегменте  данных).  Фактическое
количество данных,  хранимое в сегменте данных  для  статического
объекта типа  большого массива сравнительно невелико (поскольку в
любом случаем основная часть  данных  хранится  в  "куче"  или  в
EMS-памяти), поэтому   выигрыш   от   объявления  таких  объектов
динамическими невелик. Фактически же лишний уровень обращения  по
ссылке, вносимый необходимостью снятия косвенности для переменной
-указателя на    динамический    экземпляр    объекта    ухудшает
характеристики быстродействия.

Файлы, хранящие большие объекты
-------------------------------

     Все типы больших объектов могут записывать и снова загружать
данные с  диска.  Файлы  массивов,  записанные  объектами  разных
типов, совместимы  между  собой.   Например,   файл,   записанный
объектом RamArray,  может  быть  прочитан  объектом VirtualArray.
Массивы считываются при вызове конструктора LoadA  и записываются
вызовом StoreA.

     Конструктор LoadA для объектов больших массивов может читать
файлы, созданные процедурой StoreA модуля TPVARRAY или процедурой
StoreVA из модуля TPRARRAY в Turbo Professional 4.0 или 5.0.

Приоритеты массивов
-------------------

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

     Список приоритетов -  это  массив  из  трех  элементов  типа
ArrayType. ArrayType   -   это  перечислимый  тип,  для  которого
определены следующие значения: lNoArray, lRamArray, lEmsArray или
lVirtualArray. OpArray  игнорирует  позиции  списка  со значением
lNoArray. При   помощи   lNoArray   массивы   можно    ограничить
использованием только  двух  или  трех  возможных  мест хранения.
Рекомендуемое использование   lNoArray   показано   в   следующем
примере:

const
  SmallHeapPriorities :  AutoPriority  =  (lRamArray,  lEmsArray, lNoArray);
var LargeArray : OpArray;
begin
  if not LargeArray.Init(MaxRows, MaxCols, ElementSize,
                         ArrayFileName, HeapToUse, 0,
                         SmallHeapPriorities) then begin
    WriteLn('Не удалось инициализировать OpArray,
             Состояние = ', InitStatus);
    Halt;
  end;
  ...

     SmallHeapPriorities задает,  что  сначала  нужно  попытаться
разместить массив в RAM,  а затем в EMS.  Если ни там, ни там нет
достаточно места,  конструктор завершится  неудачно,  не  пытаясь
даже проверить   наличие   дисковой   памяти,  так  как  заведомо
известно, что  для  данной  прикладной  программы  она   работает
слишком медленно.

     OpArray имеет два конструктора:  Init и LoadA.  Init создает
новый массив. LoadA загружает существующий массив из файла. Как и
Init, LoadA   просматривает  список  приоритетов,  чтобы  принять
решение, куда загрузить массив.

     Единственный недостаток  OpArray  состоит  в  том,  что   он
заставляет компилятор   прикомпоновывать   коды   для  всех  трех
механизмов хранения: RAM, EMS и для диска. Поскольку используемый
механизм становится   известен   только   во   время  выполнения,
компилятору ничего другого не остается.  OpArray ясно  показывает
достоинства и  недостатки механизма позднего установления связей,
принятого в OOP.

Обработка ошибок
----------------

     ErrorA представляет  собой  правило,  возвращающее состояние
последней операции с массивом. Вызов ErrorA возвращает код ошибки
типа Word и сбрасывает внутреннюю переменную состояния в 0. Коды,
возвращаемые ErrorA, совместимы с используемыми в остальной части
Object Professional; подробности см. в Приложении D.

     В качестве    альтернативы,   прикладные   программы   могут
использовать предоставляемую  OPLARRAY  стандартную  ловушку  для
обработки ошибок.  Для этого прикладная программа должна передать
правилу OPLARRAY   SetErrorProc    процедуру    типа    ErrorProc
(определенную в  OPROOT).  Далее в случае ошибки OPLARRAY вызовет
заданную процедуру для того,  чтобы  сообщить  об  ошибке.  Более
подробную информацию см. в описании SetErrorProc.

Проверка диапазона
------------------

     Подобно тому,  как Turbo Pascal  может  отлавливать  попытки
недопустимого доступа  к  элементам  обычных  массивов,  OPLARRAY
может выполнять такого рода проверки  для  всех  моделей  больших
массивов. Если  проверка  диапазона в Turbo Pascal активизируется
посредством опции  компилятора  ({$R+}),  проверка  диапазона   в
OPLARRAY включается   при   объявлении   массивов   как   имеющих
специальный производный тип объекта:  RamRCArray,  EmsRCArray или
VirtualRCArray. Когда  правилу любому из этих объектов передается
неверный индекс,  это правило записывает  код  ошибки,  доступный
посредством ErrorA,   и   вызывает   определяемый   пользователем
обработчик ошибки,  если таковой существует. Запрощенная операция
с массивом в этом случае не выполняется.

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

     Проверка диапазона  для  объектов   OpArray   активизируется
иначе, нежели  для  остальных  типов  объектов.  В  этом случае к
параметру ArrayOptions,   передаваемому   конструктору   объекта,
прибавляется значение lRangeCheck.

Распространенные ошибки
-----------------------

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

Константы
---------

     DefaultPriority :  AutoPriority  =  (lRamArray,   lEMSArray,
lVirtualArray);

     Эта константа   используется   при   инициализации   объекта
OpArray, для которого обеспечивались бы  наилучшие характеристики
производительности для доступных системных ресурсов.

     DefBeepOnError : Boolean = True;

     Определяет, будут  ли встроенные обработчики ошибки издавать
звуковой сигнал в случае ошибки.  Влияет как на обработчик ошибки
по умолчанию, так и на DisplayErrorProc.

     IdString : string[6] = 'LARRAY';

     Строка идентификации,  записываемая  в начало файлов больших
массивов, создаваемых OPLARRAY.

     lKeepDiskCurrent = $0001;
     lDeleteFile      = $0002;
     lRangeCheck      = $0004;

     Это опции,  влияющие на поведения объекта большого  массива.
Еслм lKeepDiskCurrent  задана,  то файл,  связанный с виртуальным
массивом, будет    обновляться     при     каждой     модификации
соответствующего массива.   Это  сильно  ухудшает  быстродействие
программы, но предотвращает потерю данных при  сбое  питания  или
программы. При  установленной опции lDeleteFile,  деструктор Done
виртуального массива  удаляет  дисковый  файл   или   освобождает
область "кучи",  связанные  с  объектом.  lRangeCheck определяет,
будет ли выполняться проверка диапазона при обращениях  к массиву
объектного типа OpArray.  Для всех прочх объектных типов OPLARRAY
этот флаг игнорируется.

     MaxHeapBlock = 65519;

     Представляет максимальный  организуемый  для  массива   блок
памяти "кучи", минус единица.

     VArrayID  : String[6] = 'VARRAY';

     Строка идентификации,  записываемая  в начало файлов больших
массивов, создаваемых TPVARRAY (из Turbo Professional).

Типы
----

     AbstractArrayPtr = ^AbstractArray;
     AbstractArray =
       object(Root)
         ...
       end;

     Тип абстрактного большого массива,  производными от которого
являются все прочие большие массивы.

     ArrayType  = (lNoArray, lRamArray, lEMSArray, lVirtualArray);

     Перечисляет категории   больших   массивов.   lNoArray   это
специальное значение,   описанное   выше   в  пункте  "Приоритеты
массивов".

     AutoPriority = Array[1..3] of ArrayType;

     Определяет список приоритетов для объекта типа  OpArray. См.
обсуждение "Приоритетов массивов" выше.

     EmsArrayPtr = ^EmsArray;
     EmsArray =
       object(AbstractArray)
         ...
       end;

     Объект типа EmsArray,  хранящий большой массив в расширенной
памяти Lotus/Intel/Microsoft (EMS).

     EmsRCArrayPtr = ^EmsRCArray;
     EmsRCArray =
       object(EmsArray)
         ...
       end;

     Объект EmsRCArray,  реализующий   контроль   диапазона   при
индексации EmsArray.

     InitType  = (ExactInit, FastInit);

     Определяет опции   инициализации   массивов.   При  передаче
ExactInit в ClearA  это  правило  инициализирует  каждый  элемент
массива точным значением соответствующего типа. Передача FastInit
заставляет ClearA установить каждый байт массива  равным  первому
байту инициализирующего значения.  Как становится ясным из самого
имени, вторая  опция  выполняет  инициализацию  гораздо  быстрее.
FastInit полезна  для инициализации числовых массивов нулем,  или
строковых массивов пустыми строками.

     OpArrayPtr = ^OpArray;
     OpArray =
       object(AbstractArray)
         ...
       end;

     Объект OpArray, который адаптирует свое поведение аналогично
одному из    остальных    объектных    типов    (за   исключением
PrimitiveArray), в зависимости от доступных системных ресурсов.

     PrimitiveArrayPtr = ^PrimitiveArray;
     PrimitiveArray =
       object(VirtualArray)
         ...
       end;

     Объект PrimitiveArray,  работающий аналогично  VirtualArray,
но обеспечивающий   полный  контроль  над  внутренней  страничной
структурой массива.

     RamArrayPtr = ^RamArray;
     RamArray =
       object(AbstractArray)
         ...
       end;

     Объект RamArray,  хранящий большой массив целиком  в  "куче"
TurboPascal.

     RamRCArrayPtr = ^RamRCArray;
     RamRCArray =
       object(RamArray)
         ...
       end;

     Объект RamRCArray,   реализующий   проверку   диапазона  для
индексов RamArray.

     VirtualArrayPtr = ^VirtualArray;
     VirtualArray =
       object(AbstractArray)
         ...
       end;

     Объект VirtualArray, который выполняет по мере необходимости
подкачку страниц  памяти большого массива между дисковым файлом и
буфером "кучи" Паскаля.

     VirtualRCArrayPtr = ^VirtualRCArray;
     VirtualRCArray =
       object(VirtualArray)
         ...
       end;

     Объект VirtualRCArray,   реализующий   проверку    диапазона
индексов VirtualArray.

Переменные
----------

     EmsAvailable : Boolean;

     Устанавливается кодом инициализации OPLARRAY  в  True,  если
память EMS обнаружена в системе.





Объекты - массивы
----------------------------------------------------------------

ArrayDimensions
----------------------------------------------------------------
Объявление
----------

     procedure ArrayDimensions(var Rows, Cols : Word);

Назначение
----------

     Возвращает размеры большого массива.

Описание
--------

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

Пример
------

     RA.ArrayDimensions(MaxRows, MaxCols);
     WriteLn('Размеры массива: ', MaxRows, ' строк на ',
              MaxCols, ' столбцов');

     Выводит на дисплей фактические размеры массива RA.

См. также
---------

     LoadA


ClearA
----------------------------------------------------------------
Объявление
----------

     procedure ClearA(var Value; Initialize : InitType); virtual;

Назначение
----------

     Инициализирует все элементы массива значением Value.

Описание
--------


     Поскольку Value  представляет собой параметр-переменную,  вы
должны присвоить  этой   переменной   инициализирующее   значение
соответствующего типа  и  затем  передать  ее в ClearA.  Параметр
Initialize может  быть  равен  либо  константе  ExactInit,   либо
FastInit. При    использовании    константы    FastInit    ClearA
инициализирует весь массив первым байтом Value. При использовании
ExactInit ClearA   присвоит   каждому   элементу  массива  точное
значение Value.

Пример
------

     MyElement := 3.14159265;  {приближенное значение числа Пи}
     RA.ClearA(MyElement, ExactInit);

     Инициализирует каждый элемент массива RA числом Пи.


Done
----------------------------------------------------------------
Объявление
----------

     destructor Done; virtual;

Назначение
----------

     Освобождает массив из памяти.

Описание
--------

     Освобождается вся область "кучи", выделенная массиву.

     Если массив  имеет  тип  EmsArray  или  OpArray   в   режиме
EmsArray, то  освобождается  вся память EMS,  выделенная массиву.
Прежде чем выполнить для  программы  команду  halt,  не  забудьте
вызвать Done для всех расположенных в EMS массивов, поскольку DOS
не освобождает EMS-память автоматически.

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

Пример
------

     RA.Done;

     В этом  примере  показан  статический   объект,   вызывающий
деструктор Done.  Предполагая,  что  RA это RamArray,  происходит
освобождение всей области памяти "кучи", занятой RA.

     Dispose(VAptr, Done);

     В данном примере  показан  динамический  объект,  вызывающий
деструктор Done  через  процедуру  Dispose.  Отменяется выделение
памяти для самого экземпляра объекта,  а также памяти, выделенной
этим объектом.  Если VAptr указывает на виртуальный массив, буфер
сбрасывается в  файл  массива,  и  этот  файл  закрывается.  Если
установлен флаг lDeleteFile, то файл массива удаляется.

См. также
---------

     Init


ErrorA
----------------------------------------------------------------
Объявление
----------

     function ErrorA : Word;

Назначение
----------

     Возвращает последний   код   ошибки   и  сбрасывает  в  ноль
внутреннюю переменную состояния.

Описание
--------

     Если возвращаемое  значение  равно нулю,  это означает,  что
ошибки не произошло. Возможные номера ошибок:

Номер ошибки   Константа ошибки    Описание ошибки
----------------------------------------------------------------
0002           ecFileNotFound      Файл не найден
0003           ecPathNotFound      Маршрут не найден
0008           ecOutOfMemory       Недостаточный размер памяти
0160           ecDeviceWrite       Ошибка записи в устройство
0161           ecDeviceRead        Ошибка чтения с устройства
2100           ecUnSufficientEms   Недостаточный размер EMS-памяти
2101           ecEmsAllocation     Ошибка при выделении EMS-памяти
2102           ecNoEms             Память EMS не установлена
2103           ecEmsPageMapping    Ошибка при отображении страницы
                                   EMS-памяти
2104           ecCantFreeEms       Ошибка при освобождении
                                   логического номера EMS-памяти
8050           ecElSizeIsZero      Размер элемента равен нулю
8051           ecIncorrectSize     Неверные размеры для дискового
                                   файла
8052           ecRowOutOfRange     Заданная строка вне диапазна
8053           ecColOutOfRange     Заданный столбец вне диапазна
8054           ecNotLArrayFile     Заданный файл не является
                                   файлом OPLARRAY
8055           ecElementTooBig     Размер элемента слишком велик
8056           ecBadDimensions     Задан неверный размер индекса
8057           ecLessThanOnePage   Оперативной памяти недостаточно
                                   даже для одной страницы
8058           ecFlushError        Ошибка при попытке сбросить буфер
                                   в файл массива
----------------------------------------------------------------

     Данный сптсок  номеров ошибки включает в себя и возвращаемые
OPROOT.InitStatus при неудачном завершении конструктора.

     Отметим, что к номеру ошибки  может  быть  добавлен  префикс
ошибки, как описано в Приложении D.

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

Пример
------

     RA.Set(Y, X, Value);
     Result := RA.ErrorA;
     if Result <> 0 then
     WriteLn('Операция завершилась неудачно,
              Result   =   ', Result);

     Сообщает состояние операции с большим массивом. Отметим, что
вызов ErrorA очищает текущий код ошибки.

См. также
---------

     SetErrorProc


FlushA
----------------------------------------------------------------
Объявление
----------

     procedure FlushA; virtual;

Назначение
----------

     Отображает на   диске   любые   сделанные   изменения  (если
объектный тип работает как VirtualArray).

Описание
--------

     Данное правило  относится  только к объекту VirtualArray или
OpArray в режиме виртуального массива.  Оно  сбрасывает  на  диск
буферы "кучи"  данного  массива,  а затем заставляет DOS сбросить
его собственные  файловые  буферы.  Ввод/вывод  Turbo  Pascal  не
используется в    FlushA,    поэтому   проверять   IoResult   нет
необходимости.

Пример
------

     var
       VA : VirtualArray;
     ...
     VA.FlushA;
     if VA.ErrorA <> 0 then begin
       WriteLn('Ошибка при сбросе большого массива на диск');
       ...
     end;

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


GetErrorProc
----------------------------------------------------------------
Объявление
----------

     procedure GetErrorProc(var EP : ErrorProc);

Назначение
----------

     Принимает текущую   процедуру   пользователя  для  обработки
ошибки.

Описание
--------

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

Пример
------

     var
       SaveErrorProc : ErrorProc;
     begin
       ...
       RA.GetErrorProc(SaveErrorProc);
       RA.SetErrorProc(MyErrorLProc);
       RA.SetA(101, 2, MyElement);
       RA.SetErrorProc(SaveErrorProc);
       RA.SetA(101, 2, MyElement);
       ...

     Предполагая, что  это  массив  размером  100  строк  на  100
столбцов, и что RA это объект - массив с проверкой диапазона, оба
вызова SetA  завершатся  неудачно.  Первое  неудачное  завершение
вызовет MyLErrorProc, а второе - исходную ErrorProc, которая была
раньше.

     var
       SaveErrorProc : Procedure;
     ...
       RA.GetErrorProc(SaveErrorProc);

     Неверно! SaveErrorProc должна быть определена как ErrorProc,
а не как Procedure.  Это даст  ошибку  компиляции  "Typemismatch"
("Несогласованность типов").

См. также
---------

     SetErrorProc


Init
----------------------------------------------------------------
Объявление
----------

     constructor RamArray.Init(Rows, Cols, ElementSize : Word;
                    HeapToUse : LongInt; ArrayOptions : Byte);
     constructor EmsArray.Init(Rows, Cols, ElementSize : Word;
                    HeapToUse : LongInt; ArrayOptions : Byte);
     constructor VirtualArray.Init(Rows, Cols, ElementSize : Word;
                    FileName : String;
                    HeapToUse : LongInt; ArrayOptions : Byte);
     constructor OpArray.Init(Rows, Cols, ElementSize : Word;
                    FileName : String; HeapToUse : LongInt;
                    HeapToUse : LongInt; ArrayOptions : Byte;
                    var Priority : AutoPriority);
     constructor PrimitiveArray.Init(RowsInPage : Word; ColsInPage : Word;
                    PagesDown : Word; PagesAcross : Word;
                    ElementSize : Word; FileName : String;
                    HeapToUse : LongInt; ArrayOptions : Byte);


Назначение
----------

     Создание нового большого массива.

Описание
--------

     Rows задает задает максимальное число строк массива,  а Cols
- максимальное  число   столбцов.   Поскольку   индексы   массива
отсчитываются от нуля, старшие допустимые номера строки и столбца
на единицу  меньше  заданных  размеров  массива.  Rows   и   Cols
используются во     всех     конструкторах,     за    исключением
PrimitiveArray.Init.

     ElementSize - это размер в  байтах  элементов  массива.  Для
передачи параметра ElementSize обычно используется операция Turbo
Pascal SizeOf для нужного вам типа данных.  ElementSize не  может
превышать 16384  байтов  для  типа массива EmsArray и 65521 байта
для любого другого типа.

     HeapToUse задает   максимальное   число    байтов    "кучи",
используемое для  хранения данных (не включая сюда затраты памяти
на организацию  самого  массива).  Если  размер  данных   массива
превышает HeapToUse,  конструктор завершится неудачно.  Даже если
размер данных  меньше,  чем  HeapToUse,  конструктор   завершится
неудачно в   случае   невозможности   разместить   в  "куче"  все
необходимые для организации массива  структуры  данных.  В  число
таких дополнительных структур входят структура заголовка массива,
массив указателей на страницы и,  в случае виртуального  массива,
индексы дисковой  памяти  и  страниц  для  организации страничных
буферов. Обычно объем этой вспомогательной памяти невелик (меньше
ста байтов).  В специальных же случаях, при малом размере страниц
и их большом количестве,  затраты памяти на  организацию  массива
могут быть существенными, достигая тысч и десятков тысяч байтов.

     Если вы   желаете,   чтобы  RamArray  мог  использовать  всю
доступную память,  передавайте  в  качестве  HeapToUse   значение
MemAvail. В  случае EmsArray HeapToUse игнорируется,  поскольку в
"куче" в этом случае не хранятся никакие данные. Для VirtualArray
HeaToUse задает максимальное количество памяти "кучи", выделяемое
для области,  через которую  происходит  подкачка  страниц  между
диском и вашей программой. Чем больше оперативной памяти для этой
цели будет выделено,  тем меньше операций подкачки страниц  будет
происходить, что  улучшит характеристики быстродействия программы
при обращениях к массиву. Оперативной памяти должно быть выделено
как минимум  достаточно для хранения одной страницы массива. Если
задан параметр HeapToUse, достаточный для одновременного хранения
в памяти всего массива сразу, то доступ к диску будет происходить
лишь при   необходимости   закрытия   файла   большого    массива
(посредством вызова  деструктора Done),  при вызове правил FlushA
или StoreA, либо при вызове правила SetA, когда установлена опция
lKeepDiskCurrent.

     Параметр ArrayOptions используется для передачи конструктору
Init флагов опций.  ArrayOptions имеет смысл только для  объектов
OpArray,  VirtualArray,  VirtualRCArray  и  PrimitiveArray.  Всем
прочим он передается только с  точки  зрения  совместимости.  Для
объектов   VirtualArray,   PrimitiveArray  и  OPArray  (в  режиме
VirtualArray)   существует   две   опции:   lKeepDiskCurrent    и
lDeleteFile.  Опция  lRangeCheck  допустима  только  для объектов
OpArray.  Более подробно эти опции описаны в разделе "Константы".

     Типы объектов  VirtualArray   и   PrimitiveArray   принимают
параметр FileName.  FileName  задает имя файла,  в котором должен
храниться большой массив.  Это имя может включать имя маршрута  и
дисковода. Дисковой  памяти  должно  быть  достаточно  для записи
фактического размера данных массива,  плюс некоторых издержек  на
организацию массива  в  целом.  Init  перезаписывает существующий
файл с  тем  же  именем,  не  выводя   на   этот   счет   никаких
предупреждений.

     За исключением       PrimitiveArray.Init,       конструкторы
автоматически определяют   структуру   страниц    массива.    Для
определения структуры  страниц служат следующие критерии:  каждая
страница ограничена размером в 64Кбайт,  каждая страница образует
идентичное прямоугольное подмножество массива в целом, а издержки
на организацию  массива,  плюс   теряемая   область   по   краям,
минимизируются.

     Параметр Priorities      объекта      OpArray     определяет
последовательность, в которой конструктор  пытается  использовать
машинные ресурсы  для  размещения  массива.  Использование  этого
параметра рассматривается выше, в разделе "Приоритеты массивов".

     Тип PrimitiveArray обеспечивается  для  тех  случаев,  когда
необходимы возможности полного контроля над страничной структурой
массива. По   контрасту   с   этим,   конструктор    VirtualArray
автоматически выполняет  разделение  массива  на страницы,  таким
образом, каким он считает необходимым с точки  зрения минимизации
потерь памяти.  К  сожалению,  этот автоматический конструктор не
имеет достоточной  информации,  чтобы  также   оптимизировать   и
характеристики скорости  доступа  к  массиву.  Поскольку скорость
обращения к  диску  значительно  меньше  скорости   обращений   в
оперативной памяти,    для    получения   хороших   характеристик
быстродействия массива  число  обращений  к  диску  для  подкачки
страниц также  должно быть минимизировано.  Если доступ к массиву
происходит в основном по возрастанию  номера  строки  (т.е.  если
индекс строки  растет  быстрее,  чем  номер  столбца),  то каждая
страница должна содержать как можно больше строк.  Если же доступ
к массиву  выполняется  в основном по номерам столбцов,  то верно
противоположное утверждение. В случае PrimitiveArray.Init вы сами
можете обеспечить желаемую органицацию.

     В зависимости      от      параметров,      переданных     в
PrimitiveArray.Init,  общее число строк массива равно PagesDown *
RowsInPage,  а  число столбцов - PagesAcross * ColsInPage.  Общее
число страниц равно PagesDown * PagesAcross,  а размер  в  байтах
каждой страницы составляет RowsInPage * ColsInPage * ElementSize.

     Для массивов в EMS-памяти никакой области "кучи" практически
не выделяется; все данные массива хранятся непосредственно в EMS-
памяти. EmsArray.Init     вычисляет     минимальное    количество
16К-байтных страниц для хранения данных,  и добавляет  еще  одну.
(Последняя страница нужна,  чтобы избежать специальных логических
операций при обращении к элементам массива вблизи конца массива).

     Отметим, что ни один из этих конструкторов не инициализирует
элементы массива.   Без   выполнения   последующей  инициализации
(возможно, с  помощью  ClearA)  элементы  массива  будут  хранить
произвольные значения.  Также отметим, что виртуальные массивы не
выделяют дисковой памяти для страницы,  пока не будет установлено
значение хотя  бы  одного элемента страницы.  В некоторых случаях
этим можно воспользоваться, создавая массив максимальной емкости,
но используя только необходимые его части.

     При неудачном   завершении   конструктора  InitStatus  будет
содержать поясняющий код ошибки.

     Конструкторы массивов,   реализующих   проверку    диапазона
(RamRCArray, EmsRCArray,    VirtualRCArray),   имеют   параметры,
идентичные объектам, от которых они являются производными.

Пример
------

     var RA : RamArray;
     ...
     if not RA.Init(100, 100,            {Строк, столбцов}
                    SizeOf(MyElement),   {Размер одного элемента}
                    MemAvail,            {Использовать всю доступную
                                          область "кучи"}
                    0                    {Опции}
     then begin
       WriteLn('Не удалось инициализировать RamArray.
                Состояние = ', InitStatus); Halt;
     end;

     Создает RamArray на 100 строк и 100 столбцов.

     var EA : EmsRCArray;
     ...
     if not EA.Init(100, 100,            {Строк, столбцов}
                    SizeOf(MyElement),   {Размер одного элемента}
                    MemAvail,            {HeapToUse: в данном случае
                                          не играет роли}
                    0                    {Опции}
     then begin
       WriteLn('Не удалось инициализировать EmsRCArray.
                Состояние = ', InitStatus); Halt;
     end;

     Создает объект   типа   EmsArray   с   проверкой   диапазона
(EmsRCArray) на 100 строк и 100 столбцов.

     var VA : VirtualArray;
     ...
     if not VA.Init(100, 100,            {Строк, столбцов}
                    SizeOf(MyElement),   {Размер одного элемента}
                    'TEMP.ARR',          {Файл виртуального массива}
                    MemAvail div 2,      {Используемая область "кучи"}
                    DeleteFile           {Опции}
     then begin
       WriteLn('Не удалось инициализировать VirtualArray.
                Состояние = ', InitStatus); Halt;
     end;

     Создает VirtualArray  на  100  строк  и   100   столбцов   и
записывает часть  массива,  не помещающаяся в "куче",  в дисковый
файл TEMP.ARR.   При   вызове   деструктора   Done   этот    файл
автоматически удаляется.

     var PA : PrimitiveArray;
     ...
     if not PA.Init(100, 20,           {Строк в странице RowsInPage,
                                        столбцов в странице ColsInPage}
                    1, 20,             {Страниц вниз PagesDown,
                                       страниц поперек PagesAcross}
                    SizeOf(MyElement), {Размер одного элемента}
                    'FLIGHT.DAT',      {Файл виртуального массива}
                    1024*12,           {Используемая область
                                        для подкачки страниц = 12К}
                    0                  {Опции массива
                                        (в данном случае отсутствуют)}
     then begin
       WriteLn('Не удалось инициализировать PrimitiveArray.
                Состояние = ', InitStatus); Halt;
     end;

     Создает виртуальный   массив   при   полном   контроле   над
определениями страниц.   Мы   предполагаем,   что   массив  будет
обрабатываться построчно. Данная страничная структура помещает на
каждую страницу  20  полных  столбцов,  что минимизирует подкачку
страниц при построчном сканировании.

     const MyPri : AutoPriority =
                   (lRamArray, lEmsArray, lVirtualArray);
     var OA : OpArray;
     ...
     if not OA.Init(100, 100,            {Строк, столбцов}
                    SizeOf(MyElement),   {Размер одного элемента}
                    'TEMP.ARR',          {Файл виртуального массива}
                    MemAvail div 4,      {Используемая область "кучи"}
                    0,                   {Опции массива}
                    MyPri)               {Приоритеты размещения}
     then begin
       WriteLn('Не удалось инициализировать OpArray.
                Состояние = ', InitStatus); Halt;
     end;
     case OA.TypeOfArray of
       lRamArray : WriteLn('массив в RAM');
       lEmsArray : WriteLn('массив в EMS');
       lVirtualArray : WriteLn('виртуальный массив');
     end;

     Создает массив OpArray с именем OA, затем выводит на дисплей
информацию о том, какого типа массив фактически был создан. MyPri
задает размещение массива  целиком  в  RAM,  если  он  поместится
целиком в  MemAvail  div  4  байтах;  если  это не удастся,  то в
EMS-памяти, и наконец,  в  качестве  последнего  ресурса,  массив
размещается на диске, с подкачкой страниц в память.

См. также
---------

     LoadA


LoadA
----------------------------------------------------------------
Объявление
----------

     constructor RamArray.LoadA(FileName : String;
                 HeapToUse : LongInt; ArrayOptions : Byte);
     constructor EmsArray.LoadA(FileName : String;
                 HeapToUse : LongInt; ArrayOptions : Byte);
     constructor VirtualArray.LoadA(FileName : String;
                 HeapToUse : LongInt; ArrayOptions : Byte);
     constructor OpArray.LoadA(FileName : String;
                 HeapToUse : LongInt; ArrayOptions : Byte;
                 var Priority :AutoPriotity);

Назначение
----------

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

Описание
--------

     LoadA прежде   всего   пытается  открыть  файл,  заданный  в
FileName. Если  этот  файл  не  существует,   конструктор   LoadA
завершается неудачно.  За  поиск файла при необходимости отвечает
вызывающая прикладная программа. Затем LoadA считывает заголовок,
описывающий размер  массива.  В  зависимости  от  этой информации
LoadA пытается выделить необходимые системные ресурсы ("кучу" или
EMS-память) для загрузки массива. Использование "кучи" ограничено
значением, заданным в параметре HeapToUse (так же, как и для Init
(. Затем  данные  переносятся из файла в объект большого массива.
Если объект  должен  храниться  в  RAM  или  в   EMS,   то   файл
закрывается. Если   это  виртуальный  массив,  то  файл  остается
открытым и используется как виртуальная память для этого массива.

     Ресурсы, необходимые  для  загрузки  массива,   зависят   от
используемого типа объекта массива.  Например, для загрузки в RAM
массива из 100 x 100 элементов типа Word требуется  20000  байтов
"кучи" (плюс  некоторое дополнительное количество издержек памяти
на сам массив),  тогда как для хранения того же самого массива  в
EMS-памяти почти   не   потребуется   памяти   "кучи",   но  зато
потребуется три страницы по 16К памяти EMS.

     Файлы больших массивов,  записанные любыми  объектами  этого
типа из  OPLARRAY,  могут  считываться конструкторами LoadA любых
других типов больших  массивов.  Конструктор  LoadA  может  также
читать любые  файлы  больших  массивов,  совместимые  с  форматом
TPVARRAY, который принят в Turbo Professional.

     Если ресурсы   для   загрузки   данного   большого   массива
недостаточны, то    конструктор   LoadA   завершается   неудачно.
InitStatus при этом будет содержать код  ошибки,  указывающий  на
причину этой ошибки.

Пример
------

     var RA : RamArray;
     ...
     if not RA.LoadA('RA.ARR', 0) then begin
       WriteLn('Не удалось загрузить RamArray.
                Состояние = ', InitStatus);
       Halt;
     end;
     ...

     Большой массив  загружается  из  дискового  файла  RA.ARR  в
массив в оперативной памяти.

     const
       MyPri : AutoPriority = (lEmsArray, lRamArray, lVirtualArray);
     var OA : OpArray;
     ...
     if not OA.LoadA('GRID.DAT', 0, HeapLast) then
       WriteLn('Не удалось загрузить OpArray.
                Состояние = ', InitStatus);
       Halt;
     end;

     Данный пример  пытается  открыть   файл   большого   массива
GRID.DAT. Список  приоритетов  MyPri  задает,  что  сначала будет
сделана попытка загрузки в EMS, затем в "кучу", и если ни один из
этих подходов не удастся, то массив будет виртуальным.

См. также
---------

     Init


lOptionsAreOn
----------------------------------------------------------------
Объявление
----------

     function lOptionsAreOn(OptionCodes : Word) : Boolean;

Назначение
----------

     Возвращает True, если OptionCodes установлены.

Описание
--------

     Данная подпрограмма  позволяет  определить,  установлена  ли
отдельная опция,   или   несколько   опций.  В  OPLARRAY  имеются
следующие три опции: lKeepDiskCurrent, lDeleteFile и lRangeCheck.

Пример
------

     if VA.lOptionsAreOn(lDeleteFile) then
       {выполняется некоторое действие};

     Некоторое действие будет выполнено при  установленной  опции
lDeleteFile.


lOptionsOff
----------------------------------------------------------------
Объявление
----------

     procedure lOptionsOff(OptionCodes : Word);

Назначение
----------

     Выключает заданные опции массива.

Описание
--------

     lOptionsOff игнорируют опцию lRangeCheck. Проверка диапазона
это свойство,  которое  может  быть  задано   только   во   время
конструирования OpArray.

Пример
------

     VA.lOptionsOff(lDeleteFile);

     При вызове Done дисковый файл виртуального массива  не будет
удален.


lOptionsOn
----------------------------------------------------------------
Объявление
----------

     procedure lOptionsOn(OptionCodes : Word);

Назначение
----------

     Включает заданные опции массива.

Описание
--------

     lOptionsOn игнорируют опцию lRangeCheck.  Проверка диапазона
это  свойство,  которое  может  быть  задано  только   во   время
конструирования OpArray.

Пример
------

     VA.lOptionsOn(lKeepDiskCurrent+lDeleteFile);

     Дисковый файл виртуального  массива  будет  обновляться  при
каждом  изменении  элемента  массива,  а  при  вызове  Done будет
удален.


RangeError
----------------------------------------------------------------
Объявление
----------

     function RangeError(Row, Col : Word) : Boolean; virtual;

Назначение
----------

     Возвращает True,  если  заданное  значение  строки  Row  или
столбца Col лежит вне существующего диапазона.

Описание
--------

     Данная подпрограмма  представляет  собой  средство  проверки
допустимости значений строки и столбца  перед  вызовом  SetA  или
GetA. Работает даже для типов, не имеющих проверки диапазона.

Пример
------

     {...Вычисление строки Row и столбца Col...}
     if not RA.RangeError(Row, Col) then
       RA.SetA(Row, Col, MyElement)
     else
       {Обработка ошибки в диапазоне}

     Предполагая, что  Row  и  Col  будут  вычислены  ранее,   мы
вызываем RangeError, чтобы убедиться, что для данного массива эти
значения находятся в пределах допустимых диапазонов.

См. также
---------

     SetA             RetA


RetA
----------------------------------------------------------------
Объявление
----------

     procedure RetA(Row, Col : Word; var Value); virtual;

Назначение
----------

     Возвращает элемент массива.

Описание
--------

     Все массивы, организуемые в OPLARRAY, индексированы от нуля.
Под этим мы имеем в виду, что младший индекс по любой оси массива
равен нулю.  По  той  же  причине,  старший  индекс по каждой оси
массива на  единицу  меньше  общего  числа  строк  или   столбцов
массива.

     RetA возвращает текущее значение элемента массива из позиции
Row,Col. Поскольку OPLARRAY не знает тип элемента (а  только  его
размер), то    значение    возвращается    через   нетипированный
параметр-переменную. Ваша вызывающая программа  должна передавать
в RetA  переменную  правильного типа,  иначе RetA может повредить
память, что приводит к непредсказуемым последствиям.

     Если значение  строки  или  столбца  лежит  вне  допустимого
диапазона, а   массив  имеет  проверку  диапазона,  RetA  вызовет
определяемую пользователем процедуру обработки ошибки и установит
код ошибки  последней  операции (таким образом,  что вызов ErrorA
позволит получить этот код).

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

     Например,

function RetVal(A : AbstractArray; Row, Col : Word) : MyDataType;
var
  V : MyDataType;
begin
  A.RetA(Row, Col, V);
  RetVal := V;
end;

     Такой способ   годится   для   любого    типа    MyDataType,
возвращаемого функцией.  Эти типы включают в себя Byte, ShortInt,
Word, LongInt,  указатели,  Real,  числовые типы 8087  и  строки.
Записи в  качестве  результата работы такой функции не могут быть
возвращены.

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

Пример
------

     if not RA.Init(100, 100, Sizeof(MyElement), MemAvail, 0)
     then begin
       WriteLn('Не удалось инициализировать RamArray.
                Состояние = ', InitStatus);
       Halt;
     end;
     RA.RetA(0, 0, Value);

     Считывает первый элемент массива в Value.

     MyElement := GetVal(RA, 100, 1);

     Неверно !  Предполагая,  что  это   часть   вышеприведенного
фрагмента, мы здесь ссылаемся на несуществующую строку.  В случае
объекта, не имеющего проверки диапазона индексов (типа RamArray),
возвращаемое значение   будет  содержать  "мусор".  В  случае  же
объекта, проверяющего   диапазон   (типа    RamRCArray),    будет
сгенерирована ошибка.

См. также
---------

     SetA


SafeRetA
----------------------------------------------------------------
Объявление
----------

     procedure SafeRetA(Row, Col : Word; var Value); virtual;

Назначение
----------

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

Описание
--------

     Данное правило  практически  идентично RetA,  за исключением
того, что  оно  игнорирует  значения  Row  и  Col,  выходящие  за
допустимый диапазон.  При  передаче  недопустимого  индекса Value
остается без  изменений,  а  ошибка   не   генерируется.   Данная
подпрограмма не  предназначена  для  повсеместного использования;
она дана главным образом для совместимости с правилом SafeSetA.

Пример
------

     function GetVal(Row, Col : Word) : Str20;
       {Возвращает значение одного элемента типа Str20}
     var
       S : Str20;
     begin
       S := '';
       RA.SafeRetA(Row, Col, S);
       GetVal := S;
     end;
     ...
     if not RA.Init(100, 100, SizeOf(Str20), MemAvail, 0) then begin
       WriteLn('Не удалось инициализировать RamArray.
                Состояние = ', InitStatus); Halt;
     end;
     {...Использование массива...}
     MyElement := RA.GetVal(100, 1);

     Пытается взять  элемент  по  несуществующему  адресу  строки
(100). GetA  вернет  элемент S неизмененным,  т.е.  GetVal вернет
пустую строку.

См. также
---------

     RetA                SetA



SafeSetA
----------------------------------------------------------------
Объявление
----------

     procedure SafeSetA(Row, Col : Word; var Value); virtual;

Назначение
----------

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

Описание
--------

     Данное правило  практически  идентично SetA,  за исключением
того,  что оно  игнорирует  значения  Row  и  Col,  выходящие  за
допустимый диапазон. При передаче недопустимого индекса установка
значения  элемента   массива   не   происходит,   а   ошибка   не
генерируется.  Данная  подпрограмма  предназначена в основном для
внутреннего использования.

Пример
------

     if not RA.Init(100, 100, SizeOf(MyElement), MemAvail, 0) then begin
       WriteLn('Не удалось инициализировать RamArray.
                Состояние = ', InitStatus); Halt;
     end;
     RA.SafeSet(1, 1, MyElement);

     Помещает значение MyElement в строку 1, столбец 1 RA.

     RA.SafeSet(100, 1, MyElement);

     Пытвется установить    значение   несуществующего   элемента
массива. (Это может быть ошибкой программирования).  Поскольку мы
используем SafeSetA,  то  память  после возврата из этого правила
испорчена не будет, и код ошибки также не будет записан.

См. также
---------

     RetA                SetA


SetA
----------------------------------------------------------------
Объявление
----------

     procedure SetA(Row, Col : Word; var Value); virtual;

Назначение
----------

     Устанавливает элемент   массива,    заданный    координатами
Row,Col, в значение Value.

Описание
--------

     Все массивы, организуемые в OPLARRAY, индексированы от нуля.
Под этим мы имеем в виду, что младший индекс по любой оси массива
равен нулю.  По  той  же  причине,  старший  индекс по каждой оси
массива на  единицу  меньше  общего  числа  строк  или   столбцов
массива.

     SetA устанавливает значение массива с  координатами Row,Col.
Поскольку  OPLARRAY  не знает тип элемента (а только его размер),
то значение передается через  нетипированный параметр-переменную.
Ваша    вызывающая   программа   должна   передавать   переменную
правильного типа.

     Если значение  строки  или  столбца  лежит  вне  допустимого
диапазона, а   массив  имеет  проверку  диапазона,  SetA  вызовет
определяемую пользователем процедуру обработки ошибки и установит
код ошибки  последней  операции (таким образом,  что вызов ErrorA
позволит получить этот код).

     В некоторых случаях  желательно  передавать  новые  элементы
массива с использованием констант или выражений.  Модуль OPLARRAY
не  может  поддерживать  этого  напрямую,  поскольку   в   момент
компиляции тип данных для этого массива не определен,  но в вашей
прикладной  программе  легко  можно  определить  одну  или  более
процедур,  которые будут работать с конкретными,  используемыми в
вашей прикладной программе, типами данных. Например,

procedure SetVal(A : AbstractArray; Row, Col : Word; V : MyDataType);
begin
  A.SetA(Row, Col, V);
end;

     Затем SetVal  можно вызывать с константами или выражениями в
параметре V.

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

Пример
------

     if not RA.Init(100, 100, Sizeof(MyElement), MemAvail, 0)
     then begin
       WriteLn('Не удалось инициализировать RamArray.
                Состояние = ', InitStatus);
       Halt;
     end;
     MyElement := 'Object';
     RA.SetA(1, 1, MyElement);

     Записывает значение 'Object; в RA с координатами 1,1.

     RA.SetA(100, 1, MyElement);

     Неверно !  Делается попытка записи элемента в несуществующую
строку. В случае объекта, не имеющего проверки диапазона индексов
(типа RamArray), это может повредить память. В случае же объекта,
проверяющего диапазон  (типа  RamRCArray),  произойдет  неудачное
завершение и будет сгенерирована ошибка RowOutOfRange.

См. также
---------

     RetA                SafeSetA


SetErrorProc
----------------------------------------------------------------
Объявление
----------

     procedure SetErrorProc(EP : ErrorProc);

Назначение
----------

     Устанавливает процедуру   обработки   ошибки,   определяемую
пользователем.

Описание
--------

     В случае  обнаружения ошибки OPLARRAY вызывает процедуру для
ее обработки.  Процедура  обработки  ошибки   по   умолчанию   не
выполняет никаких   действий,   за  исключением  того,  что  если
константа DefBeepOnError  равна  True,  то   раздается   звуковой
сигнал, указывающий  на ошибку.  Для того,  чтобы в случае ошибки
OPLARRAY вызывал подпрограмму,  написанную  пользователем, вместо
обработчика ошибки по умолчанию, служит SetErrorProc.

     Задаваемая процедура    обработки    ошибки   должна    быть
компилирована с моделью памяти FAR  (опция  {$F+}),  должна  быть
глобальной и  должна иметь параметры,  соответствующие следующему
прототипу:

     {$F+}
     procedure MyErrorProc(UnitCode : Byte, var ErrorCode : Word;
                           ErrorMsg : String);

     Такой тип процедуры определяется  в  OPROOT  и  совместим  с
прочими подпрограммами  обработки ошибок Object Professional. При
вызове ее из OPLARRAY в качестве  параметра  UnitCode  передается
значение ucLArray.    ErrorCode    должен   являться   значением,
совместимым с  кодами,  описанными  для  правила   ErrorA   и   в
Приложении D.  ErrorMsg  для  ошибок OPLARRAY всегда представляет
собой пустую строку.

     Стандартный обработчик ошибок,  выдающий текстовые сообщения
об ошибках,  см.  в описании DisplayLErrorProc, в приводимом ниже
разделе "Процедуры и функции".

Пример
------

   {$F+}
   procedure MyErrorHandler(UnitCode : Byte; var ErrorCode : Word;
                              ErrorMsg : String;
   const
       ClassOfError : Array[0..4] of String[9]; = ('fatal',
                  'non-fatal', 'warning', 'message', 'no error');
   var Seriousness, Err : Word;
   begin
     Seriousness := ErrorCode div 10000; {определение класса ошибки}
     Err := ErrorCode mod 10000;  {вырезать класс ошибки из кода ошибки}
     WriteLn{'Внимание, произошла ошибка ', Err);
     WriteLn('Это ошибка класса: ', ClassOfError[Seriousness]);
     if UnitCode = ucLArray then
       DisplayLErrorProc(UnitCode, ErrorCode, ErrorMsg)
     else
       WriteLn(ErrorMsg);
     if Seriousness = etFatal then
       Halt;
   end;
   {$F-}
   ...
   RA.SetErrorProc(MyErrorHandler);

     Устанавливает MyErrorHandler  в  качестве   пользовательской
процедуры обработчика   ошибки  для  RA.  MyErrorHandler  сначала
определяет серьезность     происшедшей      ошибки,      выделяет
необработанный номер  ошибки из параметра ErrorCode и выводит эти
значения на дисплей.  Если MyErrorHandler  вызывается  из  модуля
OPLARRAY, то она вызывает DisplayLErrorProc,  чтобы вывести также
сообщение, обозначающее  причину  ошибки.  И  наконец,  если  она
определяет, что  класс  ошибки  -  etFatal,  программа прекращает
выполнение.

См. также
---------

     DisplayLErrorProc              GetErrorProc
     ErrorA


StoreA
----------------------------------------------------------------
Объявление
----------

     procedure StoreA(FileName : String); virtual;

Назначение
----------

     Записывает большой массив на диск.

Описание
--------

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

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

     Сначала StoreA   выводит   запись   заголовка,   описывающую
размерность и  тип  массива.  Затем  StoreA  записывает каждую из
страниц массива,  и наконец закрывает файл. StoreA не освобождает
образ массива из "кучи".  Если после сохранения он более не нужен
в данной программе,  вызовите деструктор  Done,  который  отменит
выделенную массиву область памяти.

     Размер требуемой   дисковой   памяти   несколько   превышает
фактический размер массива и определяется как

     ArraySize = TotalRows*TotalCols*ElementSize
     (Размер_массива = Всего_строк*Всего_столбцов*Размер_элемента)

     StoreA возвращает  ошибку  в  случае невозможности создания,
записи или закрытия файла массива.

     Файлы, записанные при помощи StoreA,  могут  быть  прочитаны
при помощи  конструктора  LoadA  любого из объектов типа большого
массива.

Пример
------

     RA.Init(100, 100, SizeOf(MyElement), MemAvail, 0);
     {...}
     RA.Store('RA.ARY');
     Result := RA.ErrorA;
     if Result <> 0 then
       WriteLn('Не удалось записать массив.
                Состояние = ', Result);
     RA.Done;

     Записывает массив в файл.

См. также
---------

     LoadA


TypeOfArray
----------------------------------------------------------------
Объявление
----------

     function TypeOfArray : ArrayType;

Назначение
----------

     Возвращает тип массива.

Описание
--------

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

     Значение lNoArray возвращаться не должно.

Пример
------

     case LargeArray.TypeOfArray of
       lRamArray : WriteLn('массив в RAM');
       lEmsArray : WriteLn('массив в EMS');
       lVirtualArray : WriteLn('виртуальный массив');
     end;

     Возвращает тип массива LargeArray



Процедуры и функции
-------------------

DisplayLErrorProc
----------------------------------------------------------------
Объявление
----------

procedure DisplayLErrorProc(UnitCode : Byte; var ErrorCode : Word;
                            ErrorMsg : String);

Назначение
----------

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

Описание
--------

     Данная подпрограмма обеспечивается в  качестве  альтернативы
простейшему обработчику  ошибки,  действующему по умолчанию.  Она
выводит на  дисплей  на  английском  языке  сообщение  об  ошибке
(используя для  этого  WriteLn),  которое  указывает  на  причину
ошибки. Если типированная константа DefBeepOnError равна True, то
данная процедура  также  выдает короткий звуковой сигнал.  Работу
программы в целом она не останавливает.

Пример
------

     RA.SetErrorProc(DisplayLErrorProc);

     Сообщает RA    о   необходимости   использовать   встроенную
процедуру DisplayLErrorProc.

См. также
---------

     GetErrorProc                SetErrorProc
