Конструктор, деструктор, классовые методы
При работе с обычными типами данных простого объявления было бы достаточно для получения экземпляра типа. Однако объекты в среде Delphi относятся к динамическим данным, т.е. распределяются в динамической памяти. Поэтому переменная созданного класса — это просто ссылка на экземпляр (объект в памяти), которого физически еще не существует. Чтобы сконструировать объект (выделить память для экземпляра) класса, а вместе с этим и инициализировать переменную, нужно вызвать конструктор.
Конструктор – это специальный классовый метод, который отвечает за создание объекта. Этот процесс включает выделение памяти под экземпляр и инициализацию его полей. Совместно с этим используется ещё и деструктор – специальный классовый метод, который отвечает за разрушение — очистку полей и освобождение памяти. Действия по инициализации и очистке полей специфичны для каждого конкретного класса объектов. По этой причине язык Delphi позволяет переопределить стандартный конструктор Create и стандартный деструктор Destroy для выполнения любых полезных действий. Для создания экземпляра объекта необходимо вызвать конструктор только один раз. Он вызывается при указании имени класса и точки. Объявление конструкторов и деструкторов похоже на объявление обычных методов с той лишь разницей, что вместо зарезервированных слов function и procedure используются слова constructor и destructor.
Если объект содержит встроенные объекты или другие динамические данные, то конструктор — это как раз то место, где их нужно создавать. После создания объект можно использовать в программе: получать и устанавливать значения его полей, вызывать его методы.
Дополним пример из листинга 4:
Листинг 1.7
Type TSimpleClass = class (TObject)
…
Public
constructor Create(v: integer; FileMy: string = 'config.inf');//конструктор 1
constructorCreate(); //конструктор 2
…
end;
Конструкторов у класса может быть сколько угодно, но объект должен быть создан только одним из них.
При создании объекта в памяти выделяется место только для его полей. Методы, как и обычные процедуры и функции, помещаются в область кода программы. Они умеют работать с любыми экземплярами своего класса и не дублируются в памяти.
Конструкторы класса бывают трёх видов:
· Конструктор по умолчанию – может быть в классе только один, не имеет параметров, внутри тела инициализирует поля заранее заданными значениями. В листинге 7 это конструктор 2. Пример реализации:
constructor TSimpleClass.Create();
Begin
fv:=0;
fFileMy:=’7.txt’;
end;
· Параметрические конструкторы – число таких конструкторов не ограничено, они задают свойства будующего объекта согласно параметрам, которые передаёт им программист, допустимо использование параметров по умолчанию (только надо не забывать, что после объявления параметров по умолчанию, обычные параметры стоять не должны). В листинге 7 это конструктор 1. Пример реализации:
constructorTSimpleClass.Create(v: integer; FileMy: string = 'config.inf');
Begin
Self.fv:=v;
Self.fFileMy:=FileMy;
end;
· Конструктор копирования – позволяет создать дубликат объекта, для этого ему в качестве параметра передаётся копируемый объект того же класса. Необходимо выполнять копирование каждого поля по отдельности. Пример реализации:
ConstructorTSimpleClass.Create(obj: TSimpleClass);
Begin
self.fv:=obj.fv;
self.fFileMy:=obj.fFileMy;
self.fText:=obj.fText;
end;
Конструктор можно применять к классу или к объекту.
Если он применяется к классу:
MyClass := TSimpleClass.Create(15, 'con.inf');
, то выполняется следующая последовательность действий:
· в динамической памяти выделяется место для нового объекта;
· выделенная память заполняется нулями. В результате все числовые поля и поля порядкового типа приобретают нулевые значения, строковые поля становятся пустыми, а поля, содержащие указатели и объекты получают значение nil;
· затем выполняются заданные программистом действия конструктора;
· ссылка на созданный объект возвращается в качестве значения конструктора. Тип возвращаемого значения совпадает с типом класса, использованного при вызове (в нашем примере это тип TSimpleClass).
Если конструктор применяется к объекту:
MyClass.Create(15, 'con.inf');
, то конструктор выполняется как обычный метод. Другими словами, новый объект не создается, а происходит повторная инициализация полей существующего объекта. В этом случае конструктор не возвращает никакого значения. Далеко не все объекты корректно себя ведут при повторной инициализации, поскольку программисты редко закладывают такую возможность в свои классы. Поэтому на практике повторная инициализация применяется крайне редко.
В случае наследования классов, не имеет смысла в конструкторе каждого нового наследника инициализировать поля родителя. Здесь проще вызвать конструктор родителя, а затем только проинициализировать поля, которые добавились в наследнике. Чтобы вызвать метод или конструктор родителя достаточно указать ключевое слово inherited.
Листинг 1.8
constructor TSimpleClass.Create(v: integer; FileMy: string = 'config.inf');
Begin
inherited;
Self.fv:=fv;
Self.fFileMy:=FileMy;
end;
Destroy — это так называемый деструктор объекта. Он присутствует в классе наряду с конструктором и служит для удаления объекта из динамической памяти. После вызова деструктора переменная-объект становится несвязанной и не должна использоваться для доступа к полям и методам уже несуществующего объекта. В теле деструктора обычно должны уничтожаться встроенные объекты и динамические данные, как правило, созданные конструктором. Как и обычные методы, деструктор может иметь параметры, но эта возможность используется редко. Чтобы отличать в программе связанные объектные переменные от несвязанных, последние следует инициализировать значением nil. Например, в следующем фрагменте обращение к деструктору Destroy выполняется только в том случае, если объект реально существует:
if MyClass <> nil then MyClass.Destroy;
Вызов деструктора для несуществующих объектов недопустим и при выполнении программы приведет к ошибке. Чтобы избавить программистов от лишних ошибок, в объекты ввели предопределенный метод Free, который следует вызывать вместо деструктора. Метод Free сам вызывает деструктор Destroy, но только в том случае, если значение объектной переменной не равно nil. Поэтому последнюю строчку в приведенном выше примере можно переписать следующим образом.
MyClass.Free;
После уничтожения объекта переменная MyClass, к сожалению, сохраняет свое значение, продолжая ссылаться на место в памяти, где объекта уже нет. Если эту переменную предполагается использовать еще, то нужно присвоить ей значение nil, чтобы программа могла проверить, существует объект или нет. Таким образом, наиболее правильная последовательность действий при уничтожении объекта должна быть следующей:
MyClass.Free;
MyClass:= nil;
Можно сделать то же самое с помощью стандартной процедуры FreeAndNil(), что будет эквивалентно двум предыдущим вызовам:
FreeAndNil(MyClass);
В классе TObject уже имеются готовые деструктор и конструктор, рекомендуется использовать их, если нет необходимости в методе с особыми возможностями. В любом случае в реализации собственного конструктора желательно вызывать базовый конструктор Create с помощью директивы inherited.
Конструктор и деструктор относятся к так называемым классовым методам. Их особенность заключается в том, что их разрешается вызвать, используя для этого вызова не объект, а класс. Можно создать и другие методы, указав ключевое слово class, стоящее перед заголовком подпрограммы. Пример:
Листинг 1.9
Type TMyClass = class(TObject)
Public
constructor Create(MySize:integer);
class function Sozdanie:integer;
A:array of integer;
End;
Типы методов
В паскале каждый метод класса может иметь дополнительные характеристики, определяющие, как будет реализовываться этот метод в классах-наследниках. Характеристики задаются ключевыми словами, которые указываются при объявлении методов.
1) Статические методы. Все методы по умолчанию считаются статическими. Это означает, что их вызов будет происходить в соответствии с принципом полиморфизма. Указание дополнительных ключевых слов не требуется (в некоторых версиях допустимо static).
2) Виртуальные методы. Такие методы перекрываются в классах-наследниках методами с одноименными заголовками (т.е. названия методов и список параметров должны совпадать полностью). Для описания виртуальных методов используется ключевое слово virtual.
Type TCar = class(TObject)
Procedure move; virtual;
End;
3) Динамические методы. Аналогичны виртуальным. Разница заключается в том, что виртуальные оптимизированы для максимального быстродействия, а динамические для максимальной экономии памяти. Используется ключевое слово dynamic.
Type TCar = class(TObject)
Procedure move; dynamic;
End;
4) Перекрывающие методы. Чтобы показать компилятору, что метод перекрывает виртуальный (или динамический) метод родителя, надо использовать ключевое слово override. Перекрывать можно только методы с одноименными заголовками. Данный подход используется, когда необходимо дополнить метод родителя новыми действиями в потомке, при этом в объявление метода потомка добавить новые параметры нельзя. Подразумевается вызов в потомке метода родителя директивой inherited. Например:
Type
TVehicle = class//базовый класс
constructor Create; virtual; //конструкторбазовогокласса
protected a, c:integer;
end;
TMotorcycle = class(TVehicle)//наследник
constructor Create; override; //перекрытыйконструктор
private b, d:integer;
end;
constructor TVehicle.Create; begin a:=0; c:=0; end;
constructor TMotorcycle.Create; begin inherited; b:=0; d:=0; end;
5) Абстрактные методы. В некоторых случаях не имеет смысла выполнять реализацию определённых методов базового класса, например, когда все реализации потомков будут сильно отличаться друг от друга и родительский метод по существу использоваться не будет. Вместе с тем соответствующий метод обязан быть реализован (разрешается разная реализация) в каждом из классов-наследников. Тогда такой метод объявляется в родительском классе, как абстрактный при помощи ключевого слова abstract. Не забываем также писать ключевое слово virtual или dynamic. Если класс содержит только абстрактные методы, то такой класс называют абстрактным. Пример:
Type TCar = class(TObject)
Proceduremove; virtual; abstract;
End;
6) Перегружаемые методы. Это методы, имеющие одинаковые имена, но различные типы параметров. Компилятор автоматически определяет, какой конкретно метод надо вызвать в зависимости от типов аргументов. Такие методы помечаются ключевым словом overload. Пример:
Type TRectangle = class(TObject)
procedure Draw(x1,y1,x2,y2:integer); overload;
procedure Draw(rect:TRect); overload;
end;
7) Классовый метод. Создаётся при указании ключевого слова class перед словом functioin или procedure внутри класса. Их особенность заключается в том, что методы классов разрешается вызвать, используя для этого вызова не объект, а сам класс непосредственно. Смотреть листинг 7.
8) Обработчик внешнего события. Чтобы обычный метод стал обработчиком нужно указать ключевое слово message после точки с запятой в объявлении метода с дальнейшем указанием типа сообщения windows.
9) Встраиваемый метод. Используется директива inline. Компилятор, встретив это ключевое слово, копирует код функции вместо её вызова — это увеличивает готовый объектный код, зато заметно увеличивает быстродействие. Поддерживается не во всех функциях.
10) Экспортируемый метод. Используется ключевое слово export. Делает функцию или процедуру в DLL внешне доступной.
11) Ассемблерный метод. Используется ключевое слово assembler. Указывает, что метод написан напрямую на ассемблере.
function give_me_10: Integer; assembler;
Asm
mov eax, 10
end;
12) Переопределённый метод. Используется ключевое слово reintroduce. Обрывает цепочку перекрытия методов в случае наследования нескольких классов друг от друга, создаётся новый метод. При этом название метода наследника должно совпадать с названием метода родителя, но список параметров должен различаться. Метод родителя не обязательно должен быть виртуальным. Вызовать из переопределённого метода метод родителя директивой inherited запрещено, потому что здесь нет прямого наследования. Например:
Type
TVehicle = class
constructor Create; virtual; //базовыйконструктор
protected a, c:integer;
end;
TMoped = class(TVehicle)//переопределённыйконструктор
constructor Create(x : integer); reintroduce;
private b, d:integer;
end;
constructor TVehicle.Create; begin a:=0; c:=0; end;
constructor TMoped.Create; begin a:=0; c:=0; b:=0; d:=0; end;
13) Обработчик внешнего прерывания. Используется ключевое слово interrupt, которое указывает, что метод будет обработчиком прерывания. Позволяет использовать метод в качестве параметра функции SetIntVec(). Метод должен иметь максимально короткий и быстрый код (быть реентерабельным), потому что в единицу времени может обрабатываться только одно прерывание. Недопустимо использование внутри метода многочисленных функций рисования. Если обработчик выполняется быстрее, чем за 55 миллисекунд, то он не вызывает проблем.
var Old_Vector:Pointer;
procedure Timer_Example; interrupt;
Begin
circle(100+Random(200), 100+Random(200),10);
Port[$20]:=$20; //разрешаемповторныйвызовпрерывания
end;
Begin
…
GetIntVec($08, Old_Vector); //узнаёмстароепрерывание
SetIntVec($08, @Timer_Example);//устанавливаем собственное
…
SetIntVec($08, Old_Vector); //возвращаем старое на место
End.
cdecl, stdcall, pascal, safecall, fastcall и пр. - это соглашения по вызову процедур. Эти соглашения определяют различные правила вызова: как будут передаваться параметры - через стек, через регистры, через динамическую память, кто ответственный за очистку стека - вызывающая или вызываемая программа и др.
cdecl - параметры помещаются в стек в последовательности справа-налево. Таким образом, при этом получится так, что самый правый параметр окажется на дне стека, а самый первый слева параметр - на вершине стека. Соответственно, вызванная программа будет извлекать из стека эти значения в обратном порядке - сначала будет извлечён тот параметр, который находится на вершине стека - т. е. тот, который в списке параметров объявлен первым. Затем, из стека будет извлечён второй слева параметр и т. д. до самого правого параметра, который лежит на дне стека.
Ответственной за очистку стека является вызывающая программа.
Соглашение cdecl применяется в качестве основного в языке Си и др.
stdcall - тоже самое что и cdecl, но ответственной за очистку стека является вызываемая программа. Это соглашение является основным для API функций Windows.
cdecl и stdcall позволяют передавать произвольное количество параметров - потому что вызывающая программа всегда извлекает параметры из стека в том порядке, в котором они перечислены в спецификации процедуры. И извлечение можно продолжать до момента, когда будет достигнуто дно стека. - Т. е. признаком того, что все параметры извлечены является достижение дна стека. Это позволяет вызывать функции, которые могут содержать произвольное количество параметров.
pascal - параметры помещаются в стек в последовательности слева-направо. Таким образом, при этом получится так, что самый первый параметр слева окажется на дне стека, а самый правый параметр - на вершине стека. Вызванная программа будет извлекать из стека эти значения согласно их расположению в стеке - сначала будет извлечён тот параметр, который находится на вершине стека - т. е. тот, который в списке параметров объявлен последним. Затем, из стека будет извлечён предпоследний параметр и т. д. до самого правого параметра, который лежит на дне стека.
В спецификации pascal есть ещё некоторые особенности.
Спецификация pascal применяется, как основная, в языках Pascal, Delphi и в некоторых других языках. Эта спецификация считается скоростной. Но она не позволяет вызывать функции с произвольным количеством параметров. - По причине того, что вызванная программа первым из стека извлекает тот параметр, который в спецификации функции объявлен крайним справа. - Т. е., надо точно знать общее количество параметров, тогда будет соблюдено точное соответствие формальных параметров и извлекаемых из стека фактических параметров, объявленных в спецификации процедуры.
safecall - применяется в технологии COM.
fastcall - передача параметров через регистры.
Есть и другие соглашения.