Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
Полиморфизм. Виртуальные и динамические методыСтр 1 из 4Следующая ⇒
Рассмотрим следующий пример. Пусть имеется некое обобщенное поле для хранения данных - класс tFiled и три его потомка - для хранения строк, целых и вещественных чисел: В этом примере классы содержат разнотипные поля данных 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, с помощью которой (и только с ней! ) можно переопределять оба этих типа методов. Первый метод класса tChildClass создается заново, два остальных перекрываются. Создадим объекты этих классов: Внутренняя структура этих объектов показана ниже. Первое поле каждого экземпляра каждого объекта содержит указатель на его класс. Класс, как структура состоит из двух частей. Начиная с адреса, на который ссылается указатель на класс, располагается таблица виртуальных методов. Она содержит адреса всех виртуальных методов класса, включая и унаследованные от предков. Перед таблицей виртуальных методов расположена специальная структура, содержащая дополнительную информацию. В ней содержатся данные, полностью характеризующие класс: имя, размер экземпляров, указатели на класс-предок и т.д. Одно из полей структуры содержит адрес таблицы динамических методов класса (DMT). Таблица имеет следующий формат: в начале - слово, содержащее количество элементов таблицы. Затем - слова, соответствующие индексам методов. Нумерация индексов начинается с –1 и идет по убывающей. После индексов идут собственно адреса динамических методов. Следует обратить внимание на то, что DMT объекта Obj1 состоит из двух элементов, Obj2 - из одного, соответствующего перекрытому методу DynamicMethod1. В случае вызова Obj2.DynamicMethod2 индекс не будет найден в DMT Obj2, и произойдет обращение к DMT Obj1. Именно так экономится память при использовании динамических методов. Как указывалось выше, указатель на класс указывает на первый виртуальный метод. Служебные данные размещаются перед таблицей виртуальных методов, то есть с отрицательным смещением. Эти смещения описаны в модуле SYSTEM.PAS: Поля vmtDynamicTable, vmtDispatch и vmtDefaultHandler отвечают за вызов динамических методов. Поля vmtNewInstance, vmtFreeInstance и vmtDestroy содержат адреса методов создания и уничтожения экземпляров класса. Поля vmtIntfTable, vmtAutoTable, vmtSafeCallException введены для обеспечения совместимости с моделью объекта COM. Остальные поля доступны через методы объекта tObject. В Object Pascal эта информация играет важную роль и может использоваться программистом неявно. В языке определены два оператора - is и as, неявно обращающиеся к ней. Оператор is предназначен для проверки совместимости по присвоению экземпляра объекта с заданным классом. Выражение вида: Принимает значение True только если объект AnObject совместим по присвоению с классом tObjectType, то есть является объектом этого класса или его потомком. Оператор as введен в язык специально для приведения объектных типов. С его помощью можно рассматривать экземпляр объекта как принадлежащий к другому совместимому типу: От стандартного способа приведения типов использование оператора as отличается наличием проверки на совместимость типов во время выполнения: попытка приведения к несовместимому типу приводит к возникновению исключительной ситуации eInvalidCast. После выполнения оператора as сам объект остается неизменным, но выполняются те его методы, которые соответствуют присваиваемому классу. Вся информация, описывающая класс, создается и размещается в памяти на этапе компиляции. Доступ к информации вне методов этого класса можно получить, описав соответствующий указатель, называющийся указателем на класс или указателем на объектный тип (class reference). Он описывается при помощи зарезервированных слов class of. Например, указатель на класс tObject описан в модуле SYSTEM.PAS и называется tClass. Аналогичные указатели определены и для других важнейших классов: tComponentClass, tControlClass и т.д. С указателем на класс тесно связано понятие методов класса. Такие методы можно вызывать и без создания экземпляра объекта - с указанием имени класса в котором они описаны. Перед описанием метода класса нужно поставить ключевое слово class. Разумеется, методы класса не могут использовать значения, содержащиеся в полях класса: ведь экземпляра класса не существует!!! Методы класса служат для извлечения внутренней информации класса. Ниже перечислены методы класса tObject: Метод и Описание В Delphi 4 в класс tObject добавлены еще два виртуальных метода - AfterConstruction и BeforeDestruction. Как следует из названия, они вызываются сразу после создания экземпляра объекта и непосредственно перед уничтожением. Перегрузка методов В Delphi 4 появилась новая разновидность методов - перегружаемые. Перегрузка нужна для того, чтобы произвести одинаковые или похожие действия над разнотипными данными. Перегружаемые методы описываются с ключевым словом overload. Объявив метод SetData перегружаемым, в программе можно использовать обе его реализации одновременно. Это возможно потому, что компилятор определяет тип передаваемого параметра (целый или вещественный) и в зависимости от этого подставит вызов соответствующего метода. Для перегрузки виртуального метода используется зарезервированное слово reintroduce: На перегрузку методов накладывается ограничение: нельзя перегружать методы, находящиеся в области видимости published. Абстрактные методы Абстрактными называются методы, которые определены в классе, но не содержат никаких действий, никогда не вызываются и обязательно должны быть переопределены в классах-потомках. Абстрактными могут быть только виртуальные и динамические методы. Для описания абстрактного метода используется директива abstract: Никакого кода абстрактный метод не содержит. Его вызов приведет к созданию исключительной ситуации eAbstractError. События Операционная система Windows® основана на сообщениях. Сообщения эти возникают в результате действий пользователя, аппаратуры компьютера или других программ. Таких сообщений в Windows сотни, и по большому счету, написать программу для Windows - значит определить реакцию на некоторые из них. Работать с таким количеством сообщений, даже имея под рукой справочник, нелегко. Поэтому одним из главных преимуществ Delphi является то, что программист полностью избавлен от необходимости работать с сообщениями Windows (хотя такая возможность у него имеется). Типовых событий в Delphi - не более двух десятков, и все они имеют простую интерпретацию, не требующую глубоких знаний среды. С точки зрения языка, событие - это поле процедурного типа, предназначенное для создания пользовательской реакции на те или иные входные воздействия: Здесь OnMyEvent - поле объекта, содержащее адрес некоторого метода. Присвоить такому свойству значение - значит указать программе адрес метода, который будет вызываться в момент наступления события. Такие методы называются обработчиками событий. Внутри библиотеки времени выполнения Delphi вызовы обработчиков событий находятся внутри методов, обрабатывающих сообщения Windows. Выполнив необходимые действия, этот метод проверяет, известен ли адрес обработчика, и, если это так, вызывает его. События имеют разное количество и тип параметров, в зависимости от происхождения и назначения. Общим для всех события является параметр Sender - он указывает на объект, вызвавший событие. Самое простое событие - tNotifyEvent - не имеет других параметров. Тип метода, предназначенный для извещения о нажатии клавиши, предусматривает передачу в процедуру кода этой клавиши, о передвижении мыши - ее текущих координат и т.д. Имя события в Delphi начинается с префикса On: OnClick, OnCreate, OnMouseDown и т.д. Имена методов - обработчиков событий состоят из имени объекта, генерирующего событие, и корня имени события: OkButtonClick. Дважды щелкнув мышью в инспекторе объектов на странице Events в поле напротив любого события, вы получите скелетный код (заготовку) этого события. Поскольку события - это свойства объекта, их значения можно менять в любой момент во время выполнения программы. Эта возможность называется делегированием. Можно в любой момент взять способы реакции на события у одного объекта и присвоить (делегировать) их другому. Принцип делегирования позволяет избежать трудоемкого процесса порождения новых классов для каждого специфического случая, заменяя нго простой подстановкой процедур. Один и тот же обработчик может обрабатывать события от разных объектов. Типичный пример - в современных программах одно и то же действие можно вызвать несколькими способами - через главное меню, контекстное меню и т. д. Этого легко добиться, назначив один и тот же обработчик всем необходимым объектам в инспекторе объектов. азумеется, события должны быть одного и того же типа. Более сложный случай - когда внутри обработчика необходимо определить, какой именно объект вызвал событие. Если объекты, которые потенциально могут это сделать, имеют разный тип, то именно их тип можно применить в качестве критерия: Если же все объекты, разделяющее между собой один обработчик события, относятся к одному классу, можно использовать свойство tObject.Tag. Ниже приведены названия некоторых, наиболее употребительных событий Delphi: Событие - Тип обработчика - Когда возникает OnClick - TNotifyEvent - При нажатии левой клавиши мыши OnActivate - TnotifyEvent - После передачи объекту фокуса OnCreate - TnotifyEvent - После создание объекта OnDestroy - TnotifyEvent - Перед уничтожением объекта OnDeactivate - TnotifyEvent - Перед уходом фокуса из объекта OnKeyPress - TkeyPressEvent - При нажатии клавиши OnMouseDown - TmouseEvent - При нажатии клавиши мыши OnMouseMove - TmouseMoveEvent - При движении мыши над объектом OnMouseUp - TmouseEvent - При отпускании клавиши мыши а также их типы: Стандартные события описаны в модуле Classes. Динамические массивы В Delphi существуют динамические массивы, то есть массивы, длину которых можно изменять во время выполнения программы. аньше проблема динамических массивов стояла довольно остро. Стандартных средств для работы с ними не было, и программистам приходилось вручную создавать подобные структуры (как правило, на основе динамических переменных). Теперь компилятор Delphi сам выполняет всю “грязную” работу. Параллельно с динамическими, в Delphi сохранились и обычные, статические массивы. Длина динамических массивов определяется уже во время выполнения программы. Описав переменную как мы будем иметь указатель, соответствующий массиву вещественных чисел. Память под него выделяется процедурой SetLength: Здесь A - указатель на динамический массив, NewLength - длина массива. В динамических массивах нумерация индексов начинается только с нуля. То обстоятельство, что динамические массивы фактически являются указателями, приводит к определенным нюансам в их использовании. Первый из них касается присвоения массивов. Запись В случае со статическими массивами, означает, что всем элементам массива A1 присваиваются значения соответствующих элементов массива A2, но при этом, переменные A1и A2 - разные переменные, хранящиеся в разных областях памяти. В случае с динамическими массивами присвоение их друг другу - не более чем присвоение указателей. ассмотрим пример: Значение A2[0] также изменится и станет равно 2. Это произошло потому, что после присвоения указатели ссылаются на один и тот же адрес в памяти и изменения в одном массиве приведут к синхронным изменениям в другом. Для того, чтобы действительно скопировать элементы в A2, необходимо использовать функцию Copy: Здесь А - указатель на копируемый массив, Index - индекс первого копируемого элемента, Count - число копируемых элементов. A2: =Copy(A1, 0, Length(A1)); Освободить выделенную под массив память можно при помощи процедуры finalize: procedure Finalize( var A [; Count: Integer] ); здесь V - указатель на динамический массив, Count - количество массивов (в случае, когда несколько динамически создаваемых структур размещаются подряд в одном непрерывном блоке памяти, их можно освободить одним вызовом Finalize); или просто присвоив указателю на массив значение nil. В этом случае память не будет потеряна (как могло бы показаться на первый взгляд), а будет вызвана соответствующая процедура, корректно освобождающая память. Причем, память освободиться только тогда, когда на нее не уже не ссылается ни одна динамически созданная структура. Значения граничных индексов (как статических так и динамических) можно получить при помощи функций Low(A) - нижний граничный индекс и Hight(A) - верхний граничный индекс. Число элементов массива - функцией Length: function Length(A): Integer; Дальнейшим развитием идеи динамических массивов являются многомерные динамические массивы. Можно пойти еще дальше, и создать динамические массивы с переменной длиной по разным индексам. Для создания такого массива сначала нужно задать его размерность по первому индексу: Это означает, что массив будет состоять из десяти строк. Теперь длина каждой строки задается отдельно: В использовании многомерных динамических массивов есть одно ограничение: передавать их в процедуры как параметры нельзя. Перегружаемые функции Перегрузка функций ничем не отличается от перегрузки методов. Основная идея перегрузки функций и методов заключается в том, что в программе можно иметь две или болеефункции с одинаковыми именами и разными наборами параметров. Перегружаемые функции, также как и методы описываются с ключевым словом overload. Например: Такой подход делает ваш код более простым, его легче сопровождать и расширять. Перегрузка нужна для того, чтобы произвести одинаковые или похожие действия над разнотипными данными. При вызове перегруженной функции компилятор определяет тип фактически передаваемого параметра (в приведенном выше примере - Byte или Char) и в зависимости от этого подставит вызов соответствующего варианта функции. |
Последнее изменение этой страницы: 2019-10-03; Просмотров: 190; Нарушение авторского права страницы