![]() |
![]() |
Altair |
![]()
Сообщение
#1
|
![]() Ищущий истину ![]() ![]() ![]() ![]() ![]() ![]() Группа: Модераторы Сообщений: 4 824 Пол: Мужской Реальное имя: Олег Репутация: ![]() ![]() ![]() |
Один из подходов в программировании носит название: "Объектно-ориентированное программирование" или, сокращенно, ООП. Идея этого подхода заключается в стремлении связать данные с обрабатывающими эти данные процедурами в единое целое - ОБЪЕКТ.
В Турбо Паскале ООП реализовано начиная с версии 5.5. ООП основано на трех важных принципах, придающих объектам особые свойства и отличающих их от других типов данных. Рассмотрим их:
Сначала может показаться, что при описании нет разницы между объектами и записями. На деле разница заключается в том, что при описании объекта в него могут входить заголовки процедур и функций, ведь по определению объект это "Данные+Алгоритмы". Перефразируя Вирта, можно сказать что: "Алгоритмы+Структуры Данных=Объекты". Выглядит описание объекта так: type Переменные, описанные в объекте называются полями, а процедуры их обработки - методами. В самом типе объекта описан лишь интерфейс метода (так же, как при написании модуля в разделе INTERFACE мы описываем только заголовки), т.е. способ его вызова. Сам метод описывается в разделе процедур и функций, и заголовок имеет нестандартную форму (перед его именем обязательно указывается имя типа объекта, к которому этот метод принадлежит): procedure ObjectType.ProcedureName(...); Т.е <имя_объекта>.<имя_процедуры> Параметры описываются как обычно, за исключением того, что внутри метода всегда доступны поля объекта непосредственно по их именам. Например: type Так как задание заголовка метода в описании типа объекта является опережающим описанием, то так же, как при реализации процедур и функций в разделе IMPLEMENTATION, список параметров может опускаться, то есть, такое описание будет полностью аналогично предыдущему: procedure TA.give; Переменные типа "объект" можно создавать как обычно, объявлением в списке переменных (при таком способе для использования экземпляров объектов - переменных объектного типа - в программе, их вызывают так: <имя_объекта>.<имя_метода>): type TA = object ... end; но большее распространение получил метод их размещения в динамической памяти. Для этого нужно создать дополнительный тип - указатель на объект (в таком случае для обращения к полям или методам объекта указатель надо разыменовать): type Наследование При описании объекта-наследника (также называемого производным типом), имя его родителя указывается в скобках. Например: TA = Поля и методы родителя могут появляться в реализации методов наследника, как если бы они были описаны явно в самом наследнике. Процесс наследования является транзитивным: если TB наследуется от TA, а TC в свою очередь - наследник TB, то тип TC также считается наследником TA. Следует обратить внимание на то, что при наследовании полей в производном типе нельзя объявлять идентификаторы, которые уже использовались в одном их типов-родителей. (На методы это ограничение не распространяется, в случае повторного определения метода он считается переопределенным, и ко всем потомкам будет переходить именно переопределенный метод.) Из "умения" объектов "наследовать" вытекает правило присваивания для переменных типа "Объект": переменным такого типа можно присваивать не только значения этого же типа, но и значения любого производного типа. Например, при таком определении: type для копирования значения X записанного в переменной b в переменную a достаточно выполнить присваивание: a := b; Внимание: Операция присваивания возможна только таким путем: "Родитель <-- Наследник" При этом гарантируется заполнение всех полей типа "Родитель" содержимым соответствующих полей типа "Наследник", так как в "Наследнике" число полей не меньше, чем в родителе. В противном случае могла бы возникнуть неопределенность с "лишними" полями, которых нет в "Родителе". Операцией присваивания копируются только поля. Методы таким образом не присваиваются... Использование объектов Посмотрим теперь на практике, как работать с объектами. uses crt; Итак, рассмотрим эту программу. Это первая наша программа с объектами. В ней есть 2 объекта - родитель Т1 и потомок Т2. Я специально сделал так, чтобы в них были методы (процедуры) с одинаковыми именами. Обратите внимание, что мы не описывали в Т2 процедуру Vec, однако, при вызове: c.Vec метод заработал. Это произошло, потому что объект T2 является потомком Т1, и он знает все методы и данные своего родителя Т1. И наоборот Т1 не знает, что есть процедура Summ. На этом примере мы также убедились, что при перекрытии работает тот метод, который был описан позднее (т.е. метод потомка, а не родителя). То есть при вызове C.Print пошла запись в файл, а не на экран монитора, как есть в методе родителя. Таким образом, при наследовании объект-потомок наследует все методы родителя и перекрывает одноименные. |
![]() ![]() |
Altair |
![]()
Сообщение
#2
|
![]() Ищущий истину ![]() ![]() ![]() ![]() ![]() ![]() Группа: Модераторы Сообщений: 4 824 Пол: Мужской Реальное имя: Олег Репутация: ![]() ![]() ![]() |
Виртуальные методы
Из правил совместимости фактических и формальных параметров типа Object следует, что в качестве фактического параметра может выступать объект любого производного от формального параметра типа. Таким образом, во время компиляции процедуры неизвестно, объект какого типа будет ей передан как фактический параметр. В полной мере полиморфизм достигается не только механизмом наследования и перекрытия методов родителя, но и виртуализацией, позволяющей родительским методам обращаться к методам потомков. Метод становится виртуальным, когда за его определением в типе объекта ставится зарезервированное слово Virtual. В следующем коротком коде, реализована виртуализация метода. uses crt; Запустим ее, и увидим, что на экране окажется "5". Проследим выполнение программы... При вызове процедуры B.INIT (почему называется конструктор, чуть дальше), запускается метод из объекта потомка _B. При этом этот потомок знает и умеет все, что знал и умел его предок. Значит объект _B "знает" о методе RUN. Так и есть запускаем его следующей строчкой: B.run; Проследим выполнение этого метода. Этот метод, в свою очередь запускает метод P. Теперь смотрим метод P: Procedure _A.P; begin end; Вопрос: откуда на экране появилась пятерка. Ответ: метод P был объявлен виртуальным: procedure P; virtual; Если метод объявляется виртуальным, это значит, что объект-родитель сможет использовать метод объекта-потомка (!). Это очень важное правило. Чтобы убедиться, что это так, просто закомментируйте Virtual в коде (в 2 методах) и запустите новый код; на экране естественно окажется "0", т.к. родитель не может обращаться к методу потомка (т. к. этот метод НЕ является виртуальным, и родитель просто не знает о его существовании). Как же метод родителя узнаёт, что надо запускать не свой метод, а перекрытый виртуальным? При трансляции объекта, содержащего виртуальные методы, создается так называемая "Таблица Виртуальных Методов - ТВМ" (английское название - VMT: Virtual Methods Table), количество элементов которой равно количеству виртуальных методов. В этой таблице будут храниться адреса точек входа в каждый метод. Такая таблица создается автоматически, с помощью специальной процедуры - конструктора. Кроме этого, при использовании зарезервированного слова Virtual должны выполняться еще 2 условия:
Конструктор ничем внешне не отличается от другого метода, но указывает компилятору, что нужно создать ТВМ. Достаточно сделать "пустой" метод (ничего не делающий), но вместо Procedure написать Constructor, и этого будет достаточно для правильной работы виртуального метода. В описании объекта, содержащего виртуальные методы, должен быть описан конструктор. Перед вызовом виртуального метода (или вызова того метода, который в свою очередь вызывает виртуальный) надо обязательно запустить конструктор (вызов виртуального метода без предварительного вызова конструктора может привести к краху системы, а компилятор не проверяет порядок вызова методов). Итак, конструкторы предназначены для создания конкретного экземпляра объекта. При этом создается ТВМ (если в объекте присутствуют виртуальные методы). Если же в объекте нет виртуальных методов, то в нем может не быть и конструктора; хотя использование конструктора при отсутствии виртуальных методов не является ошибкой. Обычно конструктор наполняет значениями поля объекта (чтобы как-то его использовать), но можно оставлять его пустым. Еще один специальный метод - деструктор. Он выполняет разрушение объекта (т.е. действие, обратное конструктору). При написании объектов рекомендуется создавать деструктор, при этом он удаляет из памяти, все, что там оставил объект. Описывается деструктор так: Type {....} Т.е. деструктор - такой же метод, как и конструктор. Деструктор также может быть пустым. Программисту все время приходится выбирать: "Каким должен быть тот или иной метод - статическим или виртуальным?" Есть несколько критериев:
|
![]() ![]() |
![]() |
Текстовая версия | 26.07.2025 0:14 |