| ООП (Объектно-Ориентированное Программирование) | |
| Основные понятия ООП :: Определение объекта :: Наследование :: Использование объектов |
Виртуальные методы :: Конструкторы и деструкторы |
| Динамические объектные типы :: Обработка ошибок при работе с объектами :: Функция 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 сам себе мозги компостирует и ищет чего там вызвать надо.