МИНИСТЕРСТВО ОБРАЗОВАНИЯ РОССИИ Томский политехнический университет ____________________________________________________...
8 downloads
377 Views
273KB Size
Report
This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Report copyright / DMCA form
МИНИСТЕРСТВО ОБРАЗОВАНИЯ РОССИИ Томский политехнический университет _______________________________________________________
УТВЕРЖДАЮ Декан АВТФ ________________ Мельников Ю.С. “__” ______2002 г.
МЕТОДЫ В OBJECT PASCAL Методические указания к лабораторной работе № 135 по курсу “Программирование и основы алгоритмизации” для студентов направления 550200 “Автоматизация и управление”
Томск - 2002
УДК 681.3 Методы в Object Pascal. Методические указания к лабораторной работе № 135 по курсу “Программирование и основы алгоритмизации” для студентов направления 550200 “Автоматизация и управление”.
Составитель Н.М. Семёнов Рецензент к.т.н., доцент Е.И. Громаков
Методические указания рассмотрены и рекомендованы методическим семинаром кафедры интегрированных компьютерных систем управления “___” ___________ 2002 г. Зав. кафедрой
А.М. Малышенко
ОГЛАВЛЕНИЕ 1 Введение в методы ................................................................................. 1.1 Методы-функции и методы-процедуры ..................................... 1.2 Конструкторы ............................................................................. 1.3 Деструкторы ................................................................................ 1.4 Классовые процедуры и функции .............................................. 1.5 Реализация методов .................................................................... 1.5.1 Скрытый параметр Self ................................................ 1.5.2 Реализация методов-процедур ..................................... 1.5.3 Реализация методов-функций ...................................... 1.5.4 Реализация конструкторов .......................................... 1.5.5 Наследование конструкторов ...................................... 1.5.6 Реализация деструкторов ............................................. 1.5.7 Наследование деструкторов ........................................ 1.5.8 Необходимость использования деструкторов.............. 1.5.9 Реализация классовых методов .................................... 2 Вызов методов ........................................................................................ 2.1 Вызов конструктора .................................................................... 2.2 Вызов методов объектов ............................................................ 2.3 Вызов деструкторов .................................................................... 2.4 Вызов классовых методов .......................................................... 2.5 Методы для доступа к полям ..................................................... 3 Видимость элементов объекта ............................................................... 4 Использование методов .......................................................................... 5 Контрольные вопросы ............................................................................ 6 Задание ..................................................................................................... 7 Литература ...............................................................................................
4 5 6 7 8 9 9 10 11 12 13 17 18 18 19 20 20 21 21 22 24 24 26 26 27 27
МЕТОДЫ В OBJECT PASCAL Цель лабораторной работы состоит в знакомстве студентов с началами объектноориентированного программирования, в частности, с методами. В процессе выполнения работы студенты узнают: - как объявлять методы, в том числе конструкторы и деструкторы; - как выполнять реализацию методов; - как использовать методы; - что из себя представляют методы класса; - как “прятать” и защищать элементы класса.
1 Введение в методы Объект включает в себя данные и код. Код, в виде методов (т.е. подпрограмм, присоединенных к объекту), задает поведение объекта. Набор всех методов данного класса определяет операции, которые могут быть выполнены над представителем этого класса. Оба термина -операции и методы - по существу обозначают одно и то же. И тот, и другой относятся к концепции действия и к процессу выполнения инструкций в программе. Метод - это подпрограмма, которая определена как часть класса и включена внутрь этого класса. Именно возможность включать в себя и данные, и методы делает объекты столь мощными, и именно этим они отличаются от простых записей. В Object Pascal есть два вида самостоятельных программ: процедуры и функции. Они аналогичны процедурам - методам и функциям - методам соответственно. Для объектов также определяются и дополнительные категории методов . Всего существует шесть разновидностей методов объектов: 1) Методы - процедуры. Методы - процедуры аналогичны самостоятельным процедурам, за исключением того, что они “присоединены” к тому классу, в котором заданы, и могут быть вызваны лишь через какого-либо действительного представителя этого класса. 2) Методы - функции. Методы - функции возвращают значения и ведут себя также, как и обычные самостоятельные функции, с той разницей, что, как и все методы, они “присоединены” к тому классу, в котором заданы, и должны вызываться через действительного представителя этого класса. 3) Классовые процедуры. Классовые процедуры концептуально даже более близки к обычным самостоятельным процедурам, чем методы - процедуры. Для вызова классовых процедур не требуется экземпляр объекта. Эти процедуры объявляются как часть класса и вызываются с использованием ссылки на сам класс, а не на представителя этого класса. 4) Классовые функции. При использовании классовых функций также не требуется представитель класса. Эти функции вызываются посредством ссылки на сам класс. 5) Конструкторы. Конструкторы - это специальные методы, ведущие себя аналогично классовым функциям. Они вызываются с помощью ссылки на класс, в котором заданы, и возвращают значение. Возвращаемое конструктором класса значение является ссылкой на
4
вновь созданного представителя этого класса. Таким образом и создаются экземпляры объектов - для получения нового экземпляра надо вызвать конструктор. 6) Деструкторы. Деструкторы также являются специальными методами объектов. Они похожи на методы-процедуры и вызываются в точности также. Деструкторы отвечают за уничтожение экземпляра объекта, когда в этом есть необходимость. Деструкторы - это методы объектов, поэтому для их вызова, как и для вызова методов-процедур и методов-функций, требуется использовать представителя класса. 1.1 Методы - функции и методы - процедуры Методы-процедуры и методы-функции объявляются так же, как и обычные процедуры и функции, с той разницей, что делается не в блоке объявлений языка Pascal, а в описании класса. Заголовок метода выглядит так же, как и заголовок самостоятельной подпрограммы. Заголовок метода-процедуры имеет следующий синтаксис: type Имя Класса = class (Имя Родительского Класса) . . . procedure ИмяМетода (<Список параметров>); . . . end; Примеры: type TDateList = class (TPersistent) . . . procedure EndUpDate; procedure LoadFromFile (const FileName: String); procedure SaveToFile (const FileName: String); . . . end; Общий синтаксис объявления метода-функции выглядит так: type ИмяКласса = class (ИмяРодителя) . . . function ИмяМетода (<Список параметров>): ВозвращаемыйТип; . . . end; Примеры: type TClientWindow = class(TWindow) . . . function Hide; function GetRect: Trect; function GetProperty(const Aname: String): Thandle; 5
. . . end; Объявления методов-процедур могут быть перемешаны с объявлениями методов-функций и методов других видов (например, конструкторов). Объявления элементов класса могут быть сгруппированы в разделы (например, public). Единственное требование - чтобы в пределах каждого раздела в описании класса все поля появлялись до объявления методов. Рассмотрим пример почти полного объявления класса, включающего в себя методы функции, методы-процедуры и поля. Цель этого примера - показать, как объявляются обычные методы, поэтому среди объявленных в классе методов отсутствуют конструктор и деструктор. type TBook = class FAuthor : TAuthor; FTitle : String; function GetAuthor : TAuthor; procedure SetAuthor(const AnAuthor : TAuthor); function GetTitle : String; procedure SetTitle(ATitle : String); end; В объявлении класса TBook присутствуют два поля данных - FAuthor и FTitle, а также несколько методов - процедур и методов - функций. 1.2 Конструкторы Конструкторы объявляются почти так же, как и другие методы, только вместо слов procedure или function ставится зарезервированное слово constructor. Конструктор - специальный вид подпрограммы, присоединенной к классу. Его роль создавать представителей класса. Он ведет себя как функция, которая возвращает ссылку на вновь созданный экземпляр объекта. Общий синтаксис для объявления конструктора объекта следующий: type ИмяКласса = class(ИмяРодителя) . . . constructor Имя(<Список параметров>); . . . end; Примеры: TClientWindow = class(TWindow) ... constructor Create(AnOwner: TWindow); constructor CreateOffDesktor; ... end;
6
В классе может определяться несколько конструкторов. Однако, у класса обычно бывает только один конструктор. Общепринятое имя для единственного конструктора - Сreate. Объявление конструктора начинается с зарезервированного слова constructor, за которым следует тот же набор элементов - название, необязательный список параметров и точка с запятой - что и в случае объявления процедуры. Хотя в объявлениях конструкторов и не указывается тип возвращаемого значения и они выглядят, как объявления процедур, конструкторы используются скорее как функции, а не как процедуры. Можно сказать, что конструктор является неявной функцией - он возвращает нового представителя того класса, который использовался при его вызове. Нет необходимости явно задавать конструктору тип возвращаемого значения, поскольку этот тип и так известен в момент его вызова - это тип использованного в вызове конструктора класса. 1.3 Деструкторы Деструкторы также объявляются аналогично процедурам. Деструктор - это специальная разновидность подпрограммы, присоединенной к классу. Его роль заключается в уничтожении представителя класса. Синтаксис объявления деструктора следующий: type ИмяКласса = class(ИмяРодителя) ... destructor Имя(<Список параметров >); ... end; Примеры: type TClientWindow = class(TWindow) ... destructor Destroy; destructor DestroyAndNotify(Receipient: TManager); ... end; В одном классе может объявляться несколько деструкторов. Обычно класс имеет только один деструктор без параметров с именем Destroy. Объявление деструктора начинается с зарезервированного слова destructor, за которым следуют те же элементы, что и в случае объявления процедуры - имя, необязательный список параметров и точка с запятой. Рассмотрим в качестве примера объявления конструкторов и деструкторов в полном описании класса TBook. type TBook=class FAuthor : TAuthor; FTitle : String; constructor Create; destructor Destroy; override; 7
function GetAuthor : TAuthor; procedure SetAuthor (const AnAuthor : TAuthor); function GetTitle : String; procedure SetTitle (ATitle : String); end; Теперь в классе TBook объявляются конструктор Create и деструктор Destroy, которые могут использоваться соответственно для создания и уничтожения представителей этого класса. Обратите внимание на ключевое слово ”override”, стоящее после объявления деструктора Destroy. Это слово нужно указывать всякий раз, когда объявляется деструктор с названием Destroy. Это слово разрешает выполнение предусмотренных по умолчанию действий для уничтожения экземпляра объекта в том случае, если при его создании возникает какая-либо ошибка. Для начала деструктор Destroy всегда объявляйте как не имеющий параметров, а за его объявлением всегда должно следовать ключевое слово override: destructor Destroy ; override; 1.4 Классовые процедуры и функции В языке Object Pascal можно объявлять классовые методы. Наиболее существенное отличие обычных методов класса от классовых методов заключается в том, что для вызова классового метода не требуется действительный представитель класса. Классовые методы объявляются точно так же, как и обычные методы, но только перед их объявлением ставится зарезервированное слово class: type ИмяКласса = class (ИмяРодителя) ... class procedure ИмяМетода(<Список параметров>); ... end; Рассмотрим пример: type TDataList = class(TPersistent) ... class procedure FreeAll; class procedure SetDelimiter(ASymbol : Char); ... end; Общий синтаксис объявления классовой функции следующий: type ИмяКласса = class (ИмяРодителя) ... class function ИмяМетода(<Список параметров>): Возвращаемый Тип; 8
... end; Примеры: type TClientWindow = class (TWindow) ... class function NumInstances; class function DefaultCaption : String; class function DefaultWidth : Integer; ... end; В классе можно одновременно объявлять несколько классовых методов - как классовых процедур, так и классовых функций. В обоих случаях основная часть объявления классовых методов должна соответствовать тем же правилам, что и для обычных методов - процедур и методов - функций. 1.5 Реализация методов После успешного объявления метода в определении класса надо реализовать его, то есть дать описание действий, которые должны быть выполнены представителем класса при вызове метода. 1.5.1 Скрытый параметр Self Между самостоятельными подпрограммами и методами существует существенное различие, заключающееся в способе доступа к данным представителя класса. Самостоятельные подпрограммы могут работать с элементами данных, которые переданы подпрограммам предпочтительно в виде параметров. Самостоятельные подпрограммы имеют доступ к любым данным, объявленным глобально в модуле или программе, в которых эти подпрограммы определяются, а также ко всем глобальным данным других модулей, перечисленных в списке uses. С другой стороны, реализациям обычных (то есть не являющихся классовыми) методов доступны не только все те данные, которые были бы доступны подпрограмме. Методы также имеют неявный доступ к полям того представителя класса, который был использован в вызове. Это выглядит так, как будто каждому методу при вызове передается дополнительный параметр, указывающий на использовавшийся при вызове экземпляр объекта, и внутри метода стоит неявный оператор with, заключающий в себя все тело метода. Несмотря не то, что этот дополнительный параметр не виден в определении метода, он неявно присутствует в методе и называется Self. Параметр Self имеет тот же тип, что и класс, в котором определен метод. Все тело метода заключено в неявный оператор with Self do, который предоставляет методу доступ к полям, свойствам и другим методам данного представителя класса. Неявный оператор with действует примерно следующим образом: begin with Self do begin . . . 9
{Код метода} . . . end; end; Внешний блок begin - end представляет собой основной блок метода. Внутри метода можно непосредственно ссылаться на поля объекта, не используя при этом никаких префиксов. Хотя и нет запрета на использование префикса Self при доступе к полям объекта внутри метода, обычно в этом нет необходимости. Внутри метода и так подразумевается использование префикса Self., поэтому можно просто напрямую ссылаться на поля объекта, как если бы это были локальные переменные. Однако изредка возникают ситуации, когда приходится явно использовать параметр Self. В частности, этот параметр может понадобиться для разрешения конфликтов идентификаторов внутри метода. Для того, чтобы разрешить конфликт имен с помощью параметра Self, например, чтобы одновременно работать и с локальной переменной, и с полем объекта, имена которых совпадают, надо поставить перед именем поля объекта префикс Self с точкой (например, Self.FTitle). Self - это предопределенный идентификатор внутри метода, поэтому, несмотря на то, что он не присутствует явно в определении метода, его можно использовать. Большая разница между обычными и классовыми методами состоит в том, что у последних отсутствует неявный параметр Self. Поскольку классовые методы можно вызвать, как используя экземпляр класса, так и ссылаясь непосредственно на сам класс ( то есть, не ссылаясь при этом на какого-либо конкретного его представителя), классовый метод не может иметь доступ к полям представителя класса. Внутри классового метода отсутствует неявный параметр Self, а значит, отсутствует и возможность использовать его поля. Поскольку в классовых методах отсутствует скрытый параметр Self, они по существу ничем не отличаются от самостоятельных подпрограмм. Классовые методы - это лишь удобная возможность указать, что метод логически относится и данному классу. 1.5.2 Реализация методов - процедур Обычные методы-процедуры реализуются таким же способом, что и самостоятельные процедуры. Надо задать последовательность выполняемых операторов, которые составляют тело процедуры. Общий синтаксис реализации метода-процедуры следующий: procedure ИмяКласса. ИмяПроцедуры (<Параметры>); <Необязательный блок объявлений> begin <Исполняемые операторы> end; Реализация метода-процедуры должна располагаться в разделе implementation модуля, либо в программе или библиотеке, до основного блока begin-end. В полном имени метода стоит префикс с названием класса. Этот префикс указывает, что определяется не самостоятельная процедура, а метод. 10
Как уже отмечалось, внутри метода имеется доступ не только к локальным и любым видимым внешним идентификаторам, но и к полям экземпляра объекта, инкапсулированным в определении класса. Например, метод-процедура SetTitle определенного ранее класса Tbook (см.стр. 7) могла бы выглядеть следующим образом: procedure TBook.SetTitle (ATitle : String); begin FTitle := ATitle; end; Поле объекта FTitle не определяется явным образом, как локальная переменная, но эквивалентно Self.FTitle то есть является полем активного (использованного при вызове) представителя класса TBook. 1.5.3
Реализация методов - функций
Реализация методов-функций также очень похожа на реализацию самостоятельных функций. Общий синтаксис реализации метода-функции следующий: function ИмяКласса.ИмяФункции(<Параметры>) : Возвращаемый Тип; <Необязательный блок объявлений> begin <Исполняемые операторы> Result := . . . <Исполняемые операторы> end; Рассмотрим пример: function TForm1.CalculateScore (RawValue : Integer) : Boolean begin Result := CalcEndine.Score (RawValue); end; Как и в случае метода-процедуры, реализация метода-функции должна располагаться в разделе implementation модуля, либо в программе или библиотеке, до основного блока begin-end. Реализация начинается с зарезервированного слова function, за которым следует полное имя метода. Полное имя состоит из названия класса, к которому принадлежит метод, и собственно имени метода. Остальная часть реализации подчиняется тем же правилам, что и реализация самостоятельных функций. Как и у методов-процедур, внутри метода-функции имеется доступ не только к локальным и любым видимым внешним идентификаторам, но и к полям объекта, инкапсулированным в определении класса. Например, метод-функция GetTitle определенного ранее класса TBook могла бы выглядеть так: function TBook.GetTitle : String; begin 11
Result := FTitle; end; В данном случае метод-функция GetTitle возвращает значение, хранящееся в поле FTitle активного объекта. 1.5.4 Реализация конструкторов Конструкторы - это специальные методы, в задачи которых входит не только выполнение операторов, содержащихся в их теле, но в первую очередь создание нового экземпляра объекта. Пользователю не надо заботиться о деталях того, как именно при исполнении программы в памяти будет создан новый представитель класса. К тому моменту, когда выполнение программы доходит до первого begin главного блока begin-end конструктора, представитель класса уже создан автоматически. Назначение кода внутри конструктора - инициализировать только что созданный экземпляр объекта. Общий синтаксис реализации конструктора следующий: constructor ИмяКласса.Имя(<Параметры>); <Необязательный блок объявлений> begin <Исполняемые операторы> end; Рассмотрим пример: constructor TBook.Create (ATitle : String); begin inherited Create; FTitle ;= ATitle; FAuthor := Nil; end; Реализация конструктора, как и реализация любых других методов, должна располагаться в разделе implementation модуля, либо в программе или библиотеке, до основного блока beginend. Основная часть реализации конструктора подчиняется тем же правилам, что и реализация самостоятельных процедур. Хотя при использовании конструктора от него получается значение (то есть он используется как функция), его реализация больше похожа на реализацию процедуры. Например, внутри конструктора отсутствует явное присваивание возвращаемого значения. Такое возможно, поскольку конструктор всегда возвращает ссылку на вновь созданный экземпляр объекта, в котором он определен, поэтому нет нужды специально присваивать какие-либо значения. Поскольку внутри конструктора, как и внутри других методов, имеется доступ ко всем полям только что созданного объекта, можно ( и следует) инициализировать поля нужными значениями. После того, как выполнение конструктора заканчивается, новый представитель класса считается полностью инициализированным и действительным, если только конструктор не завершил работу вызовом встроенной функции Fail, знакомство с которой еще только предстоит.
12
В конструкторе для каждого поля должен быть оператор присваивания. Все поля должны перейти из “неопределенного” состояния в какое-либо известное состояние, предусмотренное по умолчанию. Не следует оставлять ни одно поле нового экземпляра объекта неинициализированным. Конструктор должен выполнять инициализацию всех полей. 1.5.5 Наследование конструкторов Рассмотрим классовую иерархию наследования, построенную следующим образом: класс TBook - прямой поток базового класса TObject; класс TTechnicalBook является конкретной специализацией, или расширением, класса TBook (см. рис. 1).
TOject
TBook
TTechnicalBook
Рис. 1. Иерархия наследования классов книг Листинг 1 показывает одну из возможных реализаций классов книг.
Листинг 1. 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 13
Реализация классов книг
unit Book1; interface uses Authors, Editors; type TBook = class FAuthor: TAuthor; FTitle: String; constructor Create(AnAuthor: TAuthor; const ATitle: String); function GetAuthor: TAuthor; procedure SetAuthor (const AnAuthor: TAuthor); function GetTitle: String;
17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 14
procedure SetTitle(ATitle: String); end; TTechnicalBook = class(TBook) FTechEditor: Teditor; constructor Create(AnAuthor: TAuthor; const ATitle: String; AnEditor: TEditor); function GetTechEditor: TEditor; prucedure SetTechEditor(AnEditor: TEditor); end; implementation {TBook} constructor TBook.Create; begin inherited Create; FAuthor := AnAuthor; FTitle := ATitle; end; function TBook . GetAuthor; begin Result := FAuthor; end; procedure TBook . SetAuthor; begin FAuthor := AnAuthor; end; function TBook . GetTitle; begin Result := FTitle; end; procedure TBook . SetTitle; begin FTitle := ATitle; end; (TTechnicalBook)
67: constructor TTechnicalBook . Create; 68: begin 69: inherited Create (AnAuthor, ATitle); 70: FTechEditor := AnEditor; 71: end; 72: 73: 74: function TTechnicalBook . GetTechEditor; 75: begin 76: Result := FTechEditor; 77; end; 78: 79: 80: procedure TTechnicalBook . SetTechEditor; 81: begin 82: FTechEditor :=AnEditor; 83: end; 84: 85: end.
Модуль Books1, показанный в листинге 1, определяет два специализированных класса TBook и TTechnicalBook. У каждого представителя класса TBook есть два поля - FAuthor и FTitle, в которых содержатся автор и название конкретной книги. Поле данных FTitle - это просто переменная типа String. В то же время поле FAuthor является ссылкой на другой объект, содержащий информацию об авторе данной книги. FAuthor - это тип, определенный вне данного модуля. Подразумевается, что этот тип будет импортироваться из модуля Authors, указанного в списке uses в строке 6. Хотя определение класса TAuthor не показано, можно осмысленно выполнять определенные операции с представителями этого класса. В строке 13 объявляется конструктор Create класса TBook, а в строках с 67 по 71 приведена реализация конструктора. Конструктор отвечает за инициализацию вновь созданного представителя класса, невидимо и неявно переданного ему в параметре Self. Значения, которыми надо инициализировать два поля данных класса TBook-FTitle и FAuthor - передаются в формальных параметрах конструктора AuAuthor и ATitle. Инициализация выполняется в строках 36 и 37. В строке 35 вызывается наследуемый конструктор, который заботится об инициализации непосредственного предшественника класса TBook, в нашем случае - класса TObject. В строках с 14 по 17 приведены объявления, а в строках с 41 по 62 дана реализация простых методов для доступа к полям. Эти методы считывают и устанавливают значения полей объекта TBook. Второй класс, определенный в модуле листинга 1 - TTechnicalBook-расширяет понятие книги, добавляя к наследуемому определению ссылку на технического редактора. Расширенному классу требуется дополнительная инициализация, которая не выполняется в наследуемом конструкторе. По этой причине в 22 и 23 строках объявляется локальный конструктор, а в строках с 67 по 71 приводится его реализация. Конструктор TechnicalBook.Create заботится об инициализации поля FTechEditor, специфичного для класса TTechicalBook. Однако при инициализации унаследованных полей он пользуется услугами конструктора, наследуемого из класса TBook. 15
Остановимся на взаимоотношениях трех классов, определенных в листинге 1. У класса TBook уже есть конструктор по умолчанию Create. Можно задать вопрос:”Почему классу TBook нужен свой собственный конструктор, рассчитанный только на TBook, если одна из основных причин использования иерархии наследования - это стремление повторно использовать уже написанный код? Почему же нельзя использовать для инициализации представителя класса TBook уже написанный конструктор Create класса TObject?” Причина в том, что в классе TBook присутствуют данные, которые специфичны для этого класса и поэтому должны инициализироваться на его уровне. Иными словами, TBook, являющийся потомком класса TObject, должен определить свой конструктор для того, чтобы инициализировать собственные поля. Конструктор Create, унаследованный от класса TObject, ничего не знает о полях TBook, он лишь инициализирует то, что должно быть инициализировано на уровне класса TObject. В случае классов TBook и TTechnicalBook наблюдается очень похожая ситуация. Так как класс TTechnikalBook вводит новое поле FTechEditor, ему для инициализации этого поля требуется свой конструктор. Каким образом TTechnicalBook должен выполнять инициализацию полей, унаследованных от своего предка TBook? Именно здесь и проявляются преимущества объектноориентированного подхода - повторное использование кода и инкапсуляция. Классу TTechnicalBook не надо заботиться о деталях реализации TBook, так как об этих деталях уже позаботился конструктор Create, унаследованный от класса TBook. Вместо того, чтобы копировать уже имеющийся в TBook.Create код, его можно использовать повторно, вызвав наследуемый конструктор. Оператор inherited Create (AnAuthor, ATitle); в строке 69 листинга 1 делает именно это. Он перекладывает ответственность за инициализацию полей, принадлежащих объекту - предшественнику, на конструктор этого предшественника. Резюме. Надо определять конструктор для каждого класса, в котором вводятся новые поля данных. Типичная структура конструктора: constructor Класс.ИмяМетода (<Параметры>); <Блок локальных объявлений> begin inherited ИмяМетода (<Параметры>); <Инициализация специфических полей> end; Пример: constructor TBook.Create(AuAuthor: TAuthor; ATitle: String); begin inherited Create; FAuthor := AuAuthor; FTitle := ATitle; end; Как правило, вызывать подходящий наследуемый конструктор нужно в первом же выполняемом операторе блока begin - end конструктора.
16
Если класс не добавляет к полям предшественника никаких новых полей данных, то необходимость в определении нового конструктора может и не возникнуть. 1.5.6 Реализация деструкторов Деструкторы - еще одна группа специальных методов. Их задачи прямо противоположны задачам конструкторов. Деструктор уничтожает экземпляр объекта, который использован при его вызове, автоматически освобождая любую динамическую память, которая была ранее зарезервирована в конструкторе. Деструктор вызывается тогда, когда работа с данным представителем класса закончена. Пользователь также должен обеспечить вызов деструкторов для всех экземпляров объектов, которые содержит в себе уничтожаемый экземпляр. Другими словами, если пользователь зарезервировал в конструкторе подчиненный экземпляр объекта, то пользователь же отвечает за то, чтобы при вызове деструктора верхнего уровня были также уничтожены и все экземпляры объектов, содержащиеся в экземпляре верхнего уровня. Общий синтаксис реализации деструктора следующий: destructor ИмяКласса.ИмяДеструктора (<Параметры>); <Необязательный блок объявлений> begin <Исполняемые операторы> end; Рассмотрим пример: destructor TBook.Destroy; begin FTitle:=‘ ‘. inherited Destroy; end; Реализация деструктора также должна располагаться в разделе implementation модуля, либо в программе или библиотеке, до основного блока begin-end. После того, как деструктор заканчивает работу, экземпляр, через который он был вызван, перестает существовать. Попытка использовать этот экземпляр приведет к ошибке. 1.5.7 Наследование деструкторов Аналогично конструкторам можно использовать код уже существующих в иерархии наследования деструкторов. Сеть наследования позволяет уничтожать только те поля, которые класс добавил к полям, унаследованным от классов-предшественников. Всю работу, связанную с очисткой наследуемых полей, новый класс передает наследуемым деструкторам. Типичная структура деструктора: destructor Класс.ИмяДеструктора (<Параметры>); <Блок локальных объявлений> begin <Уничтожение собственных полей> inherited ИмяДеструктора (<Параметры>); end; 17
Приведем пример: destructor TBook.Destroy; begin FAuthor.Destroy; FTitle.Destroy; inherited Destroy; end; Для вызова наследуемого деструктора необходимо использовать ключевое слово inherited и после него имя деструктора. Как правило, подходящий наследуемый деструктор надо вызывать в последнем исполняемом операторе блока begin-end деструктора. Вместо использования ключевого слова inherited можно вызвать наследуемый деструктор, указав его полное имя (то есть название класса-предшественника, точку и название наследуемого деструктора). Только в очень редких случаях деструктору бывают нужны параметры. Параметр, передаваемый деструктору, подразумевает, что требуется сделать нечто большее, нежели простое уничтожение экземпляра объекта, и что помимо уничтожения будет проделана некоторая дополнительная обработка, которой требуется этот параметр. 1.5.8
Необходимость использования деструктора
Простое правило хорошего тона таково: если вы создали конструктор для класса, то скорее всего вам в этом классе потребуется и деструктор. В частности, если конструктор резервирует какие-то ресурсы или в процессе работы создает другие экземпляры объектов, то обычно деструктор должен освободить ресурсы и уничтожить созданные экземпляры. Исключение из этого правила встречается внутри компонентов, когда создается другой, подчиненный, компонент и указывается, что текущий экземпляр объекта является его владельцем. Подчиненный компонент, созданный таким образом, при необходимости уничтожается автоматически. Другое исключение из правила, согласно которому при наличии конструктора нужно определять и деструктор - ситуация, когда у класса нет никаких косвенных полей данных. Разница между встроенным (прямым) и ссылочным (косвенным) полем заключается в том, что встроенные поля непосредственно содержат значения атрибута данных, в то время как ссылочные поля содержат ссылку на какой-нибудь другой объект. Значения встроенным полям присваиваются непосредственно. Значения ссылочных полей должны присваиваться посредством доступа к экземпляру объекта, на который указывает ссылка. В случае, когда объект не создает и не включает в себя никаких подчиненных объектов, деструктор может оказаться ненужным. В такой ситуации, даже если в определении класса были добавлены новые поля, класс не отвечает за их уничтожение, так что о них можно не заботиться. 1.5.9 Реализация классовых методов При реализации классового метода надо придерживаться тех же правил, что и для обычных методов, за исключением того, что заголовок реализации должен начинаться с зарезервированного слова class. Надо помнить, что внутри классового метода нет доступа к полям объекта даже в том случае, когда для вызова этого метода используется действительный представитель класса.
18
Попытка сослаться на какое-либо поле объекта приведет к синтаксической ошибке при трансляции. Другими словами, с реализацией классового метода надо обращаться так, как будто пишется самостоятельная подпрограмма. Общий синтаксис реализации классовой процедуры: class procedure ИмяКласса.ИмяПроцедуры (<Параметры>); <Необязательный блок объявлений> begin <Выполняемые операторы> end; Приведем пример: class procedure TParser.SetDelimeter (const ASymbol: String); begin ParserDelimeter:=ASymbol; end; Реализация классовой процедуры должна располагаться в разделе implementation модуля, либо в программе или библиотеке, до основного блока begin-end. Кроме использования зарезервированного слова class, в остальном реализация выглядит так же, как и реализация обычного метода. Обычный синтаксис реализации классовой функции: class function ИмяКласса.ИмяФункции(<Параметры>): ТипРезультата; <Необязательный блок объявлений> begin <Выполняемые операторы> Result := . . . <Выполняемые операторы> end; Пример: class function TForm1.NumInstances:Integer; begin Result:=GlobalInstanceCount; {GlobalInstanceCount - это глобальная переменная модуля} end; Внутри реализации доступ к полям представителя класса отсутствует, но можно использовать любые данные, которые были бы доступны в этом же контексте самостоятельной функции. Как всегда, в функции должен быть хотя бы один оператор, присваивающий ей либо предопределенной переменной Result возвращаемое значение.
2 Вызов методов
19
Поскольку обычные методы присоединены к определенным классам, они не могут просто быть вызваны тем же способом, что и самостоятельные подпрограммы. Они могут быть активированы только с помощью представителя того класса, в котором они определяются. С другой стороны, конструкторам и классовым методам для работы не требуется действительный представитель класса. Когда вызывается конструктор, то вызов делается с помощью ссылки на сам класс, а не на представителя этого класса. Аналогично, в случае классовых методов также можно использовать ссылку на класс для их вызова. Однако классовые методы можно вызвать и через представителя класса, таким же способом, что и в случае обычных методов. Фундаментальное различие методов и обычных подпрограмм заключается в том, что метод доступен только через представителя класса, в котором он определяется. Кроме того, методы рассматриваются как код, встроенный в контекст их класса, и поэтому у них во время работы есть доступ к полям экземпляра объекта, использованного при вызове. Обычные методы вызываются так, как будто в их списке параметров присутствует дополнительный параметр Self, который во время выполнения связывает код метода с конкретным представителем класса. Любые поля этого дополнительного параметра Self автоматически доступны внутри метода. 2.1 Вызов конструктора Если в некотором классе не определен конструктор, то при вызове надо использовать конструктор, унаследованный от класса-предшественника. В любом случае у всех объектов есть доступ к конструктору Create, определенному в классе TObject. Общий синтаксис вызова конструктора следующий: var AnInstance:ИмяКласса; begin ... AnInstance:=ИмяКласса.ИмяКонструктора(<Параметры>); ... end; Например: var FormInstance:TForm1; begin FormInstance:=TForm1.Create(Self); end; В отличие от случая большинства других методов конструктор вызывается при помощи ссылки на класс объекта. Поскольку представителя этого класса еще не существует, то нет возможности на него сослаться. Поэтому мы просим сам класс создать для нас экземпляр объекта.
20
Определение класса создает активную структуру, способную создавать представителей этого класса. Переменные, использующие при объявлении в качестве типа класс, способны хранить ссылки на вновь создаваемых представителей класса. Поэтому классовый тип в Object Pascal - нечто большее, чем статическое описание, или рецепт, для создания экземпляров объектов. Класс - это активная структура, существующая в программе и способная выполнять определенные действия. 2.2 Вызов методов объектов После того, как представитель класса создан, можно вызвать любые операции, определенные для этого класса объектов, то есть любые его методы. Синтаксис для вызова, или активации, метода следующий: ИмяЭкземпляра.ИмяМетода(<Параметры>); ИмяМетода - это действительная ссылочная переменная, то есть, переменная, которая была предварительно инициализирована посредством вызова конструктора. Примеры: Form1.Create; Button1.Free; Для доступа к методу надо указать действительного представителя класса и соответствующее имя метода, разделенные точкой. Префикс имя экземпляра - плюс - точка создает полностью квалифицированное имя метода. Вместо полностью квалифицированных имен можно воспользоваться оператором with для получения прямого доступа к методам, аналогично тому, как используется оператор with для доступа к полям. 2.3 Вызов деструкторов Деструкторы - это методы, предназначенные для очистки и уничтожения экземпляров объектов. Деструкторы вызываются точно так же, как и большинство других методов класса - через его действительного представителя. Синтаксис для вызова деструктора: ИмяЭкземпляра.ИмяДеструктора(<Параметры>); Пример: Button1.Destroy; После вызова деструктора переменная экземпляра становится недействительной, неопределенной. Единственная операция, которую можно после этого с ней проделать - вызов конструктора для получения нового представителя класса. Поэтому рекомендуется явно присваивать переменной значение Nil сразу же после того, как ее экземпляр объекта уничтожен. Тогда можно впоследствии определить, что она не содержит действительного экземпляра объекта и , если понадобится, вновь его создать. 2.4 Вызов классовых методов 21
Классовые методы могут быть вызваны двумя способами: - через представителей класса - точно так же, как и обычные методы; - с использованием непосредственно имени класса - при этом не требуется ссылаться на какого-либо представителя этого класса. Для вызова классовой процедуры используется синтаксис: begin ... ИмяЭкземпляра.ИмяКлассовогоМетода(<Параметры>); ... end; или begin ... ИмяКласса.ИмяКлассовогоМетода(<Параметры>); ... end; Примеры: type TDatabase = class class procedure SetDateDelimiter(AChar: Char); end; ... class procedure TDatabase.SetDateDelimiter; begin ... end; ... var TheDatabase : TDatabase; begin ... TheDatabase.SetDateDelimiter( ‘/ ‘); TDatabase.SetDateDelimiter( ‘ - ‘); ... end; Синтаксис оператора присваивания, содержащего вызов классовой функции: begin ... Результат:=ИмяЭкземпляра.ИмяКлассовогоМетода(<Параметры>); ... end;
22
или begin ... Результат := ИмяКласса.ИмяКлассовогоМетода(<Параметры>); ... end; Так же, как и обычные, классовые функции могут использоваться внутри выражений. Пример: type TDatabase=class class function InstanceCount : Integer; end; ... class function TDatabase.InstanceCount; begin ... Result := . . . ... end; ... var TheDatabase: TDatabase; NumInstances: Integer; begin ... NumInstances:=TheDatabase.InstanceCount; NumInstances:=TDatabase.InstanceCount; ... end; Как видно из примеров, классовые методы могут быть вызваны не только через представителя класса, но также и непосредственно через сам тип класса. 2.5 Методы для доступа к полям Хотя с помощью оператора with можно получить прямой доступ к полям объекта, использование такого подхода не рекомендуется. Одно из больших преимуществ ООП - это инкапсуляция и ее поддержка для скрытия информации. Если работать с полями напрямую или даже просто показывать их, то в результате детали реализации объекта становятся известными и могут взаимодействовать с внешним миром, что в значительной мере губит преимущества инкапсуляции. Каждый раз, когда понадобится модифицировать или оптимизировать реализацию такого объекта, ваши действия будут сказываться на его возможных пользователях. По этой причине не стоит представлять прямого доступа ко внутреннему предоставлению объекта и надо защищать его пользователей от ненужных им деталей реализации.
23
Гораздо лучший подход - написать метод для доступа к полям объекта, что позволит добиться настоящей инкапсуляции и исключит возможность прямой модификации данных объекта. В этом случае вместо доступа к полю объекта вызывается соответствующий метод. У такого подхода много преимуществ, например: 1) Надежность данных. Можно предотвратить некорректные изменения данных, выполнив в методе дополнительную проверку значения на допустимость. Тем самым повышается гарантия того, что экземпляр объекта будет всегда находиться в “хорошем” состоянии. 2) Целостность ссылок. Перед доступом к объекту, связанному с данным объектом, можно удостовериться, что косвенное поле содержит корректное значение. Более того, если обнаружится некорректное значение (например, если экземпляр объекта, на который указывает ссылка, больше не существует), то можно инициировать действия по исправлению положения, например, заново создать связанный экземпляр объекта. 3) Предусмотренные побочные эффекты. Можно гарантировать, что каждый раз, когда выполняется обращение к полю объекта, синхронно с обращением выполняется какое-либо специальное действие. Например, можно прослеживать изменения в объекте или подсчитывать число обращений к нему для целей сбора статистики. 4) Сокрытие информации. Когда доступ к данным осуществляется только через методы, можно скрыть детали реализации объекта. Позднее, если реализация изменится, придется изменить лишь реализацию методов доступа к полям. Те же части программы, которые использовали задействованный класс, не будут затронуты.
3 Видимость элементов объекта До сих пор предполагалось, что все поля и методы какого-либо объекта одинаково видимы и доступны при условии, что для доступа к ним используется ссылка на действительного представителя класса. Если в случае переменных типа record (запись) это действительно так, то в случае объектов такое допущение относительно видимости элементов не всегда приемлемо. Техника сокрытия информации - одна из базовых концепций ООП. Object Pascal определяет несколько стандартных ключевых слов, называемых директивами , которые изменяют видимость элементов, объявленных внутри интерфейса класса. Определение класса может быть разделено на произвольное количество разделов, каждый из которых начинается с одного из ключевых слов, известных как стандартные директивы: private, protected, public и published. Синтаксис для разделения определения класса на разделы видимости: type ИмяКласса = class(Родитель) <объявление поля или метода> ... <объявление поля или метода> <директива> <объявление поля или метода> ... <объявление поля или метода> ... <директива> <объявление поля или метода> ... <объявление поля или метода>
24
... end; Пример: type TColorRectandgle=class(TObject) private Left,Top,Right,Bottom:Integer; Color: TColor; protected function GetLeft : Integer; function GetTop : Integer; function GetRight :Integer; function GetColor : TColor; public constructor Create; destructor Destroy; end; Существуют четыре стандартных ключевых слова - директивы, относящиеся к видимости элементов интерфейса класса: 1) private (частный) Используется для обозначения высшего уровня ограничения видимости элемента в определении класса. Элементы интерфейса класса, объявленные как private, видны только в пределах модуля, в котором определяется данный класс. Вне этого модуля private - элементы интерфейса класса не видны и недоступны. Однако, если в одном модуле определяется несколько классов, они “видят” private - разделы интерфейса друг - друга, как если бы те были объявлены public. 2) protected (защищенный) Обозначает частично ограниченную видимость элемента в определении класса. Элемент из этого раздела виден только внутри потомков этого класса. Получить доступ к защищенному элементу из кода, не принадлежащего “защищенному” классу, невозможно. Иными словами, вне методов класса, объявившего элемент защищенным, этот элемент не виден. Однако потом он может быть объявлен и реализован отдельно, и защищенные элементы предшественника будут видны внутри его методов. 3) public (публичный) Обозначает полную видимость. Элементы класса видны в любой точке, в которой виден сам представитель класса. Всегда можно обратиться к public - элементам интерфейса класса как из методов этого класса, так и из кода, не относящегося к нему 4) published (опубликованный) Эта директива обеспечивает еще более широкий уровень доступа, чем директива public. По умолчанию, если не задана никакая директива, элемент в объявлении класса рассматривается, как published. Первый раздел определения класса - до появления первой директивы видимости - по умолчанию является published - разделом.
4 Использование методов
25
Так как метод является частью объявления класса, то доступ к методу должен осуществляться с помощью той же нотации (точка либо ключевое слово wiht), что и в случае полей объекта. Для вызова метода объекта надо иметь доступ к экземпляру этого объекта. Тогда можно вызывать методы этого конкретного экземпляра, указав перед названием метода имя переменной-экземпляра и точку (например, Form1.Close или ListBox1.Clear). Из одного метода можно вызывать другие методы класса, как наследуемые, так и определенные на этом же уровне. Конкретный класс ничего не “знает” о своих потенциальных потомках. Базовый класс всех этих потомков может быть определен и оттранслирован задолго до создания классовпотомков. Когда делается попытка использовать метод какого-либо объекта, транслятор просматривает определение соответствующего класса с целью найти там реализацию нужного метода. Если в классе определен вызванный метод, то он и будет вызван во время выполнения программы. Если же метод с указанным именем не задан в данном классе, то транслятор будет искать реализацию этого метода в непосредственном предшественнике этого класса, в случае неудачи в следующем классе- предшественнике и так далее (до класса самого верхнего уровня - TObject). Если транслятор пройдет весь путь вверх до класса-предшественника самого верхнего уровня и при этом не найдет определения указанного метода, то будет выдано сообщение “Unknown identifier” (неизвестный идентификатор).
5 Контрольные вопросы 1. Правильно ли утверждение: “Конструкторы могут использоваться в выражениях в качестве функций, возвращающих результат”? 2. В каком случае в описании класса обязательно должен быть описан и конструктор? 3. Каково назначение деструктора? 4. Для чего деструктору могут передаваться параметры?
6 Задание 1. Завершите класс TDate, который был определен в лабораторной работе, выполненной по методическим указаниям к лабораторной работе №134. Завершение предполагает включение конструкторов и деструкторов, объявленных в интерфейсе класса. Напомним исходное задание: “Определите структуру данных для класса TDate, хранящего дату. Определите интерфейсные методы, которые возвращают год, месяц и день по отдельности. Также определите методы доступа, которые могут устанавливать как некоторые, так и все сразу элементы экземпляра класса. Всю работу попробуйте пропустить через транслятор “Objct Pascal.” Добейтесь успешной трансляции модифицированного класса. 2. В отдельном модуле Customer дайте полную реализацию класса TCustomer, который был рассмотрен в лабораторной работе №134 (см. стр. 15 методических указаний к лабораторной работе №134). Убедитесь, что ваш модуль транслируется. 3. Завершите определение класса TInvoice, начатое в лабораторной работе 134, путем выполнения его реализации и дальнейшей трансляции.
7 Литература 26
1. Возневич Э. Delphi. Освой самостоятельно. - М.: Восточная книжная компания, 1996. - 736с. 2. Рубенкинг Н. Программирование в Delphi для “чайников”. - К.: Диалектика, 1996. - 304с. 3. Фаронов В. Delphi 6: учебный курс. – СПб.: Питер, 2002. – 512 с.
МЕТОДЫ В OBJECT PASCAL Методические указания Составитель Николай Михайлович Семенов
Подписано к печати Формат 60*84/16.Бумага писчая № 2. Плоская печать. Усл.печ.л. . Уч. - изд.л. Тираж экз. Заказ № . Бесплатно. Ротапринт ТПУ. 634034, Томск, пр. Ленина, 30.
27