Помощь - Поиск - Пользователи - Календарь
Полная версия: ООП. Объектно-ориентированное программирование
Форум «Всё о Паскале» > Pascal, Object Pascal > Задачи > FAQ
Altair
Один из подходов в программировании носит название: "Объектно-ориентированное программирование" или, сокращенно, ООП. Идея этого подхода заключается в стремлении связать данные с обрабатывающими эти данные процедурами в единое целое - ОБЪЕКТ.
В Турбо Паскале ООП реализовано начиная с версии 5.5.

ООП основано на трех важных принципах, придающих объектам особые свойства и отличающих их от других типов данных. Рассмотрим их:
  1. Инкапсуляция.
    Объединение в единое целое данных и алгоритмов обработки ЭТИХ данных. В результате инкапсуляции и получается новый тип данных - объект.
  2. Наследование.
    Возможность использования уже определенных объектов для построения иерархии объектов-потомков. Объект-потомок наследует от родителя ВСЕ поля и методы, и может дополнять объекты новыми полями или заменять их.
  3. Полиморфизм.
    Возможность определения единого по имени действия (процедуры или функции), применимого одновременно ко всем объектам иерархии наследования, причем каждый объект может "заказывать" особенность реализации этого действия над "самим собой".
Определение объекта

Сначала может показаться, что при описании нет разницы между объектами и записями. На деле разница заключается в том, что при описании объекта в него могут входить заголовки процедур и функций, ведь по определению объект это "Данные+Алгоритмы".
Перефразируя Вирта, можно сказать что: "Алгоритмы+Структуры Данных=Объекты".

Выглядит описание объекта так:
type
T = object
{ переменные, или ПОЛЯ объекта }
...
{ заголовки процедур и функций, или МЕТОДЫ }
end;


Переменные, описанные в объекте называются полями, а процедуры их обработки - методами.

В самом типе объекта описан лишь интерфейс метода (так же, как при написании модуля в разделе INTERFACE мы описываем только заголовки), т.е. способ его вызова. Сам метод описывается в разделе процедур и функций, и заголовок имеет нестандартную форму (перед его именем обязательно указывается имя типа объекта, к которому этот метод принадлежит):

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.

Итак, рассмотрим эту программу. Это первая наша программа с объектами.

В ней есть 2 объекта - родитель Т1 и потомок Т2.
Я специально сделал так, чтобы в них были методы (процедуры) с одинаковыми именами. Обратите внимание, что мы не описывали в Т2 процедуру Vec, однако, при вызове:
c.Vec

метод заработал. Это произошло, потому что объект T2 является потомком Т1, и он знает все методы и данные своего родителя Т1. И наоборот Т1 не знает, что есть процедура Summ.

На этом примере мы также убедились, что при перекрытии работает тот метод, который был описан позднее (т.е. метод потомка, а не родителя).
То есть при вызове C.Print пошла запись в файл, а не на экран монитора, как есть в методе родителя. Таким образом, при наследовании объект-потомок наследует все методы родителя и перекрывает одноименные.
Altair
Пойдем дальше.

Итак, мы научились создавать простейшие объекты, и объекты потомки, теперь посмотрим для чего нужны директивы Public и Private.

Создадим простую базу данных "Книги", в которая хранит информацию в типизированном файле, а поля записи следующие:
  1. Название книги
  2. Том (если есть)
  3. Автор
  4. Инициалы автора
  5. краткая информация про книгу
  6. Место нахождения книги.
Такую базу данных хорошо использовать дома, т.к. нет лишней информации (год издания, где напечатано и т. д.) и в тоже время есть информация о нахождении книги. Например:
  1. Turbo Pascal
  2. Фаронов
  3. ВВ
  4. программирование на паскале
  5. У меня на полке
Не будем делать базу данных, которая для быстрой работы с базой помещает в память всю информацию из файла. Будем напрямую обращаться к файлу.
(Кодировка текста программы - Cyrillic Win, так что для использования программы Вам придется перекодировать текст в ДОС. Я специально сделал кодировку WIN, чтобы все объяснения делать прямо в программе используя коментарии):
Исходный код
Unit DateBase;
Interface
Uses CRT;
Type
BOOK = record
NAME : string[30];
TOM : string[2];
autor : string[30];
IO : string[2]; { инициалы }
INFO : string[30]; { доп.информ. }
GEO : string[30] { расположение }
end;
BD= object
Private
BDF: file of BOOK;
BDFileName:string;
Function UpStr(s:string):string;
Procedure Add;
Procedure PrintCRT;
Procedure Search;
Public
Procedure Run;
Procedure Init;
Procedure Done;
end;

Implementation

{------- теперь опишем методы------}
Procedure BD.Init; { подготовка к работе }
begin
If paramcount=0 then BDFileName:='C:\BOOKH.BD'
else BDFileName:=paramstr(1);
TextColor(Black); TextBackGround(7); ClrScr;
Assign(BDF,BDFileName); {$I-} Reset(BDF); {$I+}
If IOresult=0 then begin Seek(BDF,0); Exit end
else rewrite(BDF)
end;
{-----------------------------------------------}
Procedure BD.Done; { завершение работы }
begin
CLOSE(BDF); NormVideo; TextBackGround(0); ClrScr; HALT(1)
end;
{-----------------------------------------------}

{Эта функция взята из программы PWLHack,
старой прогрммы для взлома PWL, она хороша тем,
что максимально оптимизирована }
Function BD.UpStr(S:String):String; {перевод строки в верхний регистр}
Var I:Byte;
Begin
For I:=1 To ORD(S[0]) Do Begin
Case S[I] Of
'a'..'z':S[I]:=Chr(Ord(S[I])-$20);
'а'..'п':S[I]:=Chr(Ord(S[I])-$20);
'р'..'я':S[I]:=Chr(Ord(S[I])-$50)
End
End;
UpStr:=S
End;
{-----------------------------------------------}
Procedure BD.Add; { Добавление элементов }
Var POLE:book;
Begin
ClrScr;
GOTOXY(30,10); Write('Введите название книги:');
repeat
GOTOXY(25,11);
Readln(POLE.NAME)
until pole.name<>'';
GOTOXY(30,12); Write('Введите номер тома:');
GOTOXY(25,13); Readln(POLE.TOM);
GOTOXY(30,14); Write('Введите фамилию автора:');
repeat
GOTOXY(25,15); Readln(POLE.autor)
until pole.autor<>'';
GOTOXY(30,16); Writeln('Введите инициалы автора:');
GOTOXY(25,17); Readln(POLE.IO);
GOTOXY(30,18); Writeln('Дополнительная информация о книге:');
GOTOXY(25,19); Readln(POLE.INFO);
GOTOXY(30,20); Writeln('Где находится эта книга');
repeat
GOTOXY(25,21); READln(POLE.GEO)
until pole.geo<>'';
GOTOXY(35,24); Writeln('ЗАПИСЫВАЮ ...');
{------------------------}
Seek(BDF,FileSize(BDF));
Write(BDF,POLE);
GOTOXY(35,25); Writeln('ЗАПИСАЛ.')
end;
{-----------------------------------------------}
Procedure BD.PrintCRT; { печать на экран содержимого файла }
var
i:longint;
POLE:BOOK;
begin
ClrScr;
For i:=0 to FileSize(BDF)-1 do begin
Seek(BDF,i);
read(BDF,POLE);
writeln('---------------------------');
writeln('Название: ',POLE. NAME);
If POLE.TOM<>'' then writeln('том: ',POLE.TOM);
writeln('Автор: ',POLE.autor);
If POLE.IO<>'' then writeln('Инициалы: ',POLE.IO);
If POLE.INFO<>'' then writeln('Доп.Информ: ',POLE.INFO);
writeln('Расположение: ',POLE.GEO);
writeln('---------------------------');
If (i+1) mod 3 = 0 then begin
GOTOXY(20,25); Write('Нажмите клавишу ENTER для продолжения');
readkey; clrscr
end
End;
Write('Нажмите клавишу ENTER для продолжения');
readkey
end;
{-------------------------------------------------------}
Procedure BD.Search; {Поиск записи }
var
i:longint;
POLE:BOOK;
param1,param2,param3,
param4,param5,param6:string;
begin
ClrScr;
GOTOXY(30,10); Write('Введите название книги:');
GOTOXY(25,11); Readln(param1);
GOTOXY(30,12); Write('Введите номер тома:');
GOTOXY(25,13); Readln(param2);
GOTOXY(30,14); Write('Введите фамилию автора:');
GOTOXY(25,15); Readln(param3);
GOTOXY(30,16); Writeln('Введите инициалы автора:');
GOTOXY(25,17); Readln(param4);
GOTOXY(30,18); Writeln('Дополнительная информация о книге:');
GOTOXY(25,19); Readln(param5);
GOTOXY(30,20); Writeln('Где находится эта книга');
GOTOXY(25,21); Readln(param6);
ClrScr;
For i:=0 to (FILESIZE(bdf)-1) do begin
seek(bdf,i);
read(bdf,pole);
If (UPSTR(pole.name)=UPSTR(param1)) or
(UPSTR(pole.TOM)=UPSTR(param2)) or
(UPSTR(pole.autor)=UPSTR(param3))or
(UPSTR(pole.io) = UPSTR(param4)) or
(UPSTR(pole.info)=UPSTR(param5)) or
(UPSTR(pole.geo)=UPSTR(param6)) then begin
Writeln('-----------------------');
Writeln('Найденно: ');
write('Название: '); writeln(pole.name);
if pole.tom<>'' then begin
write('Том : ');writeln(pole.tom)
end;
write('Автор :'); writeln(pole.autor);
write('Инициалы:'); writeln(pole.io);
write('коментарий- '); writeln(pole.info);
write('расположение:');writeln(pole.geo);
writeln('---- для продолжения поиска нажмите любую клавишу ...');
readkey
end
end
end;
{-------------------------------------------------------}
Procedure BD.Run;
const p=true;
var
c:byte;
x,y:integer;
begin
repeat
ClrScr;
textbackground(2);
gotoxy(30,6); write('1. Ввод данных');
gotoxy(30,8); write('2. Вывод данных на экран');
gotoxy(30,10); write('3. Поиск данных');
gotoxy(30,14); write('4. Выход');
textbackground(7);
while not keypressed do;

c:=ORD(readkey);
If c=49 then add;
If c=50 then printCRT;
If c=51 then Search;
If c=52 then BD.DONE
until p=false;
end;

End.


Теперь напишем программу для тестирования этого модуля:
Uses DateBase;
var a:BD;
Begin
a.Init;
a.Run;
a.Done
end.

Все отлично работает. Теперь попробуем не запуская всю программу (базу данных), просто проверить, работает ли метод PrintCRT.

Пишем программу:
Uses DateBase;
var a:BD;
Begin
a.Init;
a.printCRT;
a.done;
end.

Запускаем... Ой, ошибка! Error 44. А почему? А вот почему:
Посмотрите внимательно на наш объект, там есть два зарезервированных слова Public и Private. Это директивы. Рассмотрим их действие.

Директива Private в описании объекта открывает секцию описания скрытых полей и методов. Перечисленные в этой секции элементы объекта "не видны" программисту, если этот объект он получил в рамках библиотечного модуля (TPU). Скрываются обычно те поля и методы, к которым программист не должен иметь непосредственного доступа. В нашем примере это процедуры (т.е. методы) ввода, вывода и сами данные.

Директива Public отменяет действие директивы Private, поэтому все следующие за Public элементы объекта доступны в любой программной единице. По умолчанию устанавливается тип доступа Public.

Соответственно мы можем в нашей программе описать тип объекта-потомка, в котором можем перекрыть любой метод.

Думаю с директивами Public и Private все стало ясно.
Altair
Виртуальные методы

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

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

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

В следующем коротком коде, реализована виртуализация метода.
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.INIT (почему называется конструктор, чуть дальше), запускается метод из объекта потомка _B. При этом этот потомок знает и умеет все, что знал и умел его предок. Значит объект _B "знает" о методе RUN.
Так и есть запускаем его следующей строчкой:
B.run;


Проследим выполнение этого метода. Этот метод, в свою очередь запускает метод P. Теперь смотрим метод P:
Procedure _A.P; begin end;


Вопрос: откуда на экране появилась пятерка.
Ответ: метод P был объявлен виртуальным:
procedure P; virtual;


Если метод объявляется виртуальным, это значит, что объект-родитель сможет использовать метод объекта-потомка (!). Это очень важное правило.

Чтобы убедиться, что это так, просто закомментируйте Virtual в коде (в 2 методах) и запустите новый код; на экране естественно окажется "0", т.к. родитель не может обращаться к методу потомка (т. к. этот метод НЕ является виртуальным, и родитель просто не знает о его существовании).

Как же метод родителя узнаёт, что надо запускать не свой метод, а перекрытый виртуальным? При трансляции объекта, содержащего виртуальные методы, создается так называемая "Таблица Виртуальных Методов - ТВМ" (английское название - VMT: Virtual Methods Table), количество элементов которой равно количеству виртуальных методов. В этой таблице будут храниться адреса точек входа в каждый метод. Такая таблица создается автоматически, с помощью специальной процедуры - конструктора.

Кроме этого, при использовании зарезервированного слова Virtual должны выполняться еще 2 условия:
  1. Если прародитель описывает метод, как виртуальный, то он должен описываться как виртуальный и во всех потомках. Другими словами, виртуальный метод не может заменяться статическим (при попытке это сделать компилятор выдаст ошибку "#149: VIRTUAL expected" - "Ожидается служебное слово Virtual"). Обратное переопределение (преобразование статической функции в виртуальную) вполне допустимо.
  2. Если переопределяется реализация виртуального метода, то его заголовок должен остаться неизменным (т. е. НЕ должны изменяться порядок расположения, количество и типы формальных параметров в одноименных виртуальных методах). При изменении заголовка компилятор выдаст ошибку "#131: Header does not match previous definition" - "Заголовок не соответствует предыдущему описанию"
Конструкторы и деструкторы

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

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

Итак, конструкторы предназначены для создания конкретного экземпляра объекта. При этом создается ТВМ (если в объекте присутствуют виртуальные методы). Если же в объекте нет виртуальных методов, то в нем может не быть и конструктора; хотя использование конструктора при отсутствии виртуальных методов не является ошибкой.

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

При написании объектов рекомендуется создавать деструктор, при этом он удаляет из памяти, все, что там оставил объект. Описывается деструктор так:
Type {....}
Procedure {.....}
Destructor {Name}
{....}


Т.е. деструктор - такой же метод, как и конструктор. Деструктор также может быть пустым.

Программисту все время приходится выбирать: "Каким должен быть тот или иной метод - статическим или виртуальным?" Есть несколько критериев:
  1. Делайте метод виртуальным, если существует вероятность его переопределения. Это должно обеспечить расширяемость программ
  2. Скорость выполнения программы: при наличии в объекте хотя бы одного виртуального метода для него создается VMT и обращения к виртуальным методам происходят через эту таблицу; статические методы вызываются напрямую (без обращения к VMT). Если же объект вообще не содержит виртуальных методов, то VMT для этого типа создана не будет.
Таким образом, выбирать приходится между небольшим увеличением скорости (статические методы) и гибкостью программы (виртуальные методы).
volvo
Динамические объектные типы

Переменные объектного типа могут быть статичекими или динамическими, т.е. располагаться в сегменте данных (статические) или в куче (динамические).
Перепишем нашу программу, используя динамические объекты.
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. В этом случае первым параметром указывается имя типа инициализируемой переменной, а вторым осуществляется вызов метода-конструктора. Такой прием характерен для техники ООП. При этом распределение объектов в динамической памяти происходит одновременно с инициализацией ТВМ.

Обработка ошибок при работе с объектами

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

При этом ссылка на экземпляр объекта становится равной nil.
{ Динамический объект }
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

Часто возникает ситуация, когда необходимо проверить фактический тип экземпляра объекта. Для этого используется функция:
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.


Стартовые значения для объектов

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

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 - параметр, который передается в каждый вызываемый метод объекта, и в большинстве случаев обрабатывается компилятором автоматически (кроме случаев, когда идентификаторы начинают "конфликтовать" в пределах одного метода)

Структура VMT
(подготовлено BlackShadow)

Структура VMT (Таблица виртуальных методов):

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 сам себе мозги компостирует и ищет чего там вызвать надо.
Это текстовая версия — только основной контент. Для просмотра полной версии этой страницы, пожалуйста, нажмите сюда.