Программирование на Delphi
st[1]:=’First’; {Первый способ}
MyList.[2]:=’Second’; {Первый способ}
End;
Употребляя ключевое слово default необходимо соблюдать осторожность, т.к.
для обычных и векторных свойств оно употребляется в разных значениях.
О роли свойств в Delphi красноречиво говорит тот факт, что у всех имеющихся
в распоряжении программиста стандартных классов 100% полей недоступны и
заменены базирующимися на них свойствами. Того же правила следует
придерживаться и при разработке собственных классов.
Наследование
Вторым “столпом” ООП является наследование. Этот простой принцип означает,
что если необходимо создать новый класс, лишь немного отличающийся от уже
имеющегося, нет необходимости в переписывании заново уже существующего
кода. Вы объявляете, что новый класс
tNewClass=class(tOldClass);
является потомком или дочерним классом класса tOldClass, называемого
предком или родительским классом, и добавляете к нему новые поля методы и
свойства.
В Delphi все классы являются потомками класса tObject. Поэтому, если вы
строите дочерний класс прямо от tObject, то в определении его можно не
упоминать. Следующие два описания одинаково верны:
tMyClass=class(tObject);
tMyClass=class;
Более подробно класс tObject будет рассмотрен ниже.
Унаследованные от класса-предка поля и методы доступны в дочернем классе;
если имеет место совпадение имен методов, говорят, что они перекрываются.
Рассмотрим поведение методов при наследовании. По тому, какие действия
происходят при вызове, методы делятся на три группы. В первую группу
отнесем статические методы, во вторую - виртуальные (virtual) и
динамические (dynamic) и, наконец, в третью - появившиеся только в Delphi 4
перегружаемые (overload) методы.
Статические методы, а также любые поля в классах-потомках ведут себя
одинаково: можно без ограничений перекрывать старые имена и при этом менять
тип методов. Код нового статического метода полностью перекрывает (заменяет
собой) код старого метода:
type
tFirstClass=class
fData:Extended;
procedure SetData(aValue:Extended);
end;
tSecondClass=class(tFirstClass)
fData:Integer;
procedure SetData(aValue:Integer);
end;
procedure tFirstClass.SetData(aValue:Extended);
Begin
fData:=1.0;
End;
procedure tFirstClass.SetData(aValue:Extended);
Begin
fData:=1;
inherited SetData(0.99);
End;
В этом примере разные методы с именем SetData присваивают значение разным
полям с именем fData. Перекрытое (одноименное) поле предка недоступно в
потомке. Поэтому два одноименных поля с именем fData приведены только для
примера.
В отличие от поля, внутри других методов перекрытый метод доступен при
указании ключевого слова inherited. По умолчанию методы объектов классов
статические - их адрес определяется еще на этапе компиляции проекта,
поэтому они вызываются быстрее всего.
Принципиально отличаются от статических виртуальные и динамические методы.
Они должны быть объявлены путем добавления соответствующей директивы
dynamic или virtual. С точки зрения наследования методы этих двух категорий
одинаковы: они могут быть перекрыты в дочернем классе только одноименными
методоми, имеющими тот же тип.
Полиморфизм. Виртуальные и динамические методы
Рассмотрим следующий пример. Пусть имеется некое обобщенное поле для
хранения данных - класс tFiled и три его потомка - для хранения строк,
целых и вещественных чисел:
type
tFiled = class
function GetData:string; virtual; abctract;
end;
tStringFiled = class(tFiled)
fData:string;
function GetData: string; override;
end;
tIntegerFiled = class(tFiled)
fData:Integer;
function GetData: string; override;
end;
tExtendedFiled = class(tFiled)
fData:Extended;
function GetData: string; override;
end;
function tStringFiled.GetData: string;
Begin
Result:=fData;
End;
function tIntegerFiled.GetData: string;
Begin
Result:=IntToStr(fData);
End;
function tExtendedFiled.GetData: string;
Begin
Result:=FloatToStr(fData,ffFixed, 7, 2);
End;
function ShowData(aFiled:tFiled): string;
Begin
Form1.Label1.Caption:=aFiled.GetData;
End;
В этом примере классы содержат разнотипные поля данных fData, а также имеют
унаследованный от tFiled виртуальный метод GetData, возвращающий данные в
виде строки. Внешняя по отношению к ним процедура ShowData получает объект
в виде параметра и показывает эту строку.
Согласно правилам контроля соответствия типов (typecasting) ObjectPascal,
объекту, как указателю на экземпляр класса, может быть присвоен адрес
экземпляра любого из дочерних типов. Это означает, что в предыдущем примере
в процедуру ShowData можно передавать объекты классов tStringFiled,
tIntegerFiled, tExtendedFiled и любого другого потомка tFiled.
Но какой (точнее, чей) метод GetData будет при этом вызван? Тот, который
соответствует классу фактически переданного объекта. Этот принцип
называется полиморфизмом.
Возвращаясь к рассмотренному выше примеру, отметим, что у компилятора нет
возможности определить класс объекта, фактически переданного в процедуру
ShowData на этапе компиляции. Механизм, позволяющий определить этот класс
прямо во время выполнения называется поздним связыванием. Естественно,
такой механизм должен быть связан с передаваемым объектом. Для этого служит
таблица виртуальных методов (Virtual Method Table, VMT) и таблица
динамических методов (Dynamic Method Table, DMT).
Различие между виртуальными и динамическими методами заключается в
особенности поиска адреса. Когда компилятор встречает обращение к
виртуальному методу, он подставляет вместо прямого вызова по конкретному
адресу код, который обращается к VMT и извлекает оттуда нужный адрес. Такая
таблица есть для каждого класса. В ней хранятся адреса всех виртуальных
методов класса, независимо от того, унаследованы ли они от предка или
перекрыты в данном классе. Отсюда и достоинства и недостатки виртуальных
методов: они вызываются сравнительно быстро, однако для хранения указателей
на них в таблице VMT требуется большое количество памяти.
Динамические методы вызываются медленнее, но позволяют более экономно
расходовать память. Каждому динамическому методу системой присваивается
уникальный индекс. В таблице динамических методов класса хранятся индексы
только тех методов только тех динамических методов, которые описаны в
данном классе. При вызове динамического метода происходит поиск в этой
таблице. В случае неудачи просматриваются DMT всех классов-предков в
порядке их иерархии и, наконец, tObject, где имеется стандартный обработчик
вызова динамических методов. Экономия памяти очевидна.
Для перекрытия и виртуальных и динамических методов служит директива
override, с помощью которой (и только с ней!) можно переопределять оба этих
типа методов.
type
tParentClass=class
fFirstFiled:Integer;
fSecondFiled:longInt;
procedure StaticMethod;
procedure VirtualMethod1; virtual;
procedure VirtualMethod2; virtual;
procedure DynamicMethod1; dynamic;
procedure DynamicMethod2; dynamic;
end;
tChildClass=class(tParentClass)
procedure StaticMethod;
procedure VirtualMethod1; override;
procedure DynamicMethod1; override;
end;
Первый метод класса tChildClass создается заново, два остальных
перекрываются. Создадим объекты этих классов:
var Obj1: tParentClass;
Obj2: tChildClass;
Внутренняя структура этих объектов показана ниже. [pic]
Первое поле каждого экземпляра каждого объекта содержит указатель на его
класс. Класс, как структура состоит из двух частей. Начиная с адреса, на
который ссылается указатель на класс, располагается таблица виртуальных
методов. Она содержит адреса всех виртуальных методов класса, включая и
унаследованные от предков. Перед таблицей виртуальных методов расположена
специальная структура, содержащая дополнительную информацию. В ней
содержатся данные, полностью характеризующие класс: имя, размер
экземпляров, указатели на класс-предок и т.д. Одно из полей структуры
содержит адрес таблицы динамических методов класса (DMT). Таблица имеет
следующий формат: в начале - слово, содержащее количество элементов
таблицы. Затем - слова, соответствующие индексам методов. Нумерация
индексов начинается с –1 и идет по убывающей. После индексов идут
собственно адреса динамических методов. Следует обратить внимание на то,
что DMT объекта Obj1 состоит из двух элементов, Obj2 - из одного,
соответствующего перекрытому методу DynamicMethod1. В случае вызова
Obj2.DynamicMethod2 индекс не будет найден в DMT Obj2, и произойдет
обращение к DMT Obj1. Именно так экономится память при использовании
динамических методов.
Как указывалось выше, указатель на класс указывает на первый виртуальный
метод. Служебные данные размещаются перед таблицей виртуальных методов, то
есть с отрицательным смещением. Эти смещения описаны в модуле SYSTEM.PAS:
vmtSelfPtr = -76
vmtIntfTable = -72
vmtAutoTable = -68
vmtInitTable = -64
vmtTypeInfo = -60
vmtFiledTable = -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
Поля vmtDynamicTable, vmtDispatch и vmtDefaultHandler отвечают за вызов
динамических методов. Поля vmtNewInstance, vmtFreeInsta
| | скачать работу |
Программирование на Delphi |