Задание для студентов 5 курса

advertisement
МОДУЛЬНОЕ ПРОГРАММИРОВАНИЕ
Назначение модулей
Стандартный Паскаль не предусматривает механизмов раздельной
компиляции частей программы с последующей их сборкой перед
выполнением. Вполне понятно стремление разработчиков коммерческих
компиляторов Паскаля включать в язык средства, повышающие его
модульность.
Модуль – это автономно компилируемая программная единица,
включающая в себя различные компоненты раздела описаний (типы,
константы, переменные, процедуры и функции) и, возможно, некоторые
исполняемые операторы инициирующей части.
Основным принципом модульного программирования является принцип
«разделяй и властвуй». Модульное программирование – это организация
программы как совокупности небольших независимых блоков, называемых
модулями, структура и поведение которых подчиняются определенным
правилам.
Использование модульного программирования позволяет упростить
тестирование программы и обнаружение ошибок. Аппаратно-зависимые
подзадачи могут быть строго отделены от других подзадач, что улучшает
мобильность создаваемых программ.
Термин «модуль» в программировании начал использоваться в связи с
внедрением модульных принципов при создании программ. В 70-х годах под
модулем понимали какую-либо процедуру или функцию, написанную в
соответствии с определенными правилами. Например: «Модуль должен
быть простым, замкнутым (независимым), обозримым (от 50 до 100
строк), реализующим только одну функцию задачи, имеющим одну входную и
одну выходную точку».
Первым основные свойства программного модуля более-менее четко
сформулировал Парнас (Parnas): «Для написания одного модуля должно
быть достаточно минимальных знаний о тексте другого». Таким образом,
в соответствии с определением, модулем могла быть любая отдельная
процедура (функция) как самого нижнего уровня иерархии (уровня
реализации), так и самого верхнего уровня, на котором происходят только
вызовы других процедур-модулей.
Таким образом, Парнас первым выдвинул концепцию скрытия
информации
(information
hiding)
в
программировании.
Однако
существовавшие в языках 70-х годов только такие синтаксические
конструкции, как процедура и функция, не могли обеспечить надежного
скрытия информации, поскольку подвержены влиянию глобальных
переменных, поведение которых в сложных программах бывает трудно
предсказуемым.
Решить эту проблему можно было только разработав новую
синтаксическую конструкцию, которая не подвержена влиянию глобальных
переменных.
Такая конструкция была создана и названа модулем. Изначально
предполагалось, что при реализации сложных программных комплексов
модуль должен использоваться наравне с процедурами и функциями как
конструкция, объединяющая и надежно скрывающая детали реализации
определенной подзадачи.
Таким образом, количество модулей в комплексе должно определяться
декомпозицией поставленной задачи на независимые подзадачи. В
предельном случае модуль может использоваться даже для заключения в
него всего лишь одной процедуры, если необходимо, чтобы выполняемое ею
локальное действие было гарантировано независимым от влияния других
частей программы при любых изменениях.
Впервые специализированная синтаксическая конструкция модуля была
предложена Н. Виртом в 1975 г. и включена в его новый язык Modula.
Насколько сильно изменяются свойства языка, при введении механизма
модулей, свидетельствует следующее замечание Н.Вирта, сделанное им по
поводу более позднего языка Модула-2: «Модули – самая важная черта,
отличающая язык Модула-2 от его предшественника Паскаля».
По своей организации и характеру использования в программе модули
Турбо Паскаля близки к модулям-пакетам (PACKAGE) языка
программирования Ада. В них так же, как и в пакетах Ады, явным образом
выделяется некоторая «видимая» интерфейсная часть, в которой
сконцентрированы описания глобальных типов, констант, переменных, а
также приводятся заголовки процедур и функций. Появление объектов в
интерфейсной части делает их доступными для других модулей и основной
программы. Тела процедур и функций располагаются в исполняемой части
модуля, которая может быть скрыта от пользователя.
Внешнее проектирование.
Уточнение задачи, сбор
исходных данных.
Разработка общей
структуры
алгоритмов
Анализ возможностей
использования стандартных и
собственных модулей
Детализация
алгоритмов
Разработка новых
модулей
Отладка программы
в целом
Отладка и
тестирование новых
модулей
Рис.2. Последовательность разработки программного проекта
Значение модулей для технологии разработки программного проекта
может быть продемонстрировано диаграммой на рис. 2.
Модули представляют собой прекрасный инструмент для разработки
библиотек прикладных программ и мощное средство модульного
программирования. Важная особенность модулей заключается в том, что
компилятор размещает их программный код в отдельном сегменте памяти.
Длина сегмента не может превышать 64 Кбайт, однако количество
одновременно используемых модулей ограничивается лишь доступной
памятью, что позволяет создавать большие программы.
Структура модулей
Всякий модуль имеет следующую структуру:
Unit <имя_модуля>;
interface
<интерфейсная часть>;
implementation
<исполняемая часть>;
begin
<инициирующая часть>;
end.
Здесь UNIT – зарезервированное слово (единица); начинает заголовок
модуля;
<имя_модуля> - имя модуля (правильный идентификатор);
INTERFACE – зарезервированное слово (интерфейс); начинает
интерфейсную часть модуля;
IMPLEMENTATION – зарезервированное слово (выполнение); начинает
исполняемую часть модуля;
BEGIN – зарезервированное слово; начинает инициирующую часть
модуля; причем конструкция begin <инициирующая часть> необязательна;
END – зарезервированное слово – признак конца модуля.
Таким образом, модуль состоит из заголовка и трех составных частей,
любая из которых может быть пустой.
Ниже показана общая структура модуля, дополненная комментариями,
поясняющими смысл и назначение каждого раздела модуля.
Unit ИдентификаторМодуля;
{Интерфейсный раздел}
interface
{В этом разделе описывается взаимодействие данного модуля}
{с другими пользовательскими и стандартными модулями, а также}
{с главной программой. Другими словами – взаимодействие }
{модуля с «внешним миром».
}
{Список импорта интерфейсного раздела}
uses
{В этом списке через запятые перечисляются идентификаторы}
{модулей, информация интерфейсных частей которых должна }
{быть доступна в данном модуле. Здесь целесообразно описывать}
{идентификаторы только тех модулей, информация из которых}
{используется в описаниях раздела interface данного модуля.}
{Список экспорта интерфейсного раздела}
const
{Список экспорта состоит из подразделов описания констант,}
type
{типов, переменных, заголовков процедур и функций, которые}
var
{определены в данном модуле, но использовать которые разре-}
procedure {шено во всех других модулях и программах, включающих имя}
function {данного модуля в своей строке uses. Для процедур и функций}
{здесь описываются только заголовки, но с обязательным}
{полным
описанием
формальных
параметров.}
{Раздел реализации}
implementation
{В этом разделе указывается реализационная (личная) часть}
{описаний данного модуля, которая недоступна для других }
{модулей и программ. Другими словами – «внутренняя кухня»}
{модуля.}
{Список импорта раздела реализации}
uses
{В этом списке через запятые перечисляются идентификаторы}
{тех модулей, информация интерфейсных частей которых должна}
{быть доступна в данном модуле. Здесь целесообразно описывать}
{идентификаторы всех необходимых модулей, информация из}
{которых не используется в описаниях раздела interface данного}
{модуля и об использовании которых не должен знать ни один}
{другой модуль.
}
{Подразделы внутренних для модуля описаний}
label
{В этих подразделах описываются метки, константы, типы,}
const
{переменные, процедуры и функции, которые описывают}
type
{алгоритмические действия, выполняемые данным модулем, и}
var
{которые являются «личной собственностью» исключительно}
procedure{только данного модуля. Эти описания недоступны ни одному}
function {другому модулю. Заголовки процедур и функций в этом}
{подразделе допускается указывать без списка формальных}
{параметров. Если заголовки указаны все же с параметрами, то}
{их список должен быть идентичен такому же списку для}
{соответствующей процедуры (функции) в разделе interface}
{Раздел инициализации}
begin
{В этом разделе указываются операторы начальных установок,}
{необходимых для запуска корректной работы модуля. Операторы}
{разделов инициализации модулей, используемых в программе,}
{выполняются при начальном запуске программы в том же }
{порядке, в каком идентификаторы модулей описаны в }
{предложении uses. Если операторы инициализации не требуются, }
{то слово begin может быть опущено.}
end.
Заголовок модуля и связь модулей друг с другом
Заголовок модуля состоит из зарезервированного слова unit и
следующего за ним имени модуля. Для правильной работы среды Турбо
Паскаля и возможности подключения средств, облегчающих разработку
больших программ, имя модуля должно совпадать с именем дискового
файла, в который помещается исходный текст модуля. Если, например,
имеем заголовок модуля
Unit primer;
то исходный текст этого модуля должен размещаться на диске в файле
primer.pas.
Имя модуля служит для его связи с другими модулями и основной
программой. Эта связь устанавливается специальным предложением:
uses <список модулей>
Здесь USES – зарезервированное слово (использует);
<список модулей> - список модулей, с которыми устанавливается
связь; элементы списка – имена модулей через запятую.
Если в программе модули используются, то предложение uses <список
модулей> должно стоять сразу после заголовка программы, т.е. должно
открывать раздел описаний основной программы. В модулях могут
использоваться другие модули. В модулях предложение uses <список
модулей> может стоять сразу после слова interface или сразу после слова
implementation. Допускается и два предложения uses, т.е. оно может стоять и
там, и там.
Интерфейсная часть
Интерфейсная
часть
открывается
зарезервированным
словом
INTERFACE. В этой части содержатся объявления всех глобальных
объектов модуля (типов, констант, переменных и подпрограмм), которые
должны быть доступны основной программе и (или) другим модулям. При
объявлении глобальных подпрограмм в интерфейсной части указывается
только их заголовок, например:
Unit complexn;
Interface
Type
Complex= record
Re, im: real;
End;
Procedure AddC(x,y: complex, var z: complex);
Procedure MulC (x,y: complex, var z: complex);
Если теперь в основной программе написать предложение
Uses complexn;
то в программе станут доступными тип cmplex и две процедуры – AddC и
MulC из модуля complexn.
Отметим, что объявление подпрограмм в интерфейсной части
автоматически сопровождается их компиляцией с использованием дальней
модели памяти. Таким образом, обеспечивается доступ к подпрограммам из
основной программы и других модулей.
Следует учесть, что все константы и переменные, объявленные в
интерфейсной части модуля, равно как и глобальные константы и
переменные основной программы, помещаются компилятором Турбо
Паскаля в общий сегмент данных (максимальная длина сегмента 65536 байт).
Порядок появления различных разделов объявлений и их количество
может быть произвольным. Если в интерфейсной части объявляются
внешние подпрограммы или подпрограммы в машинных кодах, их тела (т.е.
зарезервированное слово EXTERNAL, в первом случае, и машинные коды
вместе со словом INLINE – во втором) должны следовать сразу за их
заголовками в исполняемой части модуля (не в интерфейсной!). В
интерфейсной части модулей нельзя использовать опережающее описание.
Исполняемая часть
Исполняемая
часть
начинается
зарезервированным
словом
IMPLEMENTATION и содержит описания подпрограмм, объявленных в
интерфейсной части. В ней могут объявляться локальные для модуля
объекты – вспомогательные типы, константы, переменные и блоки, а также
метки.
Описанию подпрограммы, объявленной в интерфейсной части модуля, в
исполняемой части должен предшествовать заголовок, в котором можно
опустить список формальных параметров и тип результата для функции, так
как они уже описаны в интерфейсной части. Но если заголовок
подпрограммы приводится в полном виде, т.е. со списком параметров и
объявлением типа результата для функции, то он должен полностью
совпадать с заголовком подпрограммы в интерфейсной части, например:
Unit complexn;
{--------------------------------}
Interface
Type
Complex= record
Re, im: real;
End;
Procedure AddC(x,y: complex, var z: complex);
{---------------------------------}
Implementation
Procedure AddC;
z.re:= x.re + y.re;
z.im:= x.im + y.im;
end;
end.
Инициирующая часть
Инициирующая часть завершает модуль. Она может отсутствовать
вместе с начинающим ее словом BEGIN или быть пустой – тогда вслед за
BEGIN сразу следует признак конца модуля.
В инициирующей части размещаются исполняемые операторы,
содержащие некоторый фрагмент программы. Эти операторы исполняются
до передачи управления основной программе и обычно используются для
подготовки ее работы. Например, в инициирующей части могут
инициироваться переменные, открываться файлы, устанавливаться связи с
другими компьютерами и т.п.:
Unit fileText;
{-------------------------------}
Interface
Procedure print(s: string);
{-------------------------------}
implementation
var f: text;
const
name= ‘output.txt’;
procedure print;
begin
writeln(f, s)
end;
{---------------------------------}
{начало инициирующей части}
begin
assign(f, name);
rewrite(f);
{конец инициирующей части}
end.
Не рекомендуется делать инициирующую часть пустой, лучше ее
опустить.
Компиляция модулей
В среде Турбо Паскаль имеются средства, управляющие способом
компиляции модулей и облегчающие разработку больших программ.
Определены три режима компиляции: COMPILE, MAKE, BUILD. Режимы
отличаются способом связи компилируемого модуля или основной
программы с другими модулями, объявленными в предложении USES.
При компиляции модуля или основной программы в режиме COMPILE
все, упоминаемые в предложении USES модули, должны быть
предварительно откомпилированы, и результаты компиляции должны быть
помещены в одноименные файлы с расширением TPU (от англ. Turbo Pascal
Unit). Файл с расширением TPU создается автоматически при компиляции
модуля.
В режиме MAKE компилятор проверяет наличие TPU-файлов для
каждого объявленного модуля. Если какой-либо файл не найден, система
ищет одноименный файл с расширением PAS, т.е. файл с исходным текстом
модуля. Если таковой файл найден, система приступает к его компиляции.
Кроме того, в этом режиме система следит за возможными изменениями
исходного текста любого используемого модуля. Если в PAS-файл внесены
изменения, то независимо от того, есть ли в каталоге соответствующий TPUфайл или нет, система откомпилирует его перед компиляцией основной
программы. Более того, если изменения внесены в интерфейсную часть, то
будут откомпилированы все другие модули, обращающиеся к нему. Режим
MAKE существенно облегчает процесс разработки крупных программ с
множеством модулей: программист избавляется от необходимости следить за
соответствием TPU-файлов их исходному тексту, т.к. система делает это
автоматически.
В режиме BUILD существующие TPU-файлы игнорируются, система
пытается отыскать и откомпилировать соответствующие PAS-файлы для
каждого модуля. После компиляции можно быть уверенным, что учтены все
сделанные в текстах модулей исправления и изменения.
Подключение модулей к основной программе и их компиляция
происходит в порядке их объявления в предложении USES. При переходе к
очередному модулю система предварительно ищет все модули, на которые
он ссылается. Ссылки модулей друг на друга могут образовывать
древовидную структуру любой сложности, однако запрещается явное или
косвенное обращение модуля к самому себе. Например, недопустимы
следующие объявления:
Unit A;
interface
uses B;
…….
implementation
……..
end.
Unit B;
interface
Uses A;
……
implementation
……
end.
Это ограничение можно обойти, если «спрятать» предложение USES в
исполняемые части зависимых модулей:
Unit A;
Unit B;
interface
interface
…….
……
implementation
implementation
uses B;
Uses A;
……..
……
end.
end.
Дело в том, что Турбо Паскаль разрешает ссылки на частично
откомпилированные
модули,
что
приблизительно
соответствует
опережающему описанию подпрограммы. Если интерфейсные части
независимы (это обязательное условие!), Турбо Паскаль сможет
идентифицировать все глобальные объекты в каждом модуле, после чего
откомпилирует тела модулей обычным способом.
Доступ к объявленным в модуле объектам
Пусть, например, мы создаем модуль, реализующий сложение и
вычитание комплексных чисел с помощью процедур:
Unit complexn;
Interface
type
complex= record
re, im: real;
end;
procedure AddC (x, y: complex; var z: complex);
procedure SubC (x, y: complex; var z: complex);
const
c: complex= (re: 0.1; im: -1);
implementation
procedure AddC;
begin
z.re:= x.re + y.re;
z.im:= x.im + y.im;
end; {AddC}
procedure SubC;
begin
z.re:= x.re - y.re;
z.im:= x.im - y.im;
end; {SubC}
end.
Текст этого модуля следует поместить в файл complexn.pas. Вы можете
его откомпилировать, создав TPU-файл.
В следующей программе осуществляются арифметические операции над
комплексными числами:
Program primer;
Uses complexn;
Var
a,b,c: coplex;
begin
a.re:= 1; a.im:= 1;
b.re:= 1; b.im:= 2;
AddC(a, b, c);
Writeln (‘сложение :’, c.re: 5:1, c.im: 5:1, ‘i’);
SubC (a, b, c);
Writeln (‘вычитание :’, c.re: 5:1, c.im: 5:1, ‘i’);
End.
После объявления Uses complexn программе стали доступны все
объекты, объявленные в интерфейсной части модуля complexn. При
необходимости можно переопределить любой из этих объектов, как
произошло, например, с типизированной константой c, объявленной в
модуле. Переопределение объекта означает, что вновь объявленный объект
«закрывает» ранее определенный в модуле одноименный объект. Чтобы
получить доступ к «закрытому» объекту, нужно воспользоваться составным
именем: перед именем объекта поставить имя модуля и точку. Например:
Writeln (complexn.c.re: 5: 1, complexn.c.im: 5: 1);
Этот оператор выведет на экран содержимое «закрытой»
типизированной константы, объявленной в модуле из предыдущего примера.
Стандартные модули
В Турбо Паскале имеется 8 стандартных модулей, в которых содержится
множество различных типов, констант, процедур и функций. Этими
модулями являются SYSTEM, DOS, CRT, GRAPH, OVERLAY, TURBO3,
GRAPH3. Модули GRAPH, TURBO3, GRAPH3 выделены в отдельные TPUфайлы, а остальные входят в состав библиотечного файла TURBO.TPL. Лишь
один модуль SYSTEM подключается к любой программе автоматически, все
остальные становятся доступны только после указания их имен в списке
подключаемых модулей.
Модуль SYSTEM. В него входят все процедуры и функции стандартного
Паскаля, а также встроенные процедуры и функции, которые не вошли в
другие стандартные модули (например, INC, DEC, GETDIR и т.п.). Модуль
SYSTEM подключается к любой программе независимо от того, объявлен ли
он в предложении USES или нет, поэтому его глобальные константы,
переменные, процедуры и функции считаются встроенными в Турбо
Паскаль.
Модуль PRINTER делает доступным вывод текстов на матричный
принтер. В нем определяется файловая переменная LST типа TEXT, которая
связывается с логическим устройством PRN. После подключения данного
модуля можно выполнить, например, такое действие:
Uses printer;
Begin
Writeln(lst, ‘Турбо Паскаль’);
End.
Модуль CRT. В нем сосредоточены процедуры и функции,
обеспечивающие управление текстовым режимом работы экрана. С его
помощью можно перемещать курсор в любую точку экрана, менять цвет
выводимых символов и фона, создавать окна. Кроме того, в данный модуль
включены также процедуры «слепого» чтения клавиатуры и управления
звуком.
Модуль GRAPH. Содержит набор типов, констант, процедур и функций
для управления графическим режимом работы экрана. Этот модуль
позволяет создавать различные графические изображения и выводить на
экран надписи стандартными или созданными программистом шрифтами.
Модуль DOS. В модуле собраны процедуры и функции, открывающие
доступ к средствам дисковой операционной системы MS-DOS.
Модуль OVERLAY. Данный модуль необходим при разработке
громоздких программ с перекрытиями. Турбо Паскаль обеспечивает создание
программ, длина которых ограничивается лишь основной оперативной
памятью. Операционная система MS-DOS оставляет программе около 580
Кбайт основной памяти. Память такого размера достаточна для большинства
исполняемых программ, тем не менее, использование программ с
перекрытиями снимает это ограничение.
Модули TURBO3 и GRAPH3 введены для обеспечения совместимости с
ранней версией системы Турбо Паскаль.
ЛАБОРАТОРНАЯ РАБОТА №13
Тема: Модули
Задание:
При выполнении задания по модульному программированию студент
должен создать отдельный модуль, реализующий простейшие операции над
данными указанного типа. С помощью созданного модуля необходимо
решить основную задачу.
Перед написанием основной программы, необходимо проверить
правильность работы всех процедур модуля. Для этой цели необходимо
разработать тестовые задания, написать программу тестирования модуля.
При написании программы решения задачи необходимо уделить особое
внимание интерфейсу: оформление, контроль правильности ввода, вывод
сообщений и т.д.
Задачи:
Вариант 1.
Модуль. Реализовать операции над целыми числами в 16-ричной
системе счисления: сложения, умножения, операцию преобразования из 10тичной в 16-ричную систему, операции вычитания, деления над целыми
числами в 16-ричной системе счисления, операцию преобразования из 16ричной в 10-тичную систему.
Задача. Дан массив целых чисел, представленных в 16-ричной системе
счисления. Найти произведение наибольшего и наименьшего чисел.
Вычислить сумму элементов массива, расположенных между наибольшим и
наименьшим числами. Ответ выдать в десятичной и 16-ричной системе
счисления.
Вариант 2.
Модуль. Реализовать операции над целыми числами в 2-ичной системе
счисления: сложения, умножения, операцию преобразования из 10-тичной в
2-ичную систему, операции вычитания, деления над целыми числами в 2ичной системе счисления, операцию преобразования из 2-ичной в 10-тичную
систему.
Задача. Дан массив целых чисел, представленных в 2-ичной системе
счисления. Вычислить сумму наибольшего и наименьшего чисел. Найти
произведение чисел, расположенных в массиве левее наибольшего.
Вариант 3.
Модуль. Реализовать операции над целыми числами в 8-ричной системе
счисления: сложения, умножения, операцию преобразования из 10-тичной в
8-ричную систему, операции вычитания, деления над целыми числами в 8ричной системе счисления, операцию преобразования из 8-ричной в 10тичную систему.
Задача. Дан массив целых чисел, представленных в 8-ричной системе
счисления. Вычислить индексы наибольшего и наименьшего чисел. Найти
произведение чисел, расположенных в массиве правее наименьшего.
Вариант 4.
Модуль. Реализовать операции преобразования чисел из десятичной
системы счисления в 2-ичную, 8-ричную, 16-ричную, а также функцию
нахождения большего из двух чисел, операции преобразования чисел из 2ичной, 8-ричной, 16-ричной систем счисления в десятичную, а также
функцию нахождения меньшего из двух чисел.
Задача. Даны 4 целых числа, представленных в различных системах
счисления: A10, B2, C8, D16. Вычислить значение выражения A*D-C*B.
Результат вывести в каждой системе счисления по основанию 10, 2, 8, 16.
Вывести числа в порядке возрастания в тех системах счисления, в которых
они были введены.
Вариант 5.
Модуль. Реализовать сложение, вычитание, умножение длинных целых
чисел, операции отношения для длинных целых чисел.
Задача. Даны два натуральных длинных числа. Вычислить значение
выражения (max(n, m) – min(n,m))2.
Вариант 6.
Модуль.
Реализовать
операции
сложения,
умножения,
дифференцирования многочленов,
операции вычитания, деления,
интегрирования многочленов, вычисления значения многочлена в заданной
точке.
Задача. Даны два многочлена P(x) и Q(x). Вычислить
b
 ( P( x)
2
 Q( x)) dx
a
Вариант 7.
Модуль. Реализовать набор подпрограмм для работы с векторами:
сложение, вычитание, вычисление длины вектора, скалярное произведение
векторов, умножение вектора на число, вычисление угла между векторами.
Задача. Дан массив векторов. Найти вектор, который образует с самым
длинным вектором массива наибольший угол.
Вариант 8.
Модуль. Реализовать в виде модуля набор подпрограмм для выполнения
операций над комплексными числами: сложения, вычитания, умножения,
деления, модуля комплексного числа, возведения комплексного числа в
натуральную степень.
Задача. Дан массив комплексных чисел. Получить новый массив,
элементами которого будут модули сумм рядом стоящих комплексных чисел.
Вариант 9.
Модуль. Реализовать в виде модуля набор подпрограмм для выполнения
следующих операций над векторами: сложения, вычитания, скалярного
умножения векторов, умножения вектора на число, нахождение длины
вектора.
Задача. Дан массив векторов. Отсортировать его в порядке убывания
длин векторов, найти скалярное произведение самого короткого вектора на
самый длинный.
Вариант 10.
Модуль. Разработать набор подпрограмм для работы с длинными
неотрицательными числами (числами, выходящими за диапазон допустимых
значений любого целого типа): сложение, вычитание, умножение,
нахождение частного и остатка от деления одного числа на другое, операции
отношения. Длинное число представить следующим типом:
Type cifra=0..9;
Chislo= array [1..1000] of cifra;
Задача. Составить программу вычисления числа 264-346, в результате
сохранить все цифры.
Вариант 11.
Модуль. Разработать способ представления множеств, содержащих
более 255 элементов. Реализовать операции над множествами: объединение,
пересечение, разность, функцию проверки принадлежности элемента
множеству, функцию проверки, является ли данное множество
подмножеством (надмножеством) другого.
Задача. Даны три множества A, B, C, содержащие более 255 элементов.
Найти множество D  A ( B \ C ) .
Вариант 12.
Модуль. Реализовать набор подпрограмм для работы с дробными
двоичными числами: сложения, вычитания, умножения, деления, перевода из
двоичной в десятичную систему счисления и обратно, операции отношения.
Задача. Дан массив дробных чисел в двоичной системе счисления.
Вычислить сумму тех элементов массива, которые больше заданного
двоичного числа. Результат представить в двоичной и десятичной системах
счисления.
Вариант 13.
Модуль. Реализовать набор подпрограмм для работы с дробными
числами в 8-ричной системе счисления: сложение, вычитание, умножение,
деление, перевод из 8-ричной системы в 10-тичную и обратно, операции
отношения.
Задача. Дан массив дробных чисел в 8-ричной системе счисления.
Вычислить произведение элементов массива, не превышающих заданного
числа, представленного в десятичной системе счисления.
Вариант 14.
Модуль. Реализовать набор подпрограмм для выполнения действий над
многочленами: сложение, вычитание, умножение, деление.
Задача. Дано дробно-рациональное выражение
2 P( x)
, где P(x) и
P( x)  3Q( x)
Q(x) – многочлены. Выполнить действия и найти наибольший общий
делитель числителя и знаменателя, сократить и вывести упрощенное
выражение на экран.
1.
2.
3.
4.
ЛИТЕРАТУРА
Грызлов В.И., Грызлова Т.П. Турбо Паскаль 7.0 – М.: ДМК, 1998.
Фаронов В.В. Турбо Паскаль 7.0. Начальный курс. Учебное пособие.
– М.: «Нолидж», издатель Молгачева С.В., 2001.
Марченко А.И., Марченко Л.М. Программирование в среде Turbo
Pascal 7.0. – К.: ВЕК+, М.: ДЕСС, 1999.
Семакин И.Г., Шестаков А.П. Основы программирования: Учебник. –
М.: Мастерство; НМЦ СПО; Высшая школа, 2001.
ВВЕДЕНИЕ В ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ
ПРОГРАММИРОВАНИЕ
Абстрактные типы данных
Понятие абстрактных типов данных является ключевым в
программировании. Абстракция подразумевает разделение и независимое
рассмотрение интерфейса и реализации.
Рассмотрим пример. Все мы смотрим телевизионные программы.
Назовем телевизор модулем или объектом. Этот объект имеет интерфейс с
пользователем, т. е. средства управления (совокупность кнопок),
воспроизведения изображения и звука. Чем совершеннее интерфейс, тем
удобнее телевизор в использовании. Мы переключаем программы, нажимая
определенные кнопки, и при этом не задумываемся о физических процессах,
происходящих в телевизоре. Об этом знают специалисты. Когда мы
выбираем телевизор, нас интересуют его цена и эксплуатационные
параметры, т. е. качество изображения, звука и т. п. Однако нас не интересует
то, что находится внутри. Другими словами, мы возвращаемся к свойствам
объекта (модуля), какими являются интерфейс и реализация. Основная цель
абстракции в программировании как раз и заключается в отделении
интерфейса от реализации.
Вернемся к нашему примеру. Предположим, некоторый субъект уверен,
что хорошо знает устройство телевизора. Он снимает крышку и начинает
«усовершенствовать» его. Хотя иногда это и приводит к определенным
промежуточным (локальным) успехам, окончательный результат почти
всегда отрицательный. Поэтому подобные действия надо запрещать. В
программировании это поддерживается механизмами запрета доступа или
скрытия
внутренних
компонентов.
Каждому
объекту
(модулю)
предоставлено право самому распоряжаться «своим имуществом», т. е.
данными функциями и операциями. Игнорирование этого принципа
нарушает стабильность системы и часто приводит к ее полному разрушению.
Принцип абстракции обязывает использовать механизмы скрытия, которые
предотвращают умышленное или случайное изменение внутренних
компонентов.
Абстракция данных предполагает определение и рассмотрение
абстрактных типов данных (АТД) или, что то же самое, новых типов
данных, введенных пользователем.
Абстрактный тип данных — это совокупность данных вместе с
множеством операций, которые можно выполнять над этими данными.
Понятие объектно-ориентированного программирования
По определению авторитета в области объектно-ориентированных
методов разработки программ Гради Буча «объектно-ориентированное
программирование (ООП) – это методология программирования, которая
основана на представлении программы в виде совокупности объектов,
каждый из которых является реализацией определенного класса (типа
особого вида), а классы образуют иерархию на принципах наследуемости».
Объектно-ориентированная методология так же, как и структурная
методология, была создана с целью дисциплинировать процесс разработки
больших программных комплексов и тем самым снизить их сложность и
стоимость.
Объектно-ориентированная методология преследует те же цели, что и
структурная, но решает их с другой отправной точки и в большинстве
случаев позволяет управлять более сложными проектами, чем структурная
методология.
Как известно, одним из принципов управления сложностью проекта
является декомпозиция. Гради Буч выделяет две разновидности
декомпозиции: алгоритмическую (так он называет декомпозицию,
поддерживаемую структурными методами) и объектно-ориентированную,
отличие которых состоит, по его мнению, в следующем: «Разделение по
алгоритмам концентрирует внимание на порядке происходящих событий, а
разделение по объектам придает особое значение факторам, либо
вызывающим действия, либо являющимся объектами приложения этих
действий».
Другими словами, алгоритмическая декомпозиция учитывает в большей
степени структуру взаимосвязей между частями сложной проблемы, а
объектно-ориентированная декомпозиция уделяет больше внимания
характеру взаимосвязей.
На
практике
рекомендуется
применять
обе
разновидности
декомпозиции: при создании крупных проектов целесообразно сначала
применять объектно-ориентированный подход для создания общей иерархии
объектов, отражающих сущность программируемой задачи, а затем
использовать алгоритмическую декомпозицию на модули для упрощения
разработки и сопровождения программного комплекса.
ОО-программирование является, несомненно, одним из наиболее
интересных направлений для профессиональной разработки программ.
Объекты и классы
Базовыми блоками объектно-ориентированной программы являются
объекты и классы. Содержательно объект можно представить как что-то
ощущаемое или воображаемое и имеющее хорошо определенное поведение.
Таким образом, объект можно либо увидеть, либо потрогать, либо, по крайней мере, знать, что он есть, например, представлен в виде информации,
хранимой в памяти компьютера. Дадим определение объекта, придерживаясь
мнения Гради Буча: «Объект – осязаемая сущность, которая четко проявляет
свое поведение».
Объект — это часть окружающей нас реальности, т. е. он существует во
времени и в пространстве (впервые понятие объекта в программировании
введено в языке Simula). Формально объект определить довольно трудно. Это
можно сделать через некоторые свойства, а именно: объект имеет состояние,
поведение и может быть однозначно идентифицирован (другими словами,
имеет уникальное имя).
Класс — это множество объектов, имеющих общую структуру и общее
поведение. Класс — описание (абстракция), которое показывает, как
построить существующую во времени и пространстве переменную этого
класса, называемую объектом. Смысл предложений «описание переменных
класса» и «описание объектов класса» один и тот же.
Объект имеет состояние, поведение и паспорт (средство для его
однозначной идентификации); структура и поведение объектов описаны в
классах, переменными которых они являются.
Определим теперь понятия состояния, поведения и идентификации
объекта.
Состояние объекта объединяет все его поля данных (статический
компонент, т.е. неизменный) и текущие значения каждого из этих полей
(динамический компонент, т.е. обычно изменяющийся).
Поведение выражает динамику изменения состояний объекта и его
реакцию на поступающие сообщения, т.е. как объект изменяет свои
состояния и взаимодействует с другими объектами.
Идентификация (распознавание) объекта — это свойство, которое
позволяет отличить объект от других объектов того же или других классов.
Осуществляется идентификация посредством уникального имени (паспорта),
которым наделяется объект в программе, впрочем как и любая другая
переменная.
Выше уже говорилось, что процедурный (а также и модульный) подход
позволяет строить программы, состоящие из набора процедур
(подпрограмм), реализующих заданные алгоритмы. С другой стороны,
объектно-ориентированный подход представляет программы в виде набора
объектов, взаимодействующих между собой. Взаимодействие объектов
осуществляется через сообщения. Предположим, что нашим объектом
является окружность. Тогда сообщение, посланное этому объекту, может
быть следующим: «нарисуй себя». Когда мы говорим, что объекту
передается сообщение, то на самом деле мы вызываем некоторую функцию
этого объекта (компонент-функцию). Так, в приведенном выше примере мы
вызовем функцию, которая будет рисовать окружность на экране дисплея.
Базовые принципы ООП
К
базовым
принципам
объектно-ориентированного
стиля
программирования относятся:
1) пакетирование или инкапсуляция;
2) наследование;
3) полиморфизм;
4) передача сообщений.
Пакетирование (инкапсуляция) предполагает соединение в одном
объекте данных и функций, которые манипулируют этими данными. Доступ
к некоторым данным внутри пакета может быть либо запрещен, либо
ограничен.
Объект характеризуется как совокупностью всех своих свойств
(например, для животных – это наличие головы, ушей, глаз и т.д.) и их
текущих значений (голова – большая, уши – длинные, глаза – желтые и т.д.),
так и совокупностью допустимых для этого объекта действий (умение
принимать пищу, сидеть, стоять, бежать и т.д.). Указанное объединение в
едином объекте как «материальных» составных частей (голова, уши, хвост,
лапы), так и действий, манипулирующих этими частями (действие «бежать»
быстро перемещает лапы) называется инкапсуляцией.
В рамках ООП данные называются полями объекта, а алгоритмы –
объектными методами.
Инкапсуляция позволяет в максимальной степени изолировать объект от
внешнего
окружения.
Она
существенно
повышает
надежность
разрабатываемых программ, т.к. локализованные в объекте алгоритмы
обмениваются с программой сравнительно небольшими объемами данных,
причем количество и тип этих данных обычно тщательно контролируется. В
результате
замена
или
модификация
алгоритмов
и
данных,
инкапсулированных в объект, как правило, не влечет за собой плохо
прослеживаемых последствий для программы в целом. Другим
немаловажным следствием инкапсуляции является легкость обмена
объектами, переноса их из одной программы в другую.
Наследование
И структурная, и объектно-ориентированная методологии преследуют
цель построения иерархического дерева взаимосвязей между объектами
(подзадачами). Но если структурная иерархия строится по простому
принципу разделения целого на составные части,
Животное
голова
глаза
уши
туловище
…
рот
лапы
…
хвост
то при создании объектно-ориентированной иерархии принимается другой
взгляд на тот же исходный объект. В объектно-ориентированной иерархии
непременно отражается наследование свойств родительских (вышележащих)
типов объектов дочерним (нижележащим) типам объектов.
Животное
млекопитающее
кошка
собака
…
птица
слон
орел
…
ворон
По Гради Бучу «наследование – это такое отношение между объектами,
когда один объект повторяет структуру и поведение другого».
Принцип наследования действует в жизни повсеместно и повседневно.
Млекопитающие и птицы наследуют признаки живых организмов, в отличие
от растений, орел и ворон наследуют общее свойство для птиц – умение
летать. С другой стороны, львы, тигры, леопарды наследуют «структуру» и
поведение, характерное для представителей отряда кошачьих и т.д.
Типы верхних уровней объектно-ориентированной иерархии, как
правило, не имеют конкретных экземпляров объектов. Не существует,
например, конкретного живого организма, который бы сам по себе назывался
«млекопитающее» или «птица». Такие типы называют абстрактными.
Конкретные экземпляры объектов имеют, как правило, типы самых нижних
уровней ОО-иерархии: «крокодил Гена» – конкретный экземпляр объекта
типа «крокодил», «кот Матроскин» – конкретный экземпляр объекта типа
«кошка».
Наследование позволяет использовать библиотеки классов и развивать
их (совершенствовать и модифицировать библиотечные классы) в
конкретной программе. Наследование позволяет создавать новые объекты,
изменяя или дополняя свойства прежних. Объект-наследник получает все
поля и методы предка, но может добавить собственные поля, добавить
собственные методы или перекрыть своими методами одноименные
унаследованные методы.
Принцип наследования решает проблему модификации свойств объекта
и придает ООП в целом исключительную гибкость. При работе с объектами
программист обычно подбирает объект, наиболее близкий по своим
свойствам для решения конкретной задачи, и создает одного или нескольких
потомков от него, которые «умеют» делать то, что не реализовано в
родителе.
Последовательное проведение в жизнь принципа «наследуй и изменяй»
хорошо согласуется с поэтапным подходом к разработке крупных
программных проектов и во многом стимулирует такой подход.
Когда вы строите новый класс, наследуя его из существующего класса,
можно:
 добавить в новый класс новые компоненты-данные;
 добавить в новый класс новые компоненты-функции;
 заменить в новом классе наследуемые из старого класса компонентыфункции.
Полиморфизм позволяет использовать одни и те же функции для
решения разных задач. Полиморфизм выражается в том, что под одним
именем скрываются различные действия, содержание которых зависит от
типа объекта.
Полиморфизм – это свойство родственных объектов (т.е. объектов,
имеющих одного общего родителя) решать схожие по смыслу проблемы
разными способами. Например, действие «бежать» свойственно большинству
животных. Однако каждое из них (лев, слон, крокодил, черепаха) выполняет
это действие различным образом.
При традиционном (не объектно-ориентированном) подходе к
программированию, животных перемещать будет программист, вызывая
отдельную для конкретного животного и конкретного действия
подпрограмму.
В рамках ООП поведенческие свойства объекта определяются набором
входящих в него методов, программист только указывает, какому объекту
какое из присущих ему действий требуется выполнить, и (для
рассматриваемого примера) однажды описанные объекты-животные сами
будут себя передвигать характерным для них способом, используя входящие
в его состав методы. Изменяя алгоритм того или иного метода в потомках
объекта, программист может придавать этим потомкам отсутствующие у
родителя специфические свойства. Для изменения метода необходимо
перекрыть его в потомке, т.е. объявить в потомке одноименный метод и
реализовать в нем нужные действия. В результате в объекте-родителе и
объекте-потомке будут действовать два одноименных метода, имеющих
разную алгоритмическую основу и, следовательно, придающие объектам
разные свойства. Это и называется полиморфизмом объектов.
Таким образом, в нашем примере с объектами-животными действие
«бежать» будет называться полиморфическим действием, а многообразие
форм проявления этого действия – полиморфизмом.
Описание объектного типа
Класс или объект – это структура данных, которая содержит поля и
методы. Как всякая структура данных она начинается зарезервированным
словом и закрывается оператором 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 v1: tPredoc;
{объявление экземпляра объекта}
begin
v1.Name:= ‘Петров Николай Иванович’;
v1.Declaration;
v1.MyName
end.
Использование поля данных объекта v1 не отличается по своему
синтаксису от использования полей записей. Вызов методов экземпляра
объекта означает, что указанный метод вызывается с данными объекта v1. В
результате на экран будут выведены строчки
Я – предок!
Я – Петров Николай Иванович
Аналогично записям, к полям переменных объектного типа разрешается
обращаться как с помощью уточненных идентификаторов, так и с помощью
оператора with.
Например, в тексте программы вместо операторов
v1.Name:= ‘Петров Николай Иванович’;
v1.Declaration;
v1.MyName
возможно использование оператора with такого вида
with v1 do
begin
Name:= ‘Петров Николай Иванович’;
Declaration;
MyName
End;
Более того, применение оператора with с объектными типами, также как
и для записей не только возможно, но и рекомендуется.
Иерархия типов (наследование)
Типы можно выстроить в иерархию. Объект может наследовать
компоненты из другого объектного типа. Наследующий объект — это
потомок. Объект, которому наследуют — предок. Подчеркнем, что
наследование относится только к типам, но не к экземплярам объектов.
Если введен объектный тип (предок, родительский), а его надо дополнить полями или методами, то вводится новый тип, объявляется наследником
(потомком, дочерним типом) первого и описываются только новые поля и
методы. Потомок содержит все поля типа предка. Заметим, что поля и методы предка доступны потомку без специальных указаний. Если в описании
потомка повторяются имена полей или методов предка, то новые описания
переопределяют поля и методы предка.
Базовый класс для
CLASS2
Производный от CLASS1
Базовый для CLASS3
CLASS 1
CLASS 2
МЕТОДЫ:
METHOD A
METHOD B
METHOD C
CLASS 3
МЕТОДЫ:
METHOD A (redefined)
METHOD D
METHOD E
Методы: A,B,C
Производный
от CLASS2
МЕТОДЫ:
METHOD A (redefined)
METHOD C (redefined)
METHOD F
Методы:
B,C из CLASS1
A,D,E из CLASS2
Методы:
B из CLASS1
D,E из CLASS2
A,C,F из CLASS3
ООП всегда начинается с базового класса. Это шаблон для базового
объекта. Следующим этапом является определение нового класса, который
называется производным и является расширением базового.
Производный класс может включать дополнительные методы, которые
не существуют в базовом классе. Он может переопределять (redefined)
методы (или даже удалять их целиком).
В производном классе не должны переопределяться все методы базового
класса. Каждый новый объект наследует свойства базового класса,
необходимо лишь определить те методы, которые являются новыми или
были изменены. Все другие методы базового класса считаются частью и
производного. Это удобно, т.к. когда метод изменяется в базовом классе, он
автоматически изменяется во всех производных классах.
Процесс наследования может быть продолжен. Класс, который
произведен от базового, может сам стать базовым для других производных
классов. Таким образом, ОО программы создают иерархию классов.
Пример иерархической структуры объектов
Самый общий
базовый класс
Производный
Базовый
Производный
Производный
Базовый
Базовый
Базовый
производный
производный
Простое, немножественное
наследование
производный
Множественное
наследование
Наиболее часто структура иерархии классов описывается в виде дерева.
Вершины дерева соответствуют классам, а корню соответствует класс,
который описывает что-то общее (самое общее) для всех других классов.
Наследование дочерними типами информационных полей и методов их
родительских типов выполняется по следующим правилам.
Правило 1. Информационные поля и методы родительского типа
наследуются всеми его дочерними типами независимо от числа
промежуточных уровней иерархии.
Правило 2. Доступ к полям и методам родительских типов в рамках
описания любых дочерних типов выполняется так, как будто-бы они описаны
в самом дочернем типе.
Правило 3. Ни в одном дочернем типе не могут быть использованы
идентификаторы полей родительских типов.
Правило 4. Дочерний тип может доопределить произвольное число
собственных методов и информационных полей.
Правило 5. Любое изменение текста в родительском методе
автоматически оказывает влияние на все методы порожденных дочерних
типов, которые его вызывают.
Правило 6. В противоположность информационным полям
идентификаторы методов в дочерних типах могут совпадать с именами
методов в родительских типах. В этом случае говорят, что дочерний метод
перекрывает (подавляет) одноименный родительский метод. В рамках
дочернего типа, при указании имени такого метода, будет вызываться
именно дочерний метод, а не родительский.
Продолжим рассмотрение нашего примера. В дополнение к введенному
нами типу предка tPredoc можно ввести типы потомков:
tуре tSon = оbject (tPredoc)
{Тип, наследующий tPredoc}
procedure Declaration;
{перекрытие методов предка}
procedure МуName (Predoc : tPredoc);
end;
tуре tGrandSon= оbject (tSon)
procedure Declaration;
end;
{Тип, наследующий tSon}
{перекрытие методов предка}
Имя типа предка приводится в скобках после слова о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 – «внук». Потребуем от них, чтобы они представились.
{заголовок программы}
……………….
{раздел описания типов, в том числе и объектных типов tPredoc, tSon,
tGrandSon}
{Обратите внимание! Экземпляры объектных типов можно описать как
типизированные константы, что мы для примера и сделали ниже}
const ded: tPredoc = (Name: 'Петров Николай Иванович');
otec: tSon = (Name: 'Петров Сергей Николаевич');
vnuk: tGrandSon= (Name: 'Петров Олег Сергеевич');
{раздел описания процедур и функций, где обязательно должны быть
написаны все объявленные в объектных типах методы}
begin
ded. Declaration;
{вызов методов общего предка}
ded. МуName;
writeln;
otec.Declaration;
otec.MyName(ded);
{вызов методов объекта otec типа tSon}
writeln;
vnuk.Declaration; {вызов методов объекта vnuk типа tGrandSon}
vnuk.MyName(otec);
end.
Наша программа выведет на экран:
Я — предок!
Я — Петров Николай Иванович
Я — отец!
Я — Петров Сергей Николаевич
Я — сын Петров Николай Ивановича
Я — внук!
Я — Петров Олег Сергеевич
Я — сын Петров Сергей Николаевича
Обратите внимание, что в заголовке процедуры 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;
Задание по
объектно-ориентированному программированию
Минимальный уровень:
Для получения зачета по теме «Объектно-ориентированное
программирование» необходимо выполнить следующие задания:
1. Набрать, отладить и проверить работу программы «Аквариум»;
2. Модифицировать программу, добавив метод движения для рыб;
3. «Поселить» в аквариуме созданный вами объект, придумать его
свойства и методы, а также взаимодействие с рыбами.
Максимальный уровень:
Для получения зачета на этом уровне вам необходимо самостоятельно
решить одну из предложенных задач, используя описание объектов и
принципы ООП.
Задачи:
1. Решить задачу «Аквариум» в графическом режиме, используя следующие
рекомендации для развития задачи и изменения законов обитания рыб:
определить объект «аквариумная рыбка», имеющий свойства
(координаты, скорость движения, размер, цвет, направление движения) и
методы Init (устанавливает значения полей и рисует рыбу на экране
методом Draw), Draw (рисует рыбу в виде уголка с острием в точке с
координатами (x, y) и направленного острием по ходу движения), Look
(проверяет несколько точек на линии движения рыбы. Если хоть одна
точка отличается по цвету от воды, возвращаются ее цвет и расстояние до
рыбы), Run (перемещает рыбу в текущем направлении на расстояние,
зависящее от скорости. Иногда случайным образом меняет направление
движения рыбы. Если рыба видит препятствие, направление движения
меняется, пока препятствие не исчезнет из поля зрения рыбы).
объединить «съедобных» рыб и «хищников» в две стаи.
Позвольте пользователю пополнять стаи, вводя рыб с клавиатуры.
позволить «хищникам» поедать «съедобных» рыб как только они
их увидят или догонят (на ваше усмотрение).
2. Составить программу для игры в шашки. Шашка каждого цвета
выступает в качестве отдельного объекта. Характеристики шашки – цвет
и позиция на доске. Методы – перемещение. Не забудьте о таких
объектах, как «дамки».
3. Волчий остров (Ван Тассел Д. Стиль, разработка, эффективность,
отладка и испытание программ. М.: Мир, 1981.)
Волчий остров размером 20 * 20 заселен дикими кроликами, волками и
волчицами. Имеется по несколько представителей каждого вида. Кролики
довольно глупы: в каждый момент времени они с одинаковой
вероятностью 1/9 передвигаются в один из восьми соседних квадратов (за
исключением участков, ограниченных береговой линией) или просто
сидят неподвижно. Каждый кролик с вероятностью 0.2 превращается в
двух кроликов. Каждая волчица передвигается случайным образом, пока
в одном из соседних восьми квадратов не окажется кролик, за которым
она охотится. Если волчица и кролик оказываются в одном квадрате,
волчица съедает кролика и получает одно "очко". В противном случае она
теряет 0.1 "очка". Волки и волчицы с нулевым количеством очков
умирают.
В начальный момент времени все волки и волчицы имеют 1 очко. Волк
ведет себя подобно волчице до тех пор, пока в соседних квадратах не
исчезнут все кролики; тогда если волчица находится в одном из восьми
близлежащих квадратов, волк гонится за ней. Если волк и волчица
окажутся в одном квадрате и там нет кролика, которого нужно съесть,
они производят потомство случайного пола.
Запрограммируйте
предполагаемую
экологическую
модель,
понаблюдайте за изменением популяции в течение некоторого периода
времени.
4. Задача об инфекции стригущего лишая (Ван Тассел Д. Стиль,
разработка, эффективность, отладка и испытание программ. М.: Мир,
1981.)
Промоделируйте процесс распространения инфекции стригущего лишая
по участку кожи размером n * n (n — нечетное) клеток. Предполагается,
что исходной зараженной клеткой кожи является центральная. В каждый
интервал времени пораженная инфекцией клетка может с вероятностью
0,5 заразить любую из соседних здоровых клеток. По прошествии шести
единиц времени зараженная клетка становится невосприимчивой к
инфекции, возникший иммунитет действует в течение последующих
четырех единиц времени, а затем клетка оказывается здоровой. В ходе
моделирования описанного процесса выдавать текущее состояние
моделируемого участка кожи в каждом интервале времени, отмечая
зараженные, невосприимчивые к инфекции и здоровые клетки.
5. Игра "Две лисы и 20 кур"
На поле указанной формы (см. рисунок ниже) находятся две лисы и 20
кур. Куры могут перемещаться на один шаг вверх, влево или вправо, но
не назад и не по диагонали. Лисы также могут перемещаться только на
один шаг, но также и вверх — как вниз, влево и вправо. Лиса может
съесть курицу — как в игре в шашки: если в горизонтальном или
вертикальном направлении за курицей на один шаг следует свободное
поле, то лиса перепрыгивает через курицу и берет ее. Лисы всегда
обязаны есть и, когда у них есть выбор, — они обязаны осуществлять
наиболее длинное поедание. Если два приема пищи имеют одинаковую
длину, осуществляется один из них — по выбору лисы.
Составить программу, которая играет за лис. Игрок перемещает кур.
Партнеры играют по очереди, причем куры начинают. Они выигрывают
партию, если девяти из них удается занять 9 полей, образующих верхний
квадрат игры.
Начальное положение кур и лис изображено на рисунке.
Л
Л
К
К
К
К
К
К
К
К
К
К
К
К
К
К
К
К
К
К
К
К
Лисы выигрывают, если им удается съесть 12 кур, так как тогда
оставшихся кур недостаточно, чтобы занять 9 верхних полей.
6. Морской бой
Составить программу, позволяющую играть в морской бой игроку с
компьютером. Программа должна позволять расставлять корабли на поле
10 * 10, контролировать правильность их расстановки, делать
противникам поочередно ходы и выдавать соответствующие
информационные сообщения. Когда в качестве одного из игроков
выступает компьютер, программа должна анализировать предыдущие
ходы и следующий делать на основе проведенного анализа.
Пример решения задачи с использованием объектов
Рассмотрим пример решения следующей задачи:
в аквариуме находится N рыбок. К ним помещается «хищник».
«Хищник» может съесть рыбку, находящуюся от него на самом близком
расстоянии.
Наша задача заключается в размещении рыбок в аквариуме случайным
образом. Случайным образом к ним помещается «хищник». Необходимо
выделить и удалить с экрана ту рыбку, которая оказалась от «хищника» на
ближайшем расстоянии.
Задание 1.
Начнем с описания объекта «рыба». Этот объект должен иметь
свойства (поля): координаты, цвет, название; методы: вывода на экран
Drow, инициализации Init, т.е. установки координат, цвета и изображения на
экране с помощью метода Drow.
Такое описание объекта «рыба» в программе будет выглядеть следующим образом:
type
tfish=object
x,y,col:byte; {свойства объекта: x,y – координаты, col – цвет}
name:char; {свойство объекта – название}
procedure drow(x1,y1:byte; s:char); {метод объекта, изображающий его на
экране}
procedure init; {метод, инициализирующий объект}
end;
Замечание: решение задачи будем выполнять в текстовом режиме.
После описания объектного типа для примера и контроля введем один
экземпляр данного типа:
Var f: tfish;
Заметим сразу, что этот экземпляр объектного типа мы используем для
проверки описания методов объектного типа «рыба», чтобы проверить
правильность установки текстового режима, выбора цвета и т.д.
Далее в тексте программы в разделе описания процедур и функций
следует описание методов объектного типа «рыба»:
procedure tfish.drow;
begin
GoToXY(x1,y1); {устанавливаем курсор на позицию с координатами x1,
y1}
write(s); {«рыбу» изобразим символом }
delay(100); {поставим задержку на 100 миллисекунд для того, чтобы,
когда будем выводить на экран много рыб, они появлялись постепенно}
end;
procedure tfish.init;
begin
x:=random(80); {случайным образом задаем значение столбца от 1 до 80}
y:=random(25); {случайным образом задаем значение строки от 1 до 25}
col:=random(15); {случайным образом задаем цвет от 0 до 15}
TextColor(col); {устанавливаем цвет выводимой буквы}
Name:=chr(random(100)) {случайным образом задаем название «рыбы»
как символ, соответствующий коду от 1 до 100}
drow(x,y,name); {обращаемся к методу, изображающему «рыбу» на
экране}
end;
А сейчас напишем вспомогательную процедуру, «заполняющую аквариум
водой», эта процедура не имеет отношения к объектно-ориентированному
программированию, но для решения нашей задачи может пригодиться, хотя
бы просто для красоты.
procedure fon;
var
i,j,k:byte;
begin
TextColor(Blue); {установим цвет выводимых символов, совпадающий с
цветом фона}
for j:=24 downto 1 do
begin
for i:=40 downto 1 do
begin
k:=80-i+1;
GotoXY(i,j);
write(chr(219)); {выводим символ, соответствующий коду 219 –
прямоугольник}
GotoXY(k,j);
write(chr(219)); {выводим символ, соответствующий коду 219 –
прямоугольник}
delay(12); {эта процедура позволяет устанавливать время задержки в
миллисекундах}
end;
end;
end;
После всех предварительно проделанных действий мы можем написать
программу, в которой определим объектный тип «рыба» с его свойствами и
методами, «заведем» одну из «рыб», «заполним аквариум водой» и поместим
туда одну нашу «рыбку»:
program fish;
uses crt; {для использования процедур работы в текстовом режиме
необходимо подключить специальный модуль CRT}
type
tfish=object {описание объектного типа «рыба»}
x,y,col:byte; name:char
procedure drow(x1,y1:byte; s:char);
procedure init;
end;
var
f:tfish;
procedure tfish.drow;
begin
GoToXY(x1,y1);
write(s);
delay(100)
end;
procedure tfish.init; {метод, инициализирующий объект «рыба»}
begin
x:=random(80);
y:=random(25);
col:=random(15);
TextColor(col);
Name:=chr(random(100));
drow(x,y,name);
end;
procedure fon; {«заполнение аквариума водой»}
var
i,j,k:byte;
begin
TextColor(Blue);
for j:=24 downto 1 do
begin
for i:=40 downto 1 do
begin
k:=80-i+1;
GotoXY(i,j);
write(chr(219));
GotoXY(k,j);
write(chr(219));
delay(12);
end;
end;
end;
begin {начало основной программы}
clrscr; {очищаем экран}
randomize; {активизируем функцию генерации случайных чисел}
TextMode(CO80); {устанавливаем текстовый режим цветной, 80*25}
TextBackGround(Blue); {устанавливаем синий цвет фона}
fon; {«заполняем аквариум водой»}
f.init; {«размещаем рыбку в аквариуме», используя метод
инициализации объекта «рыба»}
readln;
end.
Задание 2.
Настало время описать объектный тип «съедобных (для хищника) рыб»,
назовем этих рыб «карп». Причем объектный тип «карп» является потомком
объектного типа «рыба», т.е. наследует его структуру и поведение. Нам
понадобится новый метод, назовем его kill, удаляющий с экрана тех
«карпов», которых съест «хищник». А также перекроем родительский
одноименный метод init, т.к. для «карпов» этот метод будет выполняться
особым для них («карпов») образом.
Описание объектного типа «карп»:
……{раздел описания типов}
tkarp=object(tfish) {в скобках указывается имя родительского типа}
{свойства (координаты, цвет и название) автоматически наследуются от
родительского типа}
{метод drow автоматически наследуется от родительского типа}
procedure kill; {новый метод, удаляющий «карпов» с экрана}
procedure init; {перекрываем метод родительского объекта}
end;
Далее по программе, в разделе описания процедур и функций,
описываем методы объектного типа «карп»:
procedure tkarp.init;
begin
x:=random(80);
y:=random(25);
col:=14; {зафиксируем цвет «карпов» – желтый}
TextColor(col);
name:='K'; {зафиксируем символ, которым на экране будут обозначаться
«карпы»}
drow(x,y,name); {с помощью метода объектного типа «карп» выведем на
экран в позиции с координатами x,y, заданной случайным образом, желтую
букву К}
end;
procedure tkarp.kill;
begin
name:=chr(219);{заменяем символ ‘K’ на прямоугольник}
Col:=Blue; {заменяем желтый цвет на цвет фона}
TextColor(col);
Drow(x,y,name) {перерисовываем объект заново, при этом синий
прямоугольник сливается с цветом фона}
End;
После описания объектного типа «карп» можно определить количество
N экземпляров данного типа и задать массив из N элементов типа «карп»:
……..{раздел описания переменных}
k: array [1..100] of tkarp;
n,i: integer;
Итак, мы завершили описание объектного типа «карп» и приступаем к
описанию объектного типа «хищник», назовем его «акула». Объектный тип
«акула» является потомком объектного типа «рыба», и, следовательно,
наследует все его свойства и методы. Перекроем родительский одноименный
метод init, т.к. для «акул» этот метод будет выполняться особым для них
(«акул») образом.
Описание объектного типа «акула»:
takula=object(tfish)
procedure init;
end;
Далее по программе, в разделе описания процедур и функций,
описываем методы объектного типа «акула»:
procedure takula.init;
begin
x:=random(80);
y:=random(25);
col:=5; {определим цвет изображения «акулы» – малиновый}
TextColor(col+128); {устанавливаем цвет выводимого символа
добавляем эффект мерцания}
name:='A'; {«акула» на экране будет изображаться символом ‘A’}
drow(x,y,name);
end;
и
В разделе описания переменных следует определить экземпляр типа
«акула»:
…….{раздел описания переменных}
a: takula;
А теперь объединим все необходимые фрагменты в программу, которая
только заполняет аквариум «карпами» и помещает туда же «акулу». В этой
программе «карпам» еще ничего не угрожает.
program fish;
uses crt;
type
tfish=object
x,y,col:byte;
name:char;
procedure drow(x1,y1:byte; s:char);
procedure init;
end;
tkarp=object(tfish)
procedure init;
procedure kill;
end;
takula=object(tfish)
procedure init;
end;
var
a:takula;
k:array[1..100] of tkarp;
n,i:integer;
procedure tfish.drow;
begin
GoToXY(x1,y1);
write(s);
end;
procedure tfish.init;
begin
x:=random(80);
y:=random(25);
col:=random(15);
TextColor(col);
Name:=chr(random(100));
drow(x,y,name);
end;
{вспомогательная процедура заполнения аквариума водой}
procedure fon;
var
i,j,k:byte;
begin
TextColor(Blue);
for j:=24 downto 1 do
begin
for i:=40 downto 1 do
begin
k:=80-i+1;
GotoXY(i,j);
write(chr(219));
GotoXY(k,j);
write(chr(219));
delay(12);
end;
end;
end;
procedure tkarp.init;
begin
x:=random(80);
y:=random(25);
col:=14;
TextColor(col);
name:='K';
drow(x,y,name);
end;
procedure tkarp.kill;
begin
name:=chr(219);
Col:=Blue;
TextColor(col);
Drow(x,y,name)
End;
procedure takula.init;
begin
x:=random(80);
y:=random(25);
col:=5;
TextColor(col+128);
name:='A';
drow(x,y,name);
end;
{начало основной программы}
begin
write(‘Задайте число карпов в аквариуме: ‘);
readln(n);
clrscr;
randomize;
TextMode(CO80);
TextBackGround(Blue);
fon;
for i:=1 to n do
k[i].init; {в цикле последовательно размещаем «карпов» на экране}
a.init; {размещаем «акулу»}
readln;
TextMode(BW80); {возвращаем экрану черно-белый режим 80*25}
end.
Задание 3.
Мы проделали основную часть работы. Что еще осталось сделать?
Теперь нам нужно среди всех «карпов» определить самого близкого к
«акуле» и «съесть» его. В этой части решения задачи уже нет элементов
объектно-ориентированного программирования, кроме обращения к методу
tkarp.kill, задача свелась к поиску минимального элемента в массиве. Эту
часть программы вы можете написать самостоятельно.
Задание 4.
Все остальные задания, начиная с этого, предлагаются вам для
самостоятельной работы. Для начала попробуйте наделить «акулу» новым
методом – способностью перемещаться. Для этого вам нужно придать
приращение координатам и перерисовать «акулу» на новом месте. А там
снова найти самого близкого «карпа» и «съесть» его.
Поместите в аквариум не одну, а несколько «акул». Позвольте каждой из
них «съедать» самого близкого к ней «карпа».
Дайте возможность перемещаться не только «акулам», но и «карпам».
Перенесите решение задачи из текстового в графический режим,
создайте объект «аквариум», в котором и живут «рыбы», напишите для этого
объекта методы: init, включающий графический режим, заполняющий
«аквариум» водой, скалами, рыбами; done, выключающий графический
режим. Придумайте продолжение и развитие нашей задачи, и, может быть,
скоро вы будете иметь на своем компьютере свой аквариум.
1.
2.
3.
4.
5.
6.
Литература
Стефен Моррис. Объектно-ориентированное программирование. Серия
«Enter». – Ростов-на-Дону: «Феникс», 1997.
Скляров В.А. Язык С++ и объектно-ориентированное программирование.
– Мн.: Выш. шк., 1997.
Фаронов В.В. Турбо Паскаль 7.0. Начальный курс/ Учебное пособие. – М.:
«Нолидж», 1997.
Бондарев В.М., Рублинецкий В.И., Качко Е.Г. Основы программирования.
– Харьков: Фолио; Ростов н/Д: Феникс, 1998.
Марченко А.И., Марченко Л.М. Программирование в среде Turbo Pascal
7.0. – К.: ВЕК+, М.: ДЕСС, 1999.
Грызлов В.И., Грызлова Т.П. Турбо Паскаль 7.0 – М.: ДМК, 1998.
Download