TURBO PASCAL

Новости

Программы   

Turbo Pascal 

Игры

Документация   

Странности

FAQ

Ссылки

Форум

Живой Журнал

Гостевая книга

Рассылка

Благодарности

Об авторе

ООП - это очень просто

Выпуск № 6

Выдавать глобальные идеи - это удовольствие.
Искать сволочные маленькие ошибки - по-прежнему настоящая работа
/Брукс и Борис/

В этом выпуске пояснения к составу программы, пояснения к которой были в предыдущем выпуске:
  1. uRec.pas.
  2. BaseObj.pas
  3. Test.pas
Извините, что в предыдущем выпуске, торопясь не заметил, что отсутствует ссылка на архив . Вот она http://www.borlpasc.narod.ru/Boris/BaseObj.zip. Сейчас это уже не важно, так так текст приводится ниже

Модуль uRec.pas

Здесь описывается хранимая единица TOne. Для удобства редактирования информации она "выведена" в отдельную запись TInfo. Обратите внимание, что для каждой строки обязательно нужно задать ее длину. Иначе длина строки будет равна 255 символам, а где Вы видели такую фамилию? То есть, если не задать длину строк, то только 5-10 % базы данных будет содержать что-то полезное.

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

Теперь, допустим, сохранили файл MyDB.db при одной структуре записи. Через неделю решили, что надо ввести поле с номером телефона, или просто изменили длину поля для хранения фамилии. Вы легко изменяете структуру записи TInfo. (Я позднее покажу "как", если сами не сообразите) и открываете файл. Длина одной единицы информации изменилась. Теперь, если будете читать "старый" файл, сохраненный по старым стандартам, то получите белиберду. Будет удобно, если программа предупредит Вас о несовпадении версий и предложит сначала запустить другую программу для конвертирования ее структуры. То есть, эта программа прочитает файл по старому стандарту, и сохранит по новому.

И вот для реализации этого первая запись в файле содержит номер варианта. При открытии файла нужно будет только сравнить первые символы в файле к константой. Да! И внеся изменения - измените константу с номером версии!

Теперь немного слов о том, как будем хранить. Данные я буду располагать в динамической памяти компьютера. И помещать их туда буду оператором New. Обращение к таким данным происходит по их адресу. В "объекте-хранителе" (TBaseObj) я буду хранить адрес первой структуры. В поле Next буду записывать адрес следующей такой структуры или константу NIL, если эта структура последняя и следующей нет. Получается цепочка, по которой можно "проходить" только в одном направлении от начала к концу. Отсюда и название "односвязный список". О таких списках написано уже много и в учебниках и в статьях. (Если Вы не разберетесь в них, напишите - поясню дполнительно)

В модуле uRec.pas описаны еще две подпрограммы, которые облегчат будущую работу.

  1. Функция NewOne размещает в динамической памяти новую структуру и заполняет ее поля. Возвращает функция указатель на структуру.
  2. Процедура PrintFullInfo выводит на экран значение полей записи, на которую указывает формальный параметр. Эта процедура нужна только на первое время. Потом, когда объект-хранитель станет получше, пользоваться ею не будем. И здесь скажется одна важная особенность модулей Паскаля. Если подпрограмму, описанную в модуле не вызывать, то она и не войдет в состав исполняемого файла! То есть, размер исполняемого файла не зависит от того, сколько и каких подпрограмм написано в модуле "на всякий случай".

В этом же модуле находится и "хитрая" функция HeapFunc. Дело в том, что Паскаль, если не хватает места для размещения чего-либо в динамической памяти компьютера, реагирует на это "жестко" - аварийно завершает работу программы! Это "не есть удобно" - лучше бы дать "знать" о проблеме программе, а она (мы так должны написать ее, программу эту самую!) пусть посмотрит, может что-то уже можно "выгрузить" из динамической памяти или, по крайней мере, пусть корректно завершит работу.

Разработчики заложили в компилятор Borland Pascal такую возможность. У "них"есть функция HeapFunc, которая вызывается, когда есть проблемы с памятью. Программа "смотрит", что возвращает эта функция. Если 0 (ноль), то аварийно завершается. Если функция вернет 2 - это значит, что все прошло хорошо (!?). Неудача была вызвана временными причинами и нужно сделать вторую попытку. Если возвращается значение 1, то это сигнал о том, что памяти нет, но и работу программы завершать не надо. В указатель переменной в этом случае записывается значение NIL. Вот мы и делаем "насильственное" действие. Разработчиками предусмотрено, что программа "узнает" о нахождении такой "Хиповой" функции из переменной процедурного типа HeapError. В исполняемом блоке модуля в эту переменную записывается адрес "нашей" функции. (Видите, все продуманно в Паскале!)

unit  uRec;
interface
  CONST
    cNameLen     = 12 ;
    cForeNameLen = 12;

    cVersion : String[cNameLen] = 'Ver.1.0 21.11.03';

  TYPE
    {Содержательная часть хранимой единицы}
    TInfo = record
      Name   : String[cNameLen];
      ForeName: String[cForeNameLen];
      Age    : Byte;
    end;

    {Хранимая единица}
    POne = ^TOne;
    TOne = record
      Info : TInfo;
      Next : POne;
    end;

    function NewOne(AName: String; AForeName: String; AAge: Byte): POne;
    procedure PrintFullInfo(AOne: Pone);

implementation

{Такая функция облегчит работу}
function NewOne(AName: String; AForeName: String; AAge: Byte): POne;
var
 p: POne;
begin
 New(p);
 with p^ do begin
   Next := nil;
   Info.Name    :=Copy(AName    , 1, cNameLen);
   Info.ForeName:=Copy(AForeName, 1, cForeNameLen);
   Info.Age:=AAge
 end;
 NewOne:=p
end;

procedure PrintFullInfo(AOne: POne);
begin
  if AOne = nil then Exit;
  With AOne^.Info do
    WriteLn('Фамилия: ',Name,'.  Имя:   ', ForeName,'.  Возраст: ',Age);
end;

{Эта функция нужна для того, чтобы при возникновении ошибок программа
 не завершалась аварийно}
function HeapFunc(Size: Word): Integer; far;
begin
   HeapFunc:=1
end;

BEGIN
{Настройка функции}
  HeapError:=@HeapFunc;
END.

Если что-то осталось непонятным, то пишите - поясню

[ в начало ]

BaseObj.pas

Основную работу совершает объект, который описан в модуле, текст которого показан ниже. В разделе TYPE описывается только структура объекта. Перед этим (это исключение из общего правила для Паскаля), определяется указатель на объект PBaseObj = ^TBaseObj. Определение объекта начинается со строчки TBaseObj = object и заканчивается зарезервированным словом end;.  В данном случае объект содержит четыре общедоступных метода:

  1. constructor Init - этот метод нужен для размещения объекта в динамической памяти, а также для работы виртуальных методов;
  2. destructor Done - это метод нужен для удаления объекта из динамической памяти, а также для удаления из памяти ЭВМ сведений о бывших уже виртуальных методах. Метод Done обычно объявляют виртуальным, добавив после точки с запятой слово virtual. Виртуальная - пока скажем о такой только одно - "легко заменяемая". Хотя на самом деле она обладает особыми свойствами;
  3. procedure Add - выполняет фактическую работу по добавлению новой структурной единицы в базу данных. В том, что эта функция виртуальная
  4. procedure PrintAll - выводит на экран содержимое всех записей. Когда я написал ее и поместил на сервер, то принял мудрое решение: нужно реализовать тот же механизм, что и в "стандартном" объекте TCollection
private Node - это поле хранит информацию (адрес) начала цепочки. Его менять ни в коем случае нельзя - по цепочке может двигаться только в одном направлении: от начала к концу. Как совершается движение - описано ниже.

unit BaseObj;
interface
uses
  uRec;

  TYPE
     PBaseObj = ^TBaseObj;
     TBaseObj = object
      constructor Init;
      destructor Done; virtual;
      procedure Add(ANewOne: POne); virtual;
      procedure PrintAll; virtual;
     private
       Node: POne;
     end;

implementation

constructor TBaseObj.Init;
begin
  {Первая запись всегда - информация о версии}
  Node:=NewOne(cVersion, cVersion, 0);
end;

procedure TBaseObj.Add(ANewOne: POne);
var p: POne;
begin
  if ANewOne = nil then Exit;
  {Находим последнюю запись}
  p:=Node;
  while  p^.Next <> nil do
       p:=p^.Next;
  p^.Next:=ANewOne;
  p^.Next^.Next := nil
end;

procedure TBaseObj.PrintAll;
var p: POne;
begin
   p:=Node;
   while p <> nil do
   begin
    if Copy(p^.Info.Name,1,3) <> 'Ver' then PrintFullInfo(p);
     p:=p^.Next
   end
end;

destructor TBaseObj.Done;
var p: POne;
begin
  While Node <> nil do
  begin
    p:=Node^.Next;
    Dispose(Node);
    Node:=p
  end
end;

BEGIN
END.

Описание методов объекта:

  • Конструктор constructor TBaseObj.Init; с помощью функции NewOne размещает в памяти одну структуру и заполняет два первых поля ее информацией о версии. По-моему, это простая работа. Конструктор выполняет еще и "скрытую" работу, но об этом позднее.
  • Деструктор destructor TBaseObj.Done; - кроме "скрытой" работы, противоположной работе конструктора, удаляет из памяти всю информацию о структуре. Для этого во временной переменной р сохраняется адрес следующей структуры, а данная удаляется вызовом процедуры Dispose. После этого в переменную Node записывается сохраненный адрес и все повторяется до тех пор, пока не дойдем по конца. А символом окончания цепочки является, напомню, указание на то, что "следующей" структуры нет - поле Next = NIL.
  • Процедура procedure TBaseObj.Add(ANewOne: POne); добавляет в цепочку - связанный список новую структуру. Для этого сначала проверяем, а точно ли есть, что добавлять. Если адрес новой ячейки информации равен NIL, то это сигнал о том, что не надо ничего делать. Вызов процедуры Exit позволяет немедленно "покинуть" тело процедуры. В противном (кому? :)) ) случае ищем конец списка. Для этого сначала делаем копию первого узла. И, пока "следующий" (Next)не окажется равным NIL. А теперь все просто: структура уже находится в динамической памяти ЭВМ. И в поле "следующий" уже записано нужное значение . Мы об этом побеспокоились, когда писали функцию NewOne. Именно вызов ее будет стоять на месте формального параметра при вызове метода. Об этом дальше.
  • Процедура procedure TBaseObj.PrintAll;  работает "просто". Способ ее работы описывался выше. Но дело в том, что когда я написал пример, то сообразил, что лучше использовать другой способ доступа к хранимым элементам. Так что я заменю ее - удалю - порежу -.... . Это будет выглядеть, как в "стандартной" коллекции из системы Turbo Vision - TCollection. Если кто знает. А использовать эту "стандартную" коллекцию не будем - она предназначена для хранения объектов только. Хотя имена методов сохраню.

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

Test.pas

Это и есть "программа". Она только вызывает объект, кое-что в него заносит, кое-что выводит. В общем, рутинная работа ...

program Test;
uses BaseObj, uRec;

var
   BO: PBaseObj;
BEGIN
  Bo:=New(PBaseObj, Init);
  if BO <> nil then
  with BO^ do
  begin
    Add(NewOne('Surin','Борис',12));
    PrintAll;
    Dispose(BO, Done);
  end;
END.
Здесь важно заметить, как объектом пользуются. То, что было описано в других модулях ( в разделе TYPE интерфейсной части модуля, и реализовано в разделе реализаций) - все было фантомом (объекты и есть объекты!). Они не способны работать, так как их нет в памяти компьютера. Для того, чтобы занести, нужно создать экземпляр объекта с помощью таких действий:
  1. Объявляется переменная типа "объект". В моем случае это переменная типа "указатель на объект"
  2. С помощью функции New (под этим именем скрывается и процедура и функция. Ситуация, исключительная для Паскаля) объект размещается в динамической памяти компьютера
  3. Затем проверяем, разместился ли объект в памяти. Для этого проверяем указатель на NIL.
  4. Если все хорошо (не NIL), то работаем. Я просто занес одну запись, а потом вывел на экран.

[ в начало ]

© Борис Сурин

Рассылку поддерживает сайт www.turbopascal.tk

При перепечатке ссылка на сайт обязательна

На первую страницу

Rambler's Top100 Rambler's Top100
PROext: Top 1000

(с)Все права защищены

По всем интересующим вопросам прошу писать на электронный адрес

Hosted by uCoz