Программирование. ООП в Pascal-Паскаль
Понятие абстрактных типов данных является ключевым в программировании. Абстракция подразумевает разделение и независимое рассмотрение интерфейса и реализации .
Рассмотрим пример. Все мы смотрим телевизионные программы. Назовем телевизор модулем или объектом. Этот объект имеет интерфейс с пользователем, т. е. средства управления (совокупность кнопок), воспроизведения изображения и звука. Чем совершеннее интерфейс, тем удобнее телевизор в использовании. Мы переключаем программы, нажимая определенные кнопки, и при этом не задумываемся о физических процессах, происходящих в телевизоре. Об этом знают специалисты. Когда мы выбираем телевизор, нас интересуют его цена и эксплуатационные параметры, т. е. качество изображения, звука и т. п. Однако нас не интересует то, что находится внутри. Другими словами, мы возвращаемся к свойствам объекта (модуля), какими являются интерфейс и реализация. Основная цель абстракции в программировании как раз и заключается в отделении интерфейса от реализации.
Вернемся к нашему примеру. Предположим, некоторый субъект уверен, что хорошо знает устройство телевизора. Он снимает крышку и начинает «усовершенствовать» его. Хотя иногда это и приводит к определенным промежуточным (локальным) успехам, окончательный результат почти всегда отрицательный. Поэтому подобные действия надо запрещать. В программировании это поддерживается механизмами запрета доступа или скрытия внутренних компонентов. Каждому объекту (модулю) предоставлено право самому распоряжаться «своим имуществом», т. е. данными функциями и операциями. Игнорирование этого принципа нарушает стабильность системы и часто приводит к ее полному разрушению. Принцип абстракции обязывает использовать механизмы скрытия, которые предотвращают умышленное или случайное изменение внутренних компонентов.
Абстракция данных предполагает определение и рассмотрение абстрактных типовданных(АТД) или, что то же самое, новых типов данных, введенных пользователем .
Абстрактный тип данных — это совокупность данных вместе с множеством операций, которые можно выполнять над этими данными .
Понятие объектно-ориентированного программирования
По определению авторитета в области объектно-ориентированных методов разработки программ Гради Буча «объектно-ориентированное программирование (ООП) – это методология программирования, которая основана на представлении программы в виде совокупности объектов, каждый из которых является реализацией определенного класса (типа особого вида), а классы образуют иерархию на принципах наследуемости».
Объектно-ориентированная методология так же, как и структурная методология, была создана с целью дисциплинировать процесс разработки больших программных комплексов и тем самым снизить их сложность и стоимость.
Объектно-ориентированная методология преследует те же цели, что и структурная, но решает их с другой отправной точки и в большинстве случаев позволяет управлять более сложными проектами, чем структурная методология.
Как известно, одним из принципов управления сложностью проекта является декомпозиция. Гради Буч выделяет две разновидности декомпозиции: алгоритмическую (так он называет декомпозицию, поддерживаемую структурными методами) и объектно-ориентированную, отличие которых состоит, по его мнению, в следующем: «Разделение по алгоритмам концентрирует внимание на порядке происходящих событий, а разделение по объектам придает особое значение факторам, либо вызывающим действия, либо являющимся объектами приложения этих действий».
Другими словами, алгоритмическая декомпозиция учитывает в большей степени структуру взаимосвязей между частями сложной проблемы, а объектно-ориентированная декомпозиция уделяет больше внимания характеру взаимосвязей.
На практике рекомендуется применять обе разновидности декомпозиции: при создании крупных проектов целесообразно сначала применять объектно-ориентированный подход для создания общей иерархии объектов, отражающих сущность программируемой задачи, а затем использовать алгоритмическую декомпозицию на модули для упрощения разработки и сопровождения программного комплекса.
ОО-программирование является, несомненно, одним из наиболее интересных направлений для профессиональной разработки программ.
Объекты и классы
Базовыми блоками объектно-ориентированной программы являются объекты и классы. Содержательно объект можно представить как что-то ощущаемое или воображаемое и имеющее хорошо определенное поведение. Таким образом, объект можно либо увидеть, либо потрогать, либо, по крайней мере, знать, что он есть, например, представлен в виде информации, хранимой в памяти компьютера. Дадим определение объекта, придерживаясь мнения Гради Буча: «Объект – осязаемая сущность, которая четко проявляет свое поведение».
Объект — это часть окружающей нас реальности, т. е. он существует во времени и в пространстве (впервые понятие объекта в программировании введено в языке Simula ). Формально объект определить довольно трудно. Это можно сделать через некоторые свойства, а именно: объект имеет состояние, поведение и может быть однозначно идентифицирован (другими словами, имеет уникальное имя).
Класс — это множество объектов, имеющих общую структуру и общее поведение. Класс — описание (абстракция), которое показывает, как построить существующую во времени и пространстве переменную этого класса, называемую объектом. Смысл предложений «описание переменных класса» и «описание объектов класса» один и тот же.
Объект имеет состояние, поведение и паспорт (средство для его однозначной идентификации); структура и поведение объектов описаны в классах, переменными которых они являются .
Определим теперь понятия состояния, поведения и идентификации объекта.
Состояние объекта объединяет все его поля данных (статический компонент, т.е. неизменный) и текущие значения каждого из этих полей (динамический компонент, т.е. обычно изменяющийся).
Поведение выражает динамику изменения состояний объекта и его реакцию на поступающие сообщения, т.е. как объект изменяет свои состояния и взаимодействует с другими объектами.
Идентификация (распознавание) объекта — это свойство, которое позволяет отличить объект от других объектов того же или других классов. Осуществляется идентификация посредством уникального имени (паспорта), которым наделяется объект в программе, впрочем как и любая другая переменная.
Выше уже говорилось, что процедурный (а также и модульный) подход позволяет строить программы, состоящие из набора процедур (подпрограмм), реализующих заданные алгоритмы. С другой стороны, объектно-ориентированный подход представляет программы в виде набора объектов, взаимодействующих между собой. Взаимодействие объектов осуществляется через сообщения. Предположим, что нашим объектом является окружность. Тогда сообщение, посланное этому объекту, может быть следующим: «нарисуй себя». Когда мы говорим, что объекту передается сообщение, то на самом деле мы вызываем некоторую функцию этого объекта (компонент-функцию). Так, в приведенном выше примере мы вызовем функцию, которая будет рисовать окружность на экране дисплея.
Базовые принципы ООП
К базовым принципам объектно-ориентированного стиля программирования относятся:
- пакетирование или инкапсуляция ;
- наследование ;
- полиморфизм ;
- передача сообщений.
Пакетирование (инкапсуляция)
предполагает соединение в одном объекте данных и функций, которые манипулируют этими данными. Доступ к некоторым данным внутри пакета может быть либо запрещен, либо ограничен.
Объект характеризуется как совокупностью всех своих свойств (например, для животных – это наличие головы, ушей, глаз и т.д.) и их текущих значений (голова – большая, уши – длинные, глаза – желтые и т.д.), так и совокупностью допустимых для этого объекта действий (умение принимать пищу, сидеть, стоять, бежать и т.д.). Указанное объединение в едином объекте как «материальных» составных частей (голова, уши, хвост, лапы), так и действий, манипулирующих этими частями (действие «бежать» быстро перемещает лапы) называется инкапсуляцией.
В рамках ООП данные называются полями объекта, а алгоритмы – объектными методами.
Инкапсуляция позволяет в максимальной степени изолировать объект от внешнего окружения. Она существенно повышает надежность разрабатываемых программ, т.к. локализованные в объекте алгоритмы обмениваются с программой сравнительно небольшими объемами данных, причем количество и тип этих данных обычно тщательно контролируется. В результате замена или модификация алгоритмов и данных, инкапсулированных в объект, как правило, не влечет за собой плохо прослеживаемых последствий для программы в целом. Другим немаловажным следствием инкапсуляции является легкость обмена объектами, переноса их из одной программы в другую.
Наследование
И структурная, и объектно-ориентированная методологии преследуют цель построения иерархического дерева взаимосвязей между объектами (подзадачами). Но если структурная иерархия строится по простому принципу разделения целого на составные части,
то при создании объектно-ориентированной иерархии принимается другой взгляд на тот же исходный объект. В объектно-ориентированной иерархии непременно отражается наследование свойств родительских (вышележащих) типов объектов дочерним (нижележащим) типам объектов.
По Гради Бучу «наследование – это такое отношение между объектами, когда один объект повторяет структуру и поведение другого».
Принцип наследования действует в жизни повсеместно и повседневно. Млекопитающие и птицы наследуют признаки живых организмов, в отличие от растений, орел и ворон наследуют общее свойство для птиц – умение летать. С другой стороны, львы, тигры, леопарды наследуют «структуру» и поведение, характерное для представителей отряда кошачьих и т.д.
Типы верхних уровней объектно-ориентированной иерархии, как правило, не имеют конкретных экземпляров объектов. Не существует, например, конкретного живого организма, который бы сам по себе назывался «млекопитающее» или «птица». Такие типы называют абстрактными. Конкретные экземпляры объектов имеют, как правило, типы самых нижних уровней ОО-иерархии: «крокодил Гена» – конкретный экземпляр объекта типа «крокодил», «кот Матроскин» – конкретный экземпляр объекта типа «кошка».
Наследование позволяет использовать библиотеки классов и развивать их (совершенствовать и модифицировать библиотечные классы) в конкретной программе. Наследование позволяет создавать новые объекты, изменяя или дополняя свойства прежних. Объект-наследник получает все поля и методы предка, но может добавить собственные поля, добавить собственные методы или перекрыть своими методами одноименные унаследованные методы.
Принцип наследования решает проблему модификации свойств объекта и придает ООП в целом исключительную гибкость. При работе с объектами программист обычно подбирает объект, наиболее близкий по своим свойствам для решения конкретной задачи, и создает одного или нескольких потомков от него, которые «умеют» делать то, что не реализовано в родителе.
Последовательное проведение в жизнь принципа «наследуй и изменяй» хорошо согласуется с поэтапным подходом к разработке крупных программных проектов и во многом стимулирует такой подход.
Когда вы строите новый класс, наследуя его из существующего класса, можно:
- добавить в новый класс новые компоненты-данные;
- добавить в новый класс новые компоненты-функции;
- заменить в новом классе наследуемые из старого класса компоненты-функции.
Полиморфизм
позволяет использовать одни и те же функции для решения разных задач. Полиморфизм выражается в том, что под одним именем скрываются различные действия, содержание которых зависит от типа объекта.
Полиморфизм – это свойство родственных объектов (т.е. объектов, имеющих одного общего родителя) решать схожие по смыслу проблемы разными способами. Например, действие «бежать» свойственно большинству животных. Однако каждое из них (лев, слон, крокодил, черепаха) выполняет это действие различным образом.
При традиционном (не объектно-ориентированном) подходе к программированию, животных перемещать будет программист, вызывая отдельную для конкретного животного и конкретного действия подпрограмму.
В рамках ООП поведенческие свойства объекта определяются набором входящих в него методов, программист только указывает, какому объекту какое из присущих ему действий требуется выполнить, и (для рассматриваемого примера) однажды описанные объекты-животные сами будут себя передвигать характерным для них способом, используя входящие в его состав методы. Изменяя алгоритм того или иного метода в потомках объекта, программист может придавать этим потомкам отсутствующие у родителя специфические свойства. Для изменения метода необходимо перекрыть его в потомке, т.е. объявить в потомке одноименный метод и реализовать в нем нужные действия. В результате в объекте-родителе и объекте-потомке будут действовать два одноименных метода, имеющих разную алгоритмическую основу и, следовательно, придающие объектам разные свойства. Это и называется полиморфизмом объектов.
Таким образом, в нашем примере с объектами-животными действие «бежать» будет называться полиморфическим действием, а многообразие форм проявления этого действия – полиморфизмом.
Описание объектного типа
Класс или объект – это структура данных, которая содержит поля и методы. Как всякая структура данных она начинается зарезервированным словом и закрывается оператором end . Формальный синтаксис не сложен: описание объектного типа получается, если в описании записи заменить слово record на слово object или class и добавить объявление функций и процедур над полями.
Type <имя типа объекта>= object
;
;
….
;
;
end ;имя>
В ObjectPascal существует специальное зарезервированное слово class для описания объектов, заимствованное из С++.
Type <имя типа объекта>= class
;
….
;
;
end ;имя>
ObjectPascal поддерживает обе модели описания объектов.
Компонент объекта – либо поле, либо метод. Поле содержит имя и тип данных. Метод – это процедура или функция, объявленная внутри декларации объектного типа, в том числе и особые процедуры, создающие и уничтожающие объекты (конструкторы и деструкторы). Объявление метода внутри описания объектного типа состоит только из заголовка. Это разновидность предварительного описания подпрограммы. Тело метода приводится вслед за объявлением объектного типа.
Пример . Вводится объектный тип «предок», который имеет поле данных Name (имя) и может выполнять два действия:
- провозглашать: «Я – предок!»;
- сообщать свое имя.
Type tPredoc = object Name : string ; <поле данных объекта>
Procedure Declaration ;
Procedure MyName ;
End ;поле>
Тексты подпрограмм, реализующих методы объекта, должны приводиться в разделе описания процедур и функций. Заголовки при описании реализации метода повторяют заголовки, приведенные в описании типа, но дополняются именем объекта, которое отделяется от имени процедуры точкой. В нашем примере:
Procedure tPredoc.Declaration ; <реализация метода объекта>
begin
writeln (‘Я – предок!’);
end ;
Procedure tPredoc.MyName ; <реализация метода объекта>
begin
writeln(‘Я –’, Name);
end;реализация>
Внутри описания методов на поля и методы данного типа ссылаются просто по имени. Так метод MyName использует поле Name без явного указания его принадлежности объекту так, если бы выполнялся неявный оператор with do .
Под объектами понимают и переменные объектного типа – их называют экземплярами. Как всякая переменная, экземпляр имеет имя и тип: их надо объявить.
……. <объявление объектного типа и описание его методов>
var v 1: tPredoc ; <объявление экземпляра объекта>
begin
v1. Name := ‘Петров Николай Иванович’;
v1.Declaration;
v1.MyName
end.объявление>
Использование поля данных объекта v1 не отличается по своему синтаксису от использования полей записей. Вызов методов экземпляра объекта означает, что указанный метод вызывается с данными объекта v 1. В результате на экран будут выведены строчки
Я – предок!
Я – Петров Николай Иванович
Аналогично записям, к полям переменных объектного типа разрешается обращаться как с помощью уточненных идентификаторов, так и с помощью оператора with .
Например, в тексте программы вместо операторов
v1.Name := ‘Петров Николай Иванович’;
v1.Declaration ;
v1.MyName
возможно использование оператора with такого вида
with v1 do
begin
Name:= ‘Петров Николай Иванович’;
Declaration ;
MyName
End ;
Более того, применение оператора with с объектными типами, также как и для записей не только возможно, но и рекомендуется.
Иерархия типов (наследование)
Типы можно выстроить в иерархию. Объект может наследовать компоненты из другого объектного типа. Наследующий объект — это потомок. Объект, которому наследуют — предок. Подчеркнем, что наследование относится только к типам, но не к экземплярам объектов.
Если введен объектный тип (предок, родительский), а его надо дополнить полями или методами, то вводится новый тип, объявляется наследником (потомком, дочерним типом) первого и описываются только новые поля и методы. Потомок содержит все поля типа предка. Заметим, что поля и методы предка доступны потомку без специальных указаний. Если в описании потомка повторяются имена полей или методов предка, то новые описания переопределяют поля и методы предка.
ООП всегда начинается с базового класса. Это шаблон для базового объекта. Следующим этапом является определение нового класса, который называется производным и является расширением базового.
Производный класс может включать дополнительные методы, которые не существуют в базовом классе. Он может переопределять ( redefined ) методы (или даже удалять их целиком).
В производном классе не должны переопределяться все методы базового класса. Каждый новый объект наследует свойства базового класса, необходимо лишь определить те методы, которые являются новыми или были изменены. Все другие методы базового класса считаются частью и производного. Это удобно, т.к. когда метод изменяется в базовом классе, он автоматически изменяется во всех производных классах.
Процесс наследования может быть продолжен. Класс, который произведен от базового, может сам стать базовым для других производных классов. Таким образом, ОО программы создают иерархию классов.
Пример иерархической структуры объектов
Наиболее часто структура иерархии классов описывается в виде дерева. Вершины дерева соответствуют классам, а корню соответствует класс, который описывает что-то общее (самое общее) для всех других классов.
Наследование дочерними типами информационных полей и методов их родительских типов выполняется по следующим правилам.
Правило 1. Информационные поля и методы родительского типа наследуются всеми его дочерними типами независимо от числа промежуточных уровней иерархии.
Правило 2. Доступ к полям и методам родительских типов в рамках описания любых дочерних типов выполняется так, как будто-бы они описаны в самом дочернем типе.
Правило 3. Ни в одном дочернем типе не могут быть использованы идентификаторы полей родительских типов.
Правило 4. Дочерний тип может доопределить произвольное число собственных методов и информационных полей.
Правило 5. Любое изменение текста в родительском методе автоматически оказывает влияние на все методы порожденных дочерних типов, которые его вызывают.
Правило 6. В противоположность информационным полям идентификаторы методов в дочерних типах могут совпадать с именами методов в родительских типах. В этом случае говорят, что дочерний метод перекрывает (подавляет) одноименный родительский метод. В рамках дочернего типа, при указании имени такого метода, будет вызываться именно дочерний метод, а не родительский.
Продолжим рассмотрение нашего примера. В дополнение к введенному нами типу предка tPredoc можно ввести типы потомков:
tуре tSon= оbject(tPredoc) <Тип, наследующий tPredoc >
procedure Declaration;
procedure Му Name(Predoc : tPredoc);
end ;
Тип,>
Имя типа предка приводится в скобках после слова оbject. Мы породили наследственную иерархию из трех типов: tSon («сын») наследник типу tPredoc , а тип tGrandSon (“внук”) - типу tSon. Тип tSon переопределяет методы Declaration и Му N а m е, но наследует поле Name . Тип tGrandSon переопределяет только метод Declaration и наследует от общего предка поле Name , а от своего непосредственного предка (типа tSon ) переопределенный метод Declaration .
Давайте разберемся, что именно мы хотим изменить в родительских методах. Дело в том, что «сын» должен провозглашать несколько иначе, чем его предок, а именно сообщить ‘Я – отец!’
procedure tSon.Declaration ;
begin
writeln (‘ Я — отец !’);
end;
А называя свое имя, “сын” должен сообщить следующие сведения:
procedure tSon .Му Name ( predoc : tPredoc );
begin
inherited Му Name ;
writeln (‘Я — сын ‘, predoc.Name, ‘ а ‘ );
end;
В нашем примере потомок tSon из метода Му Name вызывает одноименный метод непосредственного предка типа tPredoc . Такой вызов обеспечивается директивой inherited , после которой указан вызываемый метод непосредственного предка. Если возникает необходимость вызвать метод отдаленного предка в каком-нибудь дочернем типе на любом уровне иерархии, то это можно сделать с помощью уточненного идентификатора, т.е. указать явно имя типа родительского объекта и через точку – имя его метода:
TPredoc.MyName;
Теперь давайте разберемся с «внуком». Метод, в котором «внук» называет свое имя, в точности такой же, как и у его непосредственного предка (типа tSon ), поэтому нет необходимости этот метод переопределять, этот метод лучше автоматически наследовать и пользоваться им как своим собственным. А вот в методе Declaration нужно провозгласить ‘Я – внук!’, поэтому метод придется переопределить.
procedure tGrandSon.Declaration;
begin
writeln (‘ Я — внук !’);
end;
Рассмотрим пример программы, в которой определим экземпляр типа tPredoc , назовем его «дед», экземпляр типа tSon – «отец», и экземпляр типа tGrandSon – «внук». Потребуем от них, чтобы они представились.
Пример программы с испльзованием ООП
Наша программа выведет на экран:
Пример вывода на экран результата
Я —предок!
Я —Петров Николай Иванович
Я —отец!
Я —Петров Сергей Николаевич
Я —сын Петров Николай Ивановича
Я —внук!
Я —Петров Олег Сергеевич
Я —сын Петров Сергей Николаевича
Обратите внимание, что в заголовке процедуры tSon . MyName в качестве параметра приведен тип данных tPredoc , а при использовании этой процедуры ей передаются переменные как типа tPredoc , так и типа tSon . Это возможно, так как предок совместим по типу со своими потомками. Обратное несправедливо. Если мы заменим в заголовке процедуры tSon . MyName при описании параметров тип tPredoc на tSon , компилятор укажет на несовместимость типов при использовании переменной ded в строке otec . MyName ( ded ).
Полиморфизм и виртуальные методы
Полиморфизм – это свойство родственных объектов (т.е. объектов, имеющих одного родителя) решать схожие по смыслу проблемы разными способами.
Два или более класса, которые являются производными одного и того же базового класса, называются полиморфными. Это означает, что они могут иметь общие характеристики, но так же обладать собственными свойствами.
В рамках ООП поведенческие свойства объекта определяются набором входящих в него методов. Изменяя алгоритм того или иного метода в потомках объекта, программист может придавать этим потомкам отсутствующие у родителя специфические свойства. Для изменения метода необходимо перекрыть его в потомке, т.е. объявить в потомке одноименный метод и реализовать в нем нужные действия. В результате чего в объекте-родителе и объекте-потомке будут действовать два одноименных метода, имеющих разную алгоритмическую основу и, следовательно, придающие объектам разные свойства. Это и называется полиморфизмом объектов.
В рассмотренном выше примере во всех трех объектных типах tPredoc , tSon и tGrandSon действуют одноименные методы Declaration и MyName . Но в объектном типе tSon метод MyName выполняется несколько иначе, чем у его предка. А все три одноименных метода Declaration для каждого объекта выполняются по-своему.
Методы объектов бывают статическими, виртуальными и динамическими.
Статические методы
включаются в код программы при компиляции. Это означает, что до использования программы определено, какая процедура будет вызвана в данной точке. Компилятор определяет, какого типа объект используется при данном вызове, и подставляет метод этого объекта.
Объекты разных типов могут иметь одноименные статические методы. В этом случае нужный метод определяется по типу экземпляра объекта.
Это удобно, так как одинаковые по смыслу методы разных типов объектов можно и назвать одинаково, а это упрощает понимание и задачи и программы. Статическое перекрытие – первый шаг полиморфизма. Одинаковые имена – вопрос удобства программирования, а не принцип.
Виртуальные методы
в отличие от статических, подключаются к основному коду на этапе выполнения программы. Виртуальные методы дают возможность определить тип и конкретизировать экземпляр объекта в процессе исполнения, а затем вызвать методы этого объекта.
Этот принципиально новый механизм, называемый поздним связыванием, обеспечивает полиморфизм, т.е. разный способ поведения для разных, но однородных (в смысле наследования) объектов.
Описание виртуального метода отличается от описания обычного метода добавлением после заголовка метода служебного слова virtual .
procedure Method ( список параметров ); virtual;
Использование виртуальных методов в иерархии типов объектов имеет определенные ограничения:
- если метод объявлен как виртуальный, то в типе потомка его нельзя перекрыть статическим методом;
- объекты, имеющие виртуальные методы, инициализируются специальными процедурами, которые, в сущности, также являются виртуальными и носят название constructor ;
- списки переменных, типы функций в заголовках перекрывающих друг друга виртуальных процедур и функций должны совпадать полностью;
Обычно на конструктор возлагается работа по инициализации экземпляра объекта: присвоение полям исходных значений, первоначальный вывод на экран и т.п.
Помимо действий, заложенных в него программистом, конструктор выполняет подготовку механизма позднего связывания виртуальных методов. Это означает, что еще до вызова любого виртуального метода должен быть выполнен какой-нибудь конструктор.
Конструктор – это специальный метод, который инициализирует объект, содержащий виртуальные методы. Заголовок конструктора выглядит так:
constructor Method (список параметров);
Зарезервированное слово constructor заменяет слова procedure и virtual .
Основное и особенное назначение конструктора – установление связей с таблицей виртуальных методов ( VMT ) – структурой, содержащей ссылки на виртуальные методы. Таким образом, конструктор инициализирует объект установкой связи между объектом и VMT с адресами кодов виртуальных методов. При инициализации и происходит позднее связывание.
У каждого объекта своя таблица виртуальных методов VMT . Именно это и позволяет одноименному методу вызывать различные процедуры.
Упомянув о конструкторе, следует сказать и о деструкторе. Его роль противоположна: выполнить действия, завершающие работу с объектом, закрыть все файлы, очистить динамическую память, очистить экран и т.д.
Заголовок деструктора выглядит таким образом:
destructor Done ;
Основное назначение деструкторов – уничтожение VMT данного объекта. Часто деструктор не выполняет других действий и представляет собой пустую процедуру.
destructor Done ;
begin end ;
Программирование
Исходники Pascal (127)
Справочник
Справочник по паскалю: директивы, функции, процедуры, операторы и модули по алфавиту
Счетчики
2008—2023 © pascal.helpov.net | All Rights Reserved
Pascal. Объект (OBJECT)
В основе того или иного языка программирования лежит некоторая руководящая идея, оказывающая существенное влияние на стиль соответствующих программ.
Исторически первой была идея процедурного структурирования программ, в соответствии с которой программист должен был решить, какие именно процедуры он будет использовать в своей программе, а затем выбрать наилучшие алгоритмы для реализации этих процедур. Последовательное использование идеи процедурного структурирования программ привело к созданию обширных библиотек программирования, содержащих множество сравнительно небольших процедур, из которых, как из кирпичиков, можно строить “здание” программы.
По мере прогресса в области вычислительной математики акцент в программировании стал смещаться с процедур в сторону организации данных. Оказалось, что эффективная разработка сложных программ нуждается в действенных способах контроля правильности использования данных. Контроль должен осуществляться как на стадии компиляции, так и при прогоне программ, в противном случае, как показала практика, резко возрастают трудности создания крупных программных проектов. Отчетливое осознание этой проблемы привело к созданию Алгола—60, а позже — Паскаля, Модулы—2, Си и множества других языков программирования, имеющих более или менее развитые структуры типов данных.
Начиная с языка Симула—67, в программировании наметился новый подход, который получил название объектно—ориентированного программирования (ООП). Его руководящая идея заключается в стремлении связать данные с обрабатывающими эти данные процедурами в единое целое — объект. Характерной чертой объектов является инкапсуляция (объединение) данных и алгоритмов их обработки, в результате чего и данные, и процедуры во многом теряют самостоятельное значение. Фактически объектно—ориентированное программирование можно рассматривать как модульное программирование нового уровня, когда вместо во многом случайного, механического объединения процедур и данных акцент делается на их смысловую связь.
Какими мощными средствами располагает объектно—ориентированное программирование наглядно демонстрирует библиотека Turbo Vision, входящая в комплект поставки Турбо Паскаля.
Следует заметить, что преимущества ООП в полной мере проявляются лишь при разработке достаточно сложных программ.
Основные принципы ООП
Объектно—ориентированное программирование основано на китах” — трех важнейших принципах, придающих объектам новые свойства. Этими принципами являются инкапсуляция, наследование, полиморфизм.
Инкапсуляция
Инкапсуляция есть объединение в единое целое данных и алгоритмов обработки этих данных. В рамках ООП данные называются полями объекта, а алгоритмы — объектными методами.
Инкапсуляция позволяет в максимальной степени изолировать объект от внешнего окружения. Она существенно повышает надежность разрабатываемых программ, т.к. локализованные в объекте алгоритмы обмениваются с программой сравнительно небольшими объемами данных причем количество и тип этих данных обычно тщательно контролируются
Другим немаловажным следствием инкапсуляции является легкость обмена объектами, переноса их из одной программы в другую.
Наследование
Наследование есть свойство объектов порождать своих потомков. Объект — потомок автоматически наследует от родителя все поля и методы, может дополнять объекты новыми полями и заменять (перекрывать) методы родителя или дополнять их.
Принцип наследования решает проблему модификации свойств объекта и придает ООП в целом исключительную гибкость. При работе с объектами программист обычно подбирает объект, наиболее близкий по своим свойствам для решения конкретной задачи, и создает одного или нескольких потомков от него, которые “умеют” делать то, что не реализовано в родителе.
Последовательное проведение в жизнь принципа “наследуй и изменяй” хорошо согласуется с поэтапным подходом к разработке крупных программных проектов и во многом стимулирует такой подход.
Полиморфизм
Полиморфизм -это свойство родственных объектов (т.е. объектов, имеющих одного общего родителя) решать схожие по смыслу проблемы разными способами.
Для изменения метода необходимо перекрьипь его в потомке, т.е. объявить в потомке одноименный метод и реализовать в нем нужные действия. В результате в объекте — родителе и объекте — потомке будут действовать два одноименных метода, имеющие разную алгоритмическую основу и, следовательно, придающие объектам разные свойства. Это и называется полиморфизмом объектов.
В Турбо Паскале полиморфизм достигается не только описанным выше механизмом наследования и перекрытия методов родителя, но и их виртуализацией (см. ниже), позволяющей родительским методам обращаться к методам потомков.
Знакомство с техникой ООП начнем на примере следующей учебной задачи. Требуется разработать программу, которая создает на экране ряд графических изображений (точки, окружность, линия, квадрат) и может перемещать эти изображения по экрану. Для перемещения изображений в программе будут использоваться клавиши управления курсором, клавиши Ноте, End, PgUp, PgDn (для перемещения по диагональным направлениям) и клавиша Tab для выбора перемещаемого объекта. Выход из программы — клавиша Esc.Техническая реализация программы потребует использования средств двух стандартных библиотек — CRT и GRAPH.
Создание объектов
В Турбо Паскале для создания объектов используются три зарезервированных слова: object, constructor, destructor и три стандартные директивы: private, public и virtual.
Зарезервированное слово object используется для описания объекта. Описание объекта должно помещаться в разделе описания типов:
type MyObject = object end;
Если объект порождается от какого-либо родителя, имя родителя указывается в круглых скобках сразу за словом object:
type MyDescendantObject = object(MyObject) . end;
Любой объект может иметь сколько угодно потомков, но только одного родителя, что позволяет создавать иерархические деревья наследования объектов.
Для нашей учебной задачи создадим объект—родитель TGraphObject, в рамках которого будут инкапсулированы поля и методы, общие для всех детальных объектов:
type TGraphObj = object Private X,Y: Integer; Color: Word; Public Constructor Init(aX,aY: Integer; aColor: Word); Procedure Draw(aColor: Word); Virtual; Procedure Show; Procedure Hide; Procedure MoveTo(dX,dY: Integer) ; end;
В дальнейшем предполагается создать объекты-потомки от TGraphObj, реализующие все специфические свойства точки, линии, окружности и прямоугольника. Каждый из этих графических объектов будет характеризоваться положением на экране (поля Х и У) и цветом (полеColor). С помощью метода Draw он будет способен отображать себя на экране, а с помощью свойств “показать себя” (метод Show) и “спрятать себя” (метод Hide) сможет перемещаться по экрану (метод MoveTo). Учитывая общность свойств графических объектов, мы объявляем абстрактный объект TGraphObj, который не связан с конкретной графической фигурой. Он объединяет в себе все общие поля и методы реальных фигур и будет служить родителем для других объектов.
Директива Private в описании объекта открывает секцию описания скрытых полей и методов. Перечисленные в этой секции элементы объекта “не видны” программисту. В нашем примере он не может произвольно менять координаты реперной точки [X,Y), т.к. это не приведет к перемещению объекта. Для изменения полей Х и Y предусмотрены входящие в состав объекта методы Init и MoveTo. Скрытые поля и методы доступны в рамках той программной единицы (программы или модуля), где описан соответствующий объект.
Директива public отменяет действие директивы private, поэтому все следующие за public элементы объекта доступны в любой программной единице. Директивы private и public могут произвольным образом чередоваться в пределах одного объекта.
Вариант объявления объекта TGraphObj без использования механизма private..public:
type TGraphObj = object X,Y: Integer; Color: Word; Constructor Init(aX,aY: Integer; aColor: Word); Procedure Draw(aColor: Word); Virtual; Procedure Show; Procedure Hide; Procedure MoveTo(dX, dY: Integer); end;
Описания полей ничем не отличаются от описания обычных переменных. Полями могут быть любые структуры данных, в том числе и другие объекты. Используемые в нашем примере поля Х и У содержат координату реперной (характерной) точки графического объекта, а поле Color — его цвет. Реперная точка характеризует текущее положение графической фигуры на экране и, в принципе, может быть любой ее точкой.
Для описания методов в ООП используются традиционные для Паскаля процедуры и функции, а также особый вид процедур — конструкторы и деструкторы. Конструкторы предназначены для создания конкретного экземпляра объекта, ведь объект — это тип данных, т.е. “шаблон”, по которому можно создать сколько угодно рабочих экземпляров данного объектного типа (типа TGraphObj, например).
Зарезервированное слово constructor, используемое в заголовке конструктора вместо procedure, предписывает компилятору создать особый код пролога, с помощью которого настраивается так называемая таблица виртуальных методов. Если в объекте нет виртуальных методов, в нем может не быть ни одного конструктора, наоборот, если хотя бы один метод описан как виртуальный (с последующим словом Virtual, см. метод Draw), в состав объекта должен входить хотя бы один конструктор и обращение к конструктору должно предшествовать обращению к любому виртуальному методу.
Типичное действие, реализуемое конструктором, состоит в наполнении объектных полей конкретными значениями. В нашем примере конструктор Init объекта TGraphObj получает все необходимые для полного определения экземпляра данные через параметры обращенияаХ, аУ и aColor.
Процедура Draw предназначена для вычерчивания графического объекта. Эта процедура будет реализовываться в потомках объекта TGraphObj до-разному. Например, для визуализации точки следует вызвать процедуру PutPixel, для вычерчивания линии — процедуру Line и т.д. В объекте TGraphObj процедура Draw определена как виртуальная (“воображаемая”). Абстрактный объект TGraphObj не предназначен для вывода на экран, однако наличие процедуры Draw в этом объекте говорит о том, что любой потомок TGraphObj должен иметь собственный метод Draw, с помощью второго он может показать себя на экране.
При трансляции объекта, содержащего виртуальные методы, создается так называемая таблица виртуальных методов (ТВМ). В этой таблице будут храниться адреса точек входа в каждый виртуальный метод. В нашем примере ТВМ объекта TGraphObj хранит единственный элемент — адрес метода Draw.
Наличие в объекте TGraphObj виртуального метода Draw позволяет легко реализовать три других метода объекта: чтобы показать объект на экране в Методе Show, вызывается Draw с цветом aColor, равным значению поля color, а чтобы спрятать графический объект, в методе Hide вызывается Draw со значением цвета GetBkColor, т.е. с текущим цветом фона.
Рассмотрим реализацию перемещения объекта. Если .потомок TGraphObj Например, TLine) хочет переместить себя на экране, он обращается к родительскому методу MoveTo. В этом методе сначала с помощью Hide объект стирается с экрана, а затем с помощью Showпоказывается в другом месте.
Чтобы описать все свойства объекта, необходимо раскрыть содержимое объектных методов, т.е. описать соответствующие процедуры и функции. Описание методов производится обычным для Паскаля способом в любом месте раздела описаний, но после описания объекта. Например:
type TGraphObj = object . end; Constructor TGraphObj.Init; begin X := aX; Y := aY; Color := aColor end; Procedure TGraphObj.Draw; begin end; Procedure TGraphObj.Show; begin Draw(Color) end; Procedure TGraphObj.Hide; begin Draw(GetBkColor) end; Procedure TGraphObj.MoveTo; begin Hide; X := X+dX; Y := Y+dY; Show; end;
Отметим два обстоятельства. Во-первых, при описании методов имя метода дополняется спереди именем объекта, т.е. используется составное имя метода. Это необходимо по той простой причине, что в иерархий родственных объектов любой из методов может быть перекрыт в потомках. Составные имена четко указывают . принадлежность конкретной процедуры. Во-вторых, в любом объектном методе можно использовать инкапсулированные поля объекта почти так, как если бы они были определены в качестве глобальных переменных. Например, в конструкторе fGraph.Init переменные в левых частях операторов присваивания представляют собой объектные поля и не должны заново описываться в процедуре. Более того, описание
Constructor TGraphObj.Init; var X,Y: Integer; Color: Word; begin . end;
вызовет сообщение о двойном определении переменных X, Y и Color (в этом и состоит отличие в использовании полей от глобальных переменных: глобальные переменные можно переопределять в процедурах, в то время как объектные поля переопределять нельзя>.
Обратите внимание: абстрактный объект TGraphObj не предназначен для вывода на экран, поэтому его метод Draw ничего не делает. Однако методы Hide, Show и MoveTo “знают” формат вызова этого метода и реализуют необходимые действия, обращаясь к реальным методам Draw своих будущих потомков через соответствующие ТВМ. Это и есть полиморфизм объектов.
Создание объекта «Точка»
Создадим простейшего потомка от TGraphObj — объект TPoint, с помощью которого будет визуализироваться и перемещаться точка. Все основные действия, необходимые для этого, уже есть в объекте TGraphObj, поэтому в объекте TPoint перекрывается единственный метод — Draw.
type TPoint = object(TGraphObj) Procedure Draw(aColor); Virtual; end; Procedure TPoint.Draw; begin PutPixel(X,Y,Color) end;
В новом объекте TPoint можно использовать любые методы объекта-родителя TGraphObj. Например, вызвать метод MoveTo, чтобы переместить изображение точки на новое место. В этом случае родительский метод TGraphObj. MoveTo будет обращаться к методуTPoint.Draw, чтобы спрятать и затем показать изображение точки. Такой вызов станет доступен после Обращения к конструктору Init объекта TPoint, который нужным образом настроит ТВМ объекта. Если вызвать TPoint.Draw до вызова Init, его ТВМ не будет содержать правильного адреса и программа “зависнет”.
Создание объекта «Линия»
Чтобы создать объект—линию, необходимо ввести два новых поля для хранения координат второго конца. Дополнительные поля требуется наполнить конкретными значениями, поэтому нужно перекрыть конструктор родительского объекта:
type TLine = object (TGraphObj) dX,dY: Integer; Constructor Init(X1,Y1,X2,Y2: Integer; aColor: Word); Procedure Draw(aColor: Word); Virtual end; Constructor TLine.Init; begin Inherited Init(Xl,Yl,aColor); dX := X2-X1; dY := Y2-Y1 end; Procedure Draw; begin SetCoior(Color) ; Line(X, Y, X+dX, Y+dY) end;
В конструкторе TLine.Init для инициации полей X, Y и Color, унаследованных от родительского объекта, вызывается унаследованный конструктор TCraph.Init, для чего используется зарезервированное слово inherited (англ.—унаследованный):
Inherited Init(Xl,Yl,aColor);
таким же успехом мы могли бы использовать и составное имя метода
TGraphObj.Init(XI,Y1,aColor);
Для инициации полей dX и dY вычисляется расстояние в пикселах по горизонтали и вертикали от первого конца прямой, до ее второго конца. Это позволяет в методе TLine.Draw вычислить координаты второго кони по координатам первого и смещениям dX и dY. В результате простое изменение координат реперной точки Х,У в родительском i TGraph Move To перемещает всю фигуру по экрану.
Создание объекта «Круг»
Теперь нетрудно реализовать объект TCircle для создания и перемещения окружности:
type TCircle = object(TGraphObj) R: Integer; Constructor Init(aX,aY,aR: Integer; aColor: Word); Procedure Draw(aColor: Virtual); end; Constructor TCircle.Init; begin Inherited Init(aX,aY,aColor) ; R := aR end; procedure TCircle.Draw; begin SetCoior(aColor); Circle(X,Y,R) end;
Создание объекта «Прямоугольник»
В объекте TRect, с помощью которого создается и перемещается прямоугольник, учтем то обстоятельство, что для задания прямоугольника требуется указать четыре целочисленных параметра, т.е. столько же, сколько для задания линии. Поэтому объект TRect удобнее породить не от TGraphObj, а от TLine, чтобы использовать его конструктор Init:
type TRect = object(TLine) procedure Draw(aColor: Word); end; Procedure TRect.Draw; begin SetCoior(aColor) ; Rectangle(X,Y,X+dX,Y+dY) end;
Чтобы описания графических объектов не мешали созданию основной программы, оформим эти описания в отдельном модуле GraphObj:
Unit GraphObj; Interface type TGraphObj = object . end; TPoint = object(TGraphOb3) . end; TLine = object (TGraphObj) . end; TCircle = object(TGraphObj) end; TRect = object(TLine) end; Implementation Uses Graph; Constructor TGraphObj.Init; . end.
В интерфейсной части модуля приводятся лишь объявления объектов, подобно тому как описываются другие типы данных, объявляемые в модуле доступными для внешних программных единиц. Расшифровка объектных методов помещается в исполняемую частьimplementation, как если бы это были описания обычных интерфейсных процедур и функций. При описании методов можно опускать повторное описание в заголовке параметров вызова. Если они все же повторяются, они должны в точности соответствовать ранее объявленным параметрам в описании объекта. Например, заголовок конструктора TGraphObj. Init может быть таким:
Constructor TGraphObj.Init;
Constructor TGraphObj.Init(aX,aY: Integer; aColor: Word);
Использование объектов
Идею инкапсуляции полей и алгоритмов можно применить не только к графическим объектам, но и ко всей программе в целом. Ничто не мешает нам создать объект — программу и “научить” его трем основным действиям: инициации (Init), выполнению основной работы (Run) и завершению (Done). На этапе инициации экран переводится в графический режим работы и создаются и отображаются графические объекты (100 экземпляров TPoint и по одному экземпляру TLine, TCircle, TRect). На этапе Run осуществляется сканирование клавиатуры и перемещение графических объектов. Наконец, на этапе Done экран переводится в текстовый режим и завершается работа всей программы.
Назовем объект—программу именем TGrapbApp и разместим его в модуле CraphApp (пока не обращайте внимание на точки, скрывающие содержательную часть модуля — позднее будет представлен его полный текст):
Unit GraphApp; Interface type TGraphApp = object Procedure Init; Procedure Run; Destructor Done; end; Implementation Procedure TGraphApp.Init; . end; end.
В этом случае основная программа будет предельно простой:
Program Graph_0bjects; Uses GraphApp; var App: TGraphApp; begin App.Init; App.Run; App.Done end.
В ней мы создаем единственный экземпляр App объекта—программы TGrahpApp и обращаемся к трем его методам.
Создание экземпляра объекта ничуть не отличается от создания экземпляра переменной любого другого типа. Просто в разделе описания переменных мы указываем имя переменной и ее тип:
var App: TGraphApp;
Получив это указание, компилятор зарезервирует нужный объем памяти для размещения всех полей объекта TGraphApp. Чтобы обратиться к тому или иному объектному методу или полю, используется составное имя, причем первым указывается не имя объектного типа, а имя соответствующей переменной:
App.Init; App.Run; App.Done;
Ниже приводится возможный вариант модуля GraphApp для нашей учебной программы:
Unit GraphApp; Interface Uses GraphObj; const NPoints = 100; type TGraphApp = object Points: array [1..NPoints] of TPoint; Line: TLine; Rect: TRect; Circ: TCircle; ActiveObj: Integer; Procedure Init; Procedure Run; Procedure Done; Procedure ShowAll; Procedure MoveActiveObj(dX,dY: Integers; end; implementation Uses Graph, CRT; procedure TGraphApp.Init; var D, R, Err, k: Integer; begin D := Detect; InitGraph (D,R,'\tp\bgi'); Err := GraphResult; if Err<>0 then begin GraphErrorMsg(Err); Halt; end; for k := 1 to NPoints do Points[k].Init(Random(GetMaxX),Random(GetMaxY),Random(15)+1); Line.Init(GetMaxX div 3,GetMaxY div 3,2*GetMaxX div 3, 2*GetMaxY div 3, LightRed); Circ.Init(GetMaxX div 2,GetMaxY div 2,GetMaxY div 5,White); Rect.Init(2*GetMaxX div 5,2*GetMaxY div 5,3*GetMaxX div 5, 3*GetMaxY div 5, Yellow); ShowAll; ActiveObj := 1 end; Procedure TGraphApp.Run ; var Stop: Boolean; const D = 5; begin Stop := False; repeat case ReadKey of #27: Stop := True; #9: begin ActiveObj:= ActiveObj+1 ; if ActiveObj>3 then ActiveObj := 3 end; #0: case ReadKey of #71 MoveActiveObj(-D,-D) #72 MoveActiveObj( 0,-D) #73 MoveActiveObj( D,-D) #77 MoveActiveObj( D, 0) #79 MoveActiveObj(-D, D) end end ShowAll; Until Stop; end; Destructor TGraphApp.Done; begin CloseGraph; end; Procedure TGraphApp.ShowAll; var k: Integer; begin for k := 1 to NPoints do Points[k].Show; Line.Show; Rect.Show; Circ.Show end; Procedure TGraphApp.MoveActiveObj; begin case ActiveObj of 1: Rect.MoveTo(dX,dY); 2: Circ.MoveTo(dX,dY); 3: Line.MoveTo(dX,dY) end end; end.
В реализации объекта TGraphApp используется деструктор Done. Следует иметь в виду, что в отличие от конструктора, осуществляющего настройку ТВМ, деструктор не связан с какими-то специфичными действиями: для компилятора слова destructor и procedure — синонимы. Введение в ООП деструкторов носит, в основном, стилистическую направленность — просто процедуру, разрушающую экземпляр объекта, принято называть деструктором. В реальной практике ООП с деструкторами обычно связывают процедуры, которые не только прекращают работу с объектом, но и освобождают выделенную для него динамическую память.
В заключении следует сказать, что формалистика ООП в рамках реализации этой технологии в Турбо Паскале предельно проста и лаконична. Согласитесь, что введение лишь шести зарезервированных слов, из которых действительно необходимыми являются три (object, constructor и virtual), весьма небольшая плата за мощный инструмент создания современного программного обеспечения.
Паскаль – объектно-ориентированный
Мы можем представить нашу вселенную, сделанную из различных объектов, таких как солнце, земля, луна и т. Д. Точно так же мы можем представить нашу машину, сделанную из различных объектов, таких как колесо, рулевое управление, шестерня и т. Д. Таким же образом, существуют концепции объектно-ориентированного программирования, которые принять все как объект и реализовать программное обеспечение, используя различные объекты. В Паскале есть два структурных типа данных, используемых для реализации объекта реального мира –
- Типы объектов
- Типы классов
Объектно-ориентированные концепции
Прежде чем мы углубимся в детали, давайте определим важные термины Паскаля, связанные с Объектно-ориентированным Паскалем.
- Object – объект – это особый вид записи, который содержит поля, подобные записи; однако, в отличие от записей, объекты содержат процедуры и функции как часть объекта. Эти процедуры и функции хранятся как указатели на методы, связанные с типом объекта.
- Класс – класс определяется почти так же, как и объект, но существует различие в том, как они создаются. Класс размещается в куче программы, а объект – в стеке. Это указатель на объект, а не сам объект.
- Создание класса – создание означает создание переменной этого типа класса. Поскольку класс является просто указателем, когда объявляется переменная типа класса, память выделяется только для указателя, а не для всего объекта. Только когда он создается с использованием одного из своих конструкторов, память выделяется для объекта. Экземпляры класса также называются «объектами», но не путайте их с объектами Object Pascal. В этом уроке мы напишем «Объект» для объектов Pascal и «Объект» для концептуального объекта или экземпляра класса.
- Переменные-члены – это переменные, определенные внутри класса или объекта.
- Функции-члены – это функции или процедуры, определенные внутри класса или объекта и используемые для доступа к данным объекта.
- Видимость членов – члены объекта или класса также называются полями. Эти поля имеют различную видимость. Видимость относится к доступности членов, т. Е. Точно, где эти члены будут доступны. Объекты имеют три уровня видимости: открытый, закрытый и защищенный. Классы имеют пять типов видимости: публичный, частный, строго приватный, защищенный и опубликованный. Мы обсудим видимость в деталях.
- Наследование. Когда класс определяется путем наследования существующих функций родительского класса, он считается наследуемым. Здесь дочерний класс будет наследовать все или несколько функций-членов и переменных родительского класса. Объекты также могут быть унаследованы.
- Родительский класс – класс, который наследуется другим классом. Это также называется базовым классом или суперклассом.
- Дочерний класс – класс, который наследуется от другого класса. Это также называется подклассом или производным классом.
- Полиморфизм – это объектно-ориентированная концепция, в которой одна и та же функция может использоваться для разных целей. Например, имя функции останется прежним, но оно может принимать разное количество аргументов и может выполнять разные задачи. Классы Pascal реализуют полиморфизм. Объекты не реализуют полиморфизм.
- Перегрузка – это тип полиморфизма, при котором некоторые или все операторы имеют разные реализации в зависимости от типов их аргументов. Аналогично функции также могут быть перегружены с другой реализацией. Классы Pascal реализуют перегрузку, а объекты – нет.
- Абстракция данных – любое представление данных, в котором скрыты (абстрагированы) детали реализации.
- Инкапсуляция – Относится к концепции, в которой мы инкапсулируем все данные и функции-члены вместе, чтобы сформировать объект.
- Конструктор. Относится к специальному типу функции, которая будет вызываться автоматически при создании объекта из класса или объекта.
- Destructor – относится к специальному типу функции, которая будет вызываться автоматически всякий раз, когда объект или класс удаляется или выходит из области видимости.
Object – объект – это особый вид записи, который содержит поля, подобные записи; однако, в отличие от записей, объекты содержат процедуры и функции как часть объекта. Эти процедуры и функции хранятся как указатели на методы, связанные с типом объекта.
Класс – класс определяется почти так же, как и объект, но существует различие в том, как они создаются. Класс размещается в куче программы, а объект – в стеке. Это указатель на объект, а не сам объект.
Создание класса – создание означает создание переменной этого типа класса. Поскольку класс является просто указателем, когда объявляется переменная типа класса, память выделяется только для указателя, а не для всего объекта. Только когда он создается с использованием одного из своих конструкторов, память выделяется для объекта. Экземпляры класса также называются «объектами», но не путайте их с объектами Object Pascal. В этом уроке мы напишем «Объект» для объектов Pascal и «Объект» для концептуального объекта или экземпляра класса.
Переменные-члены – это переменные, определенные внутри класса или объекта.
Функции-члены – это функции или процедуры, определенные внутри класса или объекта и используемые для доступа к данным объекта.
Видимость членов – члены объекта или класса также называются полями. Эти поля имеют различную видимость. Видимость относится к доступности членов, т. Е. Точно, где эти члены будут доступны. Объекты имеют три уровня видимости: открытый, закрытый и защищенный. Классы имеют пять типов видимости: публичный, частный, строго приватный, защищенный и опубликованный. Мы обсудим видимость в деталях.
Наследование. Когда класс определяется путем наследования существующих функций родительского класса, он считается наследуемым. Здесь дочерний класс будет наследовать все или несколько функций-членов и переменных родительского класса. Объекты также могут быть унаследованы.
Родительский класс – класс, который наследуется другим классом. Это также называется базовым классом или суперклассом.
Дочерний класс – класс, который наследуется от другого класса. Это также называется подклассом или производным классом.
Полиморфизм – это объектно-ориентированная концепция, в которой одна и та же функция может использоваться для разных целей. Например, имя функции останется прежним, но оно может принимать разное количество аргументов и может выполнять разные задачи. Классы Pascal реализуют полиморфизм. Объекты не реализуют полиморфизм.
Перегрузка – это тип полиморфизма, при котором некоторые или все операторы имеют разные реализации в зависимости от типов их аргументов. Аналогично функции также могут быть перегружены с другой реализацией. Классы Pascal реализуют перегрузку, а объекты – нет.
Абстракция данных – любое представление данных, в котором скрыты (абстрагированы) детали реализации.
Инкапсуляция – Относится к концепции, в которой мы инкапсулируем все данные и функции-члены вместе, чтобы сформировать объект.
Конструктор. Относится к специальному типу функции, которая будет вызываться автоматически при создании объекта из класса или объекта.
Destructor – относится к специальному типу функции, которая будет вызываться автоматически всякий раз, когда объект или класс удаляется или выходит из области видимости.
Определение объектов Паскаля
Объект объявляется с помощью объявления типа. Общая форма объявления объекта выглядит следующим образом:
type object-identifier = object private field1 : field-type; field2 : field-type; . public procedure proc1; function f1(): function-type; end; var objectvar : object-identifier;
Давайте определим объект Rectangle, который имеет два элемента данных целочисленного типа – длину и ширину и несколько функций-членов для управления этими элементами данных и процедуру рисования прямоугольника.
type Rectangle = object private length, width: integer; public constructor init; destructor done; procedure setlength(l: inteter); function getlength(): integer; procedure setwidth(w: integer); function getwidth(): integer; procedure draw; end; var r1: Rectangle; pr1: ^Rectangle;
После создания ваших объектов вы сможете вызывать функции-члены, связанные с этим объектом. Одна функция-член сможет обрабатывать только переменную-член связанного объекта.
В следующем примере показано, как установить длину и ширину для двух прямоугольных объектов и нарисовать их, вызвав функции-члены.
r1.setlength(3); r1.setwidth(7); writeln(' Draw a rectangle: ', r1.getlength(), ' by ' , r1.getwidth()); r1.draw; new(pr1); pr1^.setlength(5); pr1^.setwidth(4); writeln(' Draw a rectangle: ', pr1^.getlength(), ' by ' ,pr1^.getwidth()); pr1^.draw; dispose(pr1);
Ниже приведен полный пример, демонстрирующий использование объектов в Паскале.
program exObjects; type Rectangle = object private length, width: integer; public procedure setlength(l: integer); function getlength(): integer; procedure setwidth(w: integer); function getwidth(): integer; procedure draw; end; var r1: Rectangle; pr1: ^Rectangle; procedure Rectangle.setlength(l: integer); begin length := l; end; procedure Rectangle.setwidth(w: integer); begin width :=w; end; function Rectangle.getlength(): integer; begin getlength := length; end; function Rectangle.getwidth(): integer; begin getwidth := width; end; procedure Rectangle.draw; var i, j: integer; begin for i:= 1 to length do begin for j:= 1 to width do write(' * '); writeln; end; end; begin r1.setlength(3); r1.setwidth(7); writeln('Draw a rectangle:', r1.getlength(), ' by ' , r1.getwidth()); r1.draw; new(pr1); pr1^.setlength(5); pr1^.setwidth(4); writeln('Draw a rectangle:', pr1^.getlength(), ' by ' ,pr1^.getwidth()); pr1^.draw; dispose(pr1); end.
Когда приведенный выше код компилируется и выполняется, он дает следующий результат –
Draw a rectangle: 3 by 7 * * * * * * * * * * * * * * * * * * * * * Draw a rectangle: 5 by 4 * * * * * * * * * * * * * * * * * * * *
Видимость членов объекта
Видимость указывает на доступность членов объекта. Члены объекта Pascal имеют три типа видимости –
общественного
Члены могут использоваться другими блоками вне программного блока
Члены доступны только в текущем блоке.
Члены доступны только для объектов, произошедших от родительского объекта.
общественного
Члены могут использоваться другими блоками вне программного блока
Члены доступны только в текущем блоке.
Члены доступны только для объектов, произошедших от родительского объекта.
По умолчанию поля и методы объекта являются общедоступными и экспортируются за пределы текущей единицы.
Конструкторы и деструкторы для объектов Pascal –
Конструкторы – это особый тип методов, которые вызываются автоматически при создании объекта. Вы создаете конструктор в Pascal, просто объявив метод с помощью конструктора ключевых слов. Условно имя метода – Init, однако вы можете указать любой собственный действительный идентификатор. Вы можете передать столько аргументов, сколько захотите, в функцию конструктора.
Деструкторы – это методы, которые вызываются при уничтожении объекта. Методы деструктора уничтожают любое выделение памяти, созданное конструкторами.
Следующий пример предоставит конструктор и деструктор для класса Rectangle, который будет инициализировать длину и ширину для прямоугольника во время создания объекта и уничтожать его, когда он выходит из области видимости.
program exObjects; type Rectangle = object private length, width: integer; public constructor init(l, w: integer); destructor done; procedure setlength(l: integer); function getlength(): integer; procedure setwidth(w: integer); function getwidth(): integer; procedure draw; end; var r1: Rectangle; pr1: ^Rectangle; constructor Rectangle.init(l, w: integer); begin length := l; width := w; end; destructor Rectangle.done; begin writeln(' Desctructor Called'); end; procedure Rectangle.setlength(l: integer); begin length := l; end; procedure Rectangle.setwidth(w: integer); begin width :=w; end; function Rectangle.getlength(): integer; begin getlength := length; end; function Rectangle.getwidth(): integer; begin getwidth := width; end; procedure Rectangle.draw; var i, j: integer; begin for i:= 1 to length do begin for j:= 1 to width do write(' * '); writeln; end; end; begin r1.init(3, 7); writeln('Draw a rectangle:', r1.getlength(), ' by ' , r1.getwidth()); r1.draw; new(pr1, init(5, 4)); writeln('Draw a rectangle:', pr1^.getlength(), ' by ',pr1^.getwidth()); pr1^.draw; pr1^.init(7, 9); writeln('Draw a rectangle:', pr1^.getlength(), ' by ' ,pr1^.getwidth()); pr1^.draw; dispose(pr1); r1.done; end.
Когда приведенный выше код компилируется и выполняется, он дает следующий результат –
Draw a rectangle: 3 by 7 * * * * * * * * * * * * * * * * * * * * * Draw a rectangle: 5 by 4 * * * * * * * * * * * * * * * * * * * * Draw a rectangle: 7 by 9 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Destructor Called
Наследование для объектов Pascal
Объекты Pascal могут при желании наследоваться от родительского объекта. Следующая программа иллюстрирует наследование в объектах Pascal. Давайте создадим еще один объект с именем TableTop , который наследуется от объекта Rectangle.
program exObjects; type Rectangle = object private length, width: integer; public procedure setlength(l: integer); function getlength(): integer; procedure setwidth(w: integer); function getwidth(): integer; procedure draw; end; TableTop = object (Rectangle) private material: string; public function getmaterial(): string; procedure setmaterial( m: string); procedure displaydetails; procedure draw; end; var tt1: TableTop; procedure Rectangle.setlength(l: integer); begin length := l; end; procedure Rectangle.setwidth(w: integer); begin width :=w; end; function Rectangle.getlength(): integer; begin getlength := length; end; function Rectangle.getwidth():integer; begin getwidth := width; end; procedure Rectangle.draw; var i, j: integer; begin for i:= 1 to length do begin for j:= 1 to width do write(' * '); writeln; end; end; function TableTop.getmaterial(): string; begin getmaterial := material; end; procedure TableTop.setmaterial( m: string); begin material := m; end; procedure TableTop.displaydetails; begin writeln('Table Top: ', self.getlength(), ' by ' , self.getwidth()); writeln('Material: ', self.getmaterial()); end; procedure TableTop.draw(); var i, j: integer; begin for i:= 1 to length do begin for j:= 1 to width do write(' * '); writeln; end; writeln('Material: ', material); end; begin tt1.setlength(3); tt1.setwidth(7); tt1.setmaterial('Wood'); tt1.displaydetails(); writeln; writeln('Calling the Draw method'); tt1.draw(); end.
Ниже приведены важные моменты, которые следует отметить –
- Объект Tabletop унаследовал все члены объекта Rectangle.
- В TableTop также есть метод draw. Когда метод draw вызывается с использованием объекта TableTop, вызывается метод рисования TableTop.
- Существует неявный экземпляр с именем self, который ссылается на текущий экземпляр объекта.
Объект Tabletop унаследовал все члены объекта Rectangle.
В TableTop также есть метод draw. Когда метод draw вызывается с использованием объекта TableTop, вызывается метод рисования TableTop.
Существует неявный экземпляр с именем self, который ссылается на текущий экземпляр объекта.
Когда приведенный выше код компилируется и выполняется, он дает следующий результат –
Урок 12 — ООП и Object Pascal
ООП — языками в настоящее время являются C++, Object Pascal, SmallTalk, Java, ADA. Собственно, «самым-самым» ООП языком является SmallTalk, который самый первый реализовал принципы объектно-ориентированного программирования. Но этот язык является интерпретируемым. К тому же программы, написанные на SmallTalk, выполняются в среде виртуальной SmallTalk-машины, что не подходит для создания эффективных приложений. А вот любимый многими Visual Basic вообще не является ООП-языком, хотя на первый взгляд этого не скажешь. Обилие различных визуальных «объектов», «объектов»-списков, визуальное построение форм и многое другое являются просто встроенными возможностями языка и среды разработки. В VB нет главного — возможности создания своих классов и наследования свойств и методов у уже существующих классов. А расширение возможностей осуществляется только за счет подключения библиотек настоящих объектов, реализованных с помощью других языков программирования, в частности Object Pascal или C++, или создания экземпляров классов, предоставляемых операционной системой (например, создание экземпляра Excel’а).
Описание классов в Object Pascal
Для создания классов в Object Pascal введена конструкция class:
Type TmyClassA = class(имя класса-родителя) End;
TmyClassB = class End;
«Так просто?» — скажете вы. Да, действительно просто — мы создали новый тип данных — класс TmyClassA ( TmyClassB ). И хотя мы еще ничего не сказали о том, что это за класс объектов, как будут вести себя объекты этого класса, какие они будет иметь свойства и многое другое, это все же настоящий класс объектов, который умеет уже очень многое!
Дело в том, что в Object Pascal все классы наследуются от базового класса TObject , который представляет из себя корень всей будущей иерархии классов, эдакий «первокирпичик». Этот «первокирпичик» умеет создавать свои экземпляры, уничтожать их, давать информацию о своем имени, имеет диспетчер сообщений и еще массу полезных функций, без которых не может существовать ни один класс. Если в заголовке класса не указан «родитель», то класс просто наследуется от TObject . Т.е. описание класса TmyClassB в данном случае равносильно следующей конструкции:
Type TmyClassB = class(TObject) End;
Попробуем теперь наш класс в действии. Но для начала следует подготовить плацдарм для боевых действий.
Запускаем Delphi, создаем новый проект (File -> New application). Затем убираем из проекта модуль Unit1 (Project -> Remove from project, выбираем Unit1 и нажимаем OK). В проекте остается только файл project.dpr. Стираем весь текст и оставляем только следующие строки:
Program ClassTest; Uses Windows; Begin End.
Все готово. Обратите внимание на 2-ю строку: < $AppType Console >. Это — команда компилятору собрать консольное приложение, т.е. приложение, работающее в текстовом режиме, подобно старым DOS-программам, написанным на Turbo Pascal. При этом программа остается полноценным Win32-приложением. Этого же результата можно добиться, открыв Опции проекта (Project -> Options…) и на вкладке Linker поставить галочку в пункте Generate console application. Таким образом мы на время избавили себя от необходимости изучения графического интерфейса Windows и получили простую возможность вывести результаты нашей работы на дисплей.
Ну а теперь приступим к испытаниям. Описание класса подобно проекту автомобиля, по которому на заводе собирают готовые экземпляры автомобилей. На проекте ездить нельзя :), а вот на автомобиле, собранном по этому проекту — пожалуйста. Точно так же следует поступить и нам:
Type TmyClassA = class(TObject) End; TmyClassB = class End; Var ObjA: TmyClassA; < Описание переменной типа TmyClassA >ObjB: TmyClassB; < Описание переменной типа TmyClassB >Begin ObjA := TmyClassA.Create; ObjB := TmyClassB.Create;
Мы создали экземпляры классов TmyClassA и TmyClassB (или собственно объекты) при помощи конструктора Create . Вообще каждый класс имеет конструктор (один или несколько) и деструктор. Цель конструктора — создание экземпляра (объекта) указанного класса. Ну а цель деструктора, как вы догадались — уничтожение объекта, надобность в котором отпала. В данном случае мы воспользовались конструкторами, унаследованными обоими классами от базового класса TObject. Конструктор класса создает объект в динамической памяти и возвращает ссылку на него, которую мы помещаем в переменную ObjА ( ObjB ).
У каждого класса есть имя и родительский класс, которые можно получить, вызвав методы ClassName (Имя Класса) и ClassParent (Родительский Класс):
Writeln('Class name:',objA.Classname, ' ', 'Parent:',objA.ClassParent.ClassName); Writeln('Class name:',objB.Classname, ' ', 'Parent:',objB.ClassParent.ClassName); readln;
Как мы видим, оба класса имеют «родителя» — TObject . Естественно, возникает вопрос, а каким образом мы можем узнать, в каких «родственных отношениях» находятся созданные объекты? Это можно узнать несколькими способами.
при помощи методов ClassType (Тип Класса) и InheritsFrom (Унаследован От):
Writeln('Is the ObjA instance of TmyClassA?', ObjA.ClassType = TmyClassA); Writeln('Is the ObjA instance of TmyClassB?', ObjA.ClassType = TmyClassB); Writeln('Is the ObjA instance of TObject?', ObjA.ClassType = TObject); Writeln('Is the ObjA descendant of TObject?', ObjA.InheritsFrom(TObject)); Writeln('Is the ObjA descendant of TmyClassB?', ObjA.InheritsFrom(TmyClassB));
при помощи оператора is :
Writeln('Is the ObjA instance of myClassA?', ObjA is TmyClassA); Writeln('Is the ObjA descendant of TObject?', ObjA is TObject);
Следует отметить, что выражение:
Writeln('Is the ObjA instance of myClassB?', ObjA is TmyClassB);
будет отметено компилятором еще во время компиляции, т.к. ObjA действительно не может быть экземпляром класса TmyClassB . Зато возможна следующая ситуация:
var Obj1, Obj2: TObject; begin Obj1 := TmyClassA.Create; Obj2 := TmyClassB.Create;
Мы описали две переменных типа TObject , в которые затем поместили ссылки на созданные экземпляры классов TmyClassA и TmyClassB . Да-да, и такая ситуация возможна! Представьте себе две коробки, на которых написано — «Яблоки». В одну мы положим антоновку, а в другую — белый налив. И это будет вполне естественно. А вот положить сливы в коробку из-под яблок будет несколько нечестно :). В таком случае мы можем не знать, какие яблоки лежат в коробках, но мы точно знаем, что лежат там именно яблоки.
В этой ситуации компилятор спокойно «переварит» приведенный текст:
Writeln('Is the Obj1 instance of myClassA?', Obj1 is TmyClassA); Writeln('Is the Obj1 descendant of TObject?', Obj1 is TObject); Writeln('Is the Obj1 instance of myClassB?', Obj1 is TmyClassB);
так как реальный тип объекта, содержащийся в Obj1 , еще не известен, но точно известно, что это будет наследник от TObject . A т.к. TmyClassB тоже унаследован от TObject , то последняя строчка вполне корректна.
Уфф! Наработались? Теперь надо за собой убрать.
ObjA.Destroy; ObjB.Destroy;
Destroy — это деструктор. Деструктор производит все завершающие действия по корректному завершению работы объекта и затем уничтожает его. После вызова деструктора ссылка, лежащая в переменной ObjA (ObjB) становится недействительной, и ее использование может привести к непредсказуемым результатам!
Но непосредственное использование деструктора в Object Pascal не рекомендуется. Более правильно для уничтожения объекта использовать метод free :
ObjA.free; ObjB.free;
Free проверяет, не является ли ссылка на объект пустой ( nil ), и если ссылка не пуста, вызывает деструктор.
Но пока классы TmyClassA и TmyClassB остаются вещью в себе. Чтобы экземпляры наших новых классов обрели хоть какой-то смысл, надо наполнить их содержимым. Этим мы и займемся далее.
Пример реализации класса очередь на Object Pascal
Одной из наиболее часто решаемых задач в программировании является работа с различными очередями, стеком, массивами. Очереди, конечно, всем знакомы :), каждому из нас приходилось встречаться с ними в своей жизни. В компьютерном мире с очередями приходится сталкиваться повсеместно — это очередь запросов серверу баз данных, очередь сетевых сообщений, очередь сообщений ОС, очередь обслуживание процессов и еще много-много других очередей. Но любая очередь характеризуется тем, что любой объект, помещенный в очередь первым, первым же из нее и выйдет, тогда как объект, оказавшийся в очереди позже всех, покинет ее последним.
Стек — аналог стопки бумаг, его логика работает наоборот — первый объект, положенный в стек, будет забран из стека в самую последнюю очередь. А объект, который оказался на вершине стека, будет забран из стека первым. Области применения стека самые различные. Самый важный стек — это стек вызовов подпрограмм центрального процессора. Перед выполнением подпрограммы (функции) процессор помещает в стек адрес возврата в основную программу. После завершения подпрограммы или вычисления функции CPU берет с вершины стека адрес возврата и возвращается в исходную точку. Вызовы подпрограмм могут быть вложенными, но благодаря наличию стека процессор всегда «знает», куда ему следует вернуться. Есть и другие области применения стека — лексические и синтаксические анализаторы, вычисление выражений, рекурсии и многое другое.
Реализовать стек и очередь можно и в обычном, процедурном Pascal’е. Но в Object Pascal эта задача решается гораздо проще и даже интереснее.
Итак, что же представляет из себя очередь? Это некий массив данных определенного размера m, который характеризует максимальную длину очереди. В каждый момент времени очередь может содержать n объектов, причем n n = 0 . Каждый новый объект помещается в хвост очереди, т.е. в конец массива, а извлекаются объекты с самого начала массива. После извлечения объекта из очереди весь массив сдвигается к началу.
Ну что ж, пора взяться за реализацию класса «очередь» или TQueue (символом T в начале имени принято обозначать названия типов, а символом P — типы -указатели, в отличие от названия переменной).
Создадим пустой проект Queues.dpr (как это сделать, было описано ранее):
program Queues; uses Windows, SysUtils; < модуль SysUtils содержит множество вспомогательных функций >type < Описание массива целых чисел и указателя на этот массив >TIntArray = array[1..65535] of Integer; PIntArray = ^TIntArray;
Первым делом следует объявить тип-массив, содержащий элементы очереди. В нашем случае это будет очередь целых чисел, поэтому массив тоже будет содержать целые числа. Т.к. максимальная длина очереди на данном этапе нам неизвестна (ее длина будет указываться во время исполнения программы, при создании очереди), то фактический размер массива нам тоже неизвестен. Чтобы избежать данной неопределенности, мы объявляем массив максимально возможного размера, а в дальнейшем создадим массив нужного нам размера в динамической памяти.
Далее объявляем класс:
TQueue = class Capacity: Integer; // Максимальная длина очереди Size: Integer; // Текущая длина очереди (кол-во элементов) Items: PIntArray; // Указатель на массив элементов. End;
Вспомним, какие действия нам надо произвести над очередью? Поместить в очередь новый элемент ( Push ) и извлечь из очереди элемент ( Pop ). Эти действия называются методами, и похожи на обычные процедуры и функции Паскаля, но помещаются они внутри описания класса:
TQueue = class Capacity: Integer; // Максимальная длина очереди Size: Integer; // Текущая длина очереди (кол-во элементов) Items: PIntArray; // Указатель на массив элементов. Procedure Push(Value: Integer); Function Pop:Integer; End;
Еще нам может понадобиться проверка, не является ли очередь пустой. Ну и, конечно же, мы забыли о конструкторе и деструкторе. Именно они «знают», как правильно выделить память для очереди и затем ее уничтожить. Конструктор и деструктор объявляются соответствующими ключевыми словами:
TQueue = class Capacity: Integer; // Максимальная длина очереди Size: Integer; // Текущая длина очереди (кол-во элементов) Items: PIntArray; // Указатель на массив элементов. // при создании очереди мы указываем ее максимальный размер Constructor Create(aCapacity:Integer); Destructor Destroy; override; Procedure Push(Value: Integer); Function Pop:Integer; Function IsEmpty:Boolean; End;
Обратите внимание на директиву override . Это указание на то, что новый деструктор перекрывает старый, который класс TQueue унаследовал от своего «родителя» TObject . В результате этого везде, где ранее вызывался деструктор Destroy класса TObject , применительно к классу TQueue будет вызываться TQueue.Destroy . Более подробно об этом мы узнаем, когда будем рассматривать виртуальные методы.
Теперь следует реализация класса:
< TQueue >constructor TQueue.Create(aCapacity: Integer); begin Inherited Create;
Ключевое слово Inherited (Унаследованный) перед именем метода означает, что нам требуется вызвать метод с тем же именем, но который достался нам в наследство от «родителя», в противном случае у нас получится обыкновенная рекурсивная процедура, которая будет бесконечно вызывать саму себя. Вспомним, что унаследованный конструктор TObject.Ctreate выполняет необходимые действия по инициализации экземпляра класса.
Capacity := aCapacity; < Получить необходимую память >GetMem(Items, aCapacity*SizeOf(Integer)); < Очистка "мусора" >ZeroMemory(Items, aCapacity*SizeOf(Integer)); end;
Деструктор освободит выделенную память и корректно завершит работу экземпляра класса:
destructor TQueue.Destroy; begin FreeMem(Items); Inherited Destroy; end;
Теперь следует реализация методов Push , Pop и IsEmpty :
procedure TQueue.Push(Value: Integer); begin if Size < Capacity // Если позволяет место, then begin Inc(Size); // увеличить длину очереди Items[Size] := Value; // и поместить в ее конец новый элемент. end; end; function TQueue.Pop: Integer; begin if Size >0 // Если очередь непустая, then begin Result := Items[1]; // вернем первый элемент, Dec(Size); // уменьшим длину очереди // и переместим все оставшиеся элементы к началу очереди Move(Items[2], Items[1], Size*SizeOf(Integer)); end; end; function TQueue.IsEmpty: Boolean; begin IsEmpty := (Size = 0); < Или Result := (Size = 0); >end;
Следует отметить, что в Object Pascal результат выполнения функции можно помещать в специальную локальную переменную Result . Помимо удобства, переменная Result обладает еще одним полезным качеством — она позволяет считывать из нее помещенное значение, что позволяет избавиться от лишних временных переменных.
Но мы забыли о стеке. Стек очень похож на очередь, только метод извлечения элемента у него несколько другой. Опишем класс TStack , который будет почти полной копией TQueue , за исключением метода Pop :
Type TStack = class . function Pop: Integer; . End; function TStack.Pop: Integer; begin if Size > 0 // Если стек непустой, then begin Result := Items[Size]; // вернем последний элемент, Dec(Size); // уменьшим длину стека end; end; А теперь опробуем наши новые классы в действии: procedure PrintQueue(Queue: TQueue); var i:Integer; Begin Write(' Size: ', Queue.Size,'; '); Write(' Items: '); if Queue.IsEmpty then Write('no items.') else for i:=1 to Queue.Size do Write(Queue.Items[i],' '); Writeln; End; procedure PrintStack(Stack: TStack); var i:Integer; Begin Write(' Size: ', Stack.Size,'; '); Write(' Items: '); if Stack.IsEmpty then Write('no items.') else for i:=1 to Stack.Size do Write(Stack.Items[i],' '); Writeln; End; Var Stack: TStack; Queue: TQueue; I, Value: Integer; Begin Stack := TStack.Create(5); // Выделим место для хранения 5-ти элементов Queue := TQueue.Create(5); // Выделим место для хранения 5-ти элементов // Заполним стек For i:=1 to 5 do Begin Write('Enter element:'); readln(Value); Stack.Push(Value); PrintStack(Stack); End; // Теперь извлечем элементы из стека while not Stack.IsEmpty do begin Write(Stack.Pop); PrintStack (Stack); End; // Заполним очередь For i:=1 to 5 do Begin Write('Enter element:'); readln(Value); Queue.Push(Value); PrintQueue(Queue); End; // Теперь извлечем элементы из очереди while not Queue.IsEmpty do begin Write(Queue.Pop); PrintQueue(Queue); End; Queue.free; Stack.free; End.
Все работает! Но… Что-то здесь не так… Чем же написанная программа лучше обычной процедурной программы? Все та же громоздкость, та же незащищенность внутреннего устройства класса от внешних «посягательств». Неужели это все, на что способны классы? Конечно, нет! Мы забыли о двух главных вещах — возможности наследования общих свойств и методов классов, и инкапсуляции, т.е. сокрытии внутренних деталей работы класса.