ИмяПеременнойОбъекта.ИмяКомпонента
причем так осуществляется прямой доступ и к полям, и к методам, и к свойствам, если конечно они не инкапсулированы.
ПРИМЕР. Вариант модуля для работы с однонаправленными линейными списками, использующий понятие класс, и пример использования этого модуля – построение ряда Фаррея.
В нижеприведенном варианте модуля удалось устранить недостатки, отмеченные для вариантов, ранее рассмотренных в теме «Модули».
Теперь модуль L1List позволяет работать одновременно с несколькими экземплярами типа «Линейный однонаправленный список». Можно, например, описать 3 списка, создать их, и независимо их обрабатывать:
VAR Sp1,Sp2,Sp3:L1List.CL1List; … Sp1:=L1List.CL1List.Create; … Sp2:=L1List.CL1List.Create; … Sp3:=L1List.CL1List.Create;
… Sp1. InsPred … Sp2. InsPred … Sp3. InsPred …
… Sp1.GetNext; … Sp2.GetNext; … Sp3.GetNext; …
При этом не возникает необходимости, ни в тиражировании модуля, ни в его параметризации – механизм тиражирования и параметризации встроен в семантику (и синтаксис) понятий класс-объект.
Также достигнут приемлемый уровень защищенности модуля. Правда, «торчат уши» – определения типов TPElem и TElem, но они не открывают прямого доступа к приватным полям PFirst,PCur,PPred и не открывают возможностей внешнего вмешательства в функционирование инструментов модуля.(*)
PROGRAM\OOP\FARREJ.003\FARREJ.DPR
{Файл UGlob.PAS}
UNIT UGlob; { Это модуль "общих понятий" - для межмодульных связей, }
{ его можно сменить и основной модуль L1List, }
{ не требуя каких либо изменений, будет работать }
{ с однонаправленными линейными списками, хранящими не }
{ "числители и знаменатели", а другую информацию. }
INTERFACE
TYPE TVal=RECORD Ch,Zn:INTEGER END;
PROCEDURE WriteTVal(VAR PrmTxtF:TextFile;PrmVal:TVal);
IMPLEMENTATION
PROCEDURE WriteTVal; BEGIN WRITE(PrmTxtF,PrmVal.Ch,':',PrmVal.Zn)
END;
END.
{Файл L1List.PAS}
UNIT L1List; { Это основной модуль примера - вариант инструментария}
{ для работы с однонаправленными линейными списками. }
INTERFACE
USES UGlob;
TYPE TPElem=^TElem; TElem=RECORD Inf:UGlob.TVal; Next:TPElem END;
CL1List=CLASS {Определение класса}
PUBLIC {Общедоступные компоненты: поле CurVal и методы}
{ResetList,EOList,ExistNext,GetNext,InsPred,WriteAll и Create}
CurVal:UGlob.TVal; { отсюда можно взять информацию текущего }
{ элемента списка - "числитель и знаменатель" }
PROCEDURE ResetList;{Текущим устанавливается 1-й элемент }
{Его значение засылается в CurVal}
FUNCTION EOList: BOOLEAN;
{Проверяет наличие текущего элемента в списке}
FUNCTION ExistNext: BOOLEAN;
{Проверяет наличие следующего (за текущим) элемента в списке}
PROCEDURE GetNext;
{Текущим устанавливается следующий элемент списка}
{Его значение засылается в CurVal}
PROCEDURE InsPred(ValEl:UGlob.TVal);
{Вставляет перед(!) текущим новый элемент со значением ValEl}
{текущим остается тот, который был до, но предыдущим - новый}
PROCEDURE WriteAll(VAR TxtF:TextFile);
{Выводит в TxtF все элементы списка}
{!!! TxtF должен быть открыт для записи}
CONSTRUCTOR Create; {Конструктор - специализированный метод}
{для создания объектов типа CL1List }
PRIVATE {Приватные компоненты - поля}
PFirst{ссылка на 1-й},PCur{ссылка на текущий},
PPred{ссылка на предшествующий текущему - приходится хранить,}
{т.к. есть желание иметь возможность вставлять новый }
{элемент не после, а перед текущим}:TPElem;
END;
IMPLEMENTATION { В секции реализации описаны реализации методов}
{класса CL1List. !!!Имена процедур и функций уточнены именем класса}
PROCEDURE CL1List.ResetList; BEGIN PPred:=NIL; PCur:=PFirst;
IF PCur<>NIL THEN CurVal:=PCur^.Inf
END;
FUNCTION CL1List.EOList; BEGIN EOList:=(PCur=NIL) END;
FUNCTION CL1List.ExistNext;
BEGIN ExistNext:=(PCur<>NIL)AND(PCur^.Next<>NIL) END;
PROCEDURE CL1List.GetNext; BEGIN
IF PCur<>NIL THEN BEGIN PPred:=PCur; PCur:=PCur^.Next;
IF PCur<>NIL THEN CurVal:=PCur^.Inf
END
END;
PROCEDURE CL1List.InsPred; VAR p:TPElem; BEGIN
NEW(p); p^.Inf:=ValEl; p^.Next:=PCur;
IF PPred=NIL THEN PFirst:=p ELSE PPred^.Next:=p; PPred:=p
END;
PROCEDURE CL1List.WriteAll; VAR p:TPElem; BEGIN p:=PFirst;
WHILE p<>NIL DO BEGIN
UGlob.WriteTVal(TxtF,p^.Inf); WRITE(TxtF,';'); p:=p^.Next
END; WRITELN(TxtF)
END {отметим, что при этом не "сбиты" значения PCur,PPred и т.д.};
CONSTRUCTOR CL1List.Create; BEGIN {Конструктор - }
{специализированный метод для создания объектов типа CL1List, }
{устанавливает значение списка пустым, }
{но(!!!) его семантика не исчерпывается этим действием }
PFirst:=NIL;PCur:=NIL;PPred:=NIL
END;
END.
{Файл Farrej.DPR}
{$B-,D+,I+,Q+,R+}
PROGRAM Farrej; { Это программа построения ряда Фаррея F(n), по }
{ алгоритму: F(1) = 0:1,1:1; F(i) строится по F(i-1)}
{ вставками между каждой парой соседних элементов }
{ a:b и c:d, таких что (b+d)=i, нового }
{ элемента (a+c):(b+d). }
USES SysUtils,UGlob,L1List;
VAR RFarrej:L1List.CL1List {Объект RFarrej объявлен, но не создан};
PredCur,x:UGlob.TVal; FIn,FOut:TextFile; i,n:INTEGER;
BEGIN RFarrej:=L1List.CL1List.Create;
{Вызов конструктора создает объект}
{Имя конструктора уточняется именем класса,}
{а ниже - имя объекта уже связано с объектом, т.к. он создан}
x.Ch:=0;x.Zn:=1;RFarrej.InsPred(x);
x.Ch:=1;{x.Zn:=1;}RFarrej.InsPred(x) {построили F(1)};
AssignFile(FIn,'FIn.TXT'); RESET(FIn); READ(FIn,n); CloseFile(FIn);
AssignFile(FOut,'FOut.TXT'); REWRITE(FOut); WRITELN(FOut,n);
FOR i:=2 TO n DO BEGIN {Строим F(i)}
RFarrej.WriteAll(FOut) {протокол для проверки - вывели F(i-1)};
RFarrej.ResetList {установились на начало списка};
REPEAT PredCur:=RFarrej.CurVal; RFarrej.GetNext{получили пару};
IF (PredCur.Zn+RFarrej.CurVal.Zn)=i THEN BEGIN
x.Ch:=PredCur.Ch+RFarrej.CurVal.Ch; x.Zn:=i;
RFarrej.InsPred(x) {вставили новый}
END
UNTIL NOT RFarrej.ExistNext;
END; RFarrej.WriteAll(FOut); CloseFile(FOut)
END.
Конструкторы и деструкторы.
Конструкторы и деструкторы являются специализированными методами класса. Синтаксис описания конструкторов и деструкторов отличается от синтаксиса описания процедур только ключевым словом CONSTRUCTOR и DESTRUCTOR вместо PROCEDURE. Имеется некоторая синтаксическая странность – конструктор описывается как процедура, а вызывается обычно как функция. Дело в том, что и в описании и в вызове имя конструктора уточняется именем класса, который и является типом значения, которое вырабатывает конструктор.
Назначение конструктора – «создать объект» и далее выполнить модифицированное тело. Назначение деструктора – выполнить модифицированное тело и далее «уничтожить объект». Из чего происходит их необходимость?
Описание переменной типа класс объявляет объект типа класс, но не создает его:
в начальном приближении это можно понимать так – описание (статической) переменной всегда подразумевает выделение памяти для хранения ее значения, но не присваивает ей никакого «нормального» значения (это надо делать отдельно, с помощью оператора присваивания или ввода);
более детально и точнее это можно понимать так – классы являются динамическими структурами данных (независимо от вида определения класса), а объекты динамическими переменными (или возможно, - их значениями), поэтому назначение конструктора – такое же, как у оператора NEW(ПеременнаяТипаСсылка).
наконец, если пытаться объяснять смысл через его реализацию средствами более низкого уровня:
переменная типа класс является классической статической переменной ссылочного типа, память ей выделяется как обычно, но значения она при этом не получает (тоже как обычно);
поэтому, пока она не имеет значения, никак иначе ее нельзя использовать, кроме как одним из допустимых способов присвоить ей значение ссылки на объект (обычно, оператором NEW);
только после этого эту переменную можно использовать для доступа к компонентам объекта.
Неявно конструкторы и деструкторы присутствуют и в классическом языке Паскаль. Конструкторы неявно отрабатывают в начале работы процедур и создают их локальные переменные, а деструкторы неявно отрабатывают в конце работы процедур и уничтожают их локальные переменные.
СХЕМА, ИЛЛЮСТРИРУЮЩАЯ МЕСТОПОЛОЖЕНИЕ ФРАГМЕНТОВ ОБЛАСТИ ВИДИМОСТИ ОБЩЕДОСТУПНЫХ (PUBLIC) КОМПОНЕНТОВ КЛАССА.
Как уже говорилось выше, область видимости PUBLIC–компонентов состоит из 4 видов фрагментов:
в определении класса;
в описаниях реализации его методов;
в объектах класса;
аналогично, во всех классах-потомках и их объектах…
В нижеприведенной схеме отмечены фрагменты, где можно использовать имена Field, Metod1, Metod2 «видимых» компонентов класса TClass.
UNIT UnitTC; ...
INTERFACE
...
TClass= CLASS ...
PUBLIC
...
Field:
ТипПоля;
Фрагмент
вида
(a)
FUNCTION Metod2 ...
... ...
END;
...
VAR Variable1:TClass; ...
IMPLEMENTATION
. ..
P
Фрагмент
вида
(b)
... Field ...
END;
FUNCTION TClass.Metod2;
... Metod1 ...
E ND;
...
V
AR
Variable2:TClass; ...
.
Фрагмент
вида
(c)
Variable1.Field ... Variable2.Field ...
Variable1.Metod1 ... Variable2.Metod1 ...
Variable1.Metod2 ... Variable2.Metod2 ...
...
E
ND.
UNIT ...
USES ... UnitTC ...
. ..
VAR Variable3:UnitTC.TClass; ...
.
Фрагмент
вида
(c)
Variable3.Field ...
...
Variable3.Metod1 ... Variable3.Metod2 ...
. ..
END.
Кроме того, в любом из вышеприведенных модулей или другом модуле может присутствовать определение класса-потомка
TClass2= CLASS(UnitTC.TClass) ...
PROCEDURE Metod3 ...
END;
в котором имена Field, Metod1, Metod2 не объявлены явно как имена компонентов, но эти компоненты наследуются, и поэтому согласно правилу (d) могут быть использованы
VAR Variable4:TClass2; ...
IMPLEMENTATION
.
Фрагмент
вида
(d
<- b)
P
ROCEDURE
TClass2.Metod3;
... Field ... Metod1 ... Metod2 ...
E ND;
...
V
AR
Variable5:TClass2; ...
...
V
Фрагмент
вида
(d
<- c)
Variable4.Metod1 ... Variable5.Metod1 ...
Variable4.Metod2 ... Variable5.Metod2 ...
. ..
