Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Учебник Емельянов.doc
Скачиваний:
12
Добавлен:
03.11.2018
Размер:
3.25 Mб
Скачать

Операции is и as

Операции Is и As применяются к объектам. С помощью операции Is оп­ределяется, принадлежит ли данный объект указанному типу или одному из его потомков. Например, выражение AnObject is TMyClass возвращает true, если переменная AnObject совместима по присваиванию с переменной типа TMyClass. Для приведения типа какого-либо объекта применяется опе­рация As. Например, with AnObject as TMyClass do ...

В операции As сначала проверяется совместимость типа с помощью опе­рации Is. Если типы несовместимы, то запускается обработчик исключи­тельной ситуации EInvalidCast. Поэтому использование следующей опера­ции является менее надежным способом неявного приведения типа: With TMyClass (AnObject) do ...

МЕТОДЫ

Методы служат для обработки информации, содержащейся в полях. Доступ к полям может осуществляться без дополнительных (формальных) параметров. Формальные параметры у методов обычно служат для обмена информацией с другими классами.

В классе метод только объявляется. Описывается он в разделе реализа­ции модуля (секция Implementation). При описании указывается имя класса-владельца и через точку имя метода. Например,

Procedure TMyClass.AMethod(Param:AType); Begin

end;.

114

Внутри begin ... end можно вызывать любые методы предков с указа­нием имени предка. Ближайший предок, имеющий такое же имя, как у дан­ного метода, может вызываться с помощью зарезервированного слова inher­ited.

Методы могут синтаксически оформляться следующим способом:

  • Procedure;

  • Function;

  • Constructor - вид Procedure, служащий для построения объекта, инициализации полей и правильного вызова так называемых виртуальных методов;

  • Destructor - вид Procedure, служащий для освобождения памяти, т.е. разрушения объекта.

При построении объекта автоматически объявляется дополнительная пе­ременная Self. Тип этой переменной совпадает с классом, породившим дан­ный объект. Переменная Self является локальной и определена только внут­ри данного экземпляра класса. При необходимости внутри любого метода можно воспользоваться этой переменной.

ВИДЫ МЕТОДОВ

Методы бывают разных видов. По умолчанию методы являются стати­ческими (Static) Методы в разных классах могут иметь одинаковые имена. При вызове методов обязательно указывается через точку имя объекта, вы­звавшего данный метод. Например, пусть класс AClassType породил объект AnObject. Если вызывается метод AStaticMethod, то вызов записывается так: AnObject.AStaticMethod;.

Если в классе AClassType непосредственно такого метода нет, то вызы­вается ближайший по линии наследования метод с заданным именем.

Недостаток статических методов заключается в следующем. Пусть ме­тод AStaticMethod вызывает внутри себя другой метод, например Second-Method. Пусть по линии наследования определено несколько методов с име­нем SecondMethod. В данном случае AStaticMethod вызовет ближайший по программному описанию SecondMethod, независимо от того объекта, который вызвал метод AStaticMethod.Таким образом, при использовании статических методов нельзя конкретно указать, какой из имеющихся по ли­нии наследования нескольких методов с одним именем требуется вызвать.

МЕТОДЫ VIRTUAL И ПОЛИМОРФИЗМ

Особенности виртуальных (Virtual) методов рассмотрим на примере. Пусть объявлен класс AClass. Этот класс имеет потомка, который, в свою очередь, также имеет потомка, например AClass -> BClass -> CClass.

116

Пусть AClass содержит метод Drag, который служит для рисования не­которой геометрической фигуры. При этом внутри метода Drag вызывается метод Show для рисования заданной фигуры. Пусть метод show реализован в каждом из трех указанных классов для рисования точки (класс AClass), ок­ружности (BClass) и квадрата (CClass). Метод Drag содержится только в AClass. Так как нужная фигура вызывается для рисования из метода Show, нет необходимости дублировать его в других классах. Пусть заданы три объ­екта: Var A:AClass; В:Bclass; С:CClass.

Пусть с помощью соответствующих конструкторов эти объекты созда­ны. Пусть вначале все три метода Show объявлены как статические. Запишем вызов на рисование геометрической фигуры: с. Drag;.

Рассмотрим, какая фигура изобразится в данном случае. По линии на­следования от CClass, вызвавшего Drag, в классе AClass найдется метод Drag. При вызове происходит обращение к Show. В данном случае вызовется ближайший к Drag метод, находящийся в классе AClass, т.е. изобразится точка. Вызвать метод Show из класса BClass для рисования окружности или вызвать Show из CClass и нарисовать квадрат с помощью статических мето­дов нельзя.

Итак, в данном случае ставится следующая задача. Вызов C.Drag; дол­жен нарисовать квадрат, B.Drag; - окружность, соответственно A.Drag; -точку, т.е. метод Show для С.Drag должен вызываться из класса CClass, для B.Drag- из класса BClass, а для A. Drag- из класса AClass. Такая задача решается с помощью виртуальных методов show. Тогда вызов show будет определяться конкретным фактическим объектом.

Виртуальные методы определяются с помощью ключевого слова Virtual. По линии наследования все реализации одного и того же виртуаль­ного метода должны иметь одинаковые заголовки. С помощью зарезервиро­ванного слова Virtual определяется самая первая реализация виртуального метода, все последующие реализации переопределяются с помощью ключе­вого слова Override. Если заданный метод появляется далее по линии насле­дования без ключевого слова Override, то свойство виртуальности теряется для данного класса и всех его потомков.

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

Свойство одного и того же метода вести себя по-разному называется по­лиморфизмом (многоформным). Например, метод Drag, указанный в приве­денном выше примере, может построить точку, окружность или квадрат. Для реализации полиморфизма используются виртуальные методы.

117

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

МЕТОДЫ DYNAMIC

Механизм Dynamic доступа к другим методам подобен механизму Vir­tual. В обоих случаях адрес нужной процедуры или функции определяется фактическим объектом. Отличие заключается в том, что в данном случае строится таблица динамических методов. Обращение к этой таблице про­исходит медленнее, чем к таблице виртуальных методов, зато объем про­граммы получается меньше. Методы Dynamic целесообразно использовать, когда класс имеет множество потомков, а число переопределяемых методов небольшое.

МЕТОДЫ MESSAGE

Методы Message - обработки сообщений - представляют собой осо­бую форму динамических методов. Обработчики сообщений всегда являют­ся процедурами. Для ускорения поиска в таблице динамических методов после ключевого слова Message записывается константа целого типа, яв­ляющаяся индексом нужного метода. В обработчике сообщений имеется один параметр Var. Методы Message - это одно из звеньев взаимодействия компонентов Delphi с Windows. Объявление обработчика сообщений выгля­дит приблизительно так: Procedure Handle_WM_Paint(var Msglnfo); Message WM_Paint;.

Для поиска нужного адреса в таблице динамических методов в данном примере записана константа WM_Paint. Многие константы для Message за­резервированы за конкретными сообщениями Windows и выбирать их про­извольно нельзя. Указанная константа поиска данного сообщения Windows может быть переопределена в каком-либо предке по линии наследования для данного объекта. Если константа Message не найдена, то вызывается метод обработки сообщений, заданный по умолчанию.

МЕТОДЫ ABSTRACT

Обычно методы создаются для выполнения каких-то конкретных дейст­вий. Если по какой-либо причине это выполнить не удается, метод в классе может быть зарезервирован с обязательным переопределением его в классах потомков. Такой метод помечается ключевым словом Abstract. Если абст­рактный метод не переопределен, то вызов такого метода приводит к вызову специальной процедуры Abstract, которая генерирует исключительную си­туацию. Абстрактным не может быть статический метод, так как статиче­ские методы нельзя переопределять. Например,

118

Type TmyParent=class

Procedure AMethod;virtual;abstract; End; TmyClass=class(TmyParent)

Procedure AMethod;override; End;

МЕТОДЫ OVERRIDE

Как указано выше, зарезервированным словом Override помечаются пе­реопределенные виртуальные или динамические методы (пример выше).

МЕТОДЫ CLASS

Исходное назначение методов - определять поведение экземпляров объ­ектов какого-либо класса. В некоторых случаях необходимо иметь ситуа­цию, когда поведение, задаваемое для метода, не должно зависеть от реально существующего объекта. Такая ситуация возникает с методом, который, на­пример, должен возвращать имя класса. В этом случае метод помечается ключевым словом Class. В отличие от других команд, таких, как Dynamic, Virtual и т.д., слово Class ставится перед заголовком метода, например Class Procedure AMethod;.

ПРИМЕР ПРИЛОЖЕНИЯ 14

Рассмотрим пример по использованию конструкторов и деструкторов в объектах. Кроме того, посмотрим, как можно использовать указатели на ме­тоды и методы class.

Пусть на форме динамически строится совокупность объектов типа TButton. Далее пусть эти объекты последовательно разрушаются с выдачей соответствующих сообщений о числе объектов. Рассматривать этот пример будем последовательно, вводя все новые и новые конструкции и проверяя их непосредственно в среде Delphi.

ДИНАМИЧЕСКОЕ СОЗДАНИЕ КОМПОНЕНТОВ

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

procedure TForm1.FormMouseDown(Sender:TObject; Button:

TMouseButton; Shift: TShiftState; X, T: Integer); Var Btn:Tbutton;

Begin

Btn:=TButton.Create(self)

Btn.Parent:=self;

Btn.Left:=x;

Btn.Top:=y;

Btn.Width:=Width+50;

Btn.Caption:=Format('Кнопка x,y= %d,%d',[x,y]); end;.

В Delphi ключевое слово self используется, например, когда нужно яв­но обратиться к текущей форме в одном из ее методов.

Динамическое создание компонентов является тем случаем, когда требу­ется передать конструктору create параметр Owner (указать владельца соз­даваемого объекта), а затем присвоить то же значение свойству Parent (ука­зать родителя). В обоих случаях нужно передавать имя текущей формы (не всегда параметры Owner и Parent совпадают, как в данном случае). Таким образом, обработчик выше будет корректно работать, если параметр self заменить на Form (пусть такое имя имеет переменная типа Form). Следу­ет отметить, что код, приведенный выше, обычно записывается с использо­ванием оператора with:

procedure TForm1.FormMouseDown(Sender: TObject/Button:

TMouseButton; Shift: TShiftState; X, Y: Integer); begin

with TButton.Create(self) do begin Parent:=self; Left:=x; Top:=y;

Width:=Width+50;

Caption:=Format('Кнопка x,y= %d,%d,[x,y]); end; end;.

Ниже приводится объявление модуля, используемого в данном случае:

unit priml4;

interface

uses Windows, Messages, SysUtils, Classes, Graphics,

Controls, Forms, Dialogs, StdCtrls, ExtCtrls, Menus; Type

TForm1 = class(TForm)

procedure FormMouseDown(Sender: TObject;Button: TMouseButton;Shift: TShiftState; X, Y: Integer);

end; var Form1: TForm1;.

Вариант решения примера представлен на рис 34.

Рис.34

ИСПОЛЬЗОВАНИЕ КЛАССА СО СЧЕТЧИКОМ ОБЪЕКТОВ

Пусть в примере 14 будем строить кнопки некоторого собственного ти­па, унаследованного от типа TButton. Построим для этих целей класс, в ко­тором предусмотрим счетчик создаваемых объектов.

Для хранения числа создаваемых объектов понадобится переменная це­лого типа. Объявим ее в секции implementation: var CountBtns: integer = 0;. Эта переменная содержит общую информацию о форме и представ­ляет данные модуля, поэтому она не включена как поле в класс. Для доступа к такой информации понадобится метод класса. Теперь представим объявле­ние нашего класса:

type

TMyButton=class(TButton) public

Constructor Create{AOwner:TComponent); override;

Destructor Destroy; override;

class function GetCount:integer; end;.

Увеличение счетчика объектов предусмотрим при вызове конструктора, уменьшение - при вызове деструктора. Для построения объектов и их раз­рушения, естественно, будем использовать стандартные методы. Ниже при­водятся описания деструктора и конструктора:

constructor

TMyButton.Create (AOwner: TComponent); begin

inherited;

Inc(CountBtns); end;

destructor TMyButton.Destroy; begin

dec(CountBtns);

inherited; end;.

120

121

Так как переменная countBtns объявлена в секции implementation, об­ратиться к ней извне модуля нельзя. Только метод класса позволяет нам про­читать ее текущее значение:

class function TMyButton.GetCount: integer; begin

Result:=CountBtns; end;.

Теперь можно создавать объекты нашего нового типа TMyButton, не­много изменив приведенный выше код обработчика FormMouseDown:

procedure TForm1.FormMouseDown(Sender: TObject; Button:

TMouseButton; Shift: TShiftState; X, Y: Integer);

begin

with TMyButton.Create(self) do begin

Parent:—self;

Left:=x;

Top:=y;

Width:=Width+60;

Caption:=Format{'%d кнопка x,y= %d,%d',[GetCount,x,y]); end;.

Класс-функция GetCount может вызываться, в отличие от обычных ме­тодов, как из объекта (в обработчике FormMouseDown), так и из класса. На­пример, добавим таймер (компонент TTimer) на форму и обеспечим при срабатывании таймера изменение заголовка формы с помощью следующего обработчика события OnTimerr

procedure TForm1.Timer1Timer(Sender: TObject);

begin

Caption:=Format('Кнопок на форме %d',[TMyButton.GetCount]);

end;.

В результате решения получим, например, следующий вариант (рис. 35).

ОТСЛЕЖИВАНИЕ РАЗРУШЕНИЯ ОБЪЕКТОВ

Проследим за разрушением объектов на форме. Очевидно, это можно увидеть в секциях initialization и finalization, т.е. после того, как форма начнет разрушаться.

initialization

MessageBox(0,PChar(Format('На форме %d кнопок', [TMyButton.GetCount])),'Инициализация',mb_ok);

finalization

MessageBox(0,PChar(Format('На форме %d кнопок', [TMyButton.GetCount])),’Финиш,mb_ok);.

В данном случае использовалась функция Windows API MessageBox, так как при разрушении формы доступ в секции finalization ко многим функциям Delphi невозможен. Запуская данную программу на выполнение, получим два сообщения до построения и после разрушения формы.

СОБЫТИЯ

В приложениях и компонентах событие возникает как результат посла­ния операционной системой сообщения, что произошло некоторое действие, которое контролирует метод послания уведомления о событии. Этот ме­тод проверяет, назначил ли пользователь соответствующий обработчик со­бытий. Если событие произошло и обработчик назначен, то метод послания уведомления о событии вызывает соответствующий обработчик. Методы послания уведомления о событии можно переопределять, используя стан­дартные методы, или создавать свои собственные.

Рассмотрим вариант переопределения методов послания уведомления о событии. Такие методы реализуются в разделе Protected. Поэтому потомки тех встроенных классов, в которых реализуются такие методы, могут делать доступными их для пользователя. Например, рассмотрим, как переопределя­ется метод послания уведомления о событии Click для кнопки, одновремен­но изменяя ее заголовок на строку, в которую записано текущее время.

TtimeButton=class(Tbutton) Protected

Procedure Click; override; End;

………………………..

Procedure TtimeButton.Click;

Inherited Click;

Caption:=DateTimeToStr(Now); End;.

Рис.35

122

123

Методы послания уведомления о событии являются указателями на ме­тод, которые вызываются в случае, когда компонент получает сообщение от Windows о том, что произошло некоторое событие, воздействующее на дан­ный компонент. Обработчики событий представляют собой свойства, ко­торые выполняют чтение и запись в указатели на метод. Например, рассмот­рим фрагмент, взятый из класса TControl, содержащегося в исходном коде VCL. Имена обработчиков событий по соглашению содержат префикс On. Вначале объявляется событие; TNotifyEvent = procedure (Sender:TObject) of object;. Далее объявляется класс, содержащий поля данно­го типа, и обработчики событий, работающие с этими полями.

Tcontrol=class(TComponent) Private

FOnClick: TNotifyEvent;

………………….

Ptotected

Property OnClick: TnotifyEvent read FOnClick write FOn­Click;

……………………………………

end;.

При создании собственных обработчиков необходимо научиться строит методы посылки уведомления о событии. Эти методы должны уметь полу­чать и обрабатывать сообщения Windows, что возможно сделать с помощью методов вида Message.

УКАЗАТЕЛИ НА МЕТОДЫ

Этот тип данных представляет собой ссылку (указатель) на метод. Ука­затель на метод содержит два адреса: одна ссылка на адрес кода метода, дру­гая - на адрес экземпляра объекта - представляет собой скрытый параметр self. Адрес self представляет собой в данном случае адрес расположения дан­ных, принадлежащих конкретному объекту.

Указатели на методы реализуют один из принципов компонентной технологии - делегирование. Если кто-то написал класс, у которого есть по­ля в виде указателей на методы, например,

Туре

TNotifyEvent=procedure(Sender:Tobject) of object; TMyButton=class

OnClick: TNotifyEvent; End;

,то можно менять поведение построенных (даже скомпилированных) объек­тов этого типа, просто присваивая этим указателям новые методы. Напри­мер, пусть объявлено:

124

туре

TForm1=class(TForm)

Procedure OnButton1Click(Sender:Tobject); Button1:MyButton; End;.

Теперь при построении компонента на форме можно делегировать обработ­чик OnButton1click из TForm1 в MyButton путем следующего присваива­ния: MyButton.OnClick:=Form1.OnButton1click;.

ПРИМЕР ПРИЛОЖЕНИЯ 15

Продолжим рассмотрение примера 14. Попытаемся не только динамиче­ски создавать новые объекты, но и разрушать их также динамически. Выбе­рем, что уничтожение очередного объекта будет наступать, как только поль­зователь нажмет на клавишу Backspace (#8). Для реализации этой идеи по­надобятся указатели на методы.

В данном случае динамически созданный объект для своего уничтоже­ния должен отслеживать нажатие клавиши #8. Для этих целей может слу­жить событие OnKeyPress. Этому событию надо делегировать собственное событие. Прежде всего, необходимо убедиться, что оно объявлено как указа­тель на метод, иначе делегирование невозможно. В справочной системе Del­phi можно найти следующее объявление:

TKeyPressEvent=procedure(Sender:Tobject;var key:char) of object;

Property OnKeyPress: TKeyPressEvent;.

Таким образом, для делегирования необходимо создать свою процедуру. В классе TForm1 добавим собственный обработчик: Procedure MyButtonKeyPress(Sender:Tobject; var key:Char);.

Кроме того, в класс TForm1 необходимо добавить переменную для запи­си того динамически построенного метода, который подлежит удалению, так как может удаляться не только текущий объект, но и предыдущий (если но­вые объекты не создаются). Добавим такое объявление в секцию Private ToDestroy:TMyButton;. Теперь определим новую процедуру обработки события OnKeyPress:

Procedure TForm1.MyButtonKeyPress(Sender: TObject;

var Key: Char); begin

if key=#8 then ToDestroy:=Sender as TButton;

end;.

125

Эта процедура не уничтожает, а запоминает подлежащий уничтожению объект. Чтобы она работала, необходимо при создании кнопки делегировать наш обработчик, записывая OnKeyPress :=MyButtonKeyPress ;.

Допишем еще одну строку для установки фокуса на текущий объект SetFocus;, т.е. после удаления очередного объекта необходимо перемес­тить фокус ввода на предыдущий объект.

Удаление кнопки реализуем в обработчике onTimer. Полный текст про­граммы приводится ниже.

unit priml5;

interface

uses Windows, Messages, SysUtils,Classes, Graphics,

Controls, Forms, Dialogs, StdCtrls, ExtCtrls; type

TMyButton=class(TButton) public

Constructor Create (AOwner:TCoraponent) ; override; Destructor Destroy; override; class function GetCount:integer; end;

TForm1 = class(TForm) Timer1: TTimer;

procedure FormMouseDown(Sender: TObject; Button:

TMouseButton; Shift: TShiftState; X, У: Integer); procedure Timer1Timer(Sender: TObject};

private

ToDestroy:TMyButton;

Procedure MyButtonKeyPress{Sender:Tobject; var key:Char); end;

var Form1: TForm1; implementation

var CountBtns: integer =0; {$R *.dfm}

Constructor TMyButton.Create(AOwner: TComponent); begin

inherited; Inc(CountBtns) ;

end;

Destructor TMyButton.Destroy; begin

.dec(CountBtns); inherited;

end;

class function TMyButton.GetCount: integer; begin

Result:=CountBtns; end;

126

procedure TForm1.MyButtonKeyPress(Sender: TObject; var Key: Char); begin

if key=#8 then ToDestroy:=Sender as TMyButton;

end;

procedure TForm1.FormMouseDown(Sender: TObject; Button:

TMouseButton; Shift: TShiftState; X, Y: Integer);

Begin

with TMyButton.Create(self) do begin Parent:=self ;

Left:=x;

Top:=у;

Width:=Width+60;

Caption:=Format('%d кнопка x,y= %d,%d', [GetCount,x, y]); OnKeyPress:=MyButtonKeyPress; SetFocus; end; end;

procedure TForm1.Timer1Timer(Sender: TObject); begin

if Assigned(ToDestroy) then begin SelectNext{ToDestroy,false,true); ToDestroy.Free;

ToDestroy:=nil;

end ;

Caption: =Format {'Кнопок %d'., [TMyButton. GetCount] ); end; end.

Вариант решения получим таким же, как на рис. 35. Однако в данном варианте, нажимая неоднократно на клавишу Backspace, можно удалить все кнопки, построенные на форме.

Все переменные типа класс (например, ToDestroy) по сути, являются указателями, поэтому для проверки, существует ли тот или иной объект, применяется функция Assigned (обработчик Timer1Timer), которая прове­ряет, равна переменная значению "пустой указатель" (т.е. nil) или нет.