Это метод программирования, опирающийся на структурную организацию программы

Вид материалаДокументы
ОСОБЕННОСТИ ЯЗЫКА OBJECT PASCAL (среда Delphi, версия 2.0 и последующие).
Для виртуальных методов синтаксис изменен
Подобный материал:
1   2   3   4   5

ОСОБЕННОСТИ ЯЗЫКА OBJECT PASCAL (среда Delphi, версия 2.0 и последующие).


Классы, объекты, поля и методы.

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

Пример описания класса и переменной соответствующего типа:

type

tMyObject=

class(tObject)

fMyField1:Integer;

procedure MyMethod1;

function MyMethod2(X:real):real;

end;

var aMyObject:tMyObject;

Переменная такого типа (в нашем случае aMyObject) называется экземпляром класса, или объектом. Поля и методы значат то же, что и в Turbo Pascal. Перед именем полей обычно ставят букву f (сокращение от field — "поле").

Тут и в дальнейшем имена типа MyObject, fMyField, MyMethod и т.п. используются для того, чтобы явно подчеркнуть произвольность соответствующего имени и не путать такие имена с зарезервированными словами или предопределенными типами.


Свойства.

В Delphi пожелание ООП "не обращаться из программы к полям, а действовать через методы" нашло отражение в новом для ООП принципе — свойствах объекта.

Свойство определяется:
  • полем, некого типа,
  • методом по чтению этого поля (функцией) того же типа,
  • методом по записи в поле (процедурой) с параметром того же типа.

type

tMyObject=

class(tObject)

fMyField:tMyType;

function getMyField:tMyType;

procedure setMyField (NewValue:tMyType);

property MyProperty:tMyType

read getMyField

write setMyField;

end;

Имена методов, конечно, задаются пользователем произвольно, и вместо этих имен могли бы, к примеру, использоваться Method1 и Method2. Тела методов getMyField и setMyField как и для всех других методов, должны быть описаны в разделе реализации. При этом у функции getMyField не должно быть параметров, а у setMyField – один параметр, через который передается (по имени или по значению) устанавливаемый параметр. Методы по чтению и записи не нужно явно вызывать в программе. Если имеются переменные, описанные как

var aMyObject:tMyObject;

aValue,aVariable:tMyType;,

то можно писать

aMyObject.MyProperty:=aValue;

aVariable:=aMyObject.MyProperty;

То есть внешне свойства выглядят как поля данных, но при чтении или записи значений в такие поля реально вызываются методы для их чтения или записи. Например, можно осуществлять проверки правильности ввода значений, а также некие другие действия (если свойство — координата фигуры, можно при присваивании координате нового значения вызвать перемещение фигуры по экрану). Если свойство должно только читаться или только записываться, может присутствовать лишь соответствующий метод (вызов другого приведет к диагностике ошибки при компиляции):

type

tAnObject=

class(tObject)

fMyField:tSomeType;

function GetValue:tSomeType;

property MyProperty:tSomeType

read GetValue;

end;

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

tSomeType

read myField

write myField;

Заметим, что свойство и его методы по чтению и записи, а также соответствующее поле данных обычно располагают в разных областях видимости: свойство делают published или public, а методы и поле данных – private или protected (см. далее).

Свойству можно присвоить значение по умолчанию с помощью ключевого слова default:

property active:Boolean

read GetActive

write SetActive {тут нет ’;’ !!!}

default true;

Свойство может выглядеть как массив (т.е. быть векторным):

property MyArray[I:Integer]:tMyType

read GetMyValue

write SetMyValue;

При этом функция GetMyValue должна быть совместима по типу с tMyType и иметь единственный параметр с тем же типом и именем, что и индекс свойства (обычно — целый тип):

function GetMyValue(I:Integer):tMyType;

Метод для записи должен первым параметром иметь индекс, а вторым — переменную нужного типа (ее можно передавать как по имени, т.е. с var, так и по значению):

procedure SetMyValue(I:Integer;NewValue:tMyType);

Можно определить некое главное, основное векторное свойство класса как default, и не упоминать его при доступе к этому свойству объекта по индексу. Не путать со значением default для свойства!

Пример: свойство Strings, являющееся массивом строк, определенное как default свойство для класса tMyObject:

type

tMyObject=

class(tObject)

...

property Strings[I:Integer]:string

read GetS

write PutS; {!!!после PutS обязательно ";"}

default;

...

end;

var aMyObject:tMyObject;

begin

...

aMyObject.Strings[1]:='обычный способ';

...

aMyObject[2]:='сокращенная форма';

...

end.

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

В Delphi предусмотрен еще один способ передачи параметров в процедуру: передача константы по имени (пишется директива const вместо var перед именем переменной). Все происходит так же, как при директиве var, но при этом внутри процедуры не может быть изменено значение этой переменной (как при передаче по значению). На этапе компиляции отслеживаются ошибочные попытки изменить значения переменной путем присвоения или передачи параметра по имени в какую-либо процедуру.

Пример двумерного свойства:

property MyMatrix[Row,Col:Integer]:real

read GetElement

write SetElement;

Описание методов в этом случае может быть таким:

function GetElement(Row,Col:Integer):real;

procedure SetElement(Row,Col:Integer; const NewValue:Real);

Если многомерное свойство определено как default для данного класса, то к нему можно обращаться по индексу через имя объекта аналогично случаю одномерного векторного свойства.


4.10. Создание и уничтожение объектов. Конструкторы и деструкторы.

В Object Pascal объекты могут быть только динамическими! Любая переменная объектного типа — это указатель, но доступ к объекту идет без 'шляпки', а создание нового объекта осуществляется не оператором New, а с помощью вызова конструктора — функции, возвращающей указатель на создаваемый объект. Соответственно, уничтожение объекта производится не с помощью Dispose, а вызовом деструктора. Поэтому обычно в Delphi конструктор называют Create ("создать"), а деструктор — Destroy ("уничтожить, разрушить"):

aMyObject:=tMyObject.Create; {создали объект aMyObject типа tMyObject

и запомнили указатель на него}

...

aMyObject.Destroy; {уничтожили объект}

Заметим, что конструктор — это метод класса, и он работает без экземпляра объекта (экземпляра еще нет, но метод создает его). В Delphi конструкторы могут быть виртуальными, и их может быть несколько. Сразу при входе в конструктор желательно проинициализировать прародителя:

constructor tMyObject.Create;

begin

inherited Create;

...

end;

В данном примере инициализация прародителя осуществляется с помощью специального ключевого слова inherited, которое появилось еще в TurboPascal 7.0. Оно означает, что следующий за ним вызов относится к непосредственному прародителю данного объекта. Многократные вызовы типа inherited inherited запрещены, чтобы не вызвать неправильной инициализации объекта.

Если наш класс описан как

tMyObject=class(tObj1)

...

end;

то вызов inherited Create абсолютно логичен вызову tObj1.Create.


4.11. Наследование. Статические, виртуальные, динамические и абстрактные методы.

Все классы Delphi — потомки класса tObject. Если нет указания, какой класс является прародителем, значит, это tObject. Определение

tMyObject=class

...

end;

полностью равносильно

tMyObject=class(tObject)

...

end;

Вызываемые методы делятся на статические (static), виртуальные (virtual) и динамические (dynamic). Есть еще методы, которые не содержат никаких действий (нет реализации), никогда не вызываются и нужны только для наследования — абстрактные (abstract). Абстрактными могут быть только виртуальные или динамические методы, причем у них имеется только заголовок (в описании класса) и отсутствует реализация.

Переопределение методов в потомках называется перекрытием этих методов, а сами переопределенные методы прародителя — перекрытыми (override) потомком.

Перекрытие статических методов происходит так же, как в Turbo Pascal. Для виртуальных методов синтаксис изменен: в первом из прародителей, где определяется виртуальный метод, ставится ключевое слово virtual, а в потомках для перекрывающих методов — override.

Для виртуальных методов в классе постоянно хранятся адреса всех имеющихся в данном классе виртуальных методов независимо от того, унаследованы они или перекрыты. Виртуальные методы вызываются быстро, но таблица виртуальных методов (VMT) может занимать много места, если имеется длинная цепочка наследования с большим количеством методов в каждом классе. Динамические методы являются более экономичными по памяти и более медленным аналогам виртуальных. Каждому динамическому методу системой присваивается уникальный индекс. В таблице динамических методов (DMT) класса хранятся индексы и адреса только тех динамических методов, которые описаны в данном классе. Если при вызове нужный метод в таблице не найден, просматривается таблица прародителя, и т.д. вплоть до tObject. В остальном разницы между виртуальными и динамическими методами нет. Стоит отметить, что Delphi предоставляет существенно больший набор средств программирования, чем, например, Java, где ни статических, ни виртуальных методов нет, а только динамические.

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

Пример использования виртуальных и абстрактных методов:

type

tField=

class function GetData:string;virtual;abstract; {нужна для работы

ShowData1}

tStringField=

class(tField)

fData:string;

function GetData:string;override;{перекрыли абстрактный метод}

end;

tIntegerField=

class(Field)

fData:Integer;

function GetData:string;override; {то же}

end;

...

{-реализация перекрывающих методов-}

function tStringField.GetData:string;

begin

GetData:=fData; {присваивание строкового поля}

end;

function tIntegerField.GetData:string;

begin

GetData:=IntToStr(fData); {преобразование из целого в строку}

end;

...

{-процедура, которая работает для разных типов полей-}

procedure ShowData(aField:tField);

begin

Form1.Label1.Caption:=aField.GetData;{вызов метода, который для

класса tField абстрактный}

end;

Надо отметить, что на этапе написания процедуры ShowData мы используем алгоритм для экземпляра класса, в котором описан наш абстрактный метод. Такой класс логично называть абстрактным и считать, что экземпляров данного класса быть не может. На деле, вместо экземпляров абстрактного класса будут подставляться экземпляры классов-потомков, в которых все абстрактные методы перекрыты. А формальное использование объекта из прародительского класса дает возможность писать единый алгоритм для всех классов-потомков разветвляющейся иерархии с разными типами аналогичных по смыслу полей в каждой ветви иерархии.

Особенность нового синтаксиса для объектов — то, что динамически созданный объект как параметр можно передавать как по значению (см. ShowData), так и по имени (см. далее ShowData1):

procedure ShowData1(var aField:tField);

begin

...

end.

При этом в случае передачи по значению котируется только указатель на объект, но не его тело. В Object Pascal, в отличие от C++, нет понятия "конструктора копии", и при необходимости скопировать тело объекта надо просто задать метод объекта, который занимается его копированием.


4.12. Области видимости объектов.

В Delphi существуют зарезервированные слова public ("общие"), protected ("защищенные") и private ("личные") для определения специальных правил видимости областей кода в объектах. Кроме того, для работы в среде Delphi используется еще зарезервированное слово published ("опубликованные"), являющееся вариантом public. Начиная с Delphi 3.0 при описании класса поля, свойства и методы должны располагаться в области, помеченной как published, public, protected и private.

Поля, свойства и методы доступны:

public, published — всем и везде, в том числе в других модулях;

protected — только в описании методов в классах потомков (в том числе в других модулях);

private — только в процедурах и функциях модуля, где описывается данный класс, а также в методах классов потомков, определенных внутри этого модуля.

Разновидностью public является published. "Опубликованные" поля, методы и свойства доступны в интерфейсе визуального проектирования Delphi (в инспекторе объектов) на стадии разработки. В первых версиях Delphi действовало правило, что если в описании класса были опущены ключевые слова для определения правил видимости, считалось, что соответствующие методы, поля и свойства published. Начиная с Delphi 3.0 область видимости объекта "published" также должна быть явно описана. Т.е. во всех примерах, где мы работали с классами, надо поставить директиву public или published перед описанием полей и методов объекта. Например:

tMyObj1=class(tObj1)

...

end;

заменить на

tMyObj1=class(tObj1)

public

...

end;

Пример правил видимости внутри модуля:

unit vis1;

interface

type

tObj1=

class

public

procedure MyPublicMethod;

...

protected

procedure MyProtectedMethod;

...

private

procedure MyPrivateMethod;

...

end;

procedure MyProc1; {процедура, видимая в других модулях}

...

{-секция реализации-}

implementation

... {реализация tObj1.MyPublicMethod, tObj1.MyProtectedMethod,

tObj1.MyPrivateMethod}

...

...

var aObj1:tObj1;

procedure MyProc1;

begin

aObj1:=tObj1.Create;

aObj1.MyPublicMethod; {можно}

{aObj1.MyProtectedMethod; -нельзя, т.к. не описание метода в

классе–наследнике}

aObj1.MyPrivateMethod; {можно, т.к. тот же модуль}

aObj1.Free; {то же, что Destroy, но с проверкой на nil}

end; {/MyProc1}

end. {/implementation}

Правила видимости в другом модуле:

unit vis2;

interface

uses vis1;

type

tObj2=

class(tObj1)

procedure MethodOfNewClass; {определение метода в потомке}

end;

procedure MyProc2; {процедура, видимая в других модулях}

{-секция реализации-}

implementation

procedure tObj2.MethodOfNewClass;

begin

MyPublicMethod; {можно, т.к. Public метод}

MyProtectedMethod; {можно, т.к. описание метода в

классе - наследнике}

{MyPrivateMethod; -нельзя, т.к. другой модуль}

end;

var aObj2:tObj2; {реализация}

procedure MyProc2;

begin

aObj2:=tObj2.Create;

aObj2.MyPublicMethod; {можно, т.к. public}

{aObj2.MyProtectedMethod; -нельзя, т.к. не описание метода

в классе - наследнике}

{aObj2.MyPrivateMethod; -нельзя, т.к. другой модуль}

aObj2.Free;

end;

end.

При описании класса-наследника можно переносить методы и свойства из одной области видимости в другую путем их простого упоминания, без переписывания реализации и сигнатуры:

type

tObj1=

class

private

fMyData:Real;

protected

function GetMyData:Real;

property MyData:Real

read GetMyData;

...

end{/class};

tObj2=

class(tObj1)

public

property MyData;

...

end{/class};

Замечание:

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

2) Иногда удобно перемещать поля или методы потомков из видимых областей в protected или private, иногда — наоборот (если это допустимо правилами прозрачности).


4.13. Обработка исключительных ситуаций.

Во время работы программы часто встречаются исключительные ситуации: деление на 0, отсутствие места на диске или попытка писать на защищенную для записи дискету, ошибочный символ при вводе (например, буква вместо цифры). В современных языках программирования для таких случаев предусмотрены специальные средства — обработчики исключительных ситуаций. В Delphi имеются 2 типа так называемых защищенных блоков кода:

1)try...finally...end;

2)try...except...end;

1) try...finally...end: если исключительных ситуаций не было, операторы в блоке try выполняются в обычном порядке, после чего выполняются операторы в блоке после finally. Если же возникла исключительная ситуация в блоке "try", выполнение блока прерывается, и сразу выполняется блок "finally" (его обычно используют для высвобождения ресурсов, и поэтому часто называют блоком "очистки кода"). Специальных операторов для этого типа обработки нет. Отличие кода внутри блока finally...end от кода, стоящего после этого end, возникает только при наличии внутри блока try операторов break или exit, т.к. в этом случае независимо от их срабатывания или несрабатывания сначала происходит выполнение блока очистки кода.

2) try...except...end: если исключений не было, выполняется только блок try. Если же возникла исключительная ситуация в блоке try, то обычная последовательность прекращается, и управление для обработки этой операции сразу передается в блок "except" со специальным синтаксисом:

try

...

except

on MyException1 do...{оператор1};

on MyException2 do...{оператор2}; ...

else {обычно отсутствует; не рекомендуется использовать}

MyOtherException {оператор обработки остальных исключений}

end{/except};

Исключительные ситуации являются классами-потомками объектного типа Exception=class(tObject) из модуля SysUtils и либо предопределены в системе (существуют исключительные ситуации: ZeroDivBy для операций с "плавающей запятой", DivByZero для целочисленных операций, InvalidGraphic и т.д.), либо описываются пользователем как потомки Exception (или его потомков).

Если ошибки не было, блок except не выполняется. Если ошибка была, выполняется блок except, после чего управление обратно в try не передается. При этом ищется первое соответствие исключения обработчику. Так, если возникла исключительная ситуация типа MyException1 (предопределенная или определенная в программе), то после выполнения оператора1 будет произведен выход из блока try...except...end без проверки на соответствие другим типам исключений.

По правилу совместимости типов исключительная ситуация типа-потомка всегда может быть обработана как исключение прародительского типа. Поэтому порядок следования обработчиков в блоке except имеет большое значение: обработчик исключения более общего типа следует писать всегда после обработчика для его типа-потомка ,иначе обработчик потомка никогда не будет вызван.

Пример обработки исключений:

procedure MyETest(var X,Y,Z:real);

begin

try

Z:=sqrt(X/Y);

except

on EZeroDivide do messageBox('Деление на ноль',’Y’,mb_Ok);

on EInvalidOp do messageBox('Корень из отрицательного числа!',

‘Y’,mb_Ok);

end{/except};

end;

Если бы мы поставили строку EInvalidOp перед EZeroDivide..., то при делении на нуль обработчик для EInvalidOp перехватывал бы управление, и обработчик для EZeroDivide никогда бы не срабатывал.

Во время обработки исключительной ситуации некого типа создается объект-исключение соответствующего типа. Обычно имени типа исключения бывает достаточно, и объект-исключение остается без имени. Но при необходимости исключение может быть поименовано в блоке on..do, если в блоке do нужен доступ к полям объекта:

...

on EZD:EZeroDivide do EZD.message:='Деление на ноль. Проверьте

данные';

Таким образом, можно не только заменять, но и переопределять стандартные обработчики исключений. Например, в файле проекта (расширение .dpr — сокращение от Delphi project) можно сделать следующую обработку исключений:

try {создание формы}

application.CreateForm(tForm1,Form1);

application.Run;

except

on E:Exception do

begin messageBox(E.message,’Y’,mb_Ok);

E.message:='Ошибка в программе';

Raise;

end{/do};

end{/except};

Замечание: исключения, в отличие от других типов, принято именовать с буквы "E", а не с "t".

Некоторые важнейшие типы исключений:
  • EMathError — все математические ошибки;
  • EInOutError — все ошибки ввода-вывода;
  • EConvertError — ошибки преобразования типов;
  • EOutOfMemory — нехватка памяти;
  • EPrinter — ошибка работы с принтером;
  • EAbort — вызывается программно вызовом процедуры Abort; не делает ничего и предназначена для обработки программистом для нужд текущей программы.

Блоки защиты ресурсов и обработчики исключительных ситуаций могут быть вложенными. Например, выделение ресурсов можно производить так:

try {1}

allocateResource1;

try {2}

allocateResource2;

UseResources;

finally{2}

FreeResource2;

end{/try2};

finally{1}

FreeResource1;

end{/try1};

Другой вариант решения этой проблемы:

const Ok1,Ok2:Boolean:=False;

...

try

AllocateResource1;

Ok1:=True;

AllocateResource2;

Ok2:=True;

except

on Exception do

begin

if Ok2 then

begin

FreeResource2;

if Ok1 then FreeResource1;

end{/if};

end{/do};

end{/except};

Можно вкладывать друг в друга обработчики исключений:

procedure ECheck(var A,B,Y,Z:Real);

var X:Real;

begin

try{1}

X:=(-B*X+sqrt(B*B-4*A*C))/(2*A);

try{2}

Z:=X/sqrt(A*Y*Y-7*B/A/(X-3sqrt(B)));

except{2}

on EMathError do

application.MessageBox('ошибка в вычислении Z',Y,mb_Ok);

end{/except2};

except{1}

on EMathError do

application.MessageBox('ошибка в вычислении корня

уравнения ',Y,mb_Ok);

end{/except1};

end{/ECheck};

Для создания собственной исключительной ситуации в случае, когда обычные исключения не возбуждаются, надо описать соответствующий класс, например:

EWrongPassword=

class(Exception)

end;

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

Например:

var S,S1:String;

try

S:=...

...

myReadPassword(S1);

if(S1<>S)then EWrongPassword.Create('Wrong password!');

...

except

on EWrongPassword do...;

end{/except};

...

Создание и обработка исключительных ситуаций позволяют структурным способом решить проблемы, в которых в обычном PASCAL приходилось использовать метки и оператор goto, а также ставить огромное число проверок на допустимость присваиваний и математических операций. Мало того, что эти проверки резко замедляли работу программы — не было гарантии, что они достаточны, и что во время работы программы не возникнет "вылет" из-за возникновения непредусмотренной исключительной ситуации. В Object Pascal, как мы видим, эта проблема решена кардинально.

Замечание: в "С-образных" языках С++, " onclick="return false">

4.14. Специальные средства для работы с MS Windows: сообщения.

Для обработки сообщений Windows в Delphi существует механизм динамических методов, называемых сообщениями. Они должны быть описаны в классе как процедуры, имеющие один параметр, передаваемый по имени (через var). После описания заголовка через ";" должно идти зарезервированное слово message, после которого идет индекс — идентификатор сообщения. Описание параметра, передаваемого в процедуру через var, произвольно (в первоначальной версии Delphi параметр должен был описываться как переменная соответствующего для message типа : tWM_Size, tWM_Move, и т.д.):

type

tMyControl1=

class(tWinControl)

procedure WMSize(var message:tWMSize);message WM_Size;

{начальный вариант синтаксиса}

end;

tMyControl2=

class(tMyControl1)

procedure Resize(var Info);message WM_Size; {так тоже можно}

end;

В модуле Messages описаны обработчики практически всех необходимых сообщений Windows. Каждый обработчик имеет идентификатор — целочисленную константу. Например, WM_Size — идентификатор сообщения об изменении размера экранной формы, WM_Move — идентификатор сообщения о ее перемещении и т.д.

В обработчиках сообщений можно вызвать прародительский метод с использованием только одного слова inherited, без указания его имени: он находится автоматически по индексу.

Самый общий обработчик — метод DefaultHandler, описанный в классе tObject. При обработке сообщения обычно надо вызвать в конце обработчик для прародителя:

procedure MyMsgHandler(var message);message WM_command;

begin

MyProcessing; {некая процедура обработки сообщения}

inherited; {в отличии от конструктора, вызов в самом конце}

end;

При получении объектом от операционной системы Windows сообщения описанного типа (идентификация сообщений происходит автоматически по индексам) вызывается соответствующий обработчик. В описанном выше примере это MyMsgHandler.

Однако обычно нет необходимости обрабатывать сообщения Windows, так как имеется механизм делегирования событий, о котором сейчас пойдет речь.

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


4.15. События и их делегирование. Обработка событий.

События — это свойства процедурного типа. Их название принято начинать с префикса "on". Для описания события некого типа (как описать новый тип события будет рассказано далее) в объекте надо описать поле того же типа, что и свойство. Реально это поле служит указателем на процедуру обработчика события. Кроме того, надо описать собственно свойство:

fOnMyEvent:tMyEvent;

property OnMyEvent:tMyEvent

read fOnMyEvent

write fOnMyEvent;

Тут поле fOnMyEvent является ссылкой на процедуру, с помощью которой осуществляется обработка события. Это поле доступно по чтению и записи! Присваивание свойству значения — это присваивание полю fOnMyEvent указателя на метод, который будет вызываться при наступлении события. Чтение — это метод. Эти методы называются обработчиками событий. Если никакой обработчик не присвоен полю-указателю, то там хранится значение notAssigned.

Пример:

{-присваивание-}

application.onActivate:=MyMethod; {MyMethod — имя некой процедуры – обработчика события}

означает, что при запуске приложения будет сначала выполнена процедура MyMethod.

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

Общим для описания типа "событие" является первый параметр Sender типа tObject и ключевая фраза of object после описания типа. Ниже приведен пример задания и использования определенного пользователем типа события tMyEvent:

type

{tNotifyEvent=procedure(Sender:tObject)of object;}

{предопределенное простейшее событие}

tMyEvent=procedure(Sender:tObject;var aMyVar:tMyVar) of object;

tObj1=

class

fOnMyEvent:tMyEvent;

property onMyEvent:tMyEvent

read fOnMyEvent

write fOnMyEvent;

end;

tObj2=

class

procedure MyEventProcessing1(Sender:tObject;

var aMyVar:tMyVar);

procedure MyEventProcessing2(Sender:tObject;

var aMyVar:tMyVar);

end;

...

var aObj1:tObj1;

aObj2:tObj2;

myKey:Boolean;

begin

aObj1:=tObj1.Create;

aObj2:=tObj2.Create;

...

if MyKey

then

aObj1.onMyEvent:=aObj2.MyEventProcessing1

else

aObj1.onMyEvent:=aObj2.MyEventProcessing2;

...

end.

При делегировании можно присваивать методы других классов! Даже не являющихся потомком и прародителем! Но при этом должно быть полное соответствие списков параметров обработчиков событий со списком параметров процедурного типа события.


4.16. Методы класса и указатели на класс.

Некоторые методы бывает нужно вызывать без создания экземпляра класса (например, для получения информации о имени класса, размере экземпляра класса и т.п.). Такие методы называются методами класса - в C++ и Java они называются статистическими методами (не путать со статистическими методами в смысле TP и Delphi!). Методы класса имеют перед словами procedure или function ключевое слово class (за исключением конструкторов, которые также являются методами класса, но для которых синтаксис, как мы знаем, другой):

type

tMyObj=

class

class function MyGetSize:String;

end;

var aMyObj:tMyObj;

begin

...

Label1.caption:=tMyObj.MyGetSize; {вызов метода класса tMyObj}

aMyObj:=tMyObj.Create; {вызов другого метода класса tMyObj}

Label2.caption:=aMyObj.MyGetSize; {вызов метода класса с

экземпляром класса}

...

end;

Возможно описание указателя на класс с помощью ключевой фразы class of. Это иногда требуется в программах, использующих сложную иерархию объектов, для унифицированного динамического создания, копирования и уничтожения объектов, принадлежащих разным классам. В модуле SysUtils указатель на tObject описан так:

type tObject=class;

tClass=class of tObject;

Имеются следующие важнейшие стандартные функции классов, определенные в tObject:
  • class function ClassName:string — возвращает имя класса, вызвавшего объект;
  • class function ClassParent:tClass — возвращает указатель на класс прародителя;
  • function ClassType:tClass — возвращает указатель на класс вызвавшего объекта;
  • class function ClassInfo:Pointer — возвращает указатель на структуру (запись), содержащую информацию о классе.

Также имеется ряд других стандартных функций классов.

Пример использования указателя на класс:

tMyObj=

class

...

end;

tMyObjRef=class of tMyObj;

tChildObj=

class(tMyObj)

...

end; {/class}

var

aMyObjRef:tMyObjRef;

aMyObj:tMyObj;

aChildObj:tChildObj;

После такого объявления можно проводить присваивания такого рода:

if...

then

aMyObjRef:=tMyObj; {при выполнении условия aMyObjRef

стало типом tMyObj}

else

aMyObjRef:=tChildObj; {иначе - типом tChildObj}

aMyObj:=aMyObjRef.Create; {создали объект, тип которого

определяется aMyObjRef}

Без указателей на класс пришлось бы писать вызовы конструктора Create внутри оператора if по отдельности для tMyObj и tChildObj.

Указатели на класс удобно использовать совместно с булевским оператором is, возвращающим true в случае совместимости по присваиванию объекта, указанного перед is, с экземпляром класса, упомянутого после is:

if aMyObj is tAnyObj then...;

Например, если aMyObj – кнопка типа tBatton, а в качестве tAnyObj написано tWinControl, оператор is возвратит true. Если же aMyObj – метка типа tLabel, он возвратит false,так как tLabel является потомком tControl, но не tWinControl.

Существует также специальный вариант приведения типов для объектов при помощи оператора as:

aObj1:=aObj2 as tObj1; {aObj2 вызывается в точности как экземпляр его

класса-прародителя tObj1}

aMyVar:=(aMyObj as tMyRecordObj).Data; {тут Data - свойство;

если это обычное поле, приведение не имеет смысла}

Понятно, что такое приведение возможно только к типу класса-прародителя.

Этот тип приведения очень часто используется в обработчиках событий. Например, если мы хотим изменить координату “x” элемента типа tImage, перетаскиваемого “мышью”, то в обработчике onDragOver элемента, над которым мы его перетаскиваем, надо написать

If Sender is tImage then( Sander as tImage).x=x;

При этом написать Sender.x=x нельзя, так как типом Sender является tObject, а в нем поле x не определено.


4.17. Дополнительные возможности Object Pascal.

4.17.4. Функции

а) могут возвращать значения определенных пользователем типов, включая сложные;

б) внутри каждой функции предопределена доступная по чтению и записи локальная переменная Result, имеющая тот же тип, что и функция. Ее значение до первого присваивания не определено. После завершения процедуры-функции эта функция возвращает значение, присвоенное переменной Result. Использование переменной Result является более наглядным и современным, чем обычный синтаксис языка PASCAL для функций, где значение возвращается через доступную только по записи локальную переменную с именем, совпадающим с именем функции.


4.17.5. Оператор CASE.

Значения вариантов (в том числе диапазоны) не могут пересекаться. Расположение вариантов в порядке возрастания позволяет компилятору оптимизировать код.


4.17.6. Открытые массивы.

Открытым массивом называется массив, используемый как формальный параметр процедуры, длина которого задается не в месте задания процедуры, а при ее вызове. При этом при ее вызове могут использоваться массивы разной длины.

Замечание: если нужен массив переменной длины, для этих целей используют список (мы уже знаем, как это делать). Правда, работа с массивом требует меньше памяти и идет быстрее, чем со списком. В Delphi начиная с версии 2.0 одномерные открытые массивы можно использовать без описания соответствующего типа. С версии 4.0 поддерживаются двумерные открытые массивы. Имеются предопределенные функции Low и High, в качестве параметра которых подставляется имя массива. При этом Low возвращает номер первого элемента, а High — последнего. Надо отметить, что в Delphi (как и в С++, " onclick="return false">
Пример:

program ArcDemo; {MyData - открытый массив}

function Mean(MyData:array of Real):Real;

var i:Integer;

begin

Result:=0;

for i:=Low(MyData)to High(MyData)do {цикл от первого до

последнего элемента}

Result:=Result+MyData[i];

Result:=Result/(High(MyData)-Low(MyData)+1);

end;

var X:Real;

begin

X:=mean([1,9,9,7]); {задаем массив путем перечисления элементов}

end.

Имеется возможность передавать в процедуру открытый массив разнотипных переменных. Такой параметр должен быть описан как array of const. Он может передаваться как по имени (т.е. по указателю на массив), так и по значению (т.е. делается его копия). Внутри процедуры элементы массива рассматриваются как записи типа TVarRec, описанные в модуле System, причем имеется поле типа VType, позволяющее идентифицировать тип фактического параметра.

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

function AllToStr(const MyParameters:array of const):string;

const

BString:array[Boolean] of string=('False','True');

var

i:Integer;

S:String;

begin

Result:='';

for i:=Low(MyParameters)to High(MyParameters)do

with TVarRec(MyParameters[i])do

begin

case VType of

vtInteger: s:=IntToStr(VInteger);

vtBoolean: s:=Bstring(Vboolean);

vtChar: s:=VChar;

vtEXtended: s:=FloatToStr(VExtended);

vtString: s:=vString;

vtPointer: s:=IntToHex(Longint(VPointer),8);

vtPChar: s:=StrPas(VPChar);

vtObject: s:=vObject.ClassName;

vtClass: s:=vClass.ClassName;

end{/case};

Result:=Result+s;

end; {/with...do}

end{/AllToStr};

При вызове

AllToStr([1,'+2','=3,',1e6,'>',1.5e-10,'is',1e6>1.5e-10,'.'])

возвратит строковое значение

1+2=3,1E6>1.5E-10 is true.


4.17.7. Переменные типа variant.

Иногда удобно писать процедуры общего назначения, которые должны выглядеть одинаково для параметров с переменными разного типа. В этом случае целесообразно вместо нескольких процедур использовать одну с параметрами типа variant.

Для переменных типа variant действуют особые правила присваивания: переменной

aMyVar:Variant;

можно присваивать выражения любых типов независимо от текущего значения переменной:

begin

...

aMyVar:=1;

...

aMyVar:=true;

...

aMyVar:='SomeString';

...

end;

При присваивании строковой переменной (или участии в строковом выражении) переменная вариантного типа возвращает строку с соответствующим значением. В предыдущем примере это было бы '1','true' и 'SomeString', соответственно.

Приведение типов для других случаев описывать не будем. Информацию по этому вопросу можно найти в специальной документации среды Delphi.

Работа с переменными типа variant гораздо медленнее чем для обычных, т.к. проверки и приведения типов во время исполнения программы занимают много времени. Кроме того, жесткость диагностики для этого случая значительно снижена, что может привести к возникновению не отслеживаемых компилятором ошибок. Поэтому, по мере возможностей, лучше избегать применения переменных variant. Их целесообразно использовать только в случае вызова DLL (Dynamic Link Libraries), написанных на языке С, для обеспечения совместимости по типу с параметрами функции из этих библиотек.