Объектно-ориентированное программирование
Выбери формат для чтения
Загружаем конспект в формате doc
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Конспект лекций
по дисциплине “Объектно-ориентированное программирование”
Содержание
Глава 1. Введение в объектно-ориентированную методологию разработки программ
§1. Различные технологии программирования
§2. Принципы объектно-ориентированного программирования
Глава 2. Классы и объекты
§1. Введение
§2. Объекты
§3. Иерархия классов
§4. Методы
§5. Основные принципы ООП
§6. Статические методы
§7. Виртуальные методы. Конструкторы
§8. Совместимость классов
§9. Классы и модули
§10. Некоторые рекомендации по созданию классов.
Рекомендуемая литература
Приложение. Фрагмент лекций по дисциплине “Программирование на языках высокого уровня. Часть 1”
Глава 1.
Введение в объектно-ориентированную методологию
разработки программ.
В этой главе Вы, уважаемый слушатель, познакомитесь с основными особенностями и понятиями объектно-ориентированной методологии программирования. До этого момента Вас обучали программированию с использованием так называемого структурного подхода к разработке программ (структурной методологии программирования), как например, в курсе “Программирование на языках высокого уровня”.
Настройтесь на чтение данной главы так, как будто Вы читаете какую-либо познавательную статью из журнала с целью просто ознакомиться, что новенького (для Вас) существует в области программирования. Не пытайтесь досконально понять и изучить все, что в ней написано. Далее, начиная со второй главы, мы с Вами будем подробно изучать упомянутые здесь термины и постепенно, шаг за шагом постигать разработку программ (в знакомой Вам системе Турбо Паскаль) с использованием объектно-ориентированного подхода.
§1. Различные технологии программирования
При изучении объектно-ориентированного программирования (ООП) наибольшей проблемой является использование новой терминологии и понимание нового подхода к решению старых задач - новой технологии программирования. Определения новых терминов и характеристики методов программирования составляют содержание данной темы.
Как в любом виде деятельности в программировании имеется своя технология: это знания, правила, навыки и инструменты, позволяющие получать гарантированный качественный результат. Но само по себе соблюдение ряда правил не дает гарантию качества результата. Это объясняется спецификой программирования. Во-первых, это не наука, где знание какой-либо формулы позволяет однозначно решить задачу, подставив в нее исходные данные и получив результат. Во-вторых, эти правила необходимо соблюдать не столько на бумаге, сколько в голове. То есть технология программирования - это скорее способ организации процесса обдумывания программы, нежели ее записи. Из сказанного следует, что если пишущий программу – мыслит, то он уже придерживается какой-то технологии программирования, даже не подозревая об этом. Простейший метод заключается в написании программы сразу от начала до конца, без использования каких-либо общих принципов.
Рассмотрим наиболее известные из технологий:
• метод "северо-западного" угла (имеется в виду лист бумаги или экран дисплея). Программа пишется сразу от начала до конца, без использования каких-либо общих принципов;
• технология структурного программирования, в ней предполагается придерживаться принципов модульности, нисходящего и пошагового проектирования программ, одновременного проектирования программ и структур данных.
• технология объектного программирования: связана с использованием при проектировании программы понятий объектов и их классов.
В традиционной технологии программирования взаимоотношения процедуры – данные имеют более-менее свободный характер, причем процедуры (функции) являются ведущими в этой связке: как правило, функция вызывает функцию, передавая данные друг другу по цепочке. Соответственно, технология структурного проектирования программ, прежде всего, уделяет внимание разработке алгоритма.
В технологии ООП взаимоотношения данных и алгоритма имеют более регулярный характер: во-первых, класс (базовое понятие этой технологии) объединяет в себе данные (структурированная переменная) и методы (функции). Во-вторых, схема взаимодействия функций и данных принципиально иная. Метод (функция), вызываемый для одного объекта, как правило, не вызывает другую функцию непосредственно. Для начала он должен иметь доступ к другому объекту (создать, получить указатель, использовать внутренний объект в текущем и т.д.), после чего он уже может вызвать для него один из известных методов. Таким образом, структура программы определяется взаимодействием объектов различных классов между собой. Как правило, имеет место иерархия классов, а технология ООП иначе может быть названа как программирование "от класса к классу".
Модульное программирование. Принцип модульности формулируется как требование разработки программы в виде совокупности модулей (функций). При этом разделение на модули должно носить не механический характер, а исходить из логики программы:
• размер модуля должен быть ограничен;
• модуль должен выполнять логически целостное и завершенное действие;
• модуль должен быть универсальным, то есть по возможности параметризованным: все изменяемые характеристики выполняемого действия должны передаваться через параметры;
• входные параметры и результат модуля желательно передавать не через глобальные переменные, а через формальные параметры и результат функции.
Еще одной, но уже физической единицей программы является текстовый файл, содержащий некоторое количество функций и определений типов данных и переменных. Модульное программирование на уровне файлов – это возможность разделить полный текст программы на несколько файлов, транслировать их независимо друг от друга.
Принцип модульности распространяется не только на программы, но и на данные: любой набор параметров, характеризующих логический или физический объект, должен быть представлен в программе в виде единой структуры данных (структурированной переменной).
Олицетворением принципа модульности является библиотека стандартных функций. Она, как правило, обеспечивает полный набор параметризованных действий, используя общие структуры данных. Библиотеки представляют собой аналогичные Си-программы, независимо оттранслированные и помещенные в каталог библиотек.
Нисходящее программирование. Нисходящее проектирование программы заключается в том, что разработка идет от общей неформальной формулировки некоторого действия программы на естественном языке, "от общего к частному", к замене ее одной из трех формальных конструкций языка программирования:
• простой последовательности действий;
• конструкции выбора или оператора if;
• конструкции повторения или цикла.
В записи алгоритма это соответствует движению от внешней (объемлющей) конструкции к внутренней (вложенной). Эти конструкции также могут содержать в своих частях неформальное описание действий, то есть нисходящее проектирование по своей природе является пошаговым. Отметим основные свойства такого подхода:
◦ первоначально программа формулируется в виде некоторого неформального действия на естественном языке;
◦ первоначально определяются входные параметры и результат действия;
◦ очередной шаг детализации не меняет структуру программы, полученную на предыдущих шагах;
◦ если в процессе проектирования получаются идентичные действия в различных ветвях, то это означает необходимость оформления этого действия отдельной функцией;
◦ необходимые структуры данных проектируются одновременно с детализацией программы.
В результате проектирования получается программа, в которой принципиально отсутствует оператор перехода goto, поэтому такая технология называется "программирование без goto".
Пошаговое программирование. Нисходящее проектирование по своей природе является пошаговым, ибо предполагает каждый раз замену одной словесной формулировки на единственную конструкцию языка. Но в процессе разработки программы могут быть и другие шаги, связанные с детализацией самой словесной формулировки в более подробную.
То, что этот принцип выделен отдельно, говорит о необходимости предотвратить соблазн детализации программы сразу от начала до конца и развивать умение выделять и сосредоточивать внимание на главных, а не второстепенных деталях алгоритма.
Вообще нисходящее пошаговое проектирование программы не дает гарантии получения "правильной" программы, но позволяет возвратиться при обнаружении тупиковой ситуации к одному из верхних шагов детализации.
Структурное программирование. При нисходящей пошаговой детализации программы необходимые для работы структуры данных и переменные появляются по мере перехода от неформальных определений к конструкциям языка, то есть процессы детализации алгоритма и данных идут параллельно. Однако это касается, прежде всего, отдельных локальных переменных и внутренних параметров. С самой же общей точки зрения предмет (в нашем случае - данные) всегда первичен по отношению к выполняемым с ним действиям (в нашем случае - алгоритм). Поэтому на самом деле способ организации данных в программе более существенно влияет на ее структуру алгоритма, чем что-либо другое, и процесс проектирования структур данных должен опережать процесс проектирования алгоритма их обработки.
Структурное программирование – это модульное нисходящее пошаговое проектирование алгоритма и структур данных.
§2. Принципы объектно-ориентированного программирования
Центральными в ООП являются понятия класса и объекта. Образно говоря, ООП заключается не столько в использовании классов и объектов в программе, сколько в замене принципа программирования "от функции к функции" принципом программирования "от класса к классу".
Технология ООП прежде всего накладывает ограничения на способы представления данных в программе. Любая программа отражает в них состояние физических предметов либо абстрактных понятий (назовем их объектами программирования), для работы с которыми она предназначена. В традиционной технологии варианты представления данных могут быть разными. В худшем случае программист может "равномерно размазать" данные о некотором объекте программирования по всей программе. В противоположность этому все данные об объекте программирования и его связях с другими объектами можно объединить в одну структурированную переменную. В первом приближении ее можно назвать объектом. Кроме того, с объектом связывается набор действий, иначе называемых методами. С точки зрения языка программирования это процедуры или функции, получающие в качестве обязательного параметра указатель на объект. Технология ООП запрещает работать с объектом иначе, чем через методы, то есть внутренняя структура объекта скрыта от внешнего пользователя. Описание множества однотипных объектов называется классом.
Объект – структурированная переменная, содержащая всю информацию о некотором физическом предмете или реализуемом в программе понятии.
Класс – описание множества таких объектов и выполняемых над ними действий.
В Паскале класс обладает синтаксическими свойствами базового типа данных:
• класс определяется как структурированный тип данных;
• объекты определяются как переменные класса;
• возможно переопределение и использование стандартных операций языка, имеющих в качестве операндов объекты класса, в виде особых методов в этом классе.
Приведем пример описания класса и объекта.
Type
TPoint= object
x: integer;
y: integer;
procedure Draw;
end;
Var p: TPoint;
Здесь TPoint является классом, а переменная p этого класса – экземпляром класса или объектом. В программе использование объектной переменной можно осуществить следующим образом.
begin
p.x:=100;
p.y:=200;
p.Draw;
end.
"Эпизодическое" использование технологии ООП заключается в разработке отдельных, не связанных между собой классов и использовании их как необходимых программисту базовых типов данных, отсутствующих в языке. При этом общая структура программы остается традиционной. ("от функции к функции").
Объектно-ориентированное программирование (ООП) – это совокупность понятий (класс, объект, инкапсуляция, полиморфизм, наследование), приемов их использования при проектировании программ, а конкретный язык программирования – инструмент этой технологии.
Строгое следование технологии ООП предполагает, что любая функция в программе представляет собой метод для объекта некоторого класса. Это не означает, что нужно вводить в программу какие попало классы ради того, чтобы написать необходимые для работы функции. Наоборот, класс должен формироваться в программе естественным образом, как только в ней возникает необходимость описания новых физических предметов или абстрактных понятий (объектов программирования). С другой стороны, каждый новый шаг в разработке алгоритма также должен представлять собой разработку нового класса на основе уже существующих. В конце концов вся программа в таком виде представляет собой объект некоторого класса с единственным методом run (выполнить). Именно этот переход (а не понятия класса и объекта, как таковые) создает психологический барьер перед программистом, осваивающим технологию ООП.
Программирование "от класса к классу" включает в себя ряд новых понятий. Прежде всего, это – инкапсуляция данных, то есть логическое связывание данных с конкретной операцией. Инкапсуляция данных означает, что данные являются не глобальными – доступными всей программе, а локальными – доступными только малой ее части. Это определение можно проиллюстрировать следующим примером. Опишем класс, реализующий точку на плоскости.
Type
TPoint= object
x: integer; //поле
y: integer;
procedure Draw; //метод
end;
Класс инкапсулирует данные и код, что позволяет использовать процедуру прорисовки точки (Draw) с координатами x, y из данного класса. Это устраняет ошибки и неточности при использовании данной процедуры.
Вторым по значимости понятием является наследование. Новый, или производный, класс может быть определен на основе уже имеющегося, или базового. При этом новый класс сохраняет все свойства старого: данные объекта базового класса включаются в данные объекта производного, а методы базового класса могут быть вызваны для объекта производного класса, причем они будут выполняться над данными включенного в него объекта базового класса. Иначе говоря, новый класс наследует как данные старого класса, так и методы их обработки. Приведем пример нового класса TColorPoint (цветная точка), который создается на основе уже существующего класса TPoint.
Type
TColorPoint= object (TPoint)
Color: integer;
procedure Draw;
end;
Третьим по значимости понятием является полиморфизм. Он основывается на возможности включения в данные объекта также и информации о методах их обработки (в виде указателей на функции). Принципиально важно, что такой объект становится "самодостаточным". Будучи доступным в некоторой точке программы, даже при отсутствии полной информации о его типе, он всегда может корректно вызвать свойственные ему методы. Полиморфной называется функция, независимо определенная в каждом из группы производных классов и имеющая в них общее имя. Полиморфная функция обладает тем свойством, что при отсутствии полной информации о том, объект какого из производных классов в данный момент обрабатывается, она тем не менее корректно вызывается в том виде, в каком она была определена для данного конкретного класса. Практический смысл полиморфизма заключается в том, что он позволяет посылать общее сообщение о сборе данных любому классу, причем и родительский класс, и классы-потомки ответят на сообщение соответствующим образом, поскольку производные классы содержат дополнительную информацию. Программист может сделать регулярным процесс обработки несовместимых объектов различных типов при наличии у них такого полиморфного метода.
Глава 2.
Классы и объекты.
§1. Введение.
Введем обозначения:
ОО – объектно-ориентированное(ая, ый, ых, …);
ООМ – объектно-ориентированная методология;
ООП – объектно-ориентированное программирование.
По определению признанного авторитета в области ОО методов разработки программ Гради Буча:
“ООП – это методология программирования, которая основана на представлении программы в виде совокупности объектов, каждый из которых является реализацией определённого класса (типа особого вида), а классы образуют иерархию на принципах наследуемости.”
Когда Вы уже немного освоитесь в ООП, рекомендую еще раз прочитать это определение, чтобы убедиться насколько оно точно его характеризует.
ООМ преследует те же цели, что и структурная методология:
- дисциплинировать процесс программирования;
- упростить разработку больших программных комплексов, что влечет за собой уменьшение вероятности ошибок, ускорение процесса разработки и, в результате, снижение стоимости готового продукта.
Однако ООМ в большинстве случаев позволяет управлять более сложными проектами. ООМ направлена на создание интерфейсных систем, создание больших диалоговых программных комплексов.
На практике, обычно, при разработке крупных проектов применяют оба подхода: сначала объектно-ориентированный, затем – структурный.
Объектно-ориентированная модель – это описание как самих объектов, так и способа их взаимодействия.
§2. Объекты.
В терминах Паскаля, объекты считаются близкими к типу Запись (Record). Как и запись, тип Объект (Object) является комбинированным типом (т.е. объединяет в себе элементы (поля) разных типов).
Например, в отличие от массива, который включает элементы одного типа. Кстати, вспомнить про Запись можно посмотрев лаб.работу №5 и соответствующий раздел лекций по дисциплине “Программирование на языках высокого уровня, ч.1”.
Однако структура типа Object является более совершенной структурой данных.
Основные отличия типов Record и Object:
1. Описание:
Например,
Type
tRec = record tObj = object
x, y: integer; x, y: integer;
Data: longint; Data: longint;
end; end;
Обратите внимание: в типе Запись после перечисления всех полей перед закрывающим словом end знак ; можно не писать, т.е. можно записать так tRec = record
x, y: integer;
Data: longint
end;
В типе Объект описание всех полей обязательно должно заканчиваться знаком ; (даже если это последнее поле перед end;), иначе компилятор (программа, которая переводит текст программы в машинные коды) выдаст ошибку.
2. В описание записи включаются только поля данных, а в описание объекта можно включать еще и действия, которые могут производиться с этими полями. Эти действия называют методами. Методы оформляются как обычные процедуры и функции.
Например,
tObj = object
x, y: integer;
Data: longint;
Procedure Init;
Function GetX:integer;
…
end;
Procedure tObj.Init;
begin
x:=12; y:=25;
end;
Function tObj.GetX:integer;
begin
GetX:=x;
end;
В описании объекта располагается только заголовок метода, а само тело метода пишется в разделе описаний до начала основной программы.
О методах подробнее поговорим позже.
Свойство (способность) объекта объединять в себе поля и методы называют инкапсуляцией.
Определение. Объект – это объединение полей данных различных типов и методов обработки этих данных, представленных в виде процедур и функций.
Рассмотрим пример.
Пример 2.1 Пусть в дальнейшем нам необходимо написать ОО программу, работающую с графическими объектами (рисует, перемещает). В данном примере рассмотрим и опишем два объекта: некий абстрактный объект – фигуру и реальный объект – точку:
tLocation – фигура;
tPoint – точка.
Что есть общего у всех фигур? Пусть, например, это будет центр фигуры – координаты x, y. К объекту точка добавим, кроме координат, еще и цвет (color).
Type
tLocation = object
x, y: integer;
end;
tPoint = object
pos: tLocation;
color: word;
end;
var Loc: tLocation; экземпляр объекта tLocation
pt: tPoint; экземпляр объекта tPoint
begin
Loc.x:= 25; Loc.y:= 30;
pt.pos.x:= 42; pt.pos.y:= 53;
pt.color:= 152;
end.
Давайте теперь разберемся с терминологией, т.е. что и как мы в дальнейшем будем называть.
В литературе используется две терминологии:
1. Тип в разделе Type называют объектом, а переменную этого типа –экземпляром объекта.
2. Тип в разделе Type называют классом, а переменную этого типа –объектом.
Таким образом, если в какой-либо литературе Вы встретили термин “экземпляр объекта”, то должны понимать, что используется терминология 1. Это очень важно.
Давайте далее будем придерживаться терминологии 2.
Еще раз разберем термины на нашем примере 2.1:
tLocation, tPoint – классы
Loc, pt – объекты классов:
Loc – объект класса tLocation;
pt – объект класса tPoint;
Класс существует в единичном экземпляре, а объектов какого-либо класса может быть сколько угодно.
Если сравнивать с уже знакомой Вам терминологией из Паскаля, то класс – это тип (например, integer), а объект – это переменная данного типа (которых в программе можно объявить столько, сколько нужно).
§3. Иерархия классов.
Класс может наследовать компоненты (элементы) из другого класса. Наследующий класс (дочерний) называется потомком, а наследуемый класс (родительский) – предком.
! Процедура наследования относится только к классам, но не к объектам.
Потомки содержат все поля и методы предков.
При создании нового класса описываются только новые поля и методы.
Если в родительском классе поле изменено, то у потомков это поле тоже изменено.
Ни у одного потомка не должно быть полей, имена которых совпадают с именами полей любого из предков. Имена методов могут совпадать.
Изменим пример 2.1. , применив принцип наследования.
Пример. 2.2
Type
tLocation = object класс tLocation
x, y: integer;
end;
tPoint = object (tLocation) класс tPoint
color: word;
end;
var Loc: tLocation; объект класса tLocation
pt: tPoint; объект класса tPoint
begin
Loc.x:= 25; Loc.y:= 30;
pt.x:= 42; pt.y:= 53;
pt.color:= 15;
end.
Что мы сделали? Мы объявили класс tPoint наследником класса tLocation. Для этого достаточно указать после слова object в круглых скобках имя родительского класса.
Что нам это дало? Теперь все поля (и методы, но в нашем примере их нет) родительского класса унаследовались дочерним классом, т.е., хотя в классе tPoint поля x и y не описаны явно, но эти поля в данном классе есть. Кстати, обратите еще раз внимание на принятую нами терминологию.
Давайте теперь представим (грубо) как будут храниться в памяти объекты (переменные) Loc и pt, и что в их полях будет записано после выполнения нашей программы:
объект Loc
объект pt
x
25
x
42
y
30
y
53
color
15
Принцип наследования является основным отличием ООП от структурного программирования.
Построение новых классов, наследующих все компоненты уже известных, может продолжаться бесконечно. В результате получается иерархия классов.
Что в этом хорошего? Любой программист начинает создавать программу не “с нуля”, а использует уже существующие, кем-то написанные ранее, полностью готовые к работе классы. (Класс, обычно, описывается в отдельном модуле и является самодостаточным готовым программным продуктом). Если программист не находит такой класс, который бы полностью удовлетворял требованиям поставленной задачи, то он может создать свой (новый) класс, но опять же не “с нуля”, а дополнив необходимым уже существующие классы. Для этого надо просто объявить свой класс наследником и добавить все необходимое. Создавая программы таким образом, т.е. используя и дополняя уже готовые отлаженные программные заготовки, на разработку программы затрачивается гораздо меньше времени, возникает меньше ошибок, а это все приводит к снижению стоимости программных продуктов.
Можно построить, например, такую иерархию графических объектов:
В лабораторных работах по данной дисциплине Вы как раз и будете работать с подобной иерархией классов. Классы, также как и другие типы, константы и переменные рекомендуется называть осмысленно. Так, например,
Фигура – класс tLocation
Точка – класс tPoint
Эллипс – класс tEllipse
Прямоугольник – класс tRect (на английском прямоугольник - Rectangel)
Иерархия строится от общего к частному. В данной иерархии общее – центр фигуры, координаты точки x, y, т.к. любую фигуру можно построить через центр.
Классы, не имеющие возможности иметь конкретные объекты, называются абстрактными (это классы верхних уровней иерархии). Методы этих классов также являются абстрактными.
Прежде, чем перейти к следующему параграфу, еще раз перечислим правила наследования.
Правило 1. Поля и методы родительского класса наследуются всеми его дочерними классами независимо от числа промежуточных уровней иерархии.
Правило 2. Доступ к полям и методам родительских классов в рамках описания любых дочерних классов выполняется так, как будто-бы они описаны в самом дочернем классе.
Отсюда следует Правило 3.
Правило 3. Ни в одном из дочерних классов не могут использоваться идентификаторы (имена) полей, совпадающие с идентификаторами полей какого-либо из родительских классов. Это же относится и к идентификаторам формальных параметров, указанным в заголовках методов.
Правило 4. Дочерний класс может доопределить произвольное число собственных методов и полей.
Правило 5. Любое изменение текста в родительском методе автоматически оказывает влияние на все методы порожденных дочерних классов, которые его вызывают.
Правило 6. В противоположность полям (см. правило 3) идентификаторы методов в дочерних классах могут совпадать с именами методов в родительских классах. В этом случае говорят, что дочерний метод подавляет одноименный ему родительский, и в рамках дочернего класса, при указании имени такого метода, будет вызываться именно дочерний метод, а не родительский.
Однако возможность обращения к подавленному родительскому методу остается с помощью указания перед именем метода (через точку) идентификатора родительского класса.
Например,
tObj = object
x, y: integer;
Data: longint;
Procedure Init;
Function GetX:integer;
…
end;
tObj_2 = object (tObj)
Procedure Init;
end;
Procedure tObj.Init;
begin
x:=12; y:=25;
end;
Procedure tObj_2.Init;
begin
tObj.Init; {обращение к одноименному родительскому методу Init}
x:=x+34; y:=y+48;
end;
Function tObj.GetX:integer;
begin
GetX:=x;
end;
§4. Методы.
Хороший стиль работы с классами исключает прямое обращение к полям. Для работы с полями используют методы.
Метод – это процедура или функция, объявленная внутри класса.
(см. далее пример 2.2.1)
В описании класса располагается только заголовок метода:
procedure (или function) имя метода …;
Объявление первого метода внутри класса располагают после описания всех полей (т.е. сначала надо описать все поля класса, а только затем перечислить заголовки методов, включенных в данный класс).
Тело метода можно описывать сразу после описания своего класса или после описания всех классов (предпочтительнее).
Т.е. лучше сначала описать все классы (чтобы нагляднее прослеживалась иерархия, т.е. кто – родители, кто - наследники), а затем уже начать описывать сами методы: сначала все методы одного класса, затем – другого и т.д.
При описании метода перед его именем необходимо указывать имя класса, к которому он относится (т.е. в котором указан заголовок данного метода):
procedure (или function) имя класса.имя метода …;
Классы и их методы имеют общую область действия в памяти, поэтому в конкретном классе имена формальных параметров методов и имена полей не должны совпадать.
Напоминаю, что формальные параметры – это параметры, которые указаны в круглых скобках после имени метода (процедуры или функции) при его описании, а фактические параметры - которые указаны в круглых скобках после имени метода при его вызове.
Недостатком примера 2.2 является обращение к полям напрямую. Исправим это, добавив метод инициализации полей в класс tLocation.
Пример 2.2.1
Type tLocation = object
x, y: integer;
procedure Init(InitX, InitY: integer);
end;
tPoint = object (tLocation)
color: word;
end;
procedure tLocation.Init (InitX, InitY: integer);
begin
x:=InitX;
y:=InitY;
end;
var Loc: tLocation;
pt: tPoint;
begin
Loc.Init(25,30);
pt.Init(42,53);
pt.Color:=15;
end.
Но и в примере 2.2.1 есть обращение напрямую к полю Color класса tPoint: pt.Color:=15; . Исправим и это:
Пример 2.2.2
Type tLocation = object
x, y: integer;
procedure Init(InitX, InitY: integer);
end;
tPoint = object (tLocation)
color: word;
procedure Init(InitX, InitY: integer; InitC:word);
end;
procedure tLocation.Init (InitX, InitY: integer);
begin
x:=InitX;
y:=InitY;
end;
procedure tPoint. Init (InitX, InitY: integer; InitC:word);
begin
x:=InitX;
y:=InitY;
color:=InitC;
end;
var Loc: tLocation;
pt: tPoint;
begin
Loc.Init(25,30);
pt.Init(42,53,15);
end.
Если в дочернем классе объявлен метод с таким же именем, как и в одном из родительских классов, то говорят, что этот метод перекрывает или подавляет родительский (см. выше Правило 6 наследования).
Перекрытие – это объявление в дочернем классе метода с таким же именем, как и в одном из родительских, но с другим содержанием.
Как мы уже говорили ранее, остается возможность использовать (вызывать) методы родительского класса из методов дочернего класса. Покажем, как это сделать, дополнив Пример 2.2.2:
Пример 2.3
Type tLocation = object
x, y: integer;
procedure Init(InitX, InitY: integer);
end;
tPoint = object (tLocation)
color: word;
procedure Init(InitX, InitY: integer; InitC:word);
end;
procedure tLocation.Init (InitX, InitY: integer);
begin
x:=InitX;
y:=InitY;
end;
procedure tPoint. Init (InitX, InitY: integer; InitC:word);
begin
tLocation.Init(InitX, InitY);
color:=InitC;
end;
var Loc: tLocation;
pt: tPoint;
begin
Loc.Init(25,30);
pt.Init(42,53,15);
end.
В чем разница примеров 2.2.2 и 2.3? В том, что мы использовали то, что ранее уже было написано и проверено. А именно, вместо того, чтобы снова переписывать весь код проверенного и отлаженного метода tLocation.Init, мы его просто вызываем (в методе tPoint.Init), уже не задумываясь об его содержании. Таким образом, мы:
- ускорили разработку программы;
- сократили вероятность ошибок.
Конечно, на данном примере эти преимущества не особенно заметны. Но представьте, если код метода tLocation.Init состоял бы, например, из 100 команд.
Порядок вызова методов.
Когда в программе встречается вызов какого-либо метода, то система осуществляет его поиск (снизу вверх по иерархии) следующим образом: сначала ищет в том классе, объект которого этот метод вызывает: если находит – выполняет, если нет – продолжает поиск метода в родительском классе и так далее вверх по иерархии. Если метод так и не был найден, то выдаётся соответствующее сообщение об ошибке.
При описании метода указывается <имя класса>.<имя метода>;
при вызове метода указывается <имя объекта>.<имя метода>.
§5. Основные принципы ООП.
1. Инкапсуляция;
2. Наследование;
3. Полиморфизм.
1. Инкапсуляция – объединение полей записи с процедурами и функциями, работающими с полями этих записей, которое (объединение) формирует новый тип данных – object.
Говорят, методы инкапсулированы (встроены) в описание класса.
2. Наследование – создание иерархии классов для того, чтобы поля и методы предков автоматически становились полями и методами потомков. Наследование полей отличается от наследования методов (см. правила наследования).
3. Полиморфизм – присвоение определённому действию одного имени, которое затем совместно используется по всей иерархии классов (сверху вниз), причем каждый класс иерархии выполняет это действие характерным именно для него способом.
Инкапсуляцию и наследование мы уже рассмотрели, полиморфизм рассмотрим несколько позже. Хотя перекрытие методов тоже можно считать одним из проявлений полиморфизма.
§6. Статические методы.
Рассмотрим подробнее особенности наследования методов.
Для этого вернёмся к примеру 2.3 и дополним программу движением по экрану точки и эллипса.
Для этого в программу надо добавить:
1. Описание класса: tEllipse
2. Метод движения, назовем его Move, который и будет двигать наши фигуры.
Алгоритм движения фигур по экрану следующий:
- рисуем фигуру цветом фона экрана (т.е. стираем фигуру на “старом” месте экрана);
- изменяем координаты (можно изменять координаты центра фигуры, а затем рисовать их через центр);
- рисуем фигуру заданным цветом (фигура появляется на новом месте экрана).
Если повторить эти действия несколько раз, то будет складываться впечатление, что фигура двигается по экрану.
Теперь запишем как, например, может выглядеть приведенный алгоритм движения на Паскале:
Возможная реализация метода Move:
Begin
Hide; {метод, рисующий фигуру цветом фона}
x:= Newx;
y:= Newy;
Draw; {метод, рисующий фигуру заданным цветом}
End;
Или так:
Begin
Hide; {метод, рисующий фигуру цветом фона}
NewPlace; {метод, изменяющий координаты фигуры}
Draw; {метод, рисующий фигуру заданным цветом}
End;
Итак, перепишем пример 2.3. с учетом новых требований к нашей программе. В описание классов надо будет добавить не только метод Move, но и все другие методы, которые используются (т.е. в нашем случае еще Hide и Draw).
Пример 2.4
Program Pr2_4;
{Раздел описания данных}
Type tLocation = object
x, y: integer;
procedure Init(InitX, InitY: integer);
end;
tPoint = object (tLocation)
color: word;
procedure Init(InitX, InitY:integer; col: word);
procedure Move(NewX, NewY: integer);
procedure Hide;
procedure Draw;
end;
tEllipse = object (tPoint)
Rx, Ry: word;
procedure Init(InitX, InitY: integer; col, NRx, NRy: word);
procedure Move(NewX, NewY: integer);
procedure Hide;
procedure Draw;
end;
{методы класса tLocation}
procedure tLocation.Init(InitX, InitY: integer);
begin
x:= InitX; y:= InitY;
end;
{методы класса tPoint}
procedure tPoint.Init(InitX, InitY:integer; col: word);
begin
tLocation.Init(InitX, InitY: integer);
color:= col;
end;
procedure tPoint.Move(NewX, NewY: integer);
begin
Hide;
x:= NewX;
y:= NewY;
Draw;
End;
procedure tPoint.Hide;
begin
PutPixel(x, y, GetBkColor); {нарисует точку цветом фона}
end;
procedure tPoint.Draw;
begin
Putpixel(x, y, color); {нарисует точку цветом color }
end;
{методы класса tEllipse}
procedure tEllipse.Init(InitX, InitY: integer; col, NRx, NRy: word);
begin
tPoint.Init(InitX, InitY,col);
Rx:= NRx;
Ry:= NRy;
end;
procedure tEllipse.Move(NewX, NewY: integer);
begin
Hide;
x:= NewX;
y:= NewY;
Draw;
End;
procedure tEllipse.Hide;
begin
SetFillStyle(1,GetBkColor);
Setcolor(GetBkcolor);
FillEllipse (x, y, Rx, Ry);
end;
procedure tEllipse.Draw;
begin
SetFillStyle(1,GetBkColor);
Setcolor(color);
FillEllipse (x, y, Rx, Ry);
end;
Var pt: tPoint;
pEl: tEllipse;
{ Раздел описания действий }
Begin
…
pt.Init(34, 48, 8);
pt.Move(56, 120);
pEl.Init(106, 85, 4, 15, 25);
pEl.Move(63, 45);
End.
Следует заметить, что приведенные в лекциях примеры не являются готовыми к работе программами. Например, чтобы что-то нарисовать, надо подключить модуль Graph, установить графический режим – это Вам известно из дисциплины ПЯВУ (лаб.раб. №4). Можете, кстати попробовать. Но сейчас нам это не важно – нам главное разобрать основные моменты работы программы.
Как после компиляции программы компилятор разместит объекты pt и pEl в памяти можно изобразить следующим образом:
Назовем область памяти, отведенной под объект pt – структурой 1, а под объект pEl – структурой 2. И еще, не путайте, мы сейчас говорим, что будет после компиляции программы, т.е. после перевода ее в машинные коды. Т.е. программа еще не работала, поэтому ячейки полей изображаем пустыми. Вместо имен методов уже проставлены их точные адреса – еще до выполнения программы. Вот это для нас важно.
Структура 1.
Объект pt
x
y
color
Init
адрес tPoint.Init
Move
адрес tPoint.Move
Hide
адрес tPoint.Hide
Draw
адрес tPoint.Draw
Структура 2.
Объект pEl
x
y
color
NRx
NRy
Init
адрес tEllipse.Init
Move
адрес tEllipse.Move
Hide
адрес tEllipse.Hide
Draw
адрес tEllipse.Draw
Методы Move после компиляции выглядят примерно так (только все это записано в машинных кодах):
procedure tPoint.Move(NewX, NewY: integer);
begin
tPoint.Hide;
{вместо всех переменных – адреса их ячеек памяти}
адрес x:= NewX;
адрес y:= NewY;
tPoint.Draw;
end;
procedure tEllipse.Move(NewX, NewY: integer);
begin
tEllipse.Hide;
адрес x:= NewX;
адрес y:= NewY;
tEllipse.Draw;
end;
Все описанные в примере методы называют статическими.
Определение:
Статическими называют методы, адреса которых связываются с объектами во время компиляции программы и не изменяются до завершения работы программы.
Рассмотрим далее некоторые тонкости реализации
правил наследования методов.
Нетрудно заметить, что метод Move является идентичным для всех классов, т.к. алгоритм движения, реализованный в этом методе, является общим для любой фигуры. Поэтому возникает естественное желание сократить текст программы (особенно если мы соберемся добавить в программу описание еще нескольких классов), убрав объявление метода Move из класса tEllipse.
Давайте попробуем это сделать. Получим следующее:
Пример 2.5
Program Pr2_5;
{Раздел описания данных}
Type tLocation = object
x, y: integer;
procedure Init(InitX, InitY: integer);
end;
tPoint = object (tLocation)
color: word;
procedure Init(InitX, InitY:integer; col: word);
procedure Move(NewX, NewY: integer);
procedure Hide;
procedure Draw;
end;
tEllipse = object (tPoint)
Rx, Ry: word;
procedure Init(InitX, InitY: integer; col, NRx, NRy: word);
procedure Hide;
procedure Draw;
end;
{методы класса tLocation}
procedure tLocation.Init(InitX, InitY: integer);
begin
x:= InitX; y:= InitY;
end;
{методы класса tPoint}
procedure tPoint.Init(InitX, InitY:integer; col: word);
begin
tLocation.Init(InitX, InitY: integer);
color:= col;
end;
procedure tPoint.Move(NewX, NewY: integer);
begin
Hide;
x:= NewX;
y:= NewY;
Draw;
End;
procedure tPoint.Hide;
begin
PutPixel(x, y, GetBkColor); {нарисует точку цветом фона}
end;
procedure tPoint.Draw;
begin
Putpixel(x, y, color); {нарисует точку цветом color }
end;
{методы класса tEllipse}
procedure tEllipse.Init(InitX, InitY: integer; col, NRx, NRy: word);
begin
tPoint.Init(InitX, InitY,col);
Rx:= NRx;
Ry:= NRy;
end;
procedure tEllipse.Hide;
begin
SetFillStyle(1,GetBkColor);
Setcolor(GetBkColor);
FillEllipse (x, y, Rx, Ry);
end;
procedure tEllipse.Draw;
begin
SetFillStyle(1,GetBkColor);
Setcolor(color);
FillEllipse (x, y, Rx, Ry);
end;
Var pt: tPoint;
pEl: tEllipse;
{ Раздел описания действий }
Begin
…
pt.Init(34, 48, 8);
pt.Move(56, 120);
pEl.Init(106, 85, 4, 15, 25);
pEl.Move(63, 45);
End.
Еще раз изобразим как после компиляции программы компилятор разместит объекты pt и pEl в памяти:
Структура 3.
Объект pt
x
y
color
Init
адрес tPoint.Init
Move
адрес tPoint.Move
Hide
адрес tPoint.Hide
Draw
адрес tPoint.Draw
Структура 4.
Объект pEl
x
y
color
NRx
NRy
Init
адрес tEllipse.Init
Move
адрес tPoint.Move
Hide
адрес tEllipse.Hide
Draw
адрес tEllipse.Draw
Сравните структуры 1 и 3, 2 и 4. Что изменилось? Изменился только один адрес в структуре 4 – адрес метода Move. Теперь с объектом pEl связан адрес метода Move – tPoint.Move (прочитайте еще раз определение статических методов).
Кстати, Вы поняли почему мы убрали объявление метода Move из класса tEllipsе, а компилятор все равно в объекте данного класса отводит место и записывает адрес метода Move? Ответ: потому что в классе tEllipsе есть метод Move, просто он теперь объявлен не в самом классе, а наследуется от родителя (в нашем примере - от tPoint).
Метод Move после компиляции теперь выглядит примерно так (но только в машинных кодах):
procedure tPoint.Move(NewX, NewY: integer);
begin
tPoint.Hide;
{вместо всех переменных – адреса их ячеек памяти}
адрес x:= NewX;
адрес y:= NewY;
tPoint.Draw;
end;
А метода procedure tEllipse.Move(); теперь нет. И если объект класса tEllipse вызовет данный метод (pEl.Move();) – то вызовется procedure tPoint.Move();
Давайте подробнее рассмотрим команды pt.Move(56,120); и pEl.Move(63,45); из примера 2.5.
Выполнение данных команд повлечет за собой следующие цепочки вызовов:
Т.е. движение произойдет после выполнения обеих команд, но двигаться и в том и в другом случае будет точка. А почему? Потому что непосредственно методы рисования (Hide и Draw) не вызываются в основной программе (в разделе описании действий). Для того чтобы передвинуть фигуру, в основной программе объектами разных классов вызывается метод Move класса tPoint.
А как мы уже говорили ранее в результате компиляции (еще до выполнения программы) внутри метода Move уже жестко проставлены адреса всех используемых в нем методов. Теперь вернитесь чуть выше и посмотрите, как выглядит метод Move после компиляции и Вам станет ясно, почему всегда выполняются методы tPoint.Hide; и tPoint.Draw;, а методы tEllipse.Hide; и tEllipse.Draw; никогда не выполняются, даже если метод Move вызван объектом класса tEllipse.
Существует такое правило наследования методов:
Если наследуемый родительский метод вызывает еще и другие методы, то вызываться уже будут только родительские или вышележащие методы, так как вызовы методов из нижележащих по иерархии классов не допускаются.
Действие данного правила мы и увидели на нашем примере.
Возникает задача:
как сделать метод Move общим (т.е. описать только в одном родительском классе), но при этом, чтобы всё работало правильно, то есть, чтобы двигалась фигура, соответствующая объекту, вызывающему метод Move.
Т.е. нам надо, чтобы после компиляции (т.е. до выполнения программы) в методе Move на месте вызовов методов Hide и Draw не были записаны какие-либо конкретные адреса. Нам надо, чтобы в процессе работы программы, при вызове методов Hide и Draw из метода Move программа определяла бы объект какого класса вызвал метод Move и выполняла бы методы Hide и Draw этого же класса. Для этого нужен какой-то специальный механизм.
Для решения поставленной задачи требуются методы, которые связываются с объектами не статически, то есть в процессе компиляции программы, а динамически, то есть в процессе выполнения программы.
Дадим определения двум механизмам ООП:
1. Раннее связывание – процесс статического связывания методов с объектом.
2. Позднее связывание – процесс динамического связывания методов с объектом.
Позднее связывание – это принципиально новый механизм, который обеспечивает полиморфизм, т.е. разный способ поведения для разных, но однородных в смысле наследования объектов.
Полиморфизм – это множественность форм, т.е. когда один и тот же метод может принимать различные формы.
§7. Виртуальные методы. Конструкторы.
Итак, как мы выяснили выше, существуют так называемые статические методы, реализующие механизм раннего (статического) связывания методов с объектом.
Рассмотрим теперь методы, которые реализуют позднее связывание, т.е. которые связываются с объектом динамически (в процессе выполнения программы). Это так называемые виртуальные методы.
Объявление виртуального метода:
Procedure имя метода ( ); virtual;
Т.е., чтобы сказать программе, что данный метод Вы хотите сделать виртуальным, Вам надо при объявлении метода внутри класса после знака ; написать слово virtual; (это слово называют директивой компилятора).
Директива – это некоторое указание компилятору, как действовать дальше. Директив существует множество. В процессе компиляции программы, если компилятор встречает какую-либо директиву – он знает как действовать дальше (реакция на каждую директиву уже запрограммирована в системе). Программисту важно знать, что получиться в результате этих действий, а как и что компилятор делает для получения нужного результата – не важно.
Правило:
Если в каком-либо родительском классе объявлен виртуальный метод, то это накладывает следующие ограничения на все его дочерние классы.
– все наследники виртуального метода должны быть тоже виртуальными;
(статические методы не могут перекрывать виртуальные)
– заголовки всех реализаций (вниз по иерархии) одноименных виртуальных методов должны быть одинаковыми (имена, количество и тип параметров);
– каждый класс, в котором объявлен виртуальный метод, должен содержать конструктор.
Конструктор – является специального типа процедурой, которая выполняет начальные установки для работы механизма виртуальных методов.
Другими словами, - это метод специального типа, который служит для обеспечения работы с виртуальными методами.
Сам конструктор является виртуальным методом, но директива virtual при его объявлении не указывается.
Объявление конструктора:
Constructor имя метода ( );
Замечание №1
Перед вызовом виртуального метода необходимо вызвать конструктор. Иначе механизм позднего связывания работать не будет, т.е. не произойдёт связи объекта с виртуальным методом. Вызов виртуального метода без предварительного вызова конструктора может привести к блокированию работы программы.
Замечание №2
Конструктор может содержать любые команды. Тело конструктора может быть и пустым. Тогда при его вызове конструктор просто выполнит свои системные функции (свяжет объект с нужным виртуальным методом).
Замечание №3
Каждый отдельный объект должен быть инициализирован отдельным вызовом конструктора.
Например, в классе tPoint заменим процедуру Init конструктором:
Constructor Init(InitX, InitY:integer; col: word);
В основной программе:
var a,b:tPoint;
begin
a.Init(32,55,6);
b.Init(12,124,8);
end.
В этом случае конструктор выполнит и свои системные функции, и то, что мы напишем в теле конструктора (т.е. в данном примере выполнит то же, что делала procedure Init).
Часто так и делают: за конструктор принимают метод инициализации полей объекта: например, Init. Это удобно, так как конструктор надо вызывать до вызова других (виртуальных) методов, а инициализацию полей объекта тоже, естественно, надо выполнять до вызова методов. Таким образом, вызвав конструктор перед работой всех методов, мы обеспечим и инициализацию объекта, и возможность работать с виртуальными методами.
А для нас, как для программистов, вся разница будет заключаться только в том, что мы при объявлении и описании тела метода будем писать не procedure, а слово – constructor. Остальное – это дело компилятора.
Замечание №4
Конструктор наследуется как обычный статический метод. Заголовок конструктора любого дочернего класса не должен совпадать с заголовком конструктора родительского класса. Работа с конструктором аналогична работе с другими методами.
Основное назначение конструктора можно объяснить так: установка связи (поздней) объекта с виртуальными методами своего класса.
Исправим пример 2.5, используя виртуальные методы.
Пример 2.6.
Наши действия:
1. Методы Hide и Draw сделаем виртуальными.
Чтобы вспомнить, зачем нам это надо, вернитесь к § 6 и прочитайте в конце постановку задачи.
2. Метод Move вынесем в родительский класс tLocation.
3. Уберём метод Move из класса tPoint.
4. Сделаем процедуру Init – конструктором.
Program Pr2_6;
{Раздел описания данных}
Type tLocation = object
x, y: integer;
constructor Init (InitX, InitY: integer);
procedure Move (NewX, NewY);
procedure Hide; virtual;
procedure Draw; virtual;
end;
tPoint = object (tLocation)
color: word;
constructor Init(InitX, InitY: integer; col: word);
procedure Hide; virtual;
procedure Draw; virtual;
end;
tEllipse = object (tPoint)
Rx, Ry: word;
procedure Init(InitX, InitY: integer; col, NRx, NRy: word);
procedure Hide; virtual;
procedure Draw; virtual;
end;
{методы класса tLocation}
constructor tLocation.Init(InitX, InitY: integer);
begin
x:= InitX; y:= InitY;
end;
procedure tLocation.Move(NewX, NewY: integer);
begin
Hide;
x:= NewX;
y:= NewY;
Draw;
end;
procedure tLocation.Hide;
begin
end;
procedure tLocation.Draw;
begin
end;
{методы класса tPoint}
constructor tPoint.Init(InitX, InitY:integer; col: word);
begin
tLocation.Init(InitX, InitY: integer);
color:= col;
end;
procedure tPoint.Hide;
begin
PutPixel(x, y, GetBkColor);
end;
procedure tPoint.Draw;
begin
Putpixel(x, y, color);
end;
{методы класса tEllipse}
constructor tEllipse.Init(InitX, InitY: integer; col, NRx, NRy: word);
begin
tPoint.Init(InitX, InitY,col);
Rx:= NRx;
Ry:= NRy;
end;
procedure tEllipse.Hide;
begin
SetFillStyle(1,GetBkColor);
Setcolor(GetBkcolor);
FillEllipse (x, y, Rx, Ry);
end;
procedure tEllipse.Draw;
begin
SetFillStyle(1,GetBkColor);
Setcolor(color);
FillEllipse (x, y, Rx, Ry);
end;
Var pt: tPoint;
pEl: tEllipse;
{ Раздел описания действий }
Begin
…
pt.Init(34, 48, 8);
pt.Move(56, 120);
pEl.Init(106, 85, 4, 15, 25);
pEl.Move(63, 45);
End.
Проанализируем работу данной программы так же, как мы это делали с программой из предыдущего примера 2.5.
Изобразим как после компиляции программы компилятор разместит объекты pt и pEl в памяти:
Структура 5.
Объект pt
x
y
color
Init
адрес tPoint.Init
Move
адрес tLocation.Move
Hide
Draw
Структура 6.
Объект pEl
x
y
color
NRx
NRy
Init
адрес tEllipse.Init
Move
адрес tLocation.Move
Hide
Draw
Заметим, что для виртуальных методов статически (в процессе компиляции) только выделяется память для адресов, а сами адреса будут занесены в ячейки в процессе установления поздней связи при вызове конструктора (в нашем примере, при вызове pt.Init(34,48,8); и pEl.Init(106,85,4,15,25);). После выполнения операторов pt.Init(34,48,8); и pEl.Init(106,85,4,15,25); структуры соответствующих объектов примут такой вид:
Структура 7.
Объект pt
x
34
y
48
color
8
Init
адрес tPoint.Init
Move
адрес tLocation.Move
Hide
адрес tPoint.Hide
Draw
адрес tPoint.Draw
Структура 8.
Объект pEl
x
106
y
85
color
4
NRx
15
NRy
25
Init
адрес tEllipse.Init
Move
адрес tLocation.Move
Hide
адрес tEllipse.Hide
Draw
адрес tEllipse.Draw
Метод tLocation.Move после компиляции теперь выглядит примерно так:
procedure tLocation.Move(NewX, NewY: integer);
begin
Hide;
{вместо всех переменных – адреса их ячеек памяти}
адрес x:= NewX;
адрес y:= NewY;
Draw;
end;
Как видно из рисунка (см. структуры 7 и 8), в объекте находятся адреса тех же самых методов, что и в рассмотренном ошибочном случае в примере 2.5.
Однако теперь, благодаря виртуальности методов Hide и Draw, в результате поздней связи метод tLocation.Move автоматически настраивается на вызов методов Hide и Draw из того класса, объект которого их вызывает.
Следует оговориться, что изменился еще один адрес: вместо tPoint.Move – tLocation.Move. Но это ни на что не влияет, можно было бы и оставить метод Move в классе tPoint. Просто так как-то логичнее – вынести общий для всех фигур метод в общий абстрактный класс.
Кстати, класс tLocation – абстрактный. Абстрактными называют классы, которые не могут иметь конкретных объектов. Например, не может быть объекта фигура, может быть квадрат, круг, эллипс и т.д.. В абстрактный класс tLocation можно, например, включить всю информацию (в виде полей) и все действия (в виде методов), которые может содержать и проделывать фигура. Методы можно оставлять пустыми (как в примере 2.6., например, в классе tLocation тела методов Hide и Draw). Затем в дочерних классах методы можно перекрыть и написать необходимые действия.
Давайте подробнее рассмотрим команды pt.Move(56,120); и pEl.Move(63,45); из примера 2.6.. Цепочки вызовов методов теперь снова будут правильными, как в примере 2.4.:
Но теперь, в отличие от примера 2.4., в нашей программе используются виртуальные методы, которые приводят в действие одно из наиболее эффективных средств ООП – полиморфизм.
Метод tLocation.Move является полиморфическим, т.к. в нем присутствуют заменяемые части (методы Hide и Draw), и поэтому он может подстраиваться под вызывающий его объект – это есть проявление полиморфизма, т.е. множественность форм одного и того же метода.
Заключение.
Положительные стороны использования статических методов:
1) быстрый вызов – не требуется запуска дополнительных механизмов для правильной работы методов.
Положительные стороны виртуальных методов:
1) универсальность программы;
2) возможность использования уже оттранслированных методов для работы с новыми объектами.
§8. Совместимость классов.
Основной принцип совместимости классов: совместимость распространяется от потомков к предкам, т.е. объекты дочерних классов могут свободно использоваться вместо объектов родительских классов, но не наоборот. Кроме того, любой дочерний класс наследует совместимость всех своих родительских классов.
Рассмотрим совместимость между объектами и между формальными и фактическими параметрами методов.
Совместимость между объектами.
!!! Объекту родительского класса можно присвоить любой объект из дочерних классов, но не наоборот.
Если разрешить присваивание наоборот, то те поля данных, которые были добавлены в дочерние классы, останутся незаполненными.
Например,
Var Loc: tLocation;
pt: tPoint;
El:tEllipse;
begin
Loc:= pt; – можно
pt:= El; – можно
El:= Loc; – нельзя
pt:= Loc; – нельзя
end.
Чтобы можно было хранить объекты разных однородных классов в одном массиве, то тип массива должен быть типом самого верхнего родительского класса, к которому принадлежит какой-либо из хранимых объектов.
Совместимость формальных и фактических параметров.
Формальный параметр данного класса может принимать в качестве фактических значений либо объект из своего класса, либо из любого дочернего.
Например,
procedure Show (t: tPoint);
begin
……..
end;
Var Loc: tLocation;
pt: tPoint;
El:tEllipse;
begin
…
show(Loc); – нельзя
show(pt); – можно
show(El); – можно
end;
§9. Классы и модули.
Модули предназначены для поддержки принципов модульного программирования при разработке программ, основным из которых является принцип скрытия информации (information hiding). Согласно этому принципу взаимовлияние логически независимых фрагментов программы должно быть сведено к минимуму. Принцип скрытия информации, поддерживаемый модулями, позволяет создавать надежно работающие и легко модифицируемые программы.
Модули используют для создания библиотек процедур, функций и классов, которые затем могут использоваться в программах, разрабатываемых пользователем.
Синтаксис модуля имеет следующий вид:
Unit Идентификатор модуля;
{ Интерфейсный раздел }
Interface
{в этом разделе показано взаимодействие модуля с другими модулями, а также с главной программой. Другими словами – взаимодействие модуля с “внешним миром”}.
{ Раздел реализации }
Implementation
{в этом разделе указывается реализационная (личная) часть описаний данного модуля, которая не доступна другим модулям и программам. Другими словами – “внутренняя кухня” модуля }.
{ Список импорта раздела реализации }
uses
{В этом списке через запятые перечисляются идентификаторы модулей, информация интерфейсных частей которых должна быть доступна в данном модуле.}
{Данный раздел может отсутствовать }
{ Подразделы внутренних для модуля описаний }
label
const
type
var
procedure
function
{любой из подразделов может отсутствовать}
{ Раздел инициализации }
begin
{В этом разделе можно размещать операторы начальных установок (например, задать начальные значения переменных), необходимых для корректной работы модуля. Указанные здесь операторы выполняются при начальном запуске программы, к которой подключен данный модуль. Если никакой инициализации для данного модуля не требуется, то ключевое слово begin может быть опущено (end. - остается) }
end.
После написания текста модуля необходимо выполнить следующее:
1) сохранить модуль в файле с точно таким же именем, которым Вы назвали сам модуль в тексте после слова Unit (т.е. выполнить команду F2 - Save).
2) из главного меню Паскаля выполнить команду
Compaile
Destination
По умолчанию установлено Destination Memory, а надо, чтобы было установлено Destination Disk. Чтобы эти установки менять, достаточно просто щелкать мышью по данной команде. После каждого очередного щелчка значение меняется с Memory на Disk и наоборот.
3) откомпилировать модуль (как обычную программу):
из главного меню Паскаля выполнить команды (или сразу нажать F9)
Compaile
Compaile Alt-F9
или
Make F9
(Запустить модуль на выполнение Ctrl-F9 не получиться.)
После проделанных действий создастся файл имя.TPU в текущем каталоге. Т.е. в текущем каталоге Вы увидите теперь два файла с одинаковым именем, но с разным расширением: имя.PAS и имя.TPU.
С этого момента Ваш модуль готов к использованию в программах и других модулях.
Можно изменить каталог, в котором будет сохраняться файл имя.TPU. Для этого перед компиляцией модуля из главного меню Паскаля выполните команду
Options
Directories…
В появившемся окне в строке EXE & TPU directory: укажите полный путь до каталога, в котором планируете сохранять файл имя.TPU .
Как подключить какой-либо модуль к программе?
Так же, как Вы подключали стандартные модули Graph, Crt в лаб. работах в курсе ПЯВУ. Для этого достаточно указать в программе:
uses имя модуля1 [, имя модуля2, …];
Чтобы создать EXE – файл Вашей программы (т.е. исполняемый файл, который запускается вне системы Турбо Паскаль - сразу из проводника) надо проделать почти те же действия, что и при создании TPU – файла, а именно: установить Destination Disk, откомпилировать программу командой F9 и запустить на выполнение (Ctrl-F9). В текущем каталоге Вы увидите теперь два файла с одинаковым именем, но с разным расширением: имя.PAS и имя.EXE.
Классы.
Разделённость описания классов и реализации их методов соответствует оформлению их (классов) в виде модуля.
Само описание класса, как правило, располагают в разделе Interface. В этом случае все его поля и методы будут доступны в других модулях и программах, а классы чаще всего и вводятся для того, чтобы их можно было использовать при написании различных программ. Описание реализации методов размещается в разделе Implementation, так как по самой сути классов требуется, чтобы конкретика работы их методов была скрыта от пользователей.
Например, пользователь хочет в своей программе двигать фигуру Эллипс, и он знает, что класс tEllipse существует. Тогда пользователь просматривает все возможности данного класса, т.е. просматривает все его методы и свойства, и вызывает то, что ему нужно, а именно – метод движения фигуры (в наших примерах – метод Move). Как реализован вызываемый метод – пользователя не интересует (да это и скрыто от него). Если пользователь не находит нужный метод или найденный метод работает не так, как требуется, он может описать новый класс – наследник класса tEllipse и дополнить его всем необходимым для решения поставленной задачи.
В разделе реализации можно использовать всё, что описано в разделе Interface.
Директивы Private и Public.
Эти директивы являются средством для управления областью видимости полей и методов.
Private.
(впервые появилась в Турбо Паскале 6.0)
Ее роль в описании класса такая же, как и раздела implementation в модуле: скрыть некоторые детали описания класса.
!!! Приватные (закрытые) поля и методы доступны только внутри того модуля, в котором описан класс.
Структура описания класса с использованием директивы Private
Type Tobj = object
Общедоступные поля;
Общедоступные методы;
Private;
Приватные (закрытые) поля;
Приватные методы;
End;
Например,
interface
Type tlocation = object
function GetX: integer;
function GetY: integer;
Private
x, y: integer;
procedure Init(InitX, InitY: integer);
end;
{хотя класс и описан в общедоступном разделе interface доступ к полям x, y и методу Init получить невозможно. Получить значения x, y можно только, вызвав соответствующие функции GetX или GetY }
implementation
function tLocation.GetX:integer;
begin
GetX:=x;
end;
function tLocation.GetY:integer;
begin
GetY:=y;
end;
procedure tLocation.Init(InitX,InitY:integer);
begin
x:= InitX;
y:= InitY;
end;
Public.
(впервые появилась в Турбо Паскале 7.0)
Директива Public позволяет сначала указывать приватные поля и методы, а потом общедоступные. Иногда это удобно, чтобы не нарушать принятый порядок в описании класса, например:
Type tLocation = object
Private
x, y: integer;
procedure Init( );
Public
function GetX: integer;
function GetY: integer;
end;
§10. Некоторые рекомендации по созданию классов.
В структуру класса желательно включать все действия (в виде методов), которые может проделывать объект данного класса, даже если какие-то действия не будут использоваться в данной программе. В процессе компиляции компилятор размещает в памяти коды только тех методов, вызов которых встречается в программе. Ни разу не вызванные методы компилятором игнорируются, так что “лишние” методы память не занимают. Обычно описание классов и методов оформляют в виде отдельных модулей. Покажем, как это делается на примере отлаженных модуля и программы, являющейся доработанной и дополненной версией программы из примера 2.6.
Пример 2.7.
(в котором приведены коды Программы GRAFICA и Модуля FIGURA.)
(Рекомендую набрать, запустить и разобрать работу данной программы)
Программа GRAFICA, в которой реализовано движение графических фигур
(сохранена в файле OOP_DO.pas )
PROGRAM GRAFICA;
Uses Crt,Graph,Figura;
const Path_of_driver='c:\tp6\bgi';
Procedure Coords(a:char; var NewX,NewY:integer; h:integer);
begin
if a=#0 then begin
a:=readkey; {в переменную a записывается код клавиши,
которую Вы нажали}
{#80,#75,#77,#72 – это коды клавиш со стрелками;
работа оператора case: в зависимости от значения переменной a выбирается
соответствующая последовательность действий; подробнее можно посмотреть в
лекциях по ПЯВУ часть1}
case a of
#80: begin
Inc(NewY,h); if NewY>GetMaxY-20 then NewY:=20;
end;
#75: begin
dec(NewX,h); if NewX<10 then NewX:=GetMaxX-10;
end;
#77: begin
inc(NewX,h); if NewX>GetMaxX-10 then NewX:=10;
end;
#72: begin
dec(NewY,h); if NewY<20 then NewY:=GetMaxY-20;
end;
end;
end
end;
VAR gd,gm,NewX,NewY,h: integer;
Q,a:char;
str:string;
pP:tPoint;
pEl:tEllipse;
BEGIN
{Начало работы программы. С этого момента и надо начинать разбирать
работу программы}
gd:=Detect;
InitGraph(gd,gm,Path_of_driver);
if GraphResult<>grok then begin
write('error:',GraphErrorMsg(GraphResult));
halt;
end;
pP.Init(GetMaxX div 2,GetMaxY div 2,10,15);
pEl.Init(GetMaxX div 2,GetMaxY div 2,10,14,70,40);
NewX:=GetMaxX div 2; {GetMaxX - максимальное значение экрана по оси x}
NewY:=GetMaxY div 2;
h:=100; {влияет на скорость передвижения фигур при нажатии
клавиш со стрелками}
RestoreCrtMode; {восстановление текстового режима (так можно выйти из
графического режима и вернуться к текстовому)}
Q:='y';
While Q<>'n' do begin
write(' Выберите фигуру, для этого введите: p (если выбрали точку)');
write (' el (если выбрали эллипс): ');
readln(str);
{то, что Вы наберете, т.е. p или el, запишется в строковую переменную str. Значение этой переменной нам понадобится далее, чтобы выбрать какую фигуру перемещать по экрану }
writeln('Нажмите клавишу Enter');
writeln('Нажимайте клавиши со стрелками, чтобы двигать фигуру');
readln;
SetGraphMode(gm); {восстановление графического режима}
repeat
a:=readkey;
Coords(a,NewX,NewY,h);
if str='p' then pP.Move(NewX,NewY);
if str='el' then pEl.Move(NewX,NewY);
until a=#27; {#27– код клавиши Esc }
RestoreCrtMode;
write('Продолжить? (y/n) ');
readln(Q); {в переменную Q запишется введенный символ: либо y, либо n}
end; {окончание тела цикла While}
closegraph;
clrscr;
END.
Модуль FIGURA, в котором реализована иерархия классов графических фигур
(сохранен в файле FIGURA.pas )
Unit FIGURA;
interface
TYPE
TLocation=object
x,y:integer;
Constructor Init(InitX,InitY:integer);
Function GetX: integer;
Function GetY: integer;
Procedure Move(NewX,NewY:integer);
Procedure Hide; virtual;
Procedure Draw; virtual;
end;
tPoint=object(tLocation)
vh:integer;
color:word;
Constructor Init(InitX,InitY,InitVh:integer; col:word);
Procedure Hide; virtual;
Procedure Draw; virtual;
end;
tEllipse=object(tPoint)
Rx,Ry:word;
Constructor Init(InitX,InitY,InitVh:integer; col,InitRx,InitRy:word);
Procedure Hide; virtual;
Procedure Draw; virtual;
end;
implementation
Uses Crt,Graph;
{методы tLocation}
Constructor tLocation.Init(InitX,InitY:integer);
begin
x:=InitX; y:=InitY;
end;
Function tLocation.GetX:integer;
begin
GetX:=X;
end;
Function tLocation.GetY:integer;
begin
GetY:=Y;
end;
Procedure tLocation.Move(NewX,NewY:integer);
begin
Hide;
x:=NewX; y:=NewY;
Draw;
end;
Procedure tLocation.Hide;
begin
end;
Procedure tLocation.Draw;
begin
end;
{методы tPoint}
Constructor tPoint.Init(InitX,InitY,InitVh:integer; col:word);
begin
tLocation.Init(InitX,InitY);
vh:=InitVh; color:=col;
end;
Procedure tPoint.Hide;
begin
PutPixel(x,y,GetBkColor);
end;
Procedure tPoint.Draw;
begin
PutPixel(x,y,color);
end;
{методы tEllipse}
Constructor tEllipse.Init(InitX,InitY,InitVh:integer;
col,InitRx,InitRy:word);
begin
tPoint.Init(InitX,InitY,InitVh,col);
Rx:=InitRx; Ry:=InitRy;
end;
Procedure tEllipse.Hide;
begin
SetFillStyle(1,GetBkColor);
SetColor(GetBkColor);
FillEllipse(x,y,Rx,Ry);
end;
Procedure tEllipse.Draw;
begin
SetFillStyle(1,GetBkColor);
SetColor(color);
FillEllipse(x,y,Rx,Ry);
end;
end.
В заключении хочу заметить, что данную программу, может быть, было бы проще написать, используя привычный для Вас структурный подход. Все-таки эффект объектно-ориентированного подхода явно виден при разработке больших программных комплексов. Но нашей целью было не только теоретически изучить, но и практически применить основные принципы ООП. Для этого мы выбрали простую в реализации задачу.
Для сравнения привожу текст программы, работающей аналогично программе из примера 2.7. , но написанной с использованием структурного подхода.
Пример 2.8.
(Рекомендую набрать, запустить и разобрать работу данной программы)
Программа GRAF_STR, в которой реализовано движение графических фигур
PROGRAM GRAF_STR;
Uses Crt,Graph;
const Path_of_driver='c:\tp6\bgi';
VAR gd,gm,NewX,NewY,h,x,y,vh,Rx,Ry: integer;
color: word;
Q,a:char;
str:string;
Procedure Init_pP(InitX,InitY,InitVh:integer; col:word);
begin
x:=InitX;
y:=InitY;
vh:=InitVh; color:=col;
end;
Procedure Hide_pP;
begin
PutPixel(x,y,GetBkColor);
end;
Procedure Draw_pP;
begin
PutPixel(x,y,color);
end;
Procedure Init_pEl(InitX,InitY,InitVh:integer;
col,InitRx,InitRy:word);
begin
Init_pP(InitX,InitY,InitVh,col);
Rx:=InitRx; Ry:=InitRy;
end;
Procedure Hide_pEl;
begin
SetFillStyle(1,GetBkColor);
SetColor(GetBkColor);
FillEllipse(x,y,Rx,Ry);
end;
Procedure Draw_pEl;
begin
SetFillStyle(1,GetBkColor);
SetColor(color);
FillEllipse(x,y,Rx,Ry);
end;
Procedure Coords(a:char; var NewX,NewY:integer; h:integer);
begin
if a=#0 then begin
a:=readkey;
case a of
#80: begin Inc(NewY,h); if NewY>GetMaxY-20 then NewY:=20;
end;
#75: begin dec(NewX,h); if NewX<10 then NewX:=GetMaxX-10;
end;
#77: begin inc(NewX,h); if NewX>GetMaxX-10 then NewX:=10;
end;
#72: begin dec(NewY,h); if NewY<20 then NewY:=GetMaxY-20;
end;
end;
end
end;
BEGIN
gd:=Detect;
InitGraph(gd,gm,Path_of_driver);
if GraphResult<>grok then begin
write('error:',GraphErrorMsg(GraphResult));
halt;
end;
Init_pP(GetMaxX div 2,GetMaxY div 2,10,15);
Init_pEl(GetMaxX div 2,GetMaxY div 2,10,14,70,40);
NewX:=GetMaxX div 2;
NewY:=GetMaxY div 2;
h:=200;
RestoreCrtMode;
Q:='y';
While Q<>'n' do begin
write(' Выберите фигуру, для этого введите: p (если выбрали точку)');
write (' el (если выбрали эллипс): ');
readln(str);
writeln('Нажмите клавишу Enter');
writeln('Нажимайте клавиши со стрелками, чтобы двигать выбранную фигуру');
readln;
SetGraphMode(gm);
repeat
a:=readkey;
Coords(a,NewX,NewY,h);
if str='p' then begin
Hide_pP;
x:=NewX; y:=NewY;
Draw_pP;
end;
if str='el' then begin
Hide_pEl;
x:=NewX; y:=NewY;
Draw_pEl;
end;
until a=#27;
RestoreCrtMode;
write('Continue? (y/n) ');
readln(Q);
end;
closegraph;
clrscr;
END.
Теперь, когда мы на примерах разобрали основные принципы ООП, рекомендую еще раз прочитать Главу 1 настоящего конспекта лекций.
Рекомендуемая литература
Настоящий конспект лекций содержит весь необходимый теоретический и практический материал по дисциплине “Объектно-ориентированное программирование”, достаточный для выполнения лабораторных работ, курсовой работы и сдачи экзамена. Однако для его изучения необходимо иметь навыки структурного программирования в среде Турбо Паскаля, полученные Вами в процессе изучения дисциплины “Программирование на языках высокого уровня. Часть 1”. (В Приложении привожу фрагмент из лекций по курсу ПЯВУ, касающийся работы в текстовом и графических режимах).
В случае непонимания каких-либо моментов при изучении дисциплины ООП желательно воспользоваться дополнительной литературой.
Например, могу посоветовать следующую книгу:
Марченко А.И., Марченко Л.А. Программирование в среде Turbo PASCAL 7.0. Базовый курс. Киев: «ВЕК+», 2003.
В этой книге в разделе “Объектно-ориентированное программирование ”
достаточно подробно и понятно, с примерами изложены основные принципы ООП.
Можете воспользоваться и любой другой книгой (не справочником ) по Турбо Паскалю, в которой есть раздел ООП. Также можно поискать нужный материал в Интернете (ключевые слова поиска: ООП или полностью Объектно-ориентированное программирование).
Есть главы ООП и в книгах, посвященных системе программирования Delphi. В них принципы ООП рассматриваются на примере языка программирования Object Pascal, который является расширенной, дополненной версией языка Турбо Паскаль. Например, опишем класс, реализующий точку на плоскости, на языке Object Pascal.
Type
TPoint= class
x: integer; //поле
y: integer;
procedure Draw; //метод
end;
Вместо слова Object используется слово Class, хотя можно написать и Object – система поймет. Таким образом, все известное Вам из Турбо Паскаля сохраняется и в Object Pascal плюс добавлено новое.
Поэтому почитать главы по ООП можно и в книгах по Delphi. Раздел может называться, не ООП, а, например, Классы. Если соберетесь купить такую книгу, советую автора Фаронов В. . Тем более что в курсе “Основы визуального программирования”, который мы начнем изучать с Вами в следующем семестре, мы рассмотрим особенности языка Object Pascal и будем учиться разрабатывать различные программы в среде Delphi 7.0. (7.0 – это версия системы. Для решения наших с Вами задач работа с версиями 4.0, 5.0 и 6.0 - аналогична).
Приложение. Фрагмент лекций по дисциплине “Программирование на языках высокого
уровня. Часть 1”
7.6 Модуль CRT
Содержит процедуры и функции управления экраном в текстовом режиме, управления звуком и работы с клавиатурой.
При работе в текстовом режиме весь экран разбивается на позиции (25 строк по 80 позиций в каждой). Для каждой позиции можно задать цвет фона и цвет символа, символ можно сделать мерцающим.
Координаты 1 позиции (верхний левый угол) – (1,1), последней позиции (нижний правый угол) – (80,25).
На экране можно создать текстовое окно, которое задается координатами верхнего левого и нижнего правого углов. При работе внутри окна координаты отсчитываются от верхнего левого угла.
Процедуры и функции модуля CRT (управление звуком и клавиатурой)
keypressed: boolean; - возвращает true, если была нажата любая клавиша (кроме ^, shift, alt…) и false в противном случае. Не останавливает программу в ожидании нажатия клавиши, поэтому можно repeat…until keypressed; (вместо readln;)
readkey: char; - считывает символ нажатой клавиши (без отображения на экране). Приостанавливает выполнение программы до нажатия любой клавиши, кроме служебных (ctrl, alt, shift…)
if keypressed then ch:=readkey:
Код обычных клавиш (цифры, алфавит) хранится в 1 байте, коды функциональных клавиш (F1…F12) – в двух байтах: первый = 0, второй – код клавиши. Поэтому, чтобы считать код функциональной клавиши, надо 2 раза вызвать функцию readkey: при первом вызове она считает 0, при втором – непосредственно код клавиши.
Пример:
uses Crt;
var ch:char;
begin
writeln(‘Нажмите клавишу’);
ch:=readkey;
if ch<>#0 then writeln(‘Клавиша ’, ch)
else begin
ch:=readkey;
writeln(‘функциональная клавиша ’, ord(ch));
end;
end.
delay (t); - приостанавливает работу программы на время t (милисекунд).
sound (f); - включает звук заданной частоты (в Гц).
nosound; - выключает звук
Пример:
sound(440); delay(500); nosound;
Пример: Имитация музыкального инструмента.
uses crt;
const m: array [1..8] of integer = (262,294,330,350,392,440,494,523);
var i: integer;
ch: char;
begin
writeln(‘Играйте на клавишах 1-8; 0 - выход’);
while true do
begin
ch:=readkey;
case ch of
#49: i:=1;
#50: i:=2;
…
#56: i:=8;
#48: halt;
end;
sound(m[i]);
delay(1000);
nosound;
end;
end.
Константы модуля CRT (коды цветов и режимов)
Коды режимов: Коды цветов:
Процедуры и функции управления экраном
clrscr; - очистка активного окна и перевод курсора в верхний левый угол.
clreol; - очистка строки активного окна от текущей позиции курсора до конца строки.
insline; - вставляет новую пустую строку в позицию курсора, заполняет ее цветом фона.
delline; - удаляет строку, на которой стоит курсор. Остальные строки сдвигаются (↑ или ↓).
highvideo; - переход к более яркому цвету символов (0→8, 1→9,…, 7→15).
lowvideo; - переход к менее яркому цвету символов (8→0, 9→1,…, 15→7).
normvideo; - переход к стандартному цвету символов (7)
textmode (m); - установка текстового режима. По умолчанию - СО80.
textbackground (col); - установка цвета фона (col = 0-7).
textcolor (col); - установка цвета символов (col = 0-15).
window (x1,y1,x2,y2); - определяет текстовое окно на экране.
gotoxy (x,y); - переводит курсор в заданную позицию окна. (х,у) – координаты нового положения курсора.
whereX: byte; - возвращает координату х текущей позиции курсора.
whereY: byte; - возвращает координату у текущей позиции курсора.
Пример 1: Сформировать на экране текстовое окно другого цвета и напечатать в середине окна приветствие.
uses crt;
begin
textmode(3);
textbackground(2);
clrscr;
textcolor(14);
window(20,10,60,15);
textbackground(6);
clrscr;
gotoxy(15,3);
writeln(‘Hello’);
delay(1000);
textmode(3);
end.
Пример 2: Программа выводит на экран последовательность цифр 0..9, стрелками ← → выбирает и подсвечивает любую цифру.
25
13
1 2 3 4 5 6 7 8 9
uses crt;
var x,y,i: integer;
ch: char;
procedure Put(x,y,i,col: integer);
begin
textbackground(col);
gotoxy(x+i*3,y);
write(‘ ’,i,’ ’);
gotoxy(x+i*3+1,y);
end;
begin
textmode(3);
clrscr;
y:=13; x:=25;
for i:=0 to 9 do put(x,y,i,1);
i:=0; put(x,y,i,2);
repeat
ch:=readkey;
put(x,y,i,1);
if ch = #75 then dec(i);
if ch = #77 then inc(i);
if i<0 then i:=9;
if i>9 then i:=0;
put(x,y,i,2);
until ch=#27; {esc}
textmode(3);
end.
Вариант без процедуры:
uses crt;
var x,y,i: integer;
ch: char;
procedure Put(x,y,i,col: integer);
begin
gotoxy(x+i*3,y);
write(‘ ’,i,’ ’);
gotoxy(x+i*3+1,y);
end;
begin
textmode(3);
clrscr;
y:=13; x:=25;
textbackground(1);
for i:=0 to 9 do
begin
gotoxy(x+i*3,y);
write(‘ ’,i,’ ’);
end;
i:=0;
textbackground(2);
gotoxy(x+i*3,y);
write(‘ ’,i,’ ’);
gotoxy(x+i*3+1,y);
repeat
ch:=readkey;
if ch = #0 then
begin
ch:=readkey;
textbackground(1);
gotoxy(x+i*3,y); стирает подсветку
write(‘ ’,i,’ ’);
if ch = #75 then dec(i);
if ch = #77 then inc(i);
if i<0 then i:=9;
if i>9 then i:=0;
textbackground(2);
gotoxy(x+i*3,y); подсвечивает другую цифру
write(‘ ’,i,’ ’);
gotoxy(x+i*3+1,y);
end;
until ch=#27; {esc}
textmode(3);
end.
7.7 Модуль GRAPH
Cодержит процедуры и функции, обеспечивающие работу с экраном в графическом режиме (создание изображений).
1) В графическом режиме весь экран разбивается на точки (пиксели – от англ. pixel). Каждую точку можно закрасить в любой цвет, таким образом из различных комбинаций точек создается изображение.
2) Каждая точка имеет координаты (х, у), х увеличивается по горизонтали слева направо, у – по вертикали сверху вниз. Верхний левый угол имеет координаты (0, 0), количество пикселей зависит от типа видеоадаптера и режима его работы. Для адаптера VGA – 640х480 (стандартный режим). Чем больше пикселей (точек), тем выше качество изображения.
{Драйвер устройства – программа управления устройством.
Видеоадаптер – системное устройство, непосредственно управляющее монитором и выводом информации на экран. Основные части – видеоконтроллер, видеоBIOS, видеопамять и т.д.}
3) В графическом режиме можно выделить на экране графическое окно, тогда координаты отсчитываются внутри окна, верхний левый угол считается точкой (0,0).
4) В графическом режиме, в отличие от текстового, курсор невидим на экране, но также можно переместить его в любую точку экрана, получить его текущие координаты.
Процедуры и функции управления графическими режимами.
initgraph (gd, gm, path); - процедура инициализации графического режима.
gd – код драйвер графического адаптера:
для CGA gd=1 или CGA
для EGA gd=3 или EGA
для VGA gd=9 или VGA
для автоматического определения драйвера gd=0 или Detect;
gm – код графического режима, устанавливается процедурой.
gd, gm – переменные целого типа!
path – строка, содержащая путь к драйверу: ‘c:\tp6\bgi’, т.е. вам надо сначала найти файл GRAPH.TPU и
здесь указать путь до этого файла.
closegraph; - процедура завершения графического режима и перехода в текстовый режим.
graphresult: integer; - возвращает код ошибки последней графической операции (0 – успешное выполнение).
grapherrormsg (cool: intrger):string; - возвращает сообщение об ошибке для заданного кода ошибки.
restorecrtmode; - процедура восстановления текстового режима.
setgraphmode (gm); - процедура установления графического режима.
Пример 1.
uses crt, graph;
var gd, gm: integer;
begin
gd:=detect;
initgraph (gd, gm, ‘c:\tp6\bgi’); {укажите правильный путь до bgi}
if graphresult <> 0 then
begin
write (‘ошибка графики:’, grapherrormsg (graphresult));
halt;
end;
…
closegraph;
end.
Процедуры управления экраном и окном.
cleardevice; - процедура очистки графического экрана, заполнения его цветом фона, установления курсора в (0,0).
setviewport (x1, y1, x2, y2: integer; clip: boolean); - процедура установки графического окна.
x1, y1 – верхний левый угол,
x2, y2 – нижний правый угол,
clip – ограничитель фигур. Если clip=true, то построение фигур происходит только в пределах окна, в противном случае фигуры могут выходить за пределы окна.
clearviewport; - процедура очистки графического окна (заполняет цаетом фона, курсор в (0,0)).
Процедуры и функции работы с точками.
GetmaxX: integer; - возвращает максимальное значение координаты x экрана.
GetmaxY: integer; - возвращает максимальное значение координаты y экрана.
GetX: integer; - возвращает координату x текущей позиции на экране.
GetY: integer; - возвращает координату y текущей позиции на экране.
GetPixel (x,y): word; - возвращает цвет точки с заданными координатами.
PutPixel (x,y,color); - процедура закрашивает точки с координатами (x,y) в цвет color.
Процедуры работы с линиями
line (x1, y1, x2, y2); - проводит прямую между точками (x1,y1) и (x2,y2).
moveto (x,y); - перемещает курсор в точку (х,у).
lineto (x,y); - проводит прямую из текущей точки в точку с координатами (x,y).
linerel (dx,dy); - проводят прямую из текущей точки в точку, сдвинутую на dx и dy: (x+dx, y+dy).
moverel (dx,dy); - перемещает курсор в точку, сдвинутую на dx, dy.
Процедуры построения фигур из линий.
bar (x1, y1, x2, y2); - построение закрашенного прямоугольника
(x1,y1)
(x2,y2)
bar3d (x1, y1, x2, y2, gr, top); - построение закрашенного параллелепипеда, gr – ширина боковой грани (отсчитывается по горизонтали), top = true – рисовать верхнюю грань
(x1, y1)
(x2, y2)
rectangle (x1, y1, x2, y2); - построение контура прямоугольника
arc (x, y, st, end, r); - построение дуги окружности, (х,у) – координаты центра, st – угол начала, end – угол конца дуги, r – радиус. Отсчет идет против часовой стрелки от горизонтальной оси справа от центра.
circle (x, y, r); - построение окружности, (х,у) – центр, r – радиус.
ellipse (x, y, st, end, xr, yr); - построение дуги эллипса, (x,y) – центр, st, end – углы начала и конца, xr, yr – радиусы по оси х и у.
fillellipse (x, y, xr, yr); - построение закрашенного эллипса.
pieslice (x, y, st, end, r); - построение закрашенного сектора круга.
sector (x, y, st, end, xr, yr); - построение закрашенного сектора эллипса.
Пример:
x1:=10; y1:=10; x2:=100; y2:=50; dx:=40; dy:=20;
for i:=1 to 10 do
begin
bar(x1, y1, x2, y2);
x1:=x1+dx; x2:=x2+dx;
y1:=y1+dy; y2:=y2+dy;
end;
Процедуры установки цветов и стилей.
setcolor (color); - устанавливает текущий цвет рисования (для линий и текста).
setbkcolor (color); - устанавливает цвет фона экрана.
setlinestyle (sl, p,t); - установка стиля и толщины линии (sl – стиль, p – шаблон при sl=4, t – толщина линии).
sl = 0 – сплошная t = 1 – нормальная
sl = 1 – пунктирная t = 3 – толстая
sl = 2 – штрихпунктирная
sl = 3 – штриховая
sl = 4 – задается пользователем (р – шаблон из 11100110101…) p: word;
setfillstyle (sl, color); - установка стиля и цвета заполнения фигур.
sl = 0 – цвет фона (нет заполнения) sl = 7 – клетка
sl = 1 – заполнение цветом sl = 8 – косая клетка
sl = 2 – заполнение пунктиром sl = 9 – частая сетка
sl = 3 – sl = 10 – редкие точки
sl = 4 – толстые sl = 11 – частые точки
sl = 5 – толстые sl = 12 – задается пользователем
sl = 6 –
floodfill (x, y, colline); - закрашивает замкнутую область, ограниченную непрерывной линией. (х,у) – координаты любой точки внутри области, colline – цвет линии, до которой закрашивается.
Пример: Построить спираль с начальной точкой (хс,ус), n витками и начальным углом α.
uses crt, graph;
const n=10; alf=pi; dt=pi/45;
var gd, gm, x, y, xc, yc, i, j: integer;
procedure Spi(xc,yc,n: integer; dt,alf: real);
begin
moveto(xc,yc);
t:=alf;
for i:=1 to n do
for j:=1 to 90 do
begin
t:=t+dt; r:=2*t;
x:=round(r*cos(t)+xc); x=r*cos(t) окружность, t-угол.
y:=round(r*sin(t)+yc); y=r*sin(t) r увеличиваем, получаем
lineto(x,y); спираль.
end;
end;
begin
gd:=detect;
initgraph (gd, gm, ‘L:\tp6\bgi’);
if graphresult <> 0 then
begin
write (‘ошибка графики:’, grapherrormsg (graphresult));
halt;
end;
xc:=getmxx div 2;
yc:=getmaxy div 2;
spi(xc,yc,n,dt,alf);
readln;
closegraph;
end.
Процедуры работы с текстом.
outtext (text: string); - выводит на экран текстовую строку, начиная с текущей позиции.
outtext(‘hello’);
outtextxy (x, y, text); - выводит на экран текстовую строку, начиная с заданных координат (х,у).
outtextxy(100,100,‘hello’);
settextstyle (font, dir, size); - устанавливает стиль текста
font – шрифт dir – направление size – размер символов
0 – стандартный 0 – слева направо (0-30)
1 – утроенный 1 – сверху вниз
2 – маленький
3 – прямой
4 – готический
settextjustify (horiz, vert); - устанавливает выравнивание текста на экране (horiz-горизонтальное, vert-вертикальное).
0 – слева (снизу)
1 – по центру
2 – справа (сверху)
Процедуры обмена с памятью
getimage (x1, y1, x2, y2, M); - копирует в массив М прямоугольную область экрана, заданную координатами (х1,у1) и (х2,у2).
putimage (x, y, M, bit); - выводит изображение из массива М на экран. (х,у) – координаты точки, в которой будет левый верхний угол изображения, М – массив, в котором было сохранено изображение, bit – операция объединения выводимого изображения с имеющимся на экране.
0 – mov экран + массив → - старое изображение исчезает
1 – xor + → - стираются точки пересечения
2 – or + → - оба изображения
3 – and + → - остаются точки пересечения
Пример1: Программа перемещения по экрану графического объекта
uses crt, graph;
var gd, gm, x, y: integer;
m: array [1..1500] of byte;
begin
gd:=detect;
initgraph (gd, gm, ‘c:\tp6\bgi’);
if graphresult <> 0 then
begin
write (‘ошибка графики:’, grapherrormsg (graphresult));
halt;
end;
circle(20,20,20);
getimage(0,0,40,40,m);
x:=0; y:=0;
readln;
while x<600 do
begin
putimage(x,y,m,1);
x:=x+2; y:=y+1;
putimage(x,y,m,1);
delay(100);
end;
closegraph;