ООП (Объектно-Ориентированное Программирование) | |
Основные понятия ООП :: Определение объекта :: Наследование :: Использование объектов |
Виртуальные методы :: Конструкторы и деструкторы |
Динамические объектные типы :: Обработка ошибок при работе с объектами :: Функция TypeOf :: Стартовые значения для объектов :: Структура VMT |
|
type T = object { переменные, или ПОЛЯ объекта } ... { заголовки процедур и функций, или МЕТОДЫ } end;Переменные, описанные в объекте называются полями, а процедуры их обработки - методами.
procedure ObjectType.ProcedureName(...);Т.е <имя_объекта>.<имя_процедуры>
type TA = object a: Integer; procedure give(x:integer); end; { ... } procedure TA.give(x: integer); begin a := a + x; end;Так как задание заголовка метода в описании типа объекта является опережающим описанием, то так же, как при реализации процедур и функций в разделе Implementation, список параметров может опускаться, то есть, такое описание будет полностью аналогично предыдущему:
procedure TA.give; begin a := a + x; end;Переменные типа "объект" можно создавать как обычно, объявлением в списке переменных (при таком способе для использования экземпляров объектов - переменных объектного типа - в программе, их вызывают так: <имя_объекта>.<имя_метода>):
type TA = object ... end; var a: TA; begin a.give(5); end.но большее распространение получил метод их размещения в динамической памяти. Для этого нужно создать дополнительный тип - указатель на объект (в таком случае для обращения к полям или методам объекта указатель надо разыменовать):
type PTA = ^TA; TA = object ... end; var p_a: PTA; begin new(p_a); p_a^.give(5); end.При описании объекта-наследника (также называемого производным типом), имя его родителя указывается в скобках. Например:
TA = object { поля и методы объекта TA } end; TA1 = object(TA) { потомок TA } { ... } end;Поля и методы родителя могут появляться в реализации методов наследника, как если бы они были описаны явно в самом наследнике. Процесс наследования является транзитивным: если TB наследуется от TA, а TC в свою очередь - наследник TB, то тип TC также считается наследником TA.
type TA = object x: integer; {...} end; TB = object(TA) y: integer; {...} end; var a: TA; b: TB;для копирования значения X записанного в переменной b в переменную a достаточно выполнить присваивание:
a := b;Внимание: Операция присваивания возможна только таким путем: "Родитель <-- Наследник"
uses crt; { Глобальная константа } const NN = 10; type { это наш массив, на основе которого будем делать объекты } atype = array[1 .. nn] of integer; T1 = object a:atype; n:integer; procedure Vec; { процедура ввода массива } procedure Print; { процедура вывода массива } end; T2 = object(T1) s:integer; { сумма } procedure Print; { перекроем новым методом, посмотрим результат } procedure Summ; { посчитаем сумму эл-тов } end; { ---------- теперь опишем методы ------- } procedure T1.Vec; var c, i: Integer; begin repeat write('Введите длинну массива n = '); readln(n) until (N > 0) and (n <= NN); for i := 1 to n do begin write('a[',i,'] = '); readln(a[i]) end end; { ----- } procedure T1.Print; var i: Integer; begin WriteLn; for i := 1 to n do write(a[i]:6); WriteLn end; { ----- } procedure T2.Print; var F:text; fn:string; i:integer; begin Writeln('Введите имя файла для записи туда массива'); ReadLn(fn); Assign(f, fn); ReWrite(f); for i := 1 to n do WriteLn(f,a[i]); Close(f) end; { ----- } procedure T2.Summ; var i: integer; begin s := 0; for i := 1 to n do s := s + a[i]; end; { ----- } var b: T1; c: T2; begin { Теперь проверим работу объектов. При вызове Т2, должны быть доступны ВСЕ методы объекта родителя, т.е. Т1, но метод PRINT перекрывается } с.Vec; b.Print; c.Print; c.Summ; end.Итак, рассмотрим эту программу. Это первая наша программа с объектами.
c.Vecметод заработал. Это произошло, потому что объект T2 является потомком Т1, и он знает все методы и данные своего родителя Т1. И наоборот Т1 не знает, что есть процедура Summ.
uses crt; Type _A = Object per:integer; constructor Init; procedure P; virtual; procedure run; end; _B = Object(_A) constructor INIT; procedure P; virtual; end; constructor _A.INIT; begin per:=0 end; procedure _A.P; begin end; procedure _A.RUN; begin P end; constructor _B.INIT; begin per:=0 end; Procedure _B.p; begin per:=5; writeln(per) end; Var A:_A; B:_B; Begin Clrscr; B.INIT; B.run; end.Запустим ее, и увидим, что на экране окажется "5". Проследим выполнение программы...
B.run;Проследим выполнение этого метода. Этот метод, в свою очередь запускает метод P. Теперь смотрим метод P:
Procedure _A.P; begin end;Вопрос: откуда на экране появилась пятерка?
procedure P; virtual;Если метод объявляется виртуальным, это значит, что объект-родитель сможет использовать метод объекта-потомка (!). Это очень важное правило.
Type {....} Procedure {.....} Destructor {Name} {....}Т.е. деструктор - такой же метод, как и конструктор. Деструктор также может быть пустым.
3. Динамические объектные типы
uses crt; Type _A = Object per:integer; constructor Init; procedure P; virtual; procedure run; end; _B = Object(_A) constructor INIT; procedure P; virtual; end; constructor _A.INIT; begin per:=0 end; Procedure _A.P; begin end; procedure _A.RUN; begin P end; constructor _B.INIT; begin per:=0 end; Procedure _B.p; begin per:=5; writeln(per) end; Var A:_A; B:_B; Begin Clrscr; B:=New(_B, INIT); B^.run; end.Инициализация динамической переменной B реализуется с помощью функции New. В этом случае первым параметром указывается имя типа инициализируемой переменной, а вторым осуществляется вызов метода-конструктора.Такой прием характерен для техники ООП. При этом распределение объектов в динамической памяти происходит одновременно с инициализацией ТВМ.
Обработка ошибок при работе с объектами
{ Динамический объект } Type arrType = Array[1 .. 1000] of Integer; PTArray = ^TArray; TArray = Object A: ^arrType; Constructor Init; Destructor Done; Virtual; ... End; Constructor TArray.Init; begin new(A); { Если будет обнаружена нехватка памяти } if A = nil then begin TArray.Done; Fail { Производим откат } end; end; var arr: PTArray; begin new(arr, init); { Если в конструкторе обнаружилась нехватка памяти } If arr = nil then halt(1); { выйти из программы } end.Также нехватка памяти может произойти при использовании статических объектов с полями, размещаемыми в динамической памяти. В этом случае при вызове Fail передать nil в ссылку невозможно (объект-то статический), и выходом является использование самого имени конструктора как логической функции:
{ Статический объект } Type arrType = Array[1 .. 1000] of Integer; TArray = Object A: ^arrType; Constructor Init; Destructor Done; Virtual; ... End; Constructor TArray.Init; begin new(A); { Если будет обнаружена нехватка памяти } if A = nil then begin TArray.Done; Fail { Производим откат } end; end; Var arr: PTArray; begin If not arr.Init { Если в конструкторе обнаружилась нехватка памяти } Then halt(2); { выйти из программы } end.Часто возникает ситуация, когда необходимо проверить фактический тип экземпляра объекта. Для этого используется функция:
TypeOf(ИмяЭкземпляра_или_ИмяТипа): Pointer;, которая возвращает указатель на таблицу VMT для этого экземпляра или типа объекта... Параметром, передаваемым в функцию должен быть экземпляр или тип, имеющий VMT, иначе произойдет ошибка.
type a = object p: integer; constructor init; procedure x; virtual; end; b = object(a) constructor init; end; constructor a.init; begin end; constructor b.init; begin end; procedure a.x; begin end; procedure check(var x: a); begin if typeof(x) = typeof(a) then writeln('the object is A') else writeln('the object is B') end; var _a: A; _b: B; begin _a.init; _b.init; check(_a); check(_b); end.
Стартовые значения для объектов
Type a = object x, y: integer; constructor init; ... destructor done; virtual; end; const a_1: a = (x: 3; y: 4); a_2: a = (x: 4; y: 7);Обратите внимание, что после такого метода инициализации можно обращаться к виртуальным методам без предварительного вызова конструктора (только для экземпляра, инициализированного через Const !!!), так как компилятор автоматически обрабатывает инициализацию и создает VMT.
Type a = object _x, _y: integer; constructor Copy(x: a); ... end; constructor a.Copy(x: a); begin self := x end; const c_a: a = { инициализация начальных значений объекта } ... { далее в программе можно использовать инициализацию вида: } var v_a: a; ... v_a.Copy(c_a);где Self - параметр, который передается в каждый вызываемый метод объекта, и в большинстве случаев обрабатывается компилятором автоматически (кроме случаев, когда идентификаторы начинают "конфликтовать" в пределах одного метода). (подготовлено BlackShadow)
SizeOfInstance:Integer; {Размер экземпляра класса} MSizeOfInstance:Integer; { = -SizeOfInstance} DMTOffset:Word; {Смещение DMT} Zero:Word; {Всегда 0} Methods:Array Of Pointer {Адреса методов}Структура DMT (Таблица динамических методов):
ParentDMTOffset:Word; {Смещение DMT класса-родителя} {Кэшируемый индекс - индекс последнего вызванного метода} CacheIndex:Word; {Смещение в этом сегменте Pointer'а, который хранит адрес последнего вызванного метода } CacheAddr:Word; TableSize:Word; {Кол-во эл-тов в таблице} Indexes:Array Of Word; Methods:Array Of Pointer;Из структуры таблицы ясно видно (из документации не менее ясно), что все методы вызываются как Far вне зависимости от {$F...} Раскажу ещё как какой метод можно вызвать.
LES DI,[Instance] PUSH ES PUSH DI { Self передаём } CALL TypeName.MethodName
LES DI,[Instance] PUSH ES PUSH DI MOV DI,[ES:DI + VMTOffset] {VMTOffset = сумма размеров всех полей "самого базового" класса} CALL [DWORD PTR DI + OffsetOfMethodInVMT] {= 4*номер по счёту}
LES DI,[Instance] PUSH ES PUSH DI MOV DI,[ES:DI + VMTOffset] MOV AX,MethodIndex PUSH AX CALL DispatchИ пусть этот Dispatch сам себе мозги компостирует и ищет чего там вызвать надо.