- •Программирование в Delphi 5
- •Глава 1
- •Основные понятия
- •Создание и уничтожение объектов
- •Инкапсуляция. Свойства
- •Наследование
- •Перегрузка методов
- •Абстрактные методы
- •События и делегирование
- •If Sender is tMenuItem then ShowMessage ('Выбран пункт меню');
- •Обработка сообщений Windows
- •Области видимости
- •Как устроен объект изнутри
Как устроен объект изнутри
Что же представляет собой объект? Ясно, что каждый экземпляр класса содержит отдельную копию всех его полей. Ясно, что где-то в его недрах есть указатели на таблицу виртуальных методов и таблицу динамических методов. А что еще там имеется? И как происходит вызов методов? Вернемся к примеру из раздела данной главы "Полиморфизм":
type
TFirstClass = class FMyFieldl: Integer;
FMyField2: Longint;
procedure StatMethod;
procedure VirtMethodI; virtual;
procedure VirtMethod2; virtual;
procedure DynaMethodI; dynamic;
procedure DynaMethod2; dynamic;
end;
TSecondClass = class(TMyObject) procedure StatMethod;
procedure VirtMethodI; override;
procedure DynaMethodI; override;
end;
Objl: TFirstClass;
Obj2: TSecondClass;
На рис. 1.1 показано, как будет выглядеть внутренняя структура рассмотренных в нем объектов.
Первое поле каждого экземпляра каждого объекта содержит указатель на его класс. Класс как структура состоит из двух частей. Начиная с адреса, на который ссылается указатель на класс, располагается таблица виртуальных методов. Напомним, что она содержит адреса всех виртуальных методов класса. включая унаследованные от предков. Длина таблиц VMT объектов obj1 и obj2 одинакова — по два элемента (8 байт). Перед таблицей виртуальных методов расположена специальная структура, содержащая дополнительную служебную информацию. В ней содержатся данные, полностью характеризующие класс: его имя, размер экземпляра, указатели на класс-предок, имя класса и т. д. На рисунке 1.1 она показана одним блоком; а ее содержимое расшифровано ниже.
Рис. 1.1. Внутренняя структура объектов Obj1 и obj2
Одно из полей структуры содержит адрес таблицы динамических методов класса (DMT). Таблица имеет следующий формат — в начале слово, содержащее количество элементов таблицы; затем — слова, соответствующие индексам методов. Нумерация индексов начинается с —1 и идет по убывающей. После индексов идут собственно адреса динамических методов. Обратите внимание, что DMT объекта objl состоит из двух элементов, obj2 — из одного, соответствующего перекрытому методу DynaMethodi. В случае вызова obj2. DynaMethod2 индекс не будет найден в таблице DMT obj2, и произойдет обращение к DMT objl. Именно так экономится память при использовании динамических методов.
Напомним, что указатель на класс указывает на первый виртуальный метод. Служебные данные размещаются перед таблицей VMT, то есть с отрицательным смещением. Эти смещения описаны в модуле system. pas:
vmtSelfPtr = -76;
vmtIntfTable = -72;
vmtAutoTable = -68;
vmtInitTable = -64;
vmtTypeInfo = -60;
vmtFieldTable = -56;
vmtMethodTable = -52;
vmtDynamicTable = -48;
vmtClassName = -44;
vmtInstanceSize = -40;
vmtParent = -36;
vmtSafeCallException = -32;
vmtAfterConstruction = -28;
vmtBeforeDestruction = -24;
vmtDispatch = -20;
vmtDefaultHandler = -16;
vmtNewInstance = -12;
vmtFreeInstance = -8;
vmtDestroy= -4;
Нам уже ясен cмысл полей vmDynamicTable, vmtDispatch И vmtDefaultHandler — они отвечают за вызов динамических методов. Поля vnitNewInstance, vmtFreeInstance И vmtDestroy содержат адреса Процедур создания и уничтожения экземпляра объекта. Поля vmtIntfTable, vmtAutoTable, vmtSafeCailException введены для обеспечения совместимости с моделью объектов СОМ. Остальные же доступны через методы класса TObject: ведь в ObjectPascal информация этой таблицы играет важную роль и может использоваться программистом неявно.
В языке определены два оператора — is и as, неявно обращающиеся к ней. Оператор is предназначен для проверки совместимости по присваиванию экземпляра объекта с заданным классом. Выражение вида:
AnObject is TObjectType
принимает значение True, только если объект AnObject совместим по присваиванию с классом TObjectType, то есть является объектом этого класса или одного из классов, порожденных от него. Кстати, определенная проверка происходит еще при компиляции: если формально объект и класс несовместимы, то компилятор выдаст ошибку в этом операторе.
Оператор as введен в язык специально для приведения объектных типов. С его помощью можно рассматривать экземпляр объекта как принадлежащий к другому совместимому типу:
with ASomeObject as TAnotherType do ...
От стандартного способа приведения типов с помощью конструкции TAnotherType (ASomeObject) использование оператора as отличается наличием проверки на совместимость типов во время выполнения (как в операторе is): попытка приведения к несовместимому типу приводит к возникновению исключительной ситуации EinvalidCast (см. гл. 4). После применения оператора as сам объект остается неизменным, но вызываются те его методы, которые соответствуют присваиваемому классу.
Очень полезным может быть оператор as в методах-обработчиках событий. Для обеспечения совместимости в 99% случаев источник события Sender имеет тип TObject, хотя в тех же 99% случаев им является форма или другие компоненты. Поэтому, чтобы иметь возможность пользоваться их свойствами, применяют оператор as:
(Sender as TControl).Caption := "Thanks!";
Вся информация, описывающая класс, создается и размещается в памяти на этапе компиляции. Возникает резонный вопрос: а нельзя ли получить доступ к ней, не создавая экземпляр объекта? Да, можно. Доступ к информации класса вне методов этого класса можно получить, описав соответствующий указатель, который называется указателем на класс, или указателем на объектный тип (class reference). Он описывается при помощи зарезервированных слов class of. Например, указатель на класс TObject описан в модуле system. раз и называется TCiass:
type
TObject = class;
TCiass = class of TObject;
Аналогичные указатели уже описаны и для других важных классов. Вы можете использовать в своей программе TComponentClass, TControlClass И Т. П.
Указатели на классы тоже подчиняются правилам приведения объектных типов. Указатель на класс-предок может ссылаться и на любые дочерние классы; обратное невозможно:
type TFirst = class
...
end;
TSecond = class(TFirst)
...
end;
TFirstClass = class of TFirst;
TSecondClass = class of TSecond;
var
AFirst : TFirstClass;
ASecond : TSecondClass;
begin
AFirst := TSecond; {допустимо}
ASecond := TFirst; {недопустимо} end.
С указателем на класс тесно связано понятие методов класса. Такие методы можно вызывать без создания экземпляра объекта — с указанием имени класса, в котором они описаны. Перед описанием метода класса нужно поставить зарезервированное слово class:
type
TMyObject = class(TObject)
class function GetSize: string;
end;
var
MyObject: TMyObject;
AString: string;
begin
AString := TMyObject.GetSize;
MyObject := TMyObject.Create;
AString := MyObject.GetSize;
end.
Разумеется, методы класса не могут использовать значения, содержащиеся в полях класса: ведь экземпляра-то не существует! Возникает вопрос: для чего нужны такие методы?
Важнейшие методы класса определены в самом TObject: они как раз и позволяют, не углубляясь во внутреннюю структуру класса, извлечь оттуда практически всю необходимую информацию (табл. 1.1).
Таблица 1.1. Методы класса TObject
Метод |
Описание |
class function ClassName: ShortString; |
Возвращает имя класса |
class function ClassNameIs(const Name: string): Boolean; |
Возвращает True, если имя класса равно заданному |
class function CiassParent: TClass; |
Возвращает указатель на родительский класс |
class function Classlnfo: Pointer; |
Возвращает указатель на структуру с дополнительными данными об опубликованных методах и свойствах |
class function InstanceSize: Longint; |
Возвращает размер экземпляра класса |
class function Inherits From(AClass: TClass): Boolean; |
Возвращает True, если данный класс наследует от заданного параметром AClass |
class function MethodAddress(const Name: ShortString): Pointer; |
Возвращает адрес метода по его имени (только для опубликованных методов) |
class function MethodName(Address: Pointer): ShortString; |
Возвращает имя метода по его адресу (только для опубликованных методов) |
В следующем примере переменная AMyObjRef является указателем на класс; он по очереди указывает на TObject и TMyObject (то есть на их внутренние структуры). Посредством этой переменной-указателя вызывается функция класса className, которая извлекает имя класса из его поля vmtClassName. Обратите внимание, что эту функцию не нужно переопределять для каждого порожденного класса:
type
TMyObject = class;
TMyObjClass = class of TObject;
var AMyObjRef : TMyObjClass;
s: strings; begin
AMyObjRef := TObject;
s := AMyObjRef.ClassName;
{ s := 'TObject'}
AMyObjRef := TMyObject;
s:=AMyObjRef.ClassName;
{s := "TMyObject"} end.
Начиная с версии Delphi 4, в класс TObj ect добавлены еще два виртуальных метода — Afterconstruction и BeforeDestruction. Как следует из названия, они вызываются сразу после создания экземпляра объекта и непосредственно перед его уничтожением. Их можно использовать, если по каким-либо причинам вам не хватает собственно конструктора и деструктора.
Резюме
Не зная принципов создания и функционирования объектов, нечего и думать о серьезной работе в Delphi. Материал данной главы проецируется практически на все последующие. Однако чаще всего придется вспоминать ее:
в главе 4, где описана работа с исключительными ситуациями;
в главе 8, в которой описаны графические объекты;
в части 5, где рассказано о другой модели объектов — СОМ. Программисты фирмы Inprise включили ее полноценную поддержку в Delphi;
в частях 3 и 4, при описании объектов для работы с базами данных.
|
