Abstract — Директива Delphi


Содержание

Abstract — Директива Delphi

Методы, с которыми вы имели дело в предыдущих разделах, называются статическими. По умолчанию все методы класса именно статические. Если в классе-наследнике переопределить такой метод (ввести новый метод с тем же именем), то для объектов этого класса новый метод отменит родительский. Если обращаться к объекту этого класса, то вызываться будет новый метод. Но если обратиться к объекту как к объекту родительского класса, то вызываться будет метод родителя.

Все это мы видели на примерах в предыдущем разделе. Но представим себе такую задачу. Вы объявили в приложении массив объектов типа TPerson:
var PersArray: array[1..10] of TPerson;

Далее заполняете этот массив вперемешку объектами классов TStudent и TEmpl, т.е. создаете, например, общий список учащихся и преподавателей. В разд. 3.5.4 вы видели, что это возможно, так как переменная базового класса может принимать объекты производных классов. А затем хотите пройти в цикле элементы этого массива и отобразить в окне Memo информацию о них:

for i:=l to 10 do Memol.Lines.Add(PersArray[i].PersonToStr);

Или аналогичная задача с использованием списка TList:
объявлена переменная var List: TList; в нее заносятся указатели на объекты классов TStudent и TEmpl, а затем вы хотите пройти в цикле элементы этого списка и отобразить их в окне Memo:

for i:=0 to List.Count — 1 do Memol.Lines.Add(TPerson(List[i]).PersonToStr);

Реализуйте один из этих вариантов (не забудьте при завершении приложения удалять объекты из памяти), и посмотрите, что получится. А получится очевидный результат: во всех случаях сработает метод PersonToStr родительского класса TPerson, так как соответствующие объекты объявлены объектами этого класса. И программа, естественно, не знает, объекты какого класса хранятся в массиве или в списке.

Конечно, можно усложнить код, проверять каждый раз операцией is истинный класс объекта, и указывать операцией as этот класс (см. об этих операциях в разд. 3.5.4). Для массива это будет выглядеть так:

А для списка аналогичный код имеет вид:

Задача будет решена, но некрасиво. Во-первых, код усложняется. А во-вторых, если вы впоследствии решите создать какие-то новые классы, производные от TPerson, вам придется вводить соответствующие дополнительные проверки в эти коды.

Красивое решение подобных задач обеспечивается очень легко с помощью виртуальных методов. Виртуальные методы не связаны с другими методами с тем же именем в классах-наследниках. Если в классах-наследниках эти методы перегружены, то при обращении к такому методу во время выполнения будет вызываться тот из методов с одинаковыми именами, который соответствует истинному классу объекта. В приведенных примерах, если сделать методы PersonToStr виртуальными, то операторы

будут автоматически вызывать методы PersonToStr того класса (TStudent, TEmpl или других производных от TPerson), к которому относится каждый объект. Такой подход, облегчающий работу с множеством родственных объектов, называется полиморфизмом.

Сделать метод родительского класса виртуальным очень просто. При объявлении в классе виртуальных методов после точки с занятой, завершающей объявление метода, добавляется ключевое слово virtual. Например, чтобы объявить в базовом классе TPerson метод PersonToStr виртуальным, надо в его объявление добавить слово virtual:

function PersonToStr: string; virtual;

Чтобы перегрузить в классе-наследнике виртуальный метод, надо после его объявления поставит ключевое слово override. В наших примерах объявления метода в классах и должны выглядеть так:

function PersonToStr: string; override;
И это все! Методы стали виртуальными и приведенные в начале данного раздела операторы, будут работать безо всяких проверок if.

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

function PersonToStr: string; overr > В родительском классе виртуальный метод не обязательно должен быть реализован. Такой виртуальный метод, реализация которого не определена в том классе, в котором он объявлен, называется абстрактным. Предполагается, что этот метод будет перегружен в классах-наследниках. Только в тех классах, в которых он перегружен, его и можно вызывать.

Объявляется абстрактный метод с помощью ключевого слова abstract после слова virtual. Например, вы можете в классе TPerson объявить метод PersonToStr следующим образом:

function PersonToStr: string; virtual; abstract;

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

Удачи!
Встретимся в следующем уроке!

Источник: www.thedelphi.ru
Автор: Савельев Александр
Опубликовано: 14 Июля 2013
Просмотров:

Зарегистрируйтесь или авторизуйтесь, чтобы добавлять комментарии.

Заметки о Pascal, Delphi и Lazarus

Следует ожидать переводов разделов справочной системы Delphi, компиляций из учебников, переводы статей, «путевые заметки» и прочие интересности. Блог прежде всего ориентирован на студентов, но опытных людей я тоже буду рад видеть;-)

среда, 2 мая 2012 г.

Методы

Метод – это процедура или функция, связанная с классом. При вызове метода определяется объект (или, если это метод класса, то класс) с которым должен работать метод. Например, SomeObject.Free вызывает метод Free объекта SomeObject.

О методах

Внутри объявления класса методы представлены заголовками процедур или функций, которые работают как упреждающие объявления. Где-то после объявления класса (но в этом же модуле) каждый метод должен быть реализован добавлением определяющего объявления. Например, предположим, что объявление TMyClass включает метод с именем DoSomething:

Определяющее объявление для DoSomething должно быть включено ниже в этом же модуле:

Класс может быть объявлен в модуле как в секции interface, так и в секции implementation, но определяющие объявления методов должны быть расположены в секции implementation.

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

Объявления методов могут включать специальные директивы, которые не используются при объявлении прочих процедур и функций. Директивы должны указываться только в объявлении класса, но не в определяющем объявлении, и могут следовать только в следующем порядке: reintroduce; overload; binding; calling convention; abstract; warning, где:

  • binding может принимать значения: virtual, dynamic или override;
  • calling convention может принимать значения: register, pascal, cdecl, stdcall или safecall;
  • warning может принимать значения: platform, deprecated или library.

Ключевое слово Inherited

Ключевое слово inherited играет особую роль при реализации полиморфизма в поведении методов. Оно может встречаться в объявлении методов, причем иногда с указанимем идентификатора после него, а иногда – без.

Если за ключевым словом inherited следует имя компонента, оно обозначает обычный вызов метода, обращение к свойству или полю, за исключением того, что поиск компонента, к которому идет обращение, начинается с непосредственного предка класса, к которому относится компонент. Например, если в определяющем объявлении метода присутствует:

происходит вызов наследуемого конструктора Create.

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

встречается часто в реализации конструкторов. Вызывает наследуемый конструктор с таким же списком параметров (передаваемых наследуемому конструктору).

Идентификатор Self

При реализации метода идентификатор Self обращается к объекту, из которого происходит вызов метода. Для примера приведем реализацию метода Add класса TCollection, объявленного в модуле Classes:

Метод Add вызывает метод Create класса, на который ссылается поле FitemClass (который всегда является наследником TCollectionItem). TCollectionItem.Create принимает единственный параметр типа TСollection. Таким образом, метод Add передает экземпляр объекта типа TCollection, вызывающего его. Это аналогично коду:

Self полезен по самым различным причинам. Например, если идентификатор компонента объявлен в классе, он может быть объявлен повторно в блоке метода, относящегося к этому классу. В этом случае, вы можете получить доступ к идентификатору компонента, используя вызов Self.Identifier.

Связывание методов

Связывание методов может быть статическим (static) (по умолчанию), виртуальным (virtual) или динамическим (dynamic). Виртуальные и динамические методы могут перекрываться и они могут быть абстрактными. Это подразделение методов начинает иметь значение, когда переменная одного типа класса хранит значение типа класса-потомка. Оно определяет, какая реализация метода будет активирована при запуске метода.

Статические методы

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

При таком объявлении приведенный ниже код иллюстрирует вызов статического метода. Во втором вызове Figure.Draw, переменная Figure ссылается на класс TRectangle, но вызов метода активирует реализацию метода Draw в классе TFigure, поскольку объявленный тип переменной Figure – это TFigure:

Виртуальные и динамические методы

Для того, чтобы сделать метод виртуальным или динамическим необходимо включить в его объявление соответствующую директиву (virtual или dynamic). Виртуальные и динамические методы, в отличии от статических могут быть перекрыты в классе-предке. При вызове перекрытого метода, решение о том, какую реализацию метода активировать, принимает действительный (определенный в процессе работы программы) тип класса или объекта, использванный при вызове метода.

Чтобы перекрыть метод его следует объявить повторно с указанием директивы override. Перекрывающее объявление должно совпадать с объявлением в классе-предке по порядку и типу параметров, а так же типу возвращаемого результата (для функций). В следующем примере метод Draw, объявленный в классе TFigure перекрывается в двух классах предках:

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

Только виртуальные и динамические методы могут быть перекрыты. Тем не менее, любые методы могут быть перегружены. Компилятор Delphi так же поддерживает концепцию финального виртуального метода. Если при объявлении виртуального метода было использовано ключевое слово final, классы-предки не смогут перекрывать его. Применение ключевого слова final может быть хорошим инженерным решением, которое может помочь документировать планируемое применение класса. Кроме того, оно помогает компилятору оптимизировать код.

Виртуальный или динамический?

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

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

Замечание: Динамические методы следует использовать только в тех случаях, когда выгода от их применения очевидна. Во всех обычных ситуациях следует использовать виртуальные методы.

Скрывать или перекрывать?

Если объявление метода определяет такой же идентификатор и набор параметров, какой уже присутствует в объявлении метода в классе-предке, но при этом не использует директиву override, новое объявление просто скрывает наследуемый метод, не перекрывая его. Оба метода будут существовать в классе-предке, в котором имя метода связано статически. Например:

Директива Reintroduce

Директива reintroduce подавляет предупреждения компилятора о сокрытии объявленных ранее виртуальных методов. Например:

Директиву reintroduce следует использовать, когда вы хотите скрыть наследуемый виртуальный метод, заменив его новым методом.

Абстрактные методы

Абстрактные методы – это виртуальные или динамические методы, не имеющие реализации в том классе, где они объявлены. Они должны быть реализованы в классе-потомке. Абстрактные методы должны объявляться с использованием директивы abstract, следующей за директивой virtual или dynamic. Например:

Вы можете вызывать абстрактный метод только в классе или экземпляре, перекрывающем этот метод.

Методы класса

Большая часть методов называются методами экземпляров, поскольку они работают с отдельными экземплярами объекта. Метод класса – это метод (отличный от конструктора), который оперирует классами, но не объектами. Существует два типа методов класса: обычные и статические.

Обычные методы класса

Объявление метода класса должно начинаться с зарезервированного слова class. Например:

Определяющее объявление метода класса также начинается с зарезервированного слова class:

В определяющем объявлении метода класса идентификатор Self представляет класс, в котором вызывается метод (может быть классом-потомком, по отношению к классу, в котором объявлен метод). Если методы вызывается в классе С, Self имеет тип class of C.

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

Статические методы класса

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

Для объявления статического метода класса следует добавить ключевое слово класса к объявлению:

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

Перегрузка методов

Метод может быть объявлен повторно с указанием директивы overload. В этом случае объявленный повторно метод имеет набор параметров, отличный от объявленного в классе-предке, и перегружает наследуемый метод, не скрывая его. Вызов метод в классе-потомке активирует реализацию метода с параметрами, совпадающими с фактическими.

Если вы перегружаете виртуальный метод, — пользуйтесь директивой reintroduce:

Внутри класса вы не можете установить видимость перегружаемых методов с одинаковыми именами в published. Обработка информации о типах в режиме выполнения программы требует уникального имени для компонентов с такой спецификацией видимости.

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

Конструкторы

Конструктор – это особый метод, который создает и инициализирует объекты. Объявление конструктора выглядит как объявление обычной процедуры, но начинается с ключевого слова constructor. Примеры:

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

Класс, может иметь более, чем один конструктор, но большая часть классов имеет только один. Обычно для конструкторов используется имя Create.

Для создания объекта следует вызывать конструктор, специфицируя название типа класса. Например:

Эта инструкция выделяет память для нового объекта, устанавливает значения для всех целочисленных полей в 0, присваивает значение nil для всех полей указательного типа и устанавливает пустое значение для всех строковых полей. Прочие операции, указанные в реализации конструктора, выполняются позже. Обычно объекты инициализируются значениями, которые передаются как параметры в конструктор. Наконец, конструктор возвращает ссылку на только что созданный и инициализированный объект. Тип возвращаемого значения совпадает с типом, указанным при вызове конструктора.

Если при выполнении конструктора, вызванного инструкцией с указанием ссылки на класс, возникает исключение деструктор Destroy вызывается автоматически для разрушения незаконченного объекта.

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

Далее приведен пример класса и его конструктора:

Первой инструкцией в конструкторе обычно бывает вызов наследуемого конструктора для инициализации унаследованных полей. Затем конструктор инициализирует поля, объявленные в классе-потомке. Поскольку конструктор всегда очищает память, которую он выделяет для создания объекта, все поля порядковых типов принимают значение 0, указатели и переменные типа класс – nil, строковые типы инициализируются пустым значением, а вариантные типы – значением Unassigned. Таким образом нет необходимости в теле конструктора инициализировать поля значениями, отличными от ноля или пустых значений.

При вызове через идентификатор типа класса конструктор, объявленный как virtual, ведет себя так же как обычный статический конструктор. Тем не менее, при вызове через ссылку на класс виртуальные конструкторы допускают полиморфное создание объектов – то есть создание объектов, тип которых неизвестен на этапе компиляции.

Деструкторы

Деструктор – это особый метод, который при вызове разрушает объект и освобождает занимаемую им память. Объявление деструктора выглядит как объявление обычной процедуры, но начинается с ключевого слова destructor. Например:

Деструкторы на платформе Win32 используют по умолчанию конвенцию вызова register. Хоть класс и может иметь несколько деструкторов, рекомендуется перекрывать наследуемый метод Destroy, без объявления прочих деструкторов.

Для вызова деструктора вы должны специфицировать экземпляр объекта:

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

Далее приведен пример реализации деструктора:

Последней инструкцией в теле деструктора обычно бывает вызов наследуемого деструктора, которые уничтожает наследуемые поля. При возникновении исключительной ситуации в процессе создании объекта, для разрушения незаконченного объекта автоматически вызывается деструктор Destroy. Это обозначает, что деструктор Destroy должен быть подготовлен к разрушению частично созданных объектов. Поскольку конструктор устанавливает нулевые и пустые значения полей перед выполнением каких-либо других действий, поля типа класс и указатели в частично созданном объекте всегда имеют значение nil. Прежде чем работать с такими полями деструктор должен проверять их на наличие этого значения. Вызов метода Free (объявлен в TObject) – это более подходящий способ для разрушения объекта с предварительной проверкой значений его полей.

Конструкторы классов

Конструктор класса – это особый метод класса, который недосупен разработчикам. Вызовы конструкторов автоматически вставляются компилятором в код секции инициализации модуля, в котором объявлен класс. Обычно конструкторы классов применяются для инициализации статических полей класса или для выполнения особого типа инициализации, требуемой для того, чтобы класс или какой-либо его экземпляр функционировал корректно. Даже если такой же результат может быть достигнут размещением кода инициализации класса в секции initialization, конструкторы класса в большей степени помогают компилятору решить, какой именно класс должен быть включен в результирующий двоичный файл, а какой – удален из него.

Следующий пример показывает обычную инициализацию полей класса:

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

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

Замечание: Несмотря на то, что компилятор уделяет внимание порядку инициализации классов, в некоторых сложных ситуациях он может оказаться случайным. Это происходит, когда конструктор класса зависит от состояния другого класса, который, в свою очередь, зависит от первого.
Замечание: конструктор класса-дженерика или записи может быть выполнен несколько раз. Точное количество запусков конструктора зависит от количества специализированных версий типа-дженерика. Например, конструктор класса для специализированного класса TList&ltString&gt может быть выполнен несколько раз в одном и том же приложении.

Деструкторы классов

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

Замечание: деструктор класса-дженерика или записи может быть выполнен несколько раз. Точное количество запусков деструктора зависит от количества специализированных версий типа-дженерика. Например, деструктор класса для специализированного класса TList&ltString&gt может быть выполнен несколько раз в одном и том же приложении.

Методы-обработчики сообщений

Обработчики сообщений реализуют реакцию на динамически передаваемые сообщения. Синтаксис метода-обработчика сообщения поддерживается для всех платформ. Приложения VCL применяют обработчики сообщений для реагирования на сообщения Windows.

Обработчик сообщения объявляется при помощи директивы message, за которой следует целочисленная константа с диапазоном значений от 1 до 49151, представляющая собой идентификатор сообщения. Для обработчиков сообщений в элементах управления VCL эта константа может быть идентификатором одного из сообщений Win32, определенных в модуле Messages. Метод-обработчик сообщения должен быть процедурой с единственным параметром, передаваемым по ссылке.

Обработчик сообщения не должен содержать директивы override для переопределения наследуемого метода. На самом деле он даже не должен повторять имени или типа параметра перекрываемого метода. Только идентификатор сообщения определяет, какое сообщение должен обработать метод и является ли он перекрывающим методом.

Реализация методов-обработчиков сообщений

Реализация обработчика может вызывать наследуемый метод-обработчик. Например:

Инструкция inherited выполняет поиск по иерархии класса и подключает первый найденный метод с соответствующим идентификатором сообщения и передает ему запись сообщения. Если классы-предки не реализуют метод для обработки сообщения с данным идентификатором, директива inherited вызывает метод DefaultHandler , объявленный в TObject.

Реализация DefaultHandler в TObject не подразумевает выполнения каких-либо операций. Перекрывая DefaultHandler, класс может реализовать собственную обработку сообщений по умолчанию. Для элементов управления на платформе Win32, метод DefaultHandler вызывает Win32 API DefWindowProc.

Обработка сообщений

Обработчики сообщений редко вызываются явно. Обычно сообщения передаются объекту при помощи метода Dispatch , наследуемого от TObject:

Параметр Message передаваемый методу Dispatch должен быть записью, первым полем которой должно быть поле типа Word, содержащее идентификатор сообщения.

Dispatch выполняет поиск обработчика сообщения в иерархии классов (начиная с класса объекта, в котором был вызван этот метод) и запускает первый найденный обработчик для идентификатора, который был ему передан. Если обработчика для сообщения с данным идентификатором не находится, Dispatch вызывает DefaultHandler.

Abstract — Директива Delphi

for i:= ControlCount — 1 downto 0 do
if controls[i] is TImage then
if (controls[i] as TImage).tag <> 0 then
begin
if (Controls[i] as TImage).Left = m.Left then
if (Controls[i] as TImage).Top = m.Top + 64 then (controls[i] as TImage).Free;

if (Controls[i] as TImage).Left = m.Left then
if (Controls[i] as TImage).Top = m.Top — 64 then (controls[i] as TImage).Free;

if (Controls[i] as TImage).Left = m.Left — 64 then
if (Controls[i] as TImage).Top = m.Top then (controls[i] as TImage).Free;

if (Controls[i] as TImage).Left = m.Left + 64 then
if (Controls[i] as TImage).Top = m.Top then (controls[i] as TImage).Free;
end;

Здаствуйте, подскажите пожалуйста, что мне делать, это мой код, свою работу он выполнает, но почему-то вызывает Abstarct Error. У меня собственно два вопроса:
1. Что значит этот Abstarct Error? (В хелпе к Delphi ничего про него нет.)
2. Как от него избавится?

> 1. Что значит этот Abstarct Error? (В хелпе к Delphi ничего
> про него нет.)

EAbstractError is the exception class for attempts to call an abstract method.

EAbstractError is raised when an application tries to call an Object Pascal abstract method. It is also raised at design time when a component with an Object Pascal abstract method is placed on a form.

Abstract methods are declared with the abstract directive and must be overridden in descendant classes before an instance object can be created.


> Что значит этот Abstarct Error?

Вызов абстрактного метода.

Твой код будет делать следующее — как только первый раз вызовется Free имедж будет удален из списка контролов и контрол i — это бцудет уже другой контрол. А ты обратишся уже к нему, как к TImage. Точнее, as сначала проверит, а потом уже обратится.

правильно сделать так
i:=COntrols.Count-1;
while (i > 0) do begin

Image:=TImage(Constrols[i]);
// теперь можно работать с интересующим тебя контролом
if Image.Left = . then
if Image.Top = . then begin
Image.Free;
inc (i);
Continue;
end;

2TUser
За разъяснее спасибо, но вот код жалко не работает, программа зависает =(

> 2Wish (18.01.06 18:38)

> это мой код, свою работу он выполнает, но почему-то вызывает
> Abstarct Error.

В каком месте?

Этого я не знаю, отладчик не заходит — просто Abstarct Error. Наверно когда он первый раз обращается к Image»у, то есть вот тут: if (Controls[i] as TImage).Left = m.Left then

> 2Wish (18.01.06 19:34) [5]

> Этого я не знаю, отладчик не заходит — просто Abstarct Error.

Как это — «не заходит»? Поставьте контрольную точку, пошагово выполняйте до появления исключения.

Мне, например, не совсем ясно, как этот код может вызвать EAbstractError.

«не заходит» это очепятка, имелось ввиду не находит.

поставил в коде операторы Showmessage и начал следить за происходящим:
оказалось абстрактная ошибка произходит в самом конце процедуры, после всех операторов, причем ошибка возникает только если в конце кода стоит m.free; , когда я создавал топиг я не придал значения этому оператору и даже не скопировал его. :( тогда новый вопрос: почему освобождение из памяти переменной M: TImage может вызывать абстракную ошибку.

inc (i) я там зря написал

В первоначальном варианте везде замени
then (controls[i] as TImage).Free;
на
then
begin
controls[i].Free;
Continue;
end;

повторяю: «оказалось абстрактная ошибка произходит в самом конце процедуры, после всех операторов, причем ошибка возникает только если в конце кода стоит m.free;, когда я создавал топиг я не придал значения этому оператору и даже не скопировал его. :( тогда новый вопрос: почему освобождение из памяти переменной M: TImage может вызывать абстракную ошибку.» В верхнем коде ничего менять не надо.


> 2Wish (18.01.06 20:37) [10]

Долго смотрел на код, так и не понял, что это за фигня такая «m». Да и по фиг мне, если кто-то отладчиком пользоваться не умеет, то это сугубо его личная проблема. А гадать какое ещё дерьмо из кода здесь не приведено совершенно не интересно.

«что это что это за фигня такая «m»» не имеет принципиального значения, хоть какие нить кнопка, хоть название формы.

На один пост выше написанно, «что это что это за фигня такая «m»». Если тебе влом пречитать посты выше, но очень хочется обосрать кого нить — думаю ты выбрал не тот форум.

зы. Вопрос остаётся открытым, выкладываю полный код процедуры:

var i:integer
begin
for i:= ControlCount — 1 downto 0 do
if controls[i] is TImage then
if (controls[i] as TImage).tag <> 0 then
begin
if (Controls[i] as TImage).Left = m.Left then
if (Controls[i] as TImage).Top = m.Top + 64 then (controls[i] as TImage).Free;

if (Controls[i] as TImage).Left = m.Left then
if (Controls[i] as TImage).Top = m.Top — 64 then (controls[i] as TImage).Free;

if (Controls[i] as TImage).Left = m.Left — 64 then
if (Controls[i] as TImage).Top = m.Top then (controls[i] as TImage).Free;

if (Controls[i] as TImage).Left = m.Left + 64 then
if (Controls[i] as TImage).Top = m.Top then (controls[i] as TImage).Free;
end;
m.free;
end;

Почему строчка m.free; вызывает Abstract Error.


> Почему строчка m.free; вызывает Abstract Error.

Можно увидеть как «m» создаётся? Надоело клещами всё вытягивать.

Ты процедуре компонент создаешь что-ли?
Если нет зачем .free?


> Почему строчка m.free; вызывает Abstract Error.

Варинтов тьма.
Например:
Потому, что к этому моменту m содержит «мусор», уже уничтоженный объект.

Где в коде m := ***.Create ?

Когда тя спрашивают «кто такой m», кроме всего имеется ввиду еще и «откуда он взялся, этот m?»

Нет, он не пустой и содержит именно тот объект который нужен, вся процедура работает зачетально, но ошибка вылазит все равно. Выберает эту переменную М сам юзер, так что получаем её вот таким образом m:=(Sender as TImage);


> Выберает эту переменную М сам юзер, так что получаем её
> вот таким образом m:=(Sender as TImage);

Что мешает «юзеру выбрать переменную M» дважды?

Понятия не имею причём тут AbstractError, но лично меня крайне смущает то, что во-первых:

> m:=(Sender as TImage);

, а во-вторых:

> m.free;
> end;
>
> Почему строчка m.free; вызывает Abstract Error.


Имхо, «на лице» попытка уничтожить объект из его собственного обработчика.
:(


> Гаврила © (18.01.06 20:30) [9]

+ все-таки for на while

Abstract — Директива Delphi

Профессиональная разработка приложений с помощью Delphi5

Часть 3. Оформление приложений для Windows95/98/NT/2000 в Delphi

© Сергей Трепалин
УКЦ Interface Ltd.
КомпьютерПресс #3 2001
Статья была опубликована в КомпьютерПресс

Данная публикация является первой в цикле статей, посвященном профессиональной разработке приложений с помощью Delphi 5. Статьи цикла будут содержать базовые сведения, необходимые для создания и отладки приложений, отвечающих современным требованиям. Они представляют интерес для любого программиста — независимо от области применения создаваемых приложений.

Объектно-ориентированное программирование (ООП) – это основа для создания приложений в Windows. В 80-х годах материалов, посвященных объектно-ориентированному программированию, публиковалось довольно много. Несмотря на то что за прошедшее время в синтаксисе объектно-ориентированных языков программирования произошли существенные изменения и появились новые возможности, авторы современных учебных пособий почему-то не уделяют этому вопросу должного внимания. Мы решили восполнить этот пробел. В данной публикации в первую очередь рассматривается то новое, что появилось в Delphi по мере совершенствования этого продукта.

Материалы, изложенные в настоящем разделе (если это особо не оговаривается) применимы как к Delphi, так и к C++Builder (за исключением синтаксиса).

Современное название объекта – класс; термин «объект» иногда используется для обозначения рабочей копии (экземпляра) класса.

Класс можно вкратце определить как запись record, к которой добавлены методы для работы с переменными внутри этой записи. Однако в отличие от записей и методов, объявленных вне класса, класс обладает рядом дополнительных возможностей.

Каждому классу, за исключением самого первого, должен предшествовать класс-родитель. В свою очередь, любой класс можно использовать для создания других классов, и он в этом случае будет являться их родителем. В Delphi у класса бывает только один родитель, в C++ родителей может быть несколько. Поэтому в Delphi классы образуют иерархическое дерево с классом TObject в роли корня. Иерархию классов в Delphi можно проследить при вызове команды View/Browser после успешной компиляции какого-либо проекта.

В C++ такое дерево построить невозможно: отдельные его ветви будут сливаться.

Иерархическое дерево реализуется довольно просто: при определении в среде-разработке нового класса указывается класс-предок:

Разрешается не указывать класс-предок. В этом случае по умолчанию считается, что предком является TObject. Код вида

Такое объявление однозначно определяет место нового класса в иерархии классов. Кроме того, оно означает следующее: все переменные и все методы класса-предка копируются в новый класс. Простым объявлением TMyListBox=class(TListBox) мы получили новый класс, который обладает всеми свойствами списка: в него можно добавлять строки, он будет показан на форме, при необходимости на нем автоматически появится вертикальная полоса прокрутки и т.д. Таким образом, при продвижении по ветвям иерархического дерева происходит накопление переменных и методов. Например, класс TWinControl имеет все переменные и методы, определенные в классах TControl, TComponent, TPersistent и TObject.

Самый простой класс – TObject — не имеет предка. Он также не имеет полезных при обычном написании приложений методов и переменных. Однако он играет важнейшую роль в поведении объектов.

Объявление переменных и методов класса

Переменные в объекте используются для хранения данных, в качестве которых можно использовать любые типы из Object Pascal, в том числе и сами классы. Добавляются они в новый класс так же, как и в тело приложения, но без служебного слова var:

TMy > public
FName:string;
FAge:integer;
end;

В класс TMyClass к переменным класса TObject добавлены две новые переменные: FName и FAge. Названия переменных в классе принято (но не обязательно) начинать с буквы F от слова field. Классовые переменные, определенные внутри класса, отличаются от глобальных (служебное слово var в секции interface или implementation модуля) и локальных (служебное слово var в процедуре или функции) переменных. При загрузке приложения память для глобальных переменных выделяется немедленно и освобождается по завершении приложения. Локальным же переменным память выделяется в стеке при вызове метода, и после завершения работы метода эти ресурсы возвращаются системе.Так, если была объявлена, например, одна глобальная (или локальная) переменная типа N:integer, то резервируется 4 байта памяти, куда можно поместить одно значение. При объявлении же классовых переменных во время загрузки приложения не выделяется память для их хранения – она выделяется только при создании экземпляра класса после вызова конструктора (см. ниже). Поскольку экземпляров класса может быть несколько, в работающем приложении может быть несколько копий классовых переменных (в том числе и нулевое количество). Соответственно в каждой из этих переменных могут храниться различающиеся данные. Этим и определяется отличие классовых переменных от глобальных и локальных – для последних имеется только одна копия. Еще одной интересной особенностью классовых переменных является то, что при создании экземпляра класса они инициализируются нулями (то есть все их биты заполняются нулями). Поэтому если такая переменная представляет собой указатель, то он равен nil, если целое число, то оно равно 0, если логическую переменную, то она равна False. Локальные и глобальные переменные не инициализируются.

Формально объявление метода класса похоже на объявление обычного, не относящегося к классу, метода:

TMy > public
FName:string;
FAge:integer;
procedure DoSomething(K:integer);
function AddOne(N:integer):integer;
end;

В данном примере к методам TObject добавлены два новых метода – DoSomething и AddOne. Синтаксис Object Pascal разрешает объявлять новые методы только после объявления переменных – приведенный ниже пример вызовет ошибку компиляции:

TMy > public
FName:string;
procedure DoSomething(K:integer);
FAge:integer;
function AddOne(N:integer):integer;
end;

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


interface
type
TMy > procedure DoSomething(N:integer);
end;
TSecond > procedure DoSomething(N:integer);
end;


implementation
procedure TMyClass.DoSomething(N:integer);
begin

end;

procedure TSecondClass.DoSomething(N:integer);
begin

end;

Методы, объявленные в классе

Методы, описываемые в классе, подразделяются на классовые методы и другие методы. Приведем примеры их объявления и реализации:

TMy > class procedure DoSomething(K:integer);

function AddOne(N:integer):integer; absent – means method>
end;

implementation

class procedure TMyClass.DoSomething(K:integer);
begin

end;

function TMyClass.AddOne(N:integer):integer;
begin

end;

Обратите внимание, что и в секции реализации используется служебное слово class.

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

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

Для работы с методами имеется структура TMethod, определенная в модуле SysUtils:

type
TMethod = record
Code, Data: Pointer;
end;

Эта запись позволяет «разобрать» и вновь «собрать» метод класса на две переменные типа Pointer, что бывает полезным для передачи ссылки на метод во внутренний (in-process) сервер автоматизации.

Илон Маск рекомендует:  Макет из двух колонок

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

TMy > function AddOne(N:integer):integer; – means
static method>
procedure Rotate(Angle:single); virtual;
procedure Move(Distance:single); dynamic; method>
end;

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

В классическом объектно-ориентированном программировании требуется наличие виртуальных методов. Динамические методы поддерживают не все объектно-ориентированные языки, но их использование является достаточно эффективным. Для понимания различий между ними рассмотрим создание главной формы какого-либо приложения. При этом разберемся, как в памяти компьютера распределяются три метода формы: DoEnter (динамический метод), CreateWnd (виртуальный) и DoKeyDown (статический). Каждый из этих методов определен на уровне TWinControl.

Иерархия классов, которая ведет от TWinControl к TForm1, следующая:

TWinControl -> TScrollingWinControl -> TCustomForm -> TForm ->
TForm1

При этом в памяти компьютера будет размещена единственная копия статического метода DoKeyDown. При создании экземпляра любого из вышеперечисленных классов и вызове из него метода DoKeyDown компилятор генерирует код, который ссылается на адрес этого метода. Таким образом, один экземпляр статического метода обслуживает не только несколько экземпляров класса, в котором он определен, но и все экземпляры его потомков. При этом следует обратить внимание на то, что вызов метода происходит очень быстро: в коде указывается его адрес в ОЗУ.

При динамическом методе DoEnter в памяти компьютера создается единственная его копия, а в таблице динамических методов TWinControl указывается его адрес. В таблицах динамических методов классов TScrollingWinControl…TForm1 в качестве адреса этого метода указывается nil. При вызове этого метода из экземпляра класса TForm1 первоначально происходит поиск этого метода в таблице динамических методов TForm1. Естественно, метод найден не будет, и поиск продолжится уже в таблице динамических методов класса TForm. Так будет продолжаться до тех пор, пока не начнется поиск в таблице динамических методов TWinControl, где будет найден его адрес, по которому будет передано управление процессом. Как и статические, динамические методы требуют мало ресурсов: один экземпляр динамического метода обслуживает как экземпляры класса, где он определен, так и экземпляры всех его потомков. Но вызов метода происходит достаточно долго, поскольку для этого приходится просматривать несколько таблиц. Вызов может замедляться еще и при использовании директивы компилятора <$R+>(проверка диапазона допустимых значений).

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

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

Переписанные методы. Полиморфизм. Абстрактные методы

Методы классов могут вызываться как непосредственно программистом, так и автоматически в ответ на какие-либо события. Предположим, перед вызовом метода необходимо выполнить какое-либо действие. Если метод вызывается в явном виде из кода программы, то программист может перед вызовом метода дописать необходимый код. Однако достаточно часто бывает необходимо выполнить действия перед автоматическим вызовом классом какого-либо метода в ответ на какое-либо событие (или после вызова метода). Например, класс TEdit (однострочный редактор текста) позволяет редактировать текст. При этом, например, программист хочет, чтобы пользователь не мог покинуть данный элемент управления, пока в нем не будет введено корректное значение какого-либо текста (например, целое число). Но покинуть данный элемент управления и перейти к другому пользователь может несколькими способами – щелкнуть мышкой на другом элементе, нажать акселератор или клавишу Tab. Если бы программист никак не мог изменить имеющиеся методы, он вынужден был бы перехватывать события о нажатой клавише мыши на всех имеющихся на форме элементах управления, проверять содержимое целевого элемента и при необходимости возвращать ему фокус ввода. Такие же проверки необходимо было бы сделать для обработчика событий, связанных с нажатием на клавиши…

К счастью, в классах виртуальные и динамические (но не статические!) методы можно подменить на другие, созданные программистом. При этом, если данный метод вызывается автоматически, будет выполняться уже новый метод, написанный программистом. Такая подмена осуществляется при использовании служебного слова override. В данном случае (проверка содержимого редактора перед выходом из него) решение будет заключаться в следующем. Любой объект класса TWinControl (и TEdit) вызывает метод DoExit перед тем, как он теряет фокус ввода. Этот метод является динамическим, и его можно переписать:

TMyEdit=class(TEdit)
protected
procedure DoExit; override;
end;

implementation

procedure TMyEdit.DoExit;
var
N,I:integer;
begin
inherited DoExit;
Val(Text,N,I);
if I<>0 then begin
MessageDlg(‘Illegal value’,mtError,[mbOK],0);
SetFocus;
end;
end;

Теперь, создавая копию данного класса в обработчике события OnCreate главной формы

procedure TForm1.FormCreate(Sender: TObject);
begin
with TMyEdit.Create(Self) do begin
Parent:=Self;
Left:=200;
Top:=100;
end;
end;

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

Таким образом, переписывание виртуального или динамического метода осуществляется следующим образом. В классе-потомке определяется метод с тем же самым названием и с тем же самым списком параметров, который был ранее объявлен в каком-либо из классов-предков данного класса. При этом не имеет значения, как называются формальные параметры переписываемого метода, значение имеет их порядок следования, модификаторы (var, const) и их тип. Например, на уровне TWinControl определен виртуальный метод: procedure AlignControls(AControl: TControl; var Rect: TRect); При его переписывании в каком-либо классе-потомке данный метод можно определить следующим образом:

procedure AlignControls(AC: TControl; var R: TRect); override;

Однако компилятор Delphi не пропустит перечисленные ниже определения:

procedure AControls(AControl: TControl; var Rect: TRect); override; is not consistent>
procedure AlignControls(AControl: TControl; Rect: TRect); override; missed>
procedure AlignControls(var Rect: TRect); override;

Следует обратить внимание на использованное в реализации метода DoExit служебное слово inherited. Подробно его значение мы обсудим позднее, пока же будем считать, что он вызывает метод класса-предка. При переписывании методов в большинстве случаев необходимо вызывать методы класса-предка или, по крайней мере, четко представлять себе, что случится, если не вызвать метод предка. Забывание оператора inherited, как правило, приводит к фатальным сбоям в приложении, причем часто это проявляется не сразу.

Итак, рассмотрим еще раз, что происходит в классе TEdit и его потомке TMyEdit. Класс TEdit устроен таким образом, что перед тем, как он теряет фокус ввода, приложение обращается к таблице динамических методов TEdit и извлекает оттуда адрес 27-го метода (метод DoExit среди динамических определен 27-м по счету). После этого управление процессом передается по найденному адресу. Класс-потомок TMyEdit имеет собственные таблицы виртуальных и динамических методов. Таблица динамических методов отличается тем, что в нем 27-й адрес уже указывает на реализованный нами метод DoExit. Соответственно приложением извлекается уже новый адрес, и управление процессом передается вновь реализованному методу. При его старте происходит обращение к таблице динамических методов класса TEdit и вызывается 27-й метод – это делает строка inherited DoExit. Затем проверяется содержимое свойства Text в экземпляре класса TMyEdit, и если это не целое число, то об этом сообщается пользователю и фокус ввода вновь переносится на редактор текста. Схематически это можно изобразить на рисунке следующим образом.

При забывании директивы override появляется новый, статический метод с тем же самым названием и списком параметров, что и ранее определенный виртуальный (или динамический) метод. При этом новый метод делает «невидимым» старый, то есть при явном вызове метода из кода приложения будет вызываться новый, определенный программистом метод. Если метод вызывается классом в ответ на какое-либо событие, по-прежнему будет вызываться старый метод (адрес в таблицах не изменится). Такое поведение объекта часто приводит к печальным последствиям, которые иногда очень трудно обнаруживаются.

С переписываемыми методами тесно связано и другое понятие для классов – полиморфизм, суть которого заключается в том, что родительский класс имеет какой-либо виртуальный или динамический метод, а в различных его потомках этот метод переписывается по-разному. После этого выполняя формально одни и те же методы, можно добиться принципиально разного результата . Хороший пример полиморфных классов – класс TStream (базовый) и три его потомка – TFileStream, TMemoryStream и TResourceStream. Эти классы используются для хранения и передачи данных в двоичном формате. В базовом классе TStream определено несколько абстрактных методов, например:

function Write(const Buffer; Count: Longint): Longint; virtual;

а в классах-потомках этот метод переписан таким образом, что записывает данные из переменной Buffer в файл (TFileStream), или в ОЗУ (TMemoryStream), или в ресурсы (TResourceStream). Программист при реализации приложения может определить метод для сохранения своих данных в двоичном формате:

procedure SaveToStream(Stream:TStream);
begin
Stream.Write(FYear,sizeof(FYear));
Stream.Write(FMonth,sizeof(FMonth));
<. A very long code to save all data as binary may
be inserted here …>
end;

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

procedure TForm1.Button1Click(Sender: TObject);
var
MStream:TMemoryStream;
FStream:TFileStream;
begin
MStream:=TMemoryStream.Create;
SaveToStream(MStream);
DoSomething(MStream);
MStream.Free;
FStream:=TFileStream.Create(‘C:\Test.dat’,fmCreate);
SaveToStream(FStream);
FStream.Free;
end;

В классах, являющихся базовыми для полиморфизма, часто используют абстрактные методы. Они объявляются со служебным словом abstract:

TMy > protected
procedure DisplayGraphic(Canvas:TCanvas); virtual;
abstract;
end;

При объявлении метода абстрактным реализация этих методов не требуется (более того – не допускается компилятором). В секции implementation для метода DisplayGraphic абсолютно ничего писать не надо. Для абстрактного метода в виртуальной (или динамической) таблице методов резервируется запись, куда помещается адрес nil. В классах-потомках на место этого адреса подставляются реальные адреса.

При попытке вызвать абстрактный метод возникает исключение – метод-то отсутствует! В частности, для вышеприведенного примера метод Write класса TStream является абстрактным и при попытке вызвать метод SaveToStream

procedure TForm1.Button1Click(Sender: TObject);
var
Stream:TStream;
begin
Stream:=TStream.Create;
SaveToStream(Stream);
Stream.Free;
end;

Классы, содержащие абстрактные методы, называют абстрактными; они являются базовыми для создания классов-потомков. В любом случае не следует создавать экземпляры абстрактных классов в приложении! Компилятор Delphi тем не менее позволяет осуществлять вызов конструкторов абстрактных классов с соответствующим предупреждением.

Так же как и для обычных методов, в методах класса допустима директива overload – перегружаемый метод:

TOver1 > public
procedure DoSmth(N:integer); overload;
class procedure DoSecond(N:integer); overload; dynamic;
end;

Перегружаемые методы могут быть как классовыми, так и обычными. Они способны быть статическими, динамическими и виртуальными. Следующие перегружаемые методы могут быть объявлены как в исходном классе, так и в классах-потомках:

TOver2 > public
procedure DoSmth(S:string); overload;
class procedure DoSecond(S:string); reintroduce; overload; dynamic;
end;

Теперь можно вызывать методы DoSmth и DoSecond из экземпляра TOver2Class с целочисленным и строковым параметром:

procedure TForm1.Button3Click(Sender: TObject);
var
CO:TOver2Class;
begin
CO:=TOver2Class.Create;
CO.DoSmth(1);
CO.DoSmth(‘Test’);
CO.Free;
end;

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

Создание экземпляров классов. Конструкторы и деструкторы

Переменная типа класса объявляется в приложении так же, как обычная переменная:

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

При вызове конструктора класса резервируется память, необходимая для хранения данных в объекте (переменных). Обращаться к переменным и методу класса можно только после вызова конструктора, иначе попытка их чтения/записи приводит к исключительной ситуации. Обращение к переменным и методам экземпляра класса осуществляется с указанием на экземпляр:

procedure TForm1.Button1Click(Sender: TObject);
var
SL1,SL2:TStringList;
begin
SL1:=TStringList.Create;
SL2:=TStringList.Create;
SL1.Add(‘String added to SL1 object’);
SL2.Add(‘String added to SL2 object’);
….
end;

Довольно распространенной ошибкой (которая, правда, тут же обнаруживается) является вызов конструктора с попыткой обратиться к несуществующему экземпляру класса:

procedure TForm1.Button1Click(Sender: TObject);
var
SL:TStringList;
begin
SL:=nil;
try
SL.Create; typed SL:=TStringList.Create;>

finally
if Assigned(SL) then SL.Free;
end;
end;

Данный код компилятор пропускает без каких-либо диагностик, но попытка выполнить его немедленно приводит к возникновению исключения.

Любой класс может иметь несколько конструкторов. Примером может служить класс Exception, имеющий восемь конструкторов. По соглашению имя конструктора содержит слово Create (CreateFmt, CreateFromFile…). Конструкторы могут быть как статическими, так и виртуальными или динамическими. Последние могут быть переписаны – в классах-потомках при необходимости определяется новый конструктор со служебным словом override. Переписывать конструкторы необходимо только для компонентов Delphi и для форм – во всех остальных классах их можно просто добавлять к существующим методам. Необходимость переписывания конструкторов компонентов и форм обусловлена тем, что их вызывает среда разработки. Забытая директива override в компоненте приводит к тому, что при создании формы не выполняется новый конструктор. В большинстве других классов (не потомков TComponent) конструктор вызывается в явном виде из приложения, и поэтому будет вызываться последний написанный конструктор.

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

TMyBox=class(TListBox)
private
FData:TList;
FNAccel:integer;
public
constructor Create(AOwner:TComponent); override;
end;
implementation
constructor TMyBox.Create;
begin
inherited Create(AOwner);
FData:=TList.Create;
FNAccel:=5;
Items.Add(‘1’);
end;

Следует обратить внимание на то, что в конструкторе первым вызывается inherited-метод – конструктор класса-предка и только потом пишется код для инициализации переменных. Это обязательное условие в объектно-ориентированном программировании, которое может нарушаться только в отдельных случаях (примером такого случая является класс TThread). При таком способе записи каждый конструктор предка будет вызывать конструктор своего предка – и так до уровня конструктора класса TObject, который фактически будет первым оператором при вызове конструктора любого класса. Далее происходит выполнение кода в конструкторе класса-потомка и т.д. Для класса TMyBox при обращении к конструктору сначала происходит резервирование памяти для хранения переменных, определенных в данном классе и его предках. Затем вызывается конструктор TObject. Далее происходит обращение к конструктору TComponent, который устанавливает связь экземпляра TMyBox с его владельцем, передаваемым в параметре AOwner. Выполняется код конструктора TCustomListBox, который создает экземпляр класса TStrings и инициализирует ряд переменных. И наконец выполняются операторы, определенные в конструкторе TMyBox. Если оператор inherited поставить последним в конструкторе TMyBox, произойдет исключение при выполнении оператора Items.Add(‘1’) – объект для хранения строк создается в конструкторе класса TCustomListBox, который еще не был вызван.

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

TMyBox=class(TListBox)
private
FData:TList;
FNAccel:integer;
public
constructor Create(AOwner:TComponent); override;
destructor Destroy; override;
end;
implementation
destructor TMyBox.Destroy;
begin
FData.Free;
inherited Destroy;
end;

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

Для этого примера должен быть создан новый деструктор, так как внутри экземпляра класса TMyBox создается экземпляр класса TList. Соответственно разрушаться они должны совместно.

При переписывании деструктора прежде всего разрушаются экземпляры классов, созданных внутри данного класса, и только после этого вызывается деструктор класса-предка inherited Destroy (отметим, что в конструкторе используется обратный порядок). При таком способе вызова в последнюю очередь будет вызван метод Destroy класса TObject, который вернет системе память, зарезервированную для хранения переменных класса. В примере с классом TMyBox первоначально будет разрушен экземпляр класса TList, ссылка на который содержится в переменной FData. После этого будет вызван деструктор класса TlistBox, в котором разрушается экземпляр класса TStrings. И наконец, будет вызван деструктор класса TObject, где будет освобождена память, зарезервированная для классовых переменных TMyBox.

Вместо прямого вызова деструктора рекомендуется вызывать метод Free, позволяющий проверить, была ли выделена память для разрушаемого экземпляра класса, и если да, то вызывать его деструктор. Использование этого метода важно еще и потому, что деструктор должен быть описан таким образом, чтобы он мог корректно разрушить частично созданный экземпляр класса. Частично созданный экземпляр класса получается в том случае, если в его конструкторе произошло исключение. При этом немедленно вызывается деструктор данного класса, и после его отработки nil-указатель возвращается на создаваемый экземпляр класса. Если, например, в конструкторе резервировалась память под какую-либо переменную (FPBuf):

constructor TMyBox.Create(AOwner:TComponent);
begin
inherited Create(AOwner);
FData:=TList.Create;
GetMem(FPBuf,65500);
end;
destructor TMyBox.Destroy;
begin
FData.Free;
FreeMem(FPBuf);
inherited Destroy;
end;

то исключение может произойти в конструкторе в момент вызова inherited Create или в момент вызова TList.Create — из-за нехватки системных ресурсов. Сразу же будет вызван деструктор, и в момент выполнения оператора FreeMem произойдет генерация еще одного исключения. При этом метод inherited Destroy не будет вызван, а частично созданный экземпляр TMyBox не будет разрушен. Корректная реализация деструктора выглядит так:

if FPBuf<>nil then FreeMem(FPBuf);

При этом в обязательном порядке необходимо проверить, была ли выделена освобождаемая память ранее. Такие проверки необходимо делать со всеми ресурсами, подлежащими освобождению в деструкторе. В противном случае освобождать ресурс лучше в защищенном блоке try…except…end без вызова метода raise в секции except…end. Распространение исключения из деструктора недопустимо (пользователя не должно волновать, что программист не смог корректно высвободить ресурсы!).

Следует отметить, что в случае использования в классе ссылки на какой-либо объект, разрушать его в деструкторе иногда не требуется:

TTest=class(TObject)
private
FData:TList;
public
constructor Create(AData:TList);
end;
implementation
constructor TTest.Create(AData:TList);
begin
inherited Create;
FData:=AData;
end;

Если сам объект AData будет разрушен в той процедуре, где он создан, то переписывать деструктор класса TTest для разрушения объекта FData не требуется. Повторный вызов деструктора приводит к исключению. При этом применение метода Free не спасает, он лишь проверяет, что ссылка на экземпляр класса не указывает на nil.

В отличие от конструктора, для которого может быть определено несколько методов, деструктор бывает только один. Невозможно представить себе ситуацию, когда в классе может понадобиться дополнительный деструктор. Тем не менее компилятор Delphi позволяет это сделать – а зря… Классы с двумя деструкторами – довольно частое явление на распространяемых компонентах для Delphi третьих фирм. Причиной тому программист, забывший директиву override. Это часто приводит к тому, что ресурсы, освобождением которых занимается деструктор, не освобождаются. Во-первых, метод Free обращается к первому виртуальному методу класса – Destroy. При этом будет честно вызван деструктор класса-предка, но ресурсы, освобождение которых программист старательно описывал в деструкторе с забытой директивой override, освобождены не будут. Во-вторых, при разрушении формы содержащиеся на ней компоненты также разрушаются через вызов первого метода в виртуальной таблице, что ведет к аналогичному результату.

В заключение следует рассмотреть на первый взгляд странный вопрос: а всегда ли следует вызывать деструктор (непосредственно или через метод Free) из кода приложения? Правомерность постановки такого вопроса обусловлена тем, что программист нигде не пишет кодов вызова деструкторов компонентов, помещенных на форму на этапе разработки. Ответ заключается в структуре и реализации деструктора класса TComponent. Любой компонент в конструкторе запоминает ссылку на своего хозяина (AOwner) и заносит себя в список компонентов, которыми владеет хозяин. При вызове деструктора компонента он в первую очередь вызывает деструкторы своих «вассалов», и только после этого вызывается собственный деструктор. Таким образом, нет необходимости вызывать деструктор класса TComponent или его потомка – он будет автоматически разрушен при вызове деструктора его хозяина:

TMyBox=class(TListBox)
private
FData:TComponent;
public
constructor Create(AOwner:TComponent);
override;
end;
constructor TMyBox.Create(AOwner:TComponent);
begin
inherited Create(AOwner);
FData:=TComponent.Create(Self);
end;

В данном случае деструктор для разрушения объекта FData не нуждается в переписывании, поскольку он будет разрушен автоматически при разрушении объекта TMyBox. Деструктор для TComponent (или его потомка) следует вызывать только в случае, если его владелец – nil.

Для всех классов–потомков TComponent не следует в явном виде вызывать деструктор. Для всех остальных классов необходимо в коде приложения вызывать деструктор.

В Dephi5 появились два новых виртуальных метода – AfterConstruction и BeforeDestruction, — которые вызываются сразу же после конструктора или перед вызовом деструктора соответственно. Можно поспорить насчет необходимости введения метода BeforeDestruction: любой класс имеет виртуальный деструктор, который можно переписать. Появление метода AfterConstruction следует приветствовать, поскольку виртуальный конструктор появляется только на уровне TComponent в иерархии классов VCL. Появление виртуального конструктора существенно облегчило написание приложений для распределенных вычислений. Например, TComObject – базовый класс для реализации интерфейсов в COM-серверах — является потомком TObject и не содержит виртуального конструктора. Экземпляры этого класса создаются в ответ на запрос клиентов, а не командами из кода приложений, что затрудняет выполнение инициализации переменных при создании экземпляра класса. Введение виртуального метода AfterConstruction сделало инициализацию данных в этих классах рутинной процедурой.

Дополнительную информацию Вы можете получить в компании Interface Ltd.

Абстрактный класс В Delphi

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

должен ли я переопределять все методы, которые являются виртуальными, даже если мне это не нужно? Есть ли обходной путь или решение?

4 ответов

для создания экземпляра класса необходимо переопределить все методы, объявленные как виртуальные абстрактные. Даже если ты ими не пользуешься.

если вы действительно хотите работать, вы можете использовать пустые методы. Но я не советую этого делать.

и добавить дополнительную информацию по этому вопросу:

метод является абстрактным, если он объявлен с виртуальными аннотация:

мелочи: вы даже можете переопределить метод как аннотация:

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

и вы можете объявить весь класс как абстрактный:

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

аналог является запечатанным классом:

вы получаете предупреждение, если пытаетесь наследовать от этого класса.

вы также можете запечатать методы, но вам нужно ключевое слово final для что.

Delphi не имеет абстрактных классов как таковых, только абстрактные методы. При вызове абстрактного метода возникает исключение абстрактного метода.

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

ваш класс потомков по-прежнему абстрактен, если

  1. объявления abstract или
  2. Он имеет по крайней мере один метод, объявленный абстрактным, или
  3. он не переопределяет и не реализует все абстрактные методы от своих предков

это приведет к ошибке, если вы переопределите абстрактный конструктор, поскольку он автоматически помещает унаследованный в новый конструктор, который, конечно, вызывает абстрактный конструктор, если вы используете автозаполнение кода.

ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В DELPHI

ОСНОВНЫЕ КОНЦЕПЦИИ ООП

Язык Ое1рЫ реализует концепцию объектно-ориентированного программирования. Это означает, что функциональность приложения определяется набором взаимосвязанных задач, каждая из которых становится самостоятельным объектом. У объекта есть свойства (т.е. характеристики, или атрибуты) и методы, определяющие его поведение. В основе объектно-ориентированного программирования (ООП) лежит понятие класса.

Класс представляет собой дальнейшее развитие концепции типа и объединяет в себе задание не только структуры и размера переменных, но и выполняемых над ними операций.

Объекты в программе всегда являются экземплярами того или иного класса (подобно переменным определенного типа).

К основным концепциям ООП относятся следующие: инкапсуляция, наследование, полиморфизм.

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

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

В корне этого дерева находится базовый класс ЮЬдесЧ который реализует наиболее общие для всех классов элементы, например, действия по созданию и удалению объекта. Чем дальше тот или иной класс отстоит в дереве от базового класса, тем большей специфичностью он обладает.

Пример объявления класса-потомка:

//Добавление к классу tParentClass новых

// и переопределение существующих элементов

Сущность полиморфизма заключается в том, что методы различных классов могут иметь одинаковы имена, но различное содержание. Это достигается переопределением родительского метода в классе-потомке. В результате родитель и потомок в одинаковых ситуациях ведут себя по-разному.

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

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

КЛАССЫ И ОБЪЕКТЫ

Классом в Delphi называется особая структура, состоящая из полей, методов и свойств.

fMyField: Integer; //поле fMyField

function MyMethod(Data:tData): Integer; //метод MyMethod

Все классы в Delphi являются наследниками базового класс TObject, который реализует наиболее общие для всех классов элементы, например, действия по созданию и удалению объекта.

Поля класса — это данные, уникальные для каждого созданного в программе экземпляра класса. Они могут иметь любой тип, в том числе — тип класс. В Delphi перед именами полей принято ставить символ f (от field — поле): fLength, fWidth, fMyFileld и т.п.

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

Свойство можно определить как поле, доступное для чтения и записи не напрямую, а через соответствующие методы.

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

Методы класса реализуются в разделе описания процедур и функций программы или в разделе implementation модуля. При реализации метода указывается его полное имя, состоящее из имени класса, точки и имени метода класса:

function tMyClass.MyMethod(Data:tData): Integer;

//заголовок метода tMyClass.МуMethod

// тело метода tMyClass.MyMethod

Разрешено опережающее объявление классов единственным словом class с последующим полным описанием:

Области видимости — это возможности доступа к составным частям объекта. В Delphi поля и методы могут относиться к четырем группам: «общие» (public), «личные» (private), «защищенные» (protected) и «опубликованные» (published).

  • 1. Поля, свойства и методы, находящиеся в секции public, не имеют ограничений на видимость. Они доступны из других функций и методов объектов, как в данном модуле, так и во всех прочих, ссылающихся на него.
  • 2. Поля, свойства и методы, находящиеся в секции private, доступны только в методах класса и в функциях, содержащихся в том же модуле, что и описываемый класс. Такая директива позволяет скрыть детали внутренней реализации класса от всех. Элементы из секции private можно изменять, и это не будет сказываться на программах, работающих с объектами этого класса. Обратиться к ним можно, только переписав содержащий их модуль.
  • 3. Поля, свойства и методы, находящиеся в секции protected, доступны только внутри классов, являющихся потомками данного, в том числе и в других модулях. Такие элементы особенно необходимы дня разработчиков новых компонентов — потомков уже существующих.
  • 4. Область видимости published имеет особое значение для интерфейса визуального проектирования. В этой секции должны быть собраны те свойства объекта, которые будут видны не только во время исполнения приложения, но и из среды разработки. Все свойства компонентов, доступные через Инспектор объектов, являются их опубликованными свойствами. Во время выполнения такие свойства общедоступны, как и public.

Пример, иллюстрирующий первые три варианта областей видимости:


unit First; interface type

procedure Methodl; private

procedure Method2; protected

procedure Method3; end;

procedure TestProcI; begin

Obj1:= tClassI .Create;

Оbj1.Methodl; //допустимо Obj1.Method2; //допустимо Obj1.Method3; //недопустимо Objl.Free; end;

end. // unit First

unit Second; interface uses First;

var Obj2 : tClass2;

Methodl; //допустимо Method2; //недопустимо Method3; //допустимо

Obj2 .Methodl; //допустимо Obj2 .Method2; //недопустимо Obj2 .Method3; //недопустимо Obj2 .Free;

end. //unit Second

При описании дочернего класса можно переносить методы и свойства из одной сферы видимости в другую, не переписывая их заново и даже не описывая — достаточно указать новую сферу видимости наследуемого метода или свойства в описании дочернего класса. Разумеется, если вы поместили свойство в область private, «достать» его оттуда в потомках возможности уже нет.

СОЗДАНИЕ И УНИЧТОЖЕНИЕ ОБЪЕКТОВ

В Delphi объекты могут быть только динамическими! Любая переменная объектного типа есть указатель, причем для доступа к данным, на которые ссылается указатель объекта не нужно применять символ л .

Для выделения памяти экземпляру любой динамической переменной используется процедура New, для уничтожения — процедура Dispose. С объектами эти процедуры не используются: объект создается специальным методом — конструктором, а уничтожается специальным методом — деструктором:

AMyObject := tMyClass.Create; //создание объекта класса tMyClass

// действия с созданным объектом

AMyObject.Destroy; //уничтожение объекта AMyObject

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

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

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

Если указать имя уже созданного объекта, он поведет себя по-другому: не создаст новый экземпляр, а только выполнит код, содержащийся в теле конструктора.

Конструктор класса-потомка практически всегда должен перекрывать конструктор класса-предка. Чтобы правильно проинициа-лизировать в создаваемом объекте поля, относящиеся к классу-пред-ку, нужно в начале кода конструктора вызвать конструктор предка с помощью зарезервированного слова inherited:

constructor tMyClass. Create;

// собственный код конструктора

Деструктор — это специальный виртуальный (см. ниже) метод, заголовок которого начинается зарезервированным словом destructor. Деструктор предназначен для удаления объектов. Типичное название деструктора — Destroy.

Для уничтожения объекта рекомендуется использовать метод Free (он есть у базового класса TObject), который первоначально проверяет указатель на объект (не равен ли он nil) и только затем вызывает деструктор Destroy:

Деструктор очень часто не переопределяется в классе-потомке. Но если его необходимо переопределить, то необходимо учитывать следующее. Деструктор Destroy любого класса перекрывает виртуальный деструктор базового класса TObject, поэтому деструктор потомка необходимо объявить с директивой override:

destructor Destroy: override;

При реализации перекрывающего деструктора потомка нужно в конце кода деструктора вызвать деструктор предка с помощью зарезервированного слова inherited:

// собственный код деструктора inherited Destroy; end;

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

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

function GetAProperty: tSomeType;

procedure SetAProperty(ANewValue: tSomeType);

property AProperty: tSomeType read GetAProperty write SetAProperty;

В контексте свойства слова read и write являются зарезервированными. Для доступа к значению свойства AProperty явно достаточно написать

и компилятор оттранслирует эти операторы в вызовы методов.

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

Рассмотрим следующий пример:

tPropCIass = class(tObject) fValue: Integer; procedure DoSomething; function Correct(AValue: lnteger):Boolean;

procedure SetValue(NewValue: Integer); property AValue: Integer read fValue write SetValue;

procedure tPropClass.SetValue(NewValue: Integer);

if (NewValueofValue) and Correct (NewValue) then fValue := NewValue;

В этом примере чтение значения свойства AValue означает просто чтение поля fValue. Зато при присвоении ему значения внутри SetValue вызывается сразу два метода.

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

property AProperty: tSomeType read GetValue;

Значение свойства AProperty можно лишь прочитать; попытки присвоить AProperty значение вызовет ошибку компиляции.

Для присвоения свойству значения по умолчанию используется ключевое слово default:

property Visible: Boolean read fVisible write SetVisible default True

Свойство может быть и векторным; в этом случае оно внешне выглядит как массив:

property APoints[lndex: IntegerftPoint read GetPoint write SetPoint;

На самом деле, среди полей класса может и не быть поля-массива. При помощи свойств вся обработка обращений к внутренним структурам класса может быть замаскирована.

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

function GetPoint(lndex: Integer): TPoint;

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

procedure SetPoint(lndex: Integer; NewPoint: tPoint);

У векторных свойств есть еще одна важная особенность. Некоторые классы в Delphi (списки, наборы строк) «построены» вокруг векторного свойства. Основной метод такого класса дает доступ к некоторому массиву, а все остальные методы являются вспомогательными. Специально для облегчения работы в этом случае векторное свойство может быть описано как default:

property Strings[lndex: Integer]: string read Get write Put; default;

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

var AMyObject: tMyClass;

AMyObject.Strings[1] := ‘First’; //первый способ AMyObject[2] := ‘Second’; //второй способ • • •

Будьте внимательны, применяя зарезервированное слово default, — для обычных и векторных свойств оно употребляется в разных случаях и с различным синтаксисом.

О роли свойств в Delphi красноречиво говорит следующий факт: у всех имеющихся в распоряжении стандартных классов 100% полей недоступны (помещены в секцию private) и заменены базирующимися на них свойствами. Рекомендуем при разработке своих классов придерживаться этого же правила.

Илон Маск рекомендует:  Поле для ввода чисел

Принцип наследования позволяет объявить класс

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

В Delphi все классы являются потомками класса TObject. При построении дочернего класс прямо от TObject в определении его можно не упоминать.

Следующие объявления одинаково верны:

Первый вариант предпочтительнее, хотя он и более длинный, — для устранения возможных неоднозначностей.

Класс TObject несет очень серьезную нагрузку и будет рассмотрен отдельно.

Унаследованные от предка поля и методы доступны в дочернем классе; если имеет место совпадение имен методов, то говорят, что они перекрываются.

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

  • 1) статические;
  • 2) виртуальные (virtual) и динамические (dynamic);
  • 3) перегружаемые (overload) методы.

Статические методы, а также любые поля в объектах-потомках ведут себя одинаково: можно без ограничений перекрывать старые имена, и при этом изменять тип методов. Перекрытое поле предка недоступно в потомке. Перекрытый метод доступен при указании зарезервированного слова inherited. Методы объектов по умолчанию являются статическими — их адрес определяется еще на стадии компиляции проекта. Они вызываются быстрее всего.

Принципиально отличаются от статических виртуальные и динамические методы (директива virtual или dynamic). Их адрес определяется во время выполнения программы по специальной таблице. С точки зрения наследования методы этих двух видов одинаковы: они могут быть перекрыты в дочернем классе только одноименными методами, имеющими тот же тип.

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

ВИРТУАЛЬНЫЕ И ДИНАМИЧЕСКИЕ МЕТОДЫ

Рассмотрим пример. Пусть у нас имеются некое обобщенное поле для хранения данных — абстрактный класс tField и три его потомка — для хранения строк, целых и вещественных чисел:

function GetData: string; virtual; abstract;

tStringField = class(tField) fData : string;

function GetData: string; override;

function GetData: string; override;

function GetData: string; override;

В этом примере классы содержат разнотипные данные и «умеют» сообщать о значении этих данных текстовой строкой (при помощи метода GetData). Внешняя по отношению к ним процедура Show Data получает объект в виде параметра и показывает эту строку.

Правила контроля соответствия типов (typecasting) языка Delphi гласят, что объекту как указателю на экземпляр объектного типа может быть присвоен адрес любого экземпляра любого из дочерних типов. В процедуре ShowData параметр описан как tField — это значит, что в нее можно передавать объекты классов и tStringField, и tlntegerField, и tExtendedField, и любого другого потомка tField.

Но чей метод GetData при этом будет вызван? Тот, который соответствует классу фактически переданного объекта. Этот принцип называется полиморфизмом и он, пожалуй, представляет собой наиболее важный принцип ООП. Например, чтобы смоделировать некоторую совокупность явлений или процессов средствами ООП, нужно выделить их самые общие, типовые черты. Те из них, которые не изменяют своего содержания, должны быть реализованы в виде статических методов. Те же, которые варьируются при переходе от общего к частному, лучше облечь в форму виртуальных методов. Основные, «родовые» черты (методы) нужно описать в классе-пред-ке и затем перекрывать их в классах-потомках.

При вызове виртуальных и динамических методов адрес определяется не во время компиляции, а во время выполнения — это называется поздним связыванием (late binding). Позднее связывание реализуется с помощью таблицы виртуальных методов (Virtual Method Table, VMT) и таблицы динамических методов (Dynamic Method Table, DMT).

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

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

Для перекрытия и виртуальных, и динамических методов служит директива override, с помощью которой (и только с ней!) можно переопределять оба этих типа методов.

Первый метод класса tClass2 создается заново, остальные два — перекрываются. Попытка применить директиву override к статическому методу вызовет ошибку компиляции.

ПРИМЕР РЕАЛИЗАЦИИ ПОЛИМОРФИЗМА ДЛЯ ИЕРАРХИИ ГРАФИЧЕСКИХ ОБЪЕКТОВ

Рассмотрим описание и реализацию классов «Точка» и «Окружность» для «рисования» на экране точки и окружности.

// тип — координата точки на экране

// класс — точка на экране дисплея

// поля — координаты точки на экране // полецвет рисования точки

// Описание класса tPoint tPoint = class(TObject) protected fX, fY: tCoord; fColor: Byte; public

property X: tCoord read fX write fX; // свойство — координата X

property Y: tCoord read fY write fY; // свойство — координата Y

property Color: Byte read fColor write fColor;// свойство — цвет procedure Show; //метод высвечивания точки

procedure Hide; //метод гашения точки

procedure MoveTo(NewX, NewY: tCoord); //перемещение точки end;

// класс — окружность на экране // поле — радиус окружности

// Описание класса tCircle tCircle = class(tPoint)

property Radius: tCoord read fRadius write fRadius; //св-во — радиус procedure Show; //метод высвечивания окружности

procedure Hide; //метод гашения окружности

procedure MoveTo (NewX,NewY:tCoord) //перемещение окружности

// Реализация методов класса tPoint

Writeln(‘Pi/icyio точку (‘, fx,fy,’) цветом fColor);

// «Стирание» точки Л/гйе1п(‘Стираю точку (‘, fx, ‘,fy,’) цвета fColor);

procedure tPoint.MoveTo(NewX, NewY: tCoord);

fX := NewX; fY := NewY;

// Реализация методов класса tCircle

Writeln(‘PHcyK) окружность с центром (‘, fx,fy,’) радиуса fRadius,’ цветом fColor);

procedure tCircle.Hide; begin

Л/гйе1п(‘Стираю окружность с центром (‘, fx,’,’, fy,’) радиуса ‘, fRadius,’ цвета ‘, fColor);

procedure tCircle.MoveTo (NewX,NewY: tCoord);

fX := NewX; fY := NewY;

Обратите внимание, что методы MoveTo классов tPoint и tCircle реализуются одинаково за исключением того, что в tPoint. MoveTo используются tPoint.Hide и tPoint.Show, а в tCircle.MoveTo используются tCircle.Hide и tCircle.Show. Взаимодействие методов классов tPoint и tCircle можно проиллюстрировать схемой, приведенной на рис. П3.1.

Рис. П3.1. Взаимодействие методов классов tPoint и tCircle

Поскольку методы реализуются одинаково, можно попытаться унаследовать метод МоуеТо от класса 1РотЕ При этом возникает следующая ситуация.

При компиляции метода 1РотСМоуеТо в него будут включены ссылки на коды методов 1РотС81кж и 1РотЕНк1е. Так как метод с именем МоуеТо не определен в классе 1Слгс1е, то компилятор обращается к родительскому типу и ищет в нем метод с этим именем. Если метод найден, то адрес родительского метода заменяет имя в исходном коде потомка.

Следовательно, при вызове 1СДс1е. МоуеТо в программу будут включены коды 1РотС МоуеТо (то есть класс 1:Сцс1е будет использовать метод так, как он откомпилирован). В этом случае процедура 1Сцс1е. МоуеТо будет работать неверно.

Структура вызовов методов при таком наследовании приведена на рис. П3.2.

Рис. П3.2. Структура вызовов методов при наследовании метода

MoveTo от класса tPoint

Для правильного вызова методов процедуры Show и Hide должны быть объявлены виртуальными, так как только в этом случае класс tCircle может наследовать метод MoveTo у типа tPoint. Это становится возможным потому, что подключение виртуальных методов Show и Hide к процедуре MoveTo будет осуществляться во время выполнения программы, и будут подключаться методы, определенные для типа tCircle (tCircle.Show и tCircle.Hide).

При использовании виртуальных методов Show и Hide взаимодействие методов классов tPoint и tCircle можно проиллюстрировать схемой, приведенной на рис. ПЗ.З.

Рис. ПЗ.З. Структура вызовов методов при использовании виртуальных методов Show и Hide

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

Описание классов «Точка» и «Окружность» с использованием виртуальных методов:

// Описание класса tPoint tPoint = class(TObject) protected fX, fY: tCoord; fColor: Byte; public

property X: tCoord read fX write fX; property Y: tCoord read fY write fY; property Color: Byte read fColor write fColor; procedure Show; virtual; procedure Hide; virtual; procedure MoveTo(NewX, NewY: tCoord); end;

// Описание класса tCircle tCircle = class (tPoint) protected fRadius: tCoord;

property Radius: tCoord read fRadius write fRadius; procedure Show; override; procedure Hide; override; end;

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

procedure SetData(AValue : Extended);

procedure SetData(AValue : Integer);

Попытка вызова методов

вызовет ошибку компиляции на первой из двух строк. Для компилятора внутри Obj2 статический метод с параметром типа Extended перекрыт, и он его не «признает».

Объявить методы виртуальными нельзя, так как тип и количество параметров в одноименных виртуальных методах должны совпадать. Чтобы указанные вызовы были верными, необходимо объявить методы перегружаемыми, для чего используется директива overload:

procedure SetData(AValue : Extended); overload; end;

procedure SetData(AValue : Integer); overload; end;

Объявив метод Set Data перегружаемым, в программе можно использовать обе его реализации одновременно. Это возможно потому, что компилятор определяет тип передаваемого параметра (целый или с плавающей точкой) и в зависимости от этого подставляет вызов соответствующего метода.

Можно перегружать и виртуальные методы. В этом случае необходимо добавить директиву reintroduce:

procedure SetData(AValue : Extended); virtual; overload; end;

procedure SetData(AValue : Integer); reintroduce; overload; end;

На перегрузку методов накладывается ограничение — нельзя перегружать методы, находящиеся в области видимости published.

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

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

procedure NeverCallMe; virtual; abstract;

Никакого кода для этого метода писать не нужно. Вызов абстрактного метода приведет к созданию исключительной ситуации Е Abstract Error.

Пример с классом tField из параграфа «Полиморфизм» поясняет, для чего нужны абстрактные методы. В данном случае класс tField не используется сам по себе; его основное предназначение — быть родоначальником иерархии конкретных классов-«полей» и дать возможность абстрагироваться от частностей. Хотя параметр процедуры ShowData и описан как tField, но если передать в нее объект этого класса, произойдет исключительная ситуация вызова абстрактного метода.

Рассмотрим уже знакомый пример из параграфа «Полиморфизм»:

Внутренняя структура объектов Objl и Obj2 приведена на рис. П3.4.

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

Перед таблицей виртуальных методов расположена специальная структура, которая называется информацией о типе времени выполнения (runtime type information, RTTI). В ней содержатся данные, полностью характеризующие класс: его имя, размер экземпляра, указатели на класс-предок, на имя класса и т. д. На рисунке она показана одним блоком, а ее содержимое расшифровано ниже.

Указатель на класс tClassl

Указатель на класс

Поле fFieldl Поле fField2

VMT класса tClassl

DMT класса tClassl

RTTI класса tClassl

Число динамических методов: 2

Индекс tClassl .dnMetl (-1)


Индекс tClassl ,dnMet2 (-2)

(й) tClassl .dnMetl

VMT класса tClass2

DMT класса tClass2

RTTI класса tClass2

Число динамических методов: 1

Индекс tClass2.dnMetl (-1)

(2; tClassl ,vrMet2

Рис. П3.4. Внутренняя структура объектов Obj 1 и Obj2

Одно из полей структуры содержит адрес таблицы динамических методов класса (DMT). Таблица имеет следующий формат — в начале слово, содержащее количество элементов таблицы; затем — слова, соответствующие индексам методов. Нумерация индексов начинается со значения — 1 и идет по убывающей. После индексов идут собственно адреса динамических методов.

Обратите внимание, что DMT объекта Obj 1 состоит из двух элементов, Obj2 — из одного элемента, соответствующего перекрытому методу dnMetl. В случае вызова метода Obj2.dnMet2 индекс не будет найден в таблице DMT Obj2, поиск продолжится в DMT объекта Obj 1. Именно так экономится память при использовании динамических методов.

Напомним, что указатель на класс указывает на адрес первого виртуального метода. Служебные данные размещаются перед таблицей VMT, то есть с отрицательным смещением. Эти смещения описаны в модуле SYSTEM.PAS:

= -52; = -48; = -44; = -40; -36;

Поля УггйОупагтсТаЫе, Уггй018ра1с11 и У1УйОеГаиИ:Напб1ег отвечают за вызов динамических методов. Поля угтп№у1п81апсе, угШЕгее1п81апсе и УгШЭез^оу содержат адреса процедур создания и уничтожения экземпляра класса. Поля утПшЛаЫе, УпйА^оТаЫе, уш15аГеСа11Ехсер1юп введены для обеспечения совместимости с моделью объектов СОМ. Остальные поля доступны через методы класса ТСИуесЕ в Ое1р1м информация этой таблицы играет важную роль и может использоваться программистом неявно.

ПРОВЕРКА СОВМЕСТИМОСТИ И ПРИВЕДЕНИЕ ОБЪЕКТНЫХ ТИПОВ

В языке определены два оператора: к и аз, неявно обращающиеся к информации о классе.

Оператор к предназначен для проверки совместимости по присваиванию экземпляра объекта с заданным классом. Выражение вида:

AnObject is TObjectType

принимает значение True только в том случае, если объект AnObject совместим по присваиванию с TObjectType, то есть является объектом этого класса или одного из классов, порожденных от него. Кстати, определенная проверка происходит еще при компиляции: если фактические объект и класс несовместимы, компилятор выдаст ошибку в этом операторе.

Оператор as введен в язык для приведения объектных типов. С его помощью можно рассматривать экземпляр объекта как принадлежащий к другому совместимому типу:

with ASomeObject as tAnotherType do .

Использование оператора as отличается от стандартного способа приведения типов с помощью конструкции

наличием проверки на совместимость типов во время выполнения (как в операторе is): попытка приведения к несовместимому типу приводит к возникновению исключительной ситуации EInvalidCast.

После применения оператора as сам объект остается неизменным, но вызываются только те его методы, которые есть у присваиваемого класса. Присваиваемый фактически тип должен быть известен на стадии компиляции, поэтому на месте TObjectType (после is) и tAnotherType (после as) не может стоять переменная-указатель на класс.

УКАЗАТЕЛЬ НА КЛАСС. МЕТОДЫ КЛАССА

Информация, описывающая класс, создается и размещается в памяти на этапе компиляции. Возникает вопрос: можно ли получить доступ к ней, не создавая экземпляр класса (объект)? Да, можно.

Доступ к информации класса вне методов этого класса можно получить, описав соответствующий указатель, который называется указателем на класс, или указателем на объектный тип (class reference). Он описывается при помощи зарезервированных слов class of. Например, указатель на класс TObject описан в модуле SYSTEM.PAS и называется tClass:

Указатели на классы подчиняются правилам приведения объектных типов. Указатель на класс-предок может ссылаться и на любые дочерние классы; обратное невозможно.

С указателем на класс тесно связано понятие методов класса. Такие методы можно вызывать без создания экземпляра объекта — с указанием имени класса, в котором они описаны. Перед описанием метода класса нужно поставить зарезервированное слово class:

// обращение к методу класса до создания объекта этого класса

Разумеется, методы класса не могут использовать значения, содержащиеся в полях: ведь экземпляра-то не существует! Возникает вопрос: для чего тогда нужны такие методы? Важнейшие методы класса определены в самом классе TObject: они позволяют извлечь из внутренней структуры класса практически всю необходимую информацию.

Некоторые важные методы класса TObject приведены в табл. П3.1.

Основные методы класса TObject

class function Classlnfo: Pointer;

Возвращает указатель на структуру с дополнительными данными об опубликованных методах и свойствах

class function ClassName: ShortString;

Возвращает имя класса

class function ClassNamels (const Name: ShortString): Boolean;

Возвращает True, если имя класса равно заданному

class function Class Parent: TClass;

Возвращает указатель на родительский класс (для TObject возвращает nil)

class function InheritsFrom

(AClass: TClass): Boolean;

Возвращает True, если данный класс порожден от класса AClass

class function Initlnstance (Instance: Pointer): TObject;

Инициализирует экземпляр класса

class function InstanceSize: Longint;

Возвращает размер экземпляра класса

MethodAddress(const Name: ShortString): Pointer;

Возвращает адрес метода по его имени (только для опубликованных методов)

class function MethodName (Address: Pointer): ShortString;

Возвращает имя метода по его адресу (только для опубликованных методов)

class function Newlnstance: TObject; virtual;

Создает новый экземпляр объекта.

В частности, эта функция вызывается внутри конструктора

function ClassType: TClass;

Возвращает указатель на класс данного объекта

Конструктор. Создает новый экземпляр класса и инициирует обработчик исключительных ситуаций

Вызывается сразу после создания экземпляра класса

Вызывается перед уничтожением экземпляра

destructor Destroy; virtual;

Деструктор. Производит действия по уничтожению экземпляра класса

procedure Default Handler (var Message); virtual;

Обработчик сообщений по умолчанию.

В TObject не содержит ничего, кроме кода возврата

procedure Dispatch(var Message);

Вызывает методы обработки сообщений в зависимости от значения параметра Message

Name: String): Pointer;

Возвращает адрес поля вызвавшего объекта с заданным именем

Используется вместо деструктора. Проверяет передаваемый деструктору указатель на экземпляр

procedure Free Instance; virtual;

Уничтожает экземпляр объекта. Вызывается внутри деструктора

В следующем примере переменная Object Ref является указателем на класс; он по очереди указывает на TObject и TMyObject. Посредством этой переменной-указателя вызывается функция класса ClassName:

Виртуальные методы AfterConstruction и Before Destruction вызываются сразу после создания экземпляра класса и непосредственно перед его уничтожением. Их можно использовать, если по каким-либо причинам не хватает собственно конструктора и деструктора.

Удобно использовать метод Free для уничтожения экземпляра класса, при этом переопределяется метод Destroy, в котором описываются все действия по уничтожению экземпляра, например освобождение выделенной памяти или закрытие файлов. При вызове метода Free проверяется передаваемый указатель на экземпляр и если он не равен nil, то вызывается метод Destroy и происходит уничтожение объекта.

Методы MethodName и MethodAddress используются для получения имени или адреса только опубликованных методов, это связано с тем, что система Delphi хранит имена только таких методов. Так как директива published предназначена для описания свойств, размещаемых в инспекторе объектов, где указываются их имена.

Если метод MethodName вызывается для указателя на неопубликованный метод, то возвращается пустая строка.

При передаче методу MethodAddress в качестве параметра имени неопубликованного или несуществующего метода возвращается значение nil.

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

Methods (Delphi)

Contents

A method is a procedure or function associated with a >SomeObject.Free calls the Free method in SomeObject .

This topic covers the following material:

  • Method declarations and implementation
  • Method binding
  • Overloading methods
  • Constructors and destructors
  • Message methods

About Methods

Within a >TMyClass includes a method called DoSomething :

A defining declaration for DoSomething must occur later in the module:

While a class can be declared in either the interface or the implementation section of a unit, defining declarations for a class methods must be in the implementation section.

In the heading of a defining declaration, the method name is always qualified with the name of the class to which it belongs. The heading can repeat the parameter list from the class declaration; if it does, the order, type, and names of the parameters must match exactly, and if the method is a function, the return value must match as well.

Method declarations can include special directives that are not used with other functions or procedures. Directives should appear in the class declaration only, not in the defining declaration, and should always be listed in the following order:

  • binding is virtual, dynamic, or override;
  • calling convention is register, pascal, cdecl, stdcall, or safecall;
  • warning is platform, deprecated, or library. For more information about these warning (hinting) directives, see Hinting Directives.

All Delphi directives are listed in Directives.

Inherited

The reserved word inherited plays a special role in implementing polymorphic behavior. It can occur in method definitions, with or without an identifier after it.

If inherited is followed by the name of a member, it represents a normal method call or reference to a property or field, except that the search for the referenced member begins with the immediate ancestor of the enclosing method’s class. For example, when:

occurs in the definition of a method, it calls the inherited Create .

When inherited has no identifier after it, it refers to the inherited method with the same name as the enclosing method or, if the enclosing method is a message handler, to the inherited message handler for the same message. In this case, inherited takes no explicit parameters, but passes to the inherited method the same parameters with which the enclosing method was called. For example:

occurs frequently in the implementation of constructors. It calls the inherited constructor with the same parameters that were passed to the descendant.

Within the implementation of a method, the >Add method in the Classes unit:

The Add method calls the Create method in the >FItemClass field, which is always a TCollectionItem descendant. TCollectionItem.Create takes a single parameter of type TCollection, so Add passes it the TCollection instance object where Add is called. This is illustrated in the following code:

Self is useful for a variety of reasons. For example, a member >Self.Identifier .

For information about Self in class methods, see «Class Operators» in Class References.

Method Binding

Method bindings can be static (the default), virtual, or dynamic. Virtual and dynamic methods can be overridden, and they can be abstract. These designations come into play when a variable of one class type holds a value of a descendent class type. They determine which implementation is activated when a method is called.

Static Methods

Methods are by default static. When a static method is called, the declared (compile-time) type of the >Draw methods are static:

Given these declarations, the following code illustrates the effect of calling a static method. In the second call to Figure.Draw , the Figure variable references an object of >TRectangle , but the call invokes the implementation of Draw in TFigure , because the declared type of the Figure variable is TFigure :

Virtual and Dynamic Methods

To make a method virtual or dynamic, include the virtual or dynamic directive in its declaration. Virtual and dynamic methods, unlike static methods, can be overridden in descendent classes. When an overridden method is called, the actual (run-time) type of the class or object used in the method call—not the declared type of the variable—determines which implementation to activate.

To override a method, redeclare it with the override directive. An override declaration must match the ancestor declaration in the order and type of its parameters and in its result type (if any).

In the following example, the Draw method declared in TFigure is overridden in two descendent classes:

Given these declarations, the following code illustrates the effect of calling a virtual method through a variable whose actual type varies at run time:

Only virtual and dynamic methods can be overridden. All methods, however, can be overloaded; see Overloading methods.

Final Methods

The Delphi compiler also supports the concept of final virtual and dynamic methods. Declarations of final methods have the form:

Here the virtual|dynamic syntax (two keywords and the | pipe between them) is used to specify that one and only one of the virtual or dynamic keywords should be used. Meaningful is only the virtual or dynamic keyword; the pipe symbol itself should be deleted.

When the keyword final is applied to a virtual or dynamic method, no descendent class can override that method. Use of the final keyword is an important design decision that can help document how the class is intended to be used. It can also give the compiler hints that allow it to optimize the code it produces.

Note: The virtual or dynamic keywords must be written before the final keyword.

Virtual versus Dynamic

In Delphi for Win32, virtual and dynamic methods are semantically equivalent. However, they differ in the implementation of method-call dispatching at run time: virtual methods optimize for speed, while dynamic methods optimize for code size.

In general, virtual methods are the most efficient way to implement polymorphic behavior. Dynamic methods are useful when a base class declares many overridable methods that are inherited by many descendent classes in an application, but only occasionally overridden.

Note: Only use dynamic methods if there is a clear, observable benefit. Generally, use virtual methods.

Overriding versus Hiding

If a method declaration specifies the same method identifier and parameter signature as an inherited method, but does not include override, the new declaration merely hides the inherited one without overriding it. Both methods exist in the descendent class, where the method name is statically bound. For example:

Reintroduce

The reintroduce directive suppresses compiler warnings about hiding previously declared virtual methods. For example:

Use reintroduce when you want to hide an inherited virtual method with a new one.

Abstract Methods

An abstract method is a virtual or dynamic method that has no implementation in the class where it is declared. Its implementation is deferred to a descendent class. Abstract methods must be declared with the directive abstract after virtual or dynamic. For example:

You can call an abstract method only in a class or instance of a class in which the method has been overridden.

Class Methods

Most methods are called instance methods, because they operate on an individual instance of an object. A class method is a method (other than a constructor) that operates on classes instead of objects. There are two types of class methods: ordinary class methods and class static methods.

Ordinary Class Methods

The definition of a class method must begin with the reserved word class. For example:

The defining declaration of a class method must also begin with class. For example:

In the defining declaration of a >C , then Self is of the type >C . Thus you cannot use Self to access instance fields, instance properties, and normal (object) methods. You can use Self to call constructors and other class methods, or to access class properties and class fields.

A class method can be called through a class reference or an object reference. When it is called through an object reference, the class of the object becomes the value of Self.

Class Static Methods

Like class methods, class static methods can be accessed without an object reference. Unlike ordinary class methods, class static methods have no Self parameter at all. They also cannot access any instance members. (They still have access to class fields, class properties, and class methods.) Also unlike class methods, class static methods cannot be declared virtual.

Methods are made class static by appending the word static to their declaration, for example:

Like a class method, you can call a class static method through the class type (for example, without having an object reference), such as:

Overloading Methods

A method can be redeclared using the overload directive. In this case, if the redeclared method has a different parameter signature from its ancestor, it overloads the inherited method without hiding it. Calling the method in a descendent class activates whichever implementation matches the parameters in the call.

If you overload a virtual method, use the reintroduce directive when you redeclare it in descendent classes. For example:

Within a class, you cannot publish multiple overloaded methods with the same name. Maintenance of run time type information requires a unique name for each published member:

Methods that serve as property read or write specifiers cannot be overloaded.

The implementation of an overloaded method must repeat the parameter list from the class declaration. For more information about overloading, see Overloading Procedures and Functions in Procedures and Functions (Delphi).

Constructors

A constructor is a special method that creates and initializes instance objects. The declaration of a constructor looks like a procedure declaration, but it begins with the word constructor. Examples:

Constructors must use the default register calling convention. Although the declaration specifies no return value, a constructor returns a reference to the object it creates or is called in.

To create an object, call the constructor method on a class type. For example:

This allocates storage for the new object, sets the values of all ordinal fields to zero, assigns nil to all pointer and class-type fields, and makes all string fields empty. Other actions specified in the constructor implementation are performed next; typically, objects are initialized based on values passed as parameters to the constructor. Finally, the constructor returns a reference to the newly allocated and initialized object. The type of the returned value is the same as the class type specified in the constructor call.

If an exception is raised during the execution of a constructor that was invoked on a >Destroy destructor is automatically called to destroy the unfinished object.

When a constructor is called using an object reference (rather than a class reference), it does not create an object. Instead, the constructor operates on the specified object, executing only the statements in the constructor’s implementation, and then returns a reference to the object. A constructor is typically invoked on an object reference in conjunction with the reserved word inherited to execute an inherited constructor.

Here is an example of a class type and its constructor:

The first action of a constructor is usually to call an inherited constructor to initialize the object’s inherited fields. The constructor then initializes the fields introduced in the descendent class. Because a constructor always clears the storage it allocates for a new object, all fields start with a value of zero (ordinal types), nil (pointer and class types), empty (string types), or Unassigned (variants). Hence there is no need to initialize fields in a constructor’s implementation except to nonzero or nonempty values.

When invoked through a class-type identifier, a constructor declared virtual is equivalent to a static constructor. When combined with class-reference types, however, virtual constructors allow polymorphic construction of objects—that is, construction of objects whose types are not known at compile time. (See Class References.)

Destructors

A destructor is a special method that destroys the object where it is called and deallocates its memory. The declaration of a destructor looks like a procedure declaration, but it begins with the word destructor. Example:

Destructors on Win32 must use the default register calling convention. Although a >Destroy method and declare no other destructors.

To call a destructor, you must reference an instance object. For example:

When a destructor is called, actions specified in the destructor implementation are performed first. Typically, these consist of destroying any embedded objects and freeing resources that were allocated by the object. Then the storage that was allocated for the object is disposed of.

Here is an example of a destructor implementation:

The last action in a destructor implementation is typically to call the inherited destructor to destroy the inherited fields of the object.

When an exception is raised during the creation of an object, Destroy is automatically called to dispose of the unfinished object. This means that Destroy must be prepared to dispose of partially constructed objects. Because a constructor sets the fields of a new object to zero or empty values before performing other actions, >Destroy offers a convenient way to check for nil values before destroying an object.


Class Constructors

A class constructor is a special class method that is not accessible to developers. Calls to class constructors are inserted automatically by the compiler into the initialization section of the unit where the class is defined. Normally, class constructors are used to initialize the static fields of the class or to perform a type of initialization, which is required before the class or any class instance can function properly. Even though the same result can be obtained by placing class initialization code into the initialization section, class constructors have the benefit of helping the compiler decide which classes should be included into the final binary file and which should be removed from it.

The next example shows the usual way of initializing class fields:

This method has a big disadvantage: even though an application can include the unit in which TBox is declared, it may never actually use the TBox class. In the current example, the TBox class is included into the resulting binary, because it is referenced in the initialization section. To alleviate this problem, consider using class constructors:

In this case, the compiler checks whether TBox is actually used anywhere in the application, and if it is used, a call to the class constructor is added automatically to the initialization section of the unit.

Note: Even though the compiler takes care of ordering the initialization of classes, in some complex scenarios, ordering may become random. This happens when the class constructor of a class depends on the state of another class that, in turn, depends on the first class.

Note: The class constructor for a generic class or record may execute multiple times. The exact number of times the class constructor is executed in this case depends on the number of specialized versions of the generic type. For example, the class constructor for a specialized TList class may execute multiple times in the same application.

Class Destructors

Class destructors are the opposite of class constructors in that they perform the finalization of the class. Class destructors come with the same advantages as class constructors, except for finalization purposes.

The following example builds on the example shown in class constructors and introduces the finalization routine:

Note: The class destructor for a generic class or record may execute multiple times. The exact number of times the class destructor is executed in this case depends on the number of specialized versions of the generic type. For example, the class destructor for a specialized TList class may execute multiple times in the same application.

Message Methods

Message methods implement responses to dynamically dispatched messages. The message method syntax is supported on all platforms. VCL uses message methods to respond to Windows messages.

A message method is created by including the message directive in a method declaration, followed by an integer constant from 1 through 49151 that specifies the message > For message methods in VCL controls, the integer constant can be one of the Win32 message >Messages unit. A message method must be a procedure that takes a single var parameter.

A message method does not have to include the override directive to override an inherited message method. In fact, it does not have to specify the same method name or parameter type as the method it overrides. The message ID alone determines to which message the method responds and whether it is an override.

Implementing Message Methods

The implementation of a message method can call the inherited message method, as in the following example:

The inherited statement searches backward through the >DefaultHandler method originally defined in TObject.

The implementation of DefaultHandler in TObject simply returns without performing any actions. By overr >DefaultHandler , a >DefaultHandler method for controls calls the Win32 API DefWindowProc .

Message Dispatching

Message handlers are seldom called directly. Instead, messages are dispatched to an object using the Dispatch method inherited from TObject:

The Message parameter passed to Dispatch must be a record whose first entry is a field of type Word containing a message ID.

Dispatch searches backward through the >Dispatch calls DefaultHandler .

Delphi Абстрактная ошибка в реализации уровня доступа к данным

Я разрабатываю свое первое трехуровневое приложение в Delphi XE. Я хотел бы определить свои бизнес-объекты и объекты доступа к данным таким образом, чтобы бизнес-объекты не должны были знать, какая версия объектов доступа к данным фактически используется.

Илон Маск рекомендует:  Как сделать exe файл в visual foxpro

У меня есть функция на моем уровне бизнес-объектов, которая начинается следующим образом:

В другом месте Ive объявлял TdalBase, TdalSQLbase (подкласс TDalBase) и TdalSQLCustomer (подкласс TdalSQLBase)

TdalBase и TdalSQLbase имеют виртуальную абстрактную функцию «LoadFromDatabase». TdalSQLCustomer реализует этот метод с переопределяющей директивой; GetDalObject (OBTYPE_CUSTOMER) возвращает TdalSQLCustomer.

Однако при вызове myDal.LoadFromDatabase я получаю абстрактную ошибку. Предположительно, потому что Ive объявила var в методе TDalBase, который пытается запустить функцию из этого родительского класса, а не версию TdalSQLCustomer.

Я не хочу, чтобы мой объект TCustomer знал, какая версия TdalBase действительно работает, поскольку это может быть TdalSQLCustomer или другая реализация (например, TdalMySQLCustomer).

Можно ли избежать абстрактной ошибки и по-прежнему скрывать экземпляр класса от TCustomer?

Я добавлю, что если есть инструменты ORM, которые помогут в этом в Delphi XE, я с удовольствием попробую любые предложения. Тем не менее я хотел бы понять основы этого материала.

Большое спасибо, Ричард

Спасибо за комментарии. Насколько я могу сказать, GetDALObject делает все правильно:

Интерфейс TSQLCustomer выглядит следующим образом:

Методы в Delphi

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

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

procedure NeverCallMe; virtual; abstract;

При этом никакого кода для этого метода писать не нужно. Вызов метода NeverCallMe приведет к созданию исключительной ситуации EAbstractError (исключительным ситуациям посвящена гл. 4).

Пример с классом TField из разд. «Полиморфизм» этой главы поясняет, для чего нужно использование абстрактных методов. В данном случае класс TField не используется сам по себе; его основное предназначение — быть родоначальником иерархии конкретных классов -«полей» и дать возможность абстрагироваться от частностей. Хотя параметр процедуры showData и описан как TField , но, если передать в нее объект этого класса, произойдет исключительная ситуация вызова абстрактного метода.

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

procedure SetData(AValue: Extended);

T2ndObj = class (TlstObj)

procedure SetData(AValue: Integer);

inherited SetData (0.99);

В этом примере разные методы с именем SetData присваивают значения разным полям с именем i. Перекрытое (одноименное) поле предка недоступно в потомке; поэтому, конечно, два одноименных поля с именем i — это нонсенс; так сделано только для примера.

В практике программирования принято присваивать всем идентификаторам в программе (в том числе полям объектов) осмысленные названия. Это существенно облегчит работу с исходным кодом не только другим разработчикам, но и вам.

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

Принципиально отличаются от статических виртуальные и динамические методы. Они должны быть объявлены путем добавления соответствующей директивы virtual или dynamic . Обе эти категории существовали и в прежних версиях языка Pascal. С точки зрения наследования методы этих двух видов одинаковы: они могут быть перекрыты в дочернем классе только одноименными методами, имеющими тот же тип.

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

Естественно, что этот механизм должен быть каким-то образом связан с передаваемым объектом. Для этого используются таблица виртуальных методов (Virtual Method Table, VMT) и таблица динамических методов (Dynamic Method Table, DMT).

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

Такая таблица есть для каждого класса (объектного типа). В ней хранятся адреса всех виртуальных методов класса, независимо от того, унаследованы ли они от предка или перекрыты в данном классе. Отсюда и достоинства, и недостатки виртуальных методов: они вызываются сравнительно быстро, однако для хранения указателей на них в таблице VMT требуется большое количество памяти.

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

Для перекрытия и виртуальных, и динамических методов служит директива override , с помощью которой (и только с ней!) можно переопределять оба этих типа методов. Приведем пример:

ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В DELPHI

ОСНОВНЫЕ КОНЦЕПЦИИ ООП

Язык Ое1рЫ реализует концепцию объектно-ориентированного программирования. Это означает, что функциональность приложения определяется набором взаимосвязанных задач, каждая из которых становится самостоятельным объектом. У объекта есть свойства (т.е. характеристики, или атрибуты) и методы, определяющие его поведение. В основе объектно-ориентированного программирования (ООП) лежит понятие класса.

Класс представляет собой дальнейшее развитие концепции типа и объединяет в себе задание не только структуры и размера переменных, но и выполняемых над ними операций.

Объекты в программе всегда являются экземплярами того или иного класса (подобно переменным определенного типа).

К основным концепциям ООП относятся следующие: инкапсуляция, наследование, полиморфизм.

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

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

В корне этого дерева находится базовый класс ЮЬдесЧ который реализует наиболее общие для всех классов элементы, например, действия по созданию и удалению объекта. Чем дальше тот или иной класс отстоит в дереве от базового класса, тем большей специфичностью он обладает.

Пример объявления класса-потомка:

//Добавление к классу tParentClass новых

// и переопределение существующих элементов

Сущность полиморфизма заключается в том, что методы различных классов могут иметь одинаковы имена, но различное содержание. Это достигается переопределением родительского метода в классе-потомке. В результате родитель и потомок в одинаковых ситуациях ведут себя по-разному.

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

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

КЛАССЫ И ОБЪЕКТЫ

Классом в Delphi называется особая структура, состоящая из полей, методов и свойств.

fMyField: Integer; //поле fMyField

function MyMethod(Data:tData): Integer; //метод MyMethod

Все классы в Delphi являются наследниками базового класс TObject, который реализует наиболее общие для всех классов элементы, например, действия по созданию и удалению объекта.

Поля класса — это данные, уникальные для каждого созданного в программе экземпляра класса. Они могут иметь любой тип, в том числе — тип класс. В Delphi перед именами полей принято ставить символ f (от field — поле): fLength, fWidth, fMyFileld и т.п.

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

Свойство можно определить как поле, доступное для чтения и записи не напрямую, а через соответствующие методы.

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

Методы класса реализуются в разделе описания процедур и функций программы или в разделе implementation модуля. При реализации метода указывается его полное имя, состоящее из имени класса, точки и имени метода класса:

function tMyClass.MyMethod(Data:tData): Integer;

//заголовок метода tMyClass.МуMethod

// тело метода tMyClass.MyMethod

Разрешено опережающее объявление классов единственным словом class с последующим полным описанием:

Области видимости — это возможности доступа к составным частям объекта. В Delphi поля и методы могут относиться к четырем группам: «общие» (public), «личные» (private), «защищенные» (protected) и «опубликованные» (published).

  • 1. Поля, свойства и методы, находящиеся в секции public, не имеют ограничений на видимость. Они доступны из других функций и методов объектов, как в данном модуле, так и во всех прочих, ссылающихся на него.
  • 2. Поля, свойства и методы, находящиеся в секции private, доступны только в методах класса и в функциях, содержащихся в том же модуле, что и описываемый класс. Такая директива позволяет скрыть детали внутренней реализации класса от всех. Элементы из секции private можно изменять, и это не будет сказываться на программах, работающих с объектами этого класса. Обратиться к ним можно, только переписав содержащий их модуль.
  • 3. Поля, свойства и методы, находящиеся в секции protected, доступны только внутри классов, являющихся потомками данного, в том числе и в других модулях. Такие элементы особенно необходимы дня разработчиков новых компонентов — потомков уже существующих.
  • 4. Область видимости published имеет особое значение для интерфейса визуального проектирования. В этой секции должны быть собраны те свойства объекта, которые будут видны не только во время исполнения приложения, но и из среды разработки. Все свойства компонентов, доступные через Инспектор объектов, являются их опубликованными свойствами. Во время выполнения такие свойства общедоступны, как и public.

Пример, иллюстрирующий первые три варианта областей видимости:

unit First; interface type

procedure Methodl; private

procedure Method2; protected

procedure Method3; end;

procedure TestProcI; begin

Obj1:= tClassI .Create;

Оbj1.Methodl; //допустимо Obj1.Method2; //допустимо Obj1.Method3; //недопустимо Objl.Free; end;

end. // unit First

unit Second; interface uses First;

var Obj2 : tClass2;

Methodl; //допустимо Method2; //недопустимо Method3; //допустимо

Obj2 .Methodl; //допустимо Obj2 .Method2; //недопустимо Obj2 .Method3; //недопустимо Obj2 .Free;

end. //unit Second

При описании дочернего класса можно переносить методы и свойства из одной сферы видимости в другую, не переписывая их заново и даже не описывая — достаточно указать новую сферу видимости наследуемого метода или свойства в описании дочернего класса. Разумеется, если вы поместили свойство в область private, «достать» его оттуда в потомках возможности уже нет.

СОЗДАНИЕ И УНИЧТОЖЕНИЕ ОБЪЕКТОВ

В Delphi объекты могут быть только динамическими! Любая переменная объектного типа есть указатель, причем для доступа к данным, на которые ссылается указатель объекта не нужно применять символ л .

Для выделения памяти экземпляру любой динамической переменной используется процедура New, для уничтожения — процедура Dispose. С объектами эти процедуры не используются: объект создается специальным методом — конструктором, а уничтожается специальным методом — деструктором:

AMyObject := tMyClass.Create; //создание объекта класса tMyClass

// действия с созданным объектом

AMyObject.Destroy; //уничтожение объекта AMyObject

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

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

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

Если указать имя уже созданного объекта, он поведет себя по-другому: не создаст новый экземпляр, а только выполнит код, содержащийся в теле конструктора.

Конструктор класса-потомка практически всегда должен перекрывать конструктор класса-предка. Чтобы правильно проинициа-лизировать в создаваемом объекте поля, относящиеся к классу-пред-ку, нужно в начале кода конструктора вызвать конструктор предка с помощью зарезервированного слова inherited:

constructor tMyClass. Create;

// собственный код конструктора

Деструктор — это специальный виртуальный (см. ниже) метод, заголовок которого начинается зарезервированным словом destructor. Деструктор предназначен для удаления объектов. Типичное название деструктора — Destroy.

Для уничтожения объекта рекомендуется использовать метод Free (он есть у базового класса TObject), который первоначально проверяет указатель на объект (не равен ли он nil) и только затем вызывает деструктор Destroy:

Деструктор очень часто не переопределяется в классе-потомке. Но если его необходимо переопределить, то необходимо учитывать следующее. Деструктор Destroy любого класса перекрывает виртуальный деструктор базового класса TObject, поэтому деструктор потомка необходимо объявить с директивой override:

destructor Destroy: override;

При реализации перекрывающего деструктора потомка нужно в конце кода деструктора вызвать деструктор предка с помощью зарезервированного слова inherited:

// собственный код деструктора inherited Destroy; end;

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

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

function GetAProperty: tSomeType;

procedure SetAProperty(ANewValue: tSomeType);

property AProperty: tSomeType read GetAProperty write SetAProperty;

В контексте свойства слова read и write являются зарезервированными. Для доступа к значению свойства AProperty явно достаточно написать

и компилятор оттранслирует эти операторы в вызовы методов.

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

Рассмотрим следующий пример:

tPropCIass = class(tObject) fValue: Integer; procedure DoSomething; function Correct(AValue: lnteger):Boolean;

procedure SetValue(NewValue: Integer); property AValue: Integer read fValue write SetValue;

procedure tPropClass.SetValue(NewValue: Integer);

if (NewValueofValue) and Correct (NewValue) then fValue := NewValue;

В этом примере чтение значения свойства AValue означает просто чтение поля fValue. Зато при присвоении ему значения внутри SetValue вызывается сразу два метода.

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

property AProperty: tSomeType read GetValue;

Значение свойства AProperty можно лишь прочитать; попытки присвоить AProperty значение вызовет ошибку компиляции.

Для присвоения свойству значения по умолчанию используется ключевое слово default:

property Visible: Boolean read fVisible write SetVisible default True

Свойство может быть и векторным; в этом случае оно внешне выглядит как массив:

property APoints[lndex: IntegerftPoint read GetPoint write SetPoint;

На самом деле, среди полей класса может и не быть поля-массива. При помощи свойств вся обработка обращений к внутренним структурам класса может быть замаскирована.

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

function GetPoint(lndex: Integer): TPoint;

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

procedure SetPoint(lndex: Integer; NewPoint: tPoint);

У векторных свойств есть еще одна важная особенность. Некоторые классы в Delphi (списки, наборы строк) «построены» вокруг векторного свойства. Основной метод такого класса дает доступ к некоторому массиву, а все остальные методы являются вспомогательными. Специально для облегчения работы в этом случае векторное свойство может быть описано как default:

property Strings[lndex: Integer]: string read Get write Put; default;

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

var AMyObject: tMyClass;

AMyObject.Strings[1] := ‘First’; //первый способ AMyObject[2] := ‘Second’; //второй способ • • •

Будьте внимательны, применяя зарезервированное слово default, — для обычных и векторных свойств оно употребляется в разных случаях и с различным синтаксисом.

О роли свойств в Delphi красноречиво говорит следующий факт: у всех имеющихся в распоряжении стандартных классов 100% полей недоступны (помещены в секцию private) и заменены базирующимися на них свойствами. Рекомендуем при разработке своих классов придерживаться этого же правила.

Принцип наследования позволяет объявить класс

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

В Delphi все классы являются потомками класса TObject. При построении дочернего класс прямо от TObject в определении его можно не упоминать.

Следующие объявления одинаково верны:

Первый вариант предпочтительнее, хотя он и более длинный, — для устранения возможных неоднозначностей.

Класс TObject несет очень серьезную нагрузку и будет рассмотрен отдельно.

Унаследованные от предка поля и методы доступны в дочернем классе; если имеет место совпадение имен методов, то говорят, что они перекрываются.

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

  • 1) статические;
  • 2) виртуальные (virtual) и динамические (dynamic);
  • 3) перегружаемые (overload) методы.

Статические методы, а также любые поля в объектах-потомках ведут себя одинаково: можно без ограничений перекрывать старые имена, и при этом изменять тип методов. Перекрытое поле предка недоступно в потомке. Перекрытый метод доступен при указании зарезервированного слова inherited. Методы объектов по умолчанию являются статическими — их адрес определяется еще на стадии компиляции проекта. Они вызываются быстрее всего.

Принципиально отличаются от статических виртуальные и динамические методы (директива virtual или dynamic). Их адрес определяется во время выполнения программы по специальной таблице. С точки зрения наследования методы этих двух видов одинаковы: они могут быть перекрыты в дочернем классе только одноименными методами, имеющими тот же тип.

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


ВИРТУАЛЬНЫЕ И ДИНАМИЧЕСКИЕ МЕТОДЫ

Рассмотрим пример. Пусть у нас имеются некое обобщенное поле для хранения данных — абстрактный класс tField и три его потомка — для хранения строк, целых и вещественных чисел:

function GetData: string; virtual; abstract;

tStringField = class(tField) fData : string;

function GetData: string; override;

function GetData: string; override;

function GetData: string; override;

В этом примере классы содержат разнотипные данные и «умеют» сообщать о значении этих данных текстовой строкой (при помощи метода GetData). Внешняя по отношению к ним процедура Show Data получает объект в виде параметра и показывает эту строку.

Правила контроля соответствия типов (typecasting) языка Delphi гласят, что объекту как указателю на экземпляр объектного типа может быть присвоен адрес любого экземпляра любого из дочерних типов. В процедуре ShowData параметр описан как tField — это значит, что в нее можно передавать объекты классов и tStringField, и tlntegerField, и tExtendedField, и любого другого потомка tField.

Но чей метод GetData при этом будет вызван? Тот, который соответствует классу фактически переданного объекта. Этот принцип называется полиморфизмом и он, пожалуй, представляет собой наиболее важный принцип ООП. Например, чтобы смоделировать некоторую совокупность явлений или процессов средствами ООП, нужно выделить их самые общие, типовые черты. Те из них, которые не изменяют своего содержания, должны быть реализованы в виде статических методов. Те же, которые варьируются при переходе от общего к частному, лучше облечь в форму виртуальных методов. Основные, «родовые» черты (методы) нужно описать в классе-пред-ке и затем перекрывать их в классах-потомках.

При вызове виртуальных и динамических методов адрес определяется не во время компиляции, а во время выполнения — это называется поздним связыванием (late binding). Позднее связывание реализуется с помощью таблицы виртуальных методов (Virtual Method Table, VMT) и таблицы динамических методов (Dynamic Method Table, DMT).

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

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

Для перекрытия и виртуальных, и динамических методов служит директива override, с помощью которой (и только с ней!) можно переопределять оба этих типа методов.

Первый метод класса tClass2 создается заново, остальные два — перекрываются. Попытка применить директиву override к статическому методу вызовет ошибку компиляции.

ПРИМЕР РЕАЛИЗАЦИИ ПОЛИМОРФИЗМА ДЛЯ ИЕРАРХИИ ГРАФИЧЕСКИХ ОБЪЕКТОВ

Рассмотрим описание и реализацию классов «Точка» и «Окружность» для «рисования» на экране точки и окружности.

// тип — координата точки на экране

// класс — точка на экране дисплея

// поля — координаты точки на экране // полецвет рисования точки

// Описание класса tPoint tPoint = class(TObject) protected fX, fY: tCoord; fColor: Byte; public

property X: tCoord read fX write fX; // свойство — координата X

property Y: tCoord read fY write fY; // свойство — координата Y

property Color: Byte read fColor write fColor;// свойство — цвет procedure Show; //метод высвечивания точки

procedure Hide; //метод гашения точки

procedure MoveTo(NewX, NewY: tCoord); //перемещение точки end;

// класс — окружность на экране // поле — радиус окружности

// Описание класса tCircle tCircle = class(tPoint)

property Radius: tCoord read fRadius write fRadius; //св-во — радиус procedure Show; //метод высвечивания окружности

procedure Hide; //метод гашения окружности

procedure MoveTo (NewX,NewY:tCoord) //перемещение окружности

// Реализация методов класса tPoint

Writeln(‘Pi/icyio точку (‘, fx,fy,’) цветом fColor);

// «Стирание» точки Л/гйе1п(‘Стираю точку (‘, fx, ‘,fy,’) цвета fColor);

procedure tPoint.MoveTo(NewX, NewY: tCoord);

fX := NewX; fY := NewY;

// Реализация методов класса tCircle

Writeln(‘PHcyK) окружность с центром (‘, fx,fy,’) радиуса fRadius,’ цветом fColor);

procedure tCircle.Hide; begin

Л/гйе1п(‘Стираю окружность с центром (‘, fx,’,’, fy,’) радиуса ‘, fRadius,’ цвета ‘, fColor);

procedure tCircle.MoveTo (NewX,NewY: tCoord);

fX := NewX; fY := NewY;

Обратите внимание, что методы MoveTo классов tPoint и tCircle реализуются одинаково за исключением того, что в tPoint. MoveTo используются tPoint.Hide и tPoint.Show, а в tCircle.MoveTo используются tCircle.Hide и tCircle.Show. Взаимодействие методов классов tPoint и tCircle можно проиллюстрировать схемой, приведенной на рис. П3.1.

Рис. П3.1. Взаимодействие методов классов tPoint и tCircle

Поскольку методы реализуются одинаково, можно попытаться унаследовать метод МоуеТо от класса 1РотЕ При этом возникает следующая ситуация.

При компиляции метода 1РотСМоуеТо в него будут включены ссылки на коды методов 1РотС81кж и 1РотЕНк1е. Так как метод с именем МоуеТо не определен в классе 1Слгс1е, то компилятор обращается к родительскому типу и ищет в нем метод с этим именем. Если метод найден, то адрес родительского метода заменяет имя в исходном коде потомка.

Следовательно, при вызове 1СДс1е. МоуеТо в программу будут включены коды 1РотС МоуеТо (то есть класс 1:Сцс1е будет использовать метод так, как он откомпилирован). В этом случае процедура 1Сцс1е. МоуеТо будет работать неверно.

Структура вызовов методов при таком наследовании приведена на рис. П3.2.

Рис. П3.2. Структура вызовов методов при наследовании метода

MoveTo от класса tPoint

Для правильного вызова методов процедуры Show и Hide должны быть объявлены виртуальными, так как только в этом случае класс tCircle может наследовать метод MoveTo у типа tPoint. Это становится возможным потому, что подключение виртуальных методов Show и Hide к процедуре MoveTo будет осуществляться во время выполнения программы, и будут подключаться методы, определенные для типа tCircle (tCircle.Show и tCircle.Hide).

При использовании виртуальных методов Show и Hide взаимодействие методов классов tPoint и tCircle можно проиллюстрировать схемой, приведенной на рис. ПЗ.З.

Рис. ПЗ.З. Структура вызовов методов при использовании виртуальных методов Show и Hide

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

Описание классов «Точка» и «Окружность» с использованием виртуальных методов:

// Описание класса tPoint tPoint = class(TObject) protected fX, fY: tCoord; fColor: Byte; public

property X: tCoord read fX write fX; property Y: tCoord read fY write fY; property Color: Byte read fColor write fColor; procedure Show; virtual; procedure Hide; virtual; procedure MoveTo(NewX, NewY: tCoord); end;

// Описание класса tCircle tCircle = class (tPoint) protected fRadius: tCoord;

property Radius: tCoord read fRadius write fRadius; procedure Show; override; procedure Hide; override; end;

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

procedure SetData(AValue : Extended);

procedure SetData(AValue : Integer);

Попытка вызова методов

вызовет ошибку компиляции на первой из двух строк. Для компилятора внутри Obj2 статический метод с параметром типа Extended перекрыт, и он его не «признает».

Объявить методы виртуальными нельзя, так как тип и количество параметров в одноименных виртуальных методах должны совпадать. Чтобы указанные вызовы были верными, необходимо объявить методы перегружаемыми, для чего используется директива overload:

procedure SetData(AValue : Extended); overload; end;

procedure SetData(AValue : Integer); overload; end;

Объявив метод Set Data перегружаемым, в программе можно использовать обе его реализации одновременно. Это возможно потому, что компилятор определяет тип передаваемого параметра (целый или с плавающей точкой) и в зависимости от этого подставляет вызов соответствующего метода.

Можно перегружать и виртуальные методы. В этом случае необходимо добавить директиву reintroduce:

procedure SetData(AValue : Extended); virtual; overload; end;

procedure SetData(AValue : Integer); reintroduce; overload; end;

На перегрузку методов накладывается ограничение — нельзя перегружать методы, находящиеся в области видимости published.

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

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

procedure NeverCallMe; virtual; abstract;

Никакого кода для этого метода писать не нужно. Вызов абстрактного метода приведет к созданию исключительной ситуации Е Abstract Error.

Пример с классом tField из параграфа «Полиморфизм» поясняет, для чего нужны абстрактные методы. В данном случае класс tField не используется сам по себе; его основное предназначение — быть родоначальником иерархии конкретных классов-«полей» и дать возможность абстрагироваться от частностей. Хотя параметр процедуры ShowData и описан как tField, но если передать в нее объект этого класса, произойдет исключительная ситуация вызова абстрактного метода.

Рассмотрим уже знакомый пример из параграфа «Полиморфизм»:

Внутренняя структура объектов Objl и Obj2 приведена на рис. П3.4.

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

Перед таблицей виртуальных методов расположена специальная структура, которая называется информацией о типе времени выполнения (runtime type information, RTTI). В ней содержатся данные, полностью характеризующие класс: его имя, размер экземпляра, указатели на класс-предок, на имя класса и т. д. На рисунке она показана одним блоком, а ее содержимое расшифровано ниже.

Указатель на класс tClassl

Указатель на класс

Поле fFieldl Поле fField2

VMT класса tClassl

DMT класса tClassl

RTTI класса tClassl

Число динамических методов: 2

Индекс tClassl .dnMetl (-1)

Индекс tClassl ,dnMet2 (-2)

(й) tClassl .dnMetl

VMT класса tClass2

DMT класса tClass2

RTTI класса tClass2

Число динамических методов: 1

Индекс tClass2.dnMetl (-1)

(2; tClassl ,vrMet2

Рис. П3.4. Внутренняя структура объектов Obj 1 и Obj2

Одно из полей структуры содержит адрес таблицы динамических методов класса (DMT). Таблица имеет следующий формат — в начале слово, содержащее количество элементов таблицы; затем — слова, соответствующие индексам методов. Нумерация индексов начинается со значения — 1 и идет по убывающей. После индексов идут собственно адреса динамических методов.

Обратите внимание, что DMT объекта Obj 1 состоит из двух элементов, Obj2 — из одного элемента, соответствующего перекрытому методу dnMetl. В случае вызова метода Obj2.dnMet2 индекс не будет найден в таблице DMT Obj2, поиск продолжится в DMT объекта Obj 1. Именно так экономится память при использовании динамических методов.

Напомним, что указатель на класс указывает на адрес первого виртуального метода. Служебные данные размещаются перед таблицей VMT, то есть с отрицательным смещением. Эти смещения описаны в модуле SYSTEM.PAS:

= -52; = -48; = -44; = -40; -36;

Поля УггйОупагтсТаЫе, Уггй018ра1с11 и У1УйОеГаиИ:Напб1ег отвечают за вызов динамических методов. Поля угтп№у1п81апсе, угШЕгее1п81апсе и УгШЭез^оу содержат адреса процедур создания и уничтожения экземпляра класса. Поля утПшЛаЫе, УпйА^оТаЫе, уш15аГеСа11Ехсер1юп введены для обеспечения совместимости с моделью объектов СОМ. Остальные поля доступны через методы класса ТСИуесЕ в Ое1р1м информация этой таблицы играет важную роль и может использоваться программистом неявно.

ПРОВЕРКА СОВМЕСТИМОСТИ И ПРИВЕДЕНИЕ ОБЪЕКТНЫХ ТИПОВ

В языке определены два оператора: к и аз, неявно обращающиеся к информации о классе.

Оператор к предназначен для проверки совместимости по присваиванию экземпляра объекта с заданным классом. Выражение вида:

AnObject is TObjectType

принимает значение True только в том случае, если объект AnObject совместим по присваиванию с TObjectType, то есть является объектом этого класса или одного из классов, порожденных от него. Кстати, определенная проверка происходит еще при компиляции: если фактические объект и класс несовместимы, компилятор выдаст ошибку в этом операторе.

Оператор as введен в язык для приведения объектных типов. С его помощью можно рассматривать экземпляр объекта как принадлежащий к другому совместимому типу:

with ASomeObject as tAnotherType do .

Использование оператора as отличается от стандартного способа приведения типов с помощью конструкции

наличием проверки на совместимость типов во время выполнения (как в операторе is): попытка приведения к несовместимому типу приводит к возникновению исключительной ситуации EInvalidCast.

После применения оператора as сам объект остается неизменным, но вызываются только те его методы, которые есть у присваиваемого класса. Присваиваемый фактически тип должен быть известен на стадии компиляции, поэтому на месте TObjectType (после is) и tAnotherType (после as) не может стоять переменная-указатель на класс.

УКАЗАТЕЛЬ НА КЛАСС. МЕТОДЫ КЛАССА

Информация, описывающая класс, создается и размещается в памяти на этапе компиляции. Возникает вопрос: можно ли получить доступ к ней, не создавая экземпляр класса (объект)? Да, можно.

Доступ к информации класса вне методов этого класса можно получить, описав соответствующий указатель, который называется указателем на класс, или указателем на объектный тип (class reference). Он описывается при помощи зарезервированных слов class of. Например, указатель на класс TObject описан в модуле SYSTEM.PAS и называется tClass:

Указатели на классы подчиняются правилам приведения объектных типов. Указатель на класс-предок может ссылаться и на любые дочерние классы; обратное невозможно.

С указателем на класс тесно связано понятие методов класса. Такие методы можно вызывать без создания экземпляра объекта — с указанием имени класса, в котором они описаны. Перед описанием метода класса нужно поставить зарезервированное слово class:

// обращение к методу класса до создания объекта этого класса

Разумеется, методы класса не могут использовать значения, содержащиеся в полях: ведь экземпляра-то не существует! Возникает вопрос: для чего тогда нужны такие методы? Важнейшие методы класса определены в самом классе TObject: они позволяют извлечь из внутренней структуры класса практически всю необходимую информацию.

Некоторые важные методы класса TObject приведены в табл. П3.1.

Основные методы класса TObject

class function Classlnfo: Pointer;

Возвращает указатель на структуру с дополнительными данными об опубликованных методах и свойствах

class function ClassName: ShortString;

Возвращает имя класса

class function ClassNamels (const Name: ShortString): Boolean;

Возвращает True, если имя класса равно заданному

class function Class Parent: TClass;

Возвращает указатель на родительский класс (для TObject возвращает nil)

class function InheritsFrom

(AClass: TClass): Boolean;

Возвращает True, если данный класс порожден от класса AClass

class function Initlnstance (Instance: Pointer): TObject;

Инициализирует экземпляр класса

class function InstanceSize: Longint;

Возвращает размер экземпляра класса

MethodAddress(const Name: ShortString): Pointer;

Возвращает адрес метода по его имени (только для опубликованных методов)

class function MethodName (Address: Pointer): ShortString;

Возвращает имя метода по его адресу (только для опубликованных методов)

class function Newlnstance: TObject; virtual;

Создает новый экземпляр объекта.

В частности, эта функция вызывается внутри конструктора

function ClassType: TClass;

Возвращает указатель на класс данного объекта

Конструктор. Создает новый экземпляр класса и инициирует обработчик исключительных ситуаций

Вызывается сразу после создания экземпляра класса

Вызывается перед уничтожением экземпляра

destructor Destroy; virtual;

Деструктор. Производит действия по уничтожению экземпляра класса

procedure Default Handler (var Message); virtual;

Обработчик сообщений по умолчанию.

В TObject не содержит ничего, кроме кода возврата

procedure Dispatch(var Message);

Вызывает методы обработки сообщений в зависимости от значения параметра Message

Name: String): Pointer;

Возвращает адрес поля вызвавшего объекта с заданным именем

Используется вместо деструктора. Проверяет передаваемый деструктору указатель на экземпляр

procedure Free Instance; virtual;

Уничтожает экземпляр объекта. Вызывается внутри деструктора

В следующем примере переменная Object Ref является указателем на класс; он по очереди указывает на TObject и TMyObject. Посредством этой переменной-указателя вызывается функция класса ClassName:

Виртуальные методы AfterConstruction и Before Destruction вызываются сразу после создания экземпляра класса и непосредственно перед его уничтожением. Их можно использовать, если по каким-либо причинам не хватает собственно конструктора и деструктора.

Удобно использовать метод Free для уничтожения экземпляра класса, при этом переопределяется метод Destroy, в котором описываются все действия по уничтожению экземпляра, например освобождение выделенной памяти или закрытие файлов. При вызове метода Free проверяется передаваемый указатель на экземпляр и если он не равен nil, то вызывается метод Destroy и происходит уничтожение объекта.

Методы MethodName и MethodAddress используются для получения имени или адреса только опубликованных методов, это связано с тем, что система Delphi хранит имена только таких методов. Так как директива published предназначена для описания свойств, размещаемых в инспекторе объектов, где указываются их имена.

Если метод MethodName вызывается для указателя на неопубликованный метод, то возвращается пустая строка.

При передаче методу MethodAddress в качестве параметра имени неопубликованного или несуществующего метода возвращается значение nil.

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

Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL