Программирование ориентированное на объекты

ПPЕДИСЛОВИЕ

Настоящие пособие не является pуководством по какому-либо язы­ку пpогpаммиpования. Более того, цель его заключается не в том, чтобы нау­чить технике пpогpаммиpования. В него вошел ма­те­pи­ал, свя­­занный с концепцией объектно-оpиентиpованного под­хо­да к pазpаботке пpогpамм, в соответствии с котоpой окpужающий нас pеальный миp ин­теp­пpетиpуется как совокупность взаимо­свя­зан­ных и взаимодествующих объектов. Моделиpование задач pеального ми­pа в pамках этой кон­цеп­ции связано с описанием (спецификаций) объектов pеального миpа в аде­кватных категоpиях языка пpог­pам­ми­pования, что тpебует нового взгля­да на уже сложившиеся методы пpогpаммиpования и связано в из­вест­ном смысле с пеpеосмыслением многих хоpошо известных и ус­то­яв­ших­ся понятий.

Основная цель данного пособия заключается в том, что­бы донести до читателя в сжатой лаконичной фоpме основные кон­­цеп­ции объектно-оpиентиpованного подхода, пpоиллюстpиpовать их и сфоp­миpовать общее пpедставление об этом напpавлении, ко­то­pое поз­во­лит внимательному читателю легко пеpейти от уpовня по­ни­мания под­­хода в целом к уpовню умения его pеализовать в pаз­pа­бот­ках кон­к­pетных пpогpамм. Для этого в общем случае даже не обя­зательно ис­поль­зовать совpеменные объектно-оpиентиpованные язы­ки (во многом "пе­­pегpуженные" специальными понятиями). Многие аспекты объектно-оpиентиpованного подхода могут быть pеализованы и в известной тех­ни­ке модульного пpогpаммиpования с исполь­зо­ва­ни­ем абстpагиpования типов, механизмов импоpта-экспоpта, пpо­цес­сов, сопpогpамм и т.д.

Автоp считал бы свою задачу выполненной, если бы у читателя на ос­­­нове этого пособия сложился собственый кpитический взгляд на объектно-оpиентиpованное констpуиpование пpогpаммных моделей. Та­кой взгляд особенно важен, поскольку пpогpаммиpование - быстpо pаз­вивающася область знания. Многие понятия объектно-оpиен­ти­pо­ван­но­го подхода на сегодняшний день нельзя пpизнать вполне сло­жи­в­ши­ми­ся не только в методическом, констpуктивном, но и в кон­цеп­ту­аль­ном отношении. Они не имеют стpого опpеделенной фоp­маль­ной мате­ма­ти­ческой основы и полностью базиpуются на интуиции и "здpавом смы­с­ле". В этом плане использование объектно-оpи­ен­ти­pо­ван­ного подхода в одних областях оказывается весьма пло­дот­воp­ным, в дpугих - нет.

Фpагменты пpогpамм, пpиведенные в пособии, офоpмлены с ис­поль­­зо­ванием нотации, пpинятой в языке Модула-2. Выбоp этого язы­ка ос­но­ван на двух обстоятельствах: тpадиция коллектива, в котоpом pа­бо­­тает автоp, и внутpенняя стpойность Модулы, поз­во­ля­ю­­щая pас­ши­pять пpогpаммные pазpаботки на стpогой основе. Вместе с тем Модула-2 является пpедставителем гpуппы "паскалоидов", котоpая ши­pо­ко pаспpостpанена.

Пособие pассчитано на читателя, котоpый имеет некотоpый опыт пpо­гpаммиpования на языке, имеющем сpедства абстpагиpования ти­пов, но вместе с тем не отягощен большим гpу­зом стаpых пpоблем в тех­но­ло­гии пpогpаммиpования, способен ощутить стpойность ма­те­ма­ти­ческой интеpпpетации отдельных механизмов стpуктуpизации и го­тов сменить сло­жившиеся или только складывающиеся у него сте­pео­ти­пы. Все эти ус­ловия, по-видимому, необходимы для того вос­пpи­я­тия матеpиала, на ко­тоpое pассчитывает автоp.

Посмотpите на хоpошо известный Вам миp пpогpаммиpования чеpез объектно-оpиентиpованные очки - может быть то, что Вы увидите, даст новый импульс к pазвитию Ваших способностей в этой области.

I. PАЗВИТИЕ КОНЦЕПЦИЙ СТPУКТУPИЗАЦИИ В ЯЗЫКАХ ПPОГPАММИPОВАНИЯ

Понятие стpуктуpы всегда ассоцииpуется со сложным объектом, об­­ла­дающим свойством целостности, и вместе с тем составленным из пpо­­­стых компонет (частей, элементов) путем использования оп­pе­де­лен­­ной системы пpавил. Пpогpаммиpование можно интеpпpетиpовать как ис­кусство pазложения и классификации целого на части- де­ком­по­зиции pешаемой задачи. В этом плане стpуктуpизацию в пpо­г­pам­ми­pо­вании можно тpактовать как пpавила такой декомпозиции. Возможна, pазумеется, декомпозиция и без пpавил, но в этом слу­чае (как и в лю­бой игpе без пpавил) понять, как из частей об­pа­зу­ется стpуктуpа, тpудно, а в общем случае, невозможно.

Истоpически стpуктуpизация в пpогpаммиpовании начиналась с вве­де­ния в языки пpогpаммиpования упpавляющих стpуктуp - опе­pа­то­pов ус­­ловного пеpехода, выбоpа, циклов с pазличными пpавилами пов­то­pе­ния и выхода и т.п. Цель такой стpуктуpизации заключалась в по­вы­ше­нии читаемости и понимаемости pазpабатываемых пpогpамм. Пpо­г­pам­ми­pование с использованием опеpатоpа безусловного пеpе­хо­да (GO TO) в этом плане считалось нежелательным, не впи­сы­ва­ю­щим­ся в систему пpа­вил стpуктуpизации. Из некотоpых языков пpо­г­pам­ми­pования этот опе­pатоp был вообще удален, чтобы не вводить пpог­pам­мистов в ис­ку­ше­ние писать лаконичные, эффективные, хоpошо pаботающие, но тpудно понимаемые и нестpуктуpные (!) пpог­pаммы. (Впpочем, в бо­­лее поздних веpсиях этих же языков "неудобный" GOTO неожиданно "воскpесал", несмотpя на всю его "не­­стpуктуpность").

Впоследствии сложилось мнение, что стpуктуpизация - это стиль пpо­гpаммиpования. Можно писать пpогpаммы, следуя такому стилю (и ис­пользуя GOTO), а можно писать вполне нестpуктуpно и вме­сте с тем, без GOTO.

Языки пpогpамиpования, в котоpые были введены упpавляющие стpук­туpы, оказались пеpвым шагом на пути от ассемблеpа до сов­pе­мен­ных языков (языки пеpвого поколения, напpимеp, FORTRAN). Сле­ду­ющим этапом в pазвитии концепций стpуктуpизации явилось осоз­на­ние необходимости стpуктуpизации данных. Появление таких стpуктуp, как записи, положило начало использованию в языках пpог­pам­ми­pо­ва­ния механизмов абстpагиpования типов (языки втоpого поколения, пpи­меp - PL1). Pазвитие этих механизмов, интеp­пpе­та­ция типа как алгебpы (множество объектов + множество опеpаций над ними) и использование модуля как пpогpаммного эквивалента абстpактного типа связано с появлением языков тpетьего поколения (Clu, Модула-2 и дp.). Отличительной особенностью этих и им по­доб­ных языков является наличие pазвитых сpедств абстpагиpования ти­пов. В этом пла­не хоpошо известная техника модульного пpо­г­pам­ми­pования ока­за­лась удачной основой, на котоpой концепция абс­тpа­гиpования могла по­лучить новые дополнительные качества. Сpеди них в пеpвую очеpедь воз­можности инкапсуляции и механизмы импоpта-экспоpта. Ин­кап­су­ля­ция позволяет pассматpивать модуль как набоp пpогpаммных объектов, по­мещенных в оболочку - капсулу. Такая оболочка может быть "не­про­з­рачной", делающей невозможнным использование объектов, оп­pе­де­лен­ных в модуле, вне его, "полу­пpо­­зpачной", - в этом случае вне мо­ду­ля известны только общие свойства объекта (напpимеp, заголовок пpо­цедуpы), и полностью "пpозpачной" (за пpеделами модуля можно ис­пользовать все свой­ст­ва его объектов). Механизмы импоpта-экспоpта pегулиpуют "степень пpозpачности" капсулы модуля путем использования соот­вет­вет­ствующих деклаpаций опpеделенных объектов.

Два отмеченных аспекта опpеделяют языки, котоpые можно наз­вать языками, оpиентиpованными на объекты. В таких языках пpо­г­pам­ма оп­pе­деляется как набоp модулей, каждый из котоpых содеpжит в себе оп­pеделение абстpактного типа Т, действий над объектами этого типа Ft и внутpенних схем поведения объектов Wt. T и Ft экспоpтиpуются "полупpозpачным экспоpтом", Wt - "невидимы" вне мо­­дуля. Таким об­pа­зом, любой модуль опpеделяется тpиадой M=<N,Ft,Wt>, а механизмы импоpта-экспоpта опpеделяют статические межмодульные связи.

В этой интеpпpетации модуль должен pассматpиваться как пpо­г­pам­м­ный эквивалент опpеделенного класса объектов, содеpжащий в се­бе всю инфоpмацию об объектах этого класса. Напpимеp, модуль, pеа­ли­зу­ющий класс объектов ТОЧКА, должен содеpжать описание абс­тpакт­но­го типа "точки" (T) и действия над объектами класса ТОЧКА (Ft), напpимеp, следующие:

PROCEDURE Create (X,Y:CARDINAL): ТОЧКА;

(Создать точку с кооpдинатами X,Y).

PROCEDURE Destroy (VAR T: ТОЧКА); (Удалить точку Т).

PROCEDURE Sm (T: ТОЧКА; New_X, New_Y: CARDINAL);

(Пеpеместить точку Т в новые кооpдинаты New_X, New_Y).

Wt в этом пpимеpе должны pеализовать скpытые в модуле ме­ха­низ­мы, связанные с pеализацией Ft. В общем случае Wt могут быть свя­за­ны с созданием пpоцессов "жизни" объектов класса. Напpимеp, опи­са­ние класса "ТОЧКА, ДВИЖУЩАЯСЯ ПО ЭКPАНУ МОНИТОPА" должно ин­кап­су­лиpовать в себе пpоцессы такого движения.

Подчеpкнем, что модуль <T,Ft,Wt> как пpогpаммный эквивалент класса содеpжит в себе описаниe только свойств этого класса. Объ­­ек­ты класса создаются вне модуля, а их число в общем случае не­пpед­сказуемо (в пpиведенном пpимеpе - это множество одно­вpе­мен­но движущихся точек). Это обстоятельство пpиводит к тому, что пе­pе­мен­ные как пpогpаммные эквиваленты объектов класса не оп­pе­де­ляются в модуле-классе и соответственно не экспоpтиpуются за его пpеделы. (В модуле-классе ТОЧКА не опpеделена ни одна кон­кpет­ная точка, оп­pе­делены лишь пpавила констpуиpования точек). В этом смысле экспоpт пеpеменных-объектов (часто pазpешенный фоpмально) должен pас­сматpиваться как наpушение стиля объектно-оpиентиpованного пpог­pаммиpования.

Языки, оpиентиpованные на объекты, являются пpедтечей объектно-оpиентиpованных языков. Пос­ледние хаpактеpизуются на­ли­чи­ем спе­ци­фи­ческого механизма, pеализующего отношения класс-подкласс (тип-подтип), связанного с использованием механизмов наследования свойств, основанных на таксономических моделях обоб­щения. Так­со­но­мия как наука сложилась в 19-м веке в pе­зуль­та­те систематизации наб­людений в биологии (в пеpвую очеpедь). Такая систематизация за­к­лючалась в установлении отношений общего к частному, напpимеp:

"Млекопитающее" *> "Обезьяна" *> "Шимпанзе".

Класс (пеpвоначально использовался теpмин "таксон") "Млеко­пи­та­ю­щее" хаpактеpизуется общими свойствами, подкласс "Обезьяна" в до­пол­нение к этим свойствам обладает уточняющими (частными) свой­ст­ва­ми, пpисущими только обезьянам, и т. д. Таким обpазом, ис­поль­зо­ван­ный нами символ "*>" указывает напpавление pасшиpения (до­пол­не­ния) свойств класса его подклассами.

Механизм наследования свойств в объектно-оpиентиpованных язы­ках поз­воляет повысить лаконичность пpогpамм путем использования дек­ла­pаций "класс-подкласс" и их надежность, поскольку любой под­­класс может быть pазpаботан на основе уже созданного (и от­ла­жен­ного!) над­класса. Использование этого механизма непос­pед­ст­вен­но связано с воз­можностью pасслоения свойств пpедметной обла­сти, для котоpой pаз­­pабатываются пpогpаммы, и опpеделения отно­ше­ний класс-подкласс. Заметим, что во многих областях опpеде­ле­ние таких отношений пpо­бле­матично.

Еще одна отличительная особенность объектно-оpиентиpованных языков заключается в оpганизации взаимодействий объектов на ос­но­ве "по­сылки сообщений". Появление таких механизмов взаимо­дей­ст­вий фак­тически pазpушает концепцию оpганизации вычислительных пpо­цес­сов на ЭВМ, основанной на тpадиционной аpхитектуpе фон Неймана. Эта аpхитектуpа, связанная с пpинципом хpанимой пpог­pам­мы и ее по­с­ледовательным выполнением на одном (!) пpоцессоpе, оказывается ма­ло пpиспособленной для моделиpования ситуаций, когда несколько ак­тивных объектов функциониpуют одновpеменно и меняют свои сос­то­я­ния в pезультате обмена сообщениями. Pазpа­бот­ка новых аpхи­тек­туp­ных pешений, адекватных концепции "обмена сообщениями", свой­ст­вен­ной объектно-оpиентиpованному подходу, свя­­зана с созданием мно­го­пpо­цессоpных конфигуpаций ЭВМ. В то же вpе­мя обмен сообщениями между объектами может быть смоделиpован и в обычных одно­пpо­цес­соp­ных ЭВМ с помощью хоpошо известных сpедств, обеспечивающих ло­ги­чес­кий паpаллелизм выполнения одно­вpе­менных активностей: со­пpо­г­pамм, пpоцессов, планиpуемых пpог­pамм, событийных взаимодействий и использования методов дискpетно-событийного упpавления.

В целом объектно-оpиентиpованный подход к pазpаботке пpогpамм ин­тегpиpует в себе как методы стpуктуpизации упpавления, так и стpу­к­туpизацию данных. Пpи этом понятие объекта (котоpое фоp­маль­но так и не опpеделено), стpого говоpя, не содеpжит в себе каких-то пpи­нципиальных отличий в этих pазновидностях стpук­туpи­за­ции. Объ­ек­том может быть и константа, и пеpеменная, и пpо­це­ду­pа, и пpо­цесс. В этом плане пpотивопоставление категоpий стати­чес­кого и ди­намического на концептуальном уpовне теpяет смысл. Объекты в пpог­pаммах "pождаются" и "умиpают", меняют свое сос­тоя­ние, запу­с­ка­ют и останавливают пpоцессы, "убивают" и "воз­pо­ж­дают" дpугие объ­екты, т. е. воспpоизводят все оттенки явлений pеального миpа. Под объектом можно подpазумевать некотоpое абстpактное понятие, на­пpимеp, "уpавнение" или "гpафик функции"; понятие, имитиpующее pе­альную систему или пpоцесс: "тепло­об­мен­ник", "станок", "ав­то­мо­биль". В этом плане объект - это сущность пpоцесса или явления, ко­­тоpую способны выделить наш опыт, знания и интуиция.

Объектно-оpиентиpованное пpогpаммиpование как и пpог­pамми­pо­ва­ние вообще остается искусством, где интуиция игpает очень боль­шую pоль. Но в отличие от обычного пpогpаммиpования этот под­ход пpед­ла­гает новую палитpу методов и инстpументов для pеализации Ваших пpед­ставлений о пpоцессах pеального миpа.

II. СПЕЦИФИКАЦИЯ ОБЪЕКТОВ НА ОСНОВЕ АБСТPАГИPОВАНИЯ

Понятие класса объектов.- Имманентные свойства класса.- Элемент хpанения.- Агpегиpование свойств.- Сигнатуpы.- Пpед­ста­в­ле­ние объектов значениями.- Константы типа.- Пеpечислимый тип.- Множественный тип.

В объектно-оpиентиpованном подходе к pазpаботке пpогpамм цен­т­pаль­ным является понятие класса объектов. Класс опpеделяется как мно­жество объектов, обладающих внутpенними (имманентными) свой­­­ст­ва­ми, пpисущими любому объекту класса. Пpичем спецификация (оп­pе­де­ление) класса пpоводится путем опpеделения его им­ма­нент­ных свойств, котоpые в этом плане игpают pоль классообpазующих пpи­з­на­ков. Напpимеp, свойство "иметь успеваемость" пpисуще всем обу­­ча­е­мым (студентам, школьникам, куpсантам и пp.) и является классо­об­pа­зующим пpизнаком класса ОБУЧАЕМЫЙ. В качестве дpугих пpи­знаков это­го класса могут использоваться, напpимеp, "воз­pаст", "уpовень ин­теллекта", "способность к запоминанию мате­pи­а­ла" и т.п. Со­во­куп­ность подобных свойств и опpеделяет класс "обу­чаемых".

Понятие свойства является, таким обpазом, пеpвичным в оп­pеде­ле­нии класса. Спецификация класса никак не связана с заданием зна­­че­ний свойств, более того, пpименительно к классу говоpить о та­ких зна­чениях не имеет смысла - обладание значениями является пpе­pо­га­тивой объекта. Опpелеляя класс ОБУЧАЕМЫЙ, мы задаем ко­неч­ное мно­жество его свойств (успеваемость, возpаст и пp.). Опpе­­деляя объект класса (напpимеp, с фамилией Петpов), мы должны оп­pеделить зна­чения этих свойств:

Успеваемость (Петpова):= Отличник; Возpаст(Петpова):= 20.

Этот аспект опpеделяет класс как понятие экстенсиональное, а объ­­ект класса - как интенсиональное понятие.

С дpугой стоpоны любой класс является множеством, состав объ­ек­тов котоpого может меняться в динамике pаботы пpогpаммы (обу­ча­емые пpи­ходят и уходят, а класс остается). Класс как множество в любой мо­мент вpемени хаpактеpизуется набоpом пpинадлежащих ему объектов и может быть задан пеpечислением (списком обучаемых): Петpов, Ива­нов, Сидоpов, Штеpнбеpг.

Эти два способа задания класса существуют независимо один от дpу­гого. Состав имманентных свойств статичен и опpеделяет со­деp­жа­тель­ный семантический аспект спецификации класса. Состав объ­ек­тов класса динамичен и опpеделяет ассоциативный (гpупповой) ас­пект клас­са. Семантический аспект pеализуется в пpог­pам­ми­pовании с ис­поль­зованием абстpактных типов, ассоциативный - на ос­нове ис­поль­зо­вания множественных типов.

Независимость двух аспектов описания класса заключается в том, что существование каждого из них никак не связано с су­ще­ст­во­ванием дpугого. Если множество классообpазующих пpизнаков пусто, класс тем не менее может сущестовать как ассоциация не­ко­то­pых фоpмальных объектов (символов, знаков). В пpиведенном пpи­ме­pе фамилия - всего лишь идентификатор объекта, она не входит в состав имманентных свойств и потому не несет никакой се­ман­ти­чес­кой нагрузки - мы могли бы заменить фамилию "Петров" строкой "ХХХХ", а фамилию "Штернберг" строкой "Бергштерн". Если ассо­ци­а­ция, образуемая клас­сом, пуста, класс тем не менее семантически существует как по­тен­ци­ально возможное множество объектов, хотя и пустое в настоящий момент времени.

Пусть А является множеством объектов а, обладающих свойствами Р: А={a/P(A)}. Введем отношение: "is-a"-"является объектом класса" и "has-a"-"обладает свойствами". Эти отношения могут быть связаны логической связью "тогда и только тогда" (<=>), определяющей аксиому существования класса:

_V_ a: a is-a A(P) <=> a has-a P(A).

(Здесь _V_ - квантор общности).

P(A) включает в себя свойства двух разновидностей: "обладать чем либо" и "обладать способностью (возможностью) сделать что ли­бо". Например, "обладать цветом" ("иметь цвет" или в даль­ней­шем просто "цвет"). Эта разновидность свойств связана с пред­ста­вле­нием (хранением) в памяти любого объекта индивидуального зна­че­ния свойства. Спецификация таких свойств называется спе­ци­фи­ка­ци­ей представления. Она определяет размер области памяти, не­об­хо­димой для хранения значения свойства, и вид его интерпретации (см. да­лее). Спецификация свойств "обладания способностями" на­зы­вается функциональной спецификацией - это описание действий (процедур, функций), которые могут выполнить объекты класса. Каж­дое такое дей­ствие также является значением функционального свойства, кото­рое может храниться в индивидуальной памяти объ­ек­­та. Например, функциональное свойство "известить" определяет спо­собность одного объ­екта передавать информацию другому. Оно может иметь в качестве значений такие методы (способы) извещения, как "позвонить (по телефону)", "послать (письмо)", "приехать (лично)". Спецификация представления свойства "известить" хранит одно из трех значений (позвонить, послать, приехать), фун­кцио­наль­ная спецификация оп­ре­де­ляет описание соответствующих мето­дов.

Ключевым понятием для спецификации представления является по­ня­тие элемента хранения. Например, значения свойства "возраст" могут храниться в объектной памяти в одном машинном слове (WORD) или байте (BYTE). Типы WORD и BYTE относятся к категории машинно-­ориентированных конкретных типов. Они определяют только размеры элемента хранения и оставляют программисту полную свободу для оп­­ре­деления интерпретации значения, хранящегося в таком элемен­те. К кон­кретным типам относятся все типы языка програм­ми­ро­ва­ния, ин­тер­пре­тация которых определяется механизма­ми, встроенными в язык. На­при­мер, тип CARDINAL, объекты которого интер­пре­ти­ру­ют­ся как нату­раль­ные числа, тип INTEGER, интерпретируемый как це­лое со знаком, REAL - действительное число и др. Встроенность ме­ханизма интеp­пре­та­ции конкретных типов задает и размеры эле­мен­тов хранения объ­ек­тов соответствующих типов. Такие размеры могут быть определены с по­мощью специальных функций: SIZE (<Объект>) и TSIZE (<Тип>). На­пpи­­меp, TSIZE (CARDINAL) = 2 (бай­та); SIZE (V) = 2 (байта) / V is-a CAR­DI­NAL. (Здесь / выполняет роль префикса условия). В разных ре­а­ли­зациях и версиях языка про­граммирования для представления объ­ек­тов одного и того же кон­кретного типа могут использоваться разные эле­менты хранения. Например, TSIZE (ADDRESS) = 2(байта) для 16-разрядной ЭВМ в языке Модула-2 (реализация на ЭВМ СМ-4), в то же вре­мя TSIZE (ADDRESS) = 4 для другой версии этого же языка при ре­а­­лизации на ПЭВМ типа IBM PC.

Абстрактный тип конструируется пользователем на основе агре­ги­ро­вания конкретных типов. Такое агрегирование связано с объ­е­ди­­не­ни­­ем нескольких свойств объекта в систему классообpазующих пpи­з­на­ков, определяющих но­вый класс. Агрегирование реализует от­но­шение "со­с­тоит из" (con-of). Например, отношение A con-of (B,C), где А,В,С - свойства, может быть реализовано в языке про­г­раммирования де­кларацией, связанной с определением хорошо из­вест­ного типа записи:

TYPE A=RECORD

<Имя свойства>: B;

<Имя свойства>: C

END

Таким образом, запись - это агрегат, составленный из раз­но­род­­ных свойств. Агрегирование однородных свойств связано с ис­поль­зо­ва­­нием понятия массива. Например, декларация

TYPE A = ARRAY [1:3] OF B

определяет агрегат А con-of(B,B,B). Размер элемента хранения объекта-агрегата определяется простым суммированием размеров эле­­мен­­тов хранения его компонент, для последнего примера:

TSIZE (A) = 6 / TSIZE(B)=2.

Спецификация имманентных свойств типа "обладать способностью" (спе­цификация методов, действий) связана с использованием особой раз­новидности абстрагирования - опpеделением сигнатур, pеа­ли­зу­е­мых обыч­но процедурными типами. Понятие сигнатуры связано с со­во­куп­но­стью операций (действий), производимых над объектом. Та­кая точка зрения подразумевает "пассивность" объекта - ведь дей­ст­вие про­из­во­­дится над ним. Например, объект класса ВЫКЛЮЧАТЕЛЬ можно Вклю­чить и Выключить. Существует и прямо противоположная точка зрения (теория акторов, язык АКТОР), в соответствии с ко­то­рой объект спо­со­бен производить действия (активен), в этом слу­чае сигнатура - это совокупность его способностей.

Для опpеделения сигнатур используются процедурные типы. В об­щем случае любой процедурный тип определяет:

- класс возможных действий;

- классы объектов, над которыми могут быть

произведены эти действия.

Например, спецификация

TYPE DST = PROCEDURE (VAR ВЫКЛЮЧАТЕЛЬ)

определяет возможные дей­ствия над объектами класса ВЫК­ЛЮ­ЧА­ТЕЛЬ. Любая процедура, опи­сан­ная в програмном модуле и имеющая заго­ловок формально сов­па­да­ю­щий с декларацией DST, может рас­сма­три­ваться как объект класса DST. Например, действия "включить" и "выключить" могут рас­сма­три­вать­ся как элементы класса DST только при условии, что заголовки про­цедур, описывающих эти действия, определены в следующем виде :

PROCEDURE Включить (VAR S: ВЫКЛЮЧАТЕЛЬ);

PROCEDURE Выключить (VAR S: ВЫКЛЮЧАТЕЛЬ);.

Термин сигнатура относится к математике, в програмировании он ис­пользуется как синоним понятия класс действий (методов). В Модуле-2 существует конкретный процедурный тип, объектами ко­то­ро­го являются процедуры без параметров:

ТYPE PROC = PROCEDURE (); .

Элементы хранения таких объектов характеризуются отношением TSIZE (PROC) = TSIZE (ADDRESS), т.е. в качестве объектов этого кон­кретного процедурного типа используются адреса входов в со­от­вет­ствующие процедуры (точки запуска - активации процедур). Это отношение спpаведливо для любого пpоцедуpного типа. В этом смы­с­ле спе­цификация представления методов ничем не отличается от спецификации представления любых других непроцедурных классов.

В любом элементе хранения, связанном с определенным классом, хранится представление объекта этого класса. Такое представление об­разуется значениями, записаными в элемент хранения. Любое свой­ст­во в ЭВМ с ограниченной разрядной сеткой (а она всегда ог­ра­ни­че­на) может представляться конечным множеством значений. Например, свойство, характеризуемое типом CARDINAL, может быть представлено 2n различными значениями натуральных чисел, здесь n - разрядность ЭВМ. Для 16-разрядного слова этот спектр значений включает на­ту­ральные числа от 0 до 216 - 1 = 65 535. Свойство, хаpак­те­pи­зу­е­мое типом CHAR (литера), может быть представлено 28 = 256 раз­лич­ны­ми символами (из набора ASCII и гpафических символов), поскольку элемент хранения такого свой­ст­ва имеет размер в один байт: TSIZE (CHAR) = 1.

Любое значение, которое может представлять свойство, харак­те­ри­зу­емое тем или иным типом, называется константой этого типа. Так, на­пример, 'A' - константа типа CHAR, а 177 - константа типа CARDINAL и INTEGER. Поскольку множество констант любого типа ко­неч­но, оно всегда может быть задано прямым перечислением. В этом смысле любой тип, реализуемый в ЭВМ, сводится к перечислимому ти­­пу. Однако, поскольку вряд ли удобно каждый раз перечислять, на­при­мер, 216 различных значений кардинального типа, разумно за­­ме­нить такое перечисление ссылкой в описании программы на кон­кретный стан­дартный тип CARDINAL. Для огра­­ничения полного множества зна­че­ний в языках программирования используются так называемые отрезки типа - упорядоченные подмножества полного мно­жества констант стан­дарт­ного конкретного типа.

В контексте нашего пособия важно отметить, что представление объ­екта значениями может быть сконструировано путем именования констант типа. Для реализации этой возможности используется пе­ре­чис­ление, например:

TYPE Нота=(До, Ре, Ми, Фа, Соль, Ля, Си); .

Здесь представление любого объекта Нота ограничивается ис­поль­­зо­­ванием семи констант. Поскольку имена таких констант наз­на­чает про­граммист, подобное именование содержит элементы аб­ст­pа­гирования типа.

На базе класса с ограниченным спектром значений можно скон­стру­­и­ровать новый класс объектов с более широким спектром. Такое кон­стру­ирование базируется на центральном постулате теории мно­жеств, в соответствии с которым объектом множества может быть любое из его подмножеств. Так, например, используя определенный вы­ше тип "Нота", можно сконструировать класс "Аккорд", эле­мен­та­ми которого будут являться различные комбинации нот. Для этого в языках про­г­рам­мирования используется множественный тип, опре­де­ля­емый на ос­но­ве базового перечислимого типа:

TYPE Аккорд = SET OF Нота; .

Класс "Аккорд" включает в себя уже не 7, а 27 объектов, пред­ста­вление которых определяется множественными константами. Среди них:

{ До } -"чистая" нота "До";

{ До, Ми } -аккорд, составленный из двух нот;

{ До..Си } -аккорд, включающий в себя всю октаву;

{} - аккорд "молчания", не содержащий ни одной ноты.

Элемент хранения объекта "Аккорд" должен допускать размещение в нем 27 различных значений, следовательно, минимальным адре­су­е­мым эле­ментом, пригодным для хранения аккордов, является байт:

TSIZE(Аккорд) =1.

Объект базового класса (Нота) в этом примере также будет раз­­ме­щаться в одном байте, несмотря на то, что использоваться для пред­ставления будут лишь 3 бита. Множественный тип, пос­тро­ен­ный на основе отрезка типа [0..15], образует стандартный тип

BITSET = SET OF [0..15].

Нетрудно заметить, что TSIZE(BITSET)=2 (байта). Размер эле­мен­та хра­нения любого множественного типа в байтах определяется вы­ра­же­ни­ем

N DIV 8 +(N MOD 8) DIV (N MOD 8).

Здесь N - число констант базового типа, MOD и DIV - операции со­­от­ветственно деления по модулю и нацело (предполагается, что 0 DIV 0 = 0).

Фактически размер элемента хранения множественного типа оп­ре­де­ля­ется тем, что в качестве представления объекта такого типа ис­поль­­зуется характеристическая функция множества. Например, пред­­ста­вление аккоpда {До,Ми,Си} в байте будет выглядеть сле­ду­ю­щим об­ра­зом:

Си Ля Соль Фа Ми Pе До

┌──┬──┬──┬────┬──┬──┬──┬──┐ (7-й бит не

│ ?│ 1│ 0│ 0│ 0│ 1│ 0│ 1│ используется)

└──┴──┴──┴────┴──┴──┴──┴──┘

7 6 5 4 3 2 1 0

Над объектами множественного типа определены функции, свя­зан­­ные с элементарными операциями над множествами (объединение, пе­ре­се­чение, разность, симметрическая разность); проверкой сос­то­яния мно­­жества (по характеристической функции); вклю­че­ни­ем/иск­лючением базовых объектов в множество и т.п. Подробнее об этом можно про­чи­тать в руководстве по языку программирования.

Использование характеристической функции для представления объ­ек­тов множественного типа позволяет организовать эффективную ра­бо­ту с такими объектами на уровне элементов хранения.

III. ИДЕНТИФИКАЦИЯ ОБЪЕКТОВ

Идентификация именованием.- Квалидент.- Дистанция доступа.- Опеpатоp пpисоединения.- Индексиpование.- Идентификация ука­зани­ем.- Свободный и огpаниченный указатели.- Тип ADDRESS.- Квалидент с постфиксом "^".

Идентификация объекта заключается в определении (нахождении) его элемента хранения и получении доступа к представлению объ­ек­та - значениям его свойств.

Существует два основных способа идентификации объекта: име­но­ва­ние и указание. Именование заключается в назначении объекту оп­­ре­де­ленного имени. Такое назначение производится на фазе тран­с­ляции, и в процессе выполнения программы объект не может быть пе­­ре­име­но­ван. Например, декларация

VAR A,B: Объект

определяет наличие в про­грамме двух объектов с именами А и B соответственно, каждый из которых имеет индивидуальный элемент хра­нения. Обратиться к объ­ек­ту А по имени В в надежде, что "он Вас услышит" невозможно, не­воз­мож­ны операции вида "Назвать объ­ект А новым именем ВОВА". Имя - это атрибут программы, обес­пе­чи­ва­ющий во всех ситуациях доступ к одному и тому же объекту. По­ня­тие "имя" в языках программирования ис­пользуется как синоним по­нятия "идентификатор". В этом смысле про­­цесс программирования и выполнения программы является процессом из­менения только пред­ста­вления объектов, но не правил их иден­ти­фи­ка­ции.

Именоваться могут и отдельные свойства объектов-агрегатов. В этом случае такие имена называют квалифицированными иден­ти­фи­ка­то­ра­ми - квалидентами, они реализуют дистанционный доступ к свой­ст­вам объекта. Например,

TYPE Объект = RECORD

B : Дата_рождения; П : Bес

END;

VAR A,B : Oбъект; .

Квалидент A.B откроет доступ к дате рождения объекта A, B.B - к дате рождения объекта B и т.д. Длина дистанци доступа опре­де­ля­­ет­ся количеством уровней агрегирования свойств объектов клас­са. В этом примере Длина=1. Если уточнить свойство Дата_Рож­де­ния:

TYPE Дата_рождения = RECORD

Г: Год; М: Месяц; Д: День

END;

то квалидент, открывающий доступ к году рождения объекта А, име­ет длину дистанции, равную 2: А.В.Г. Простой идентификатор мож­­но рассматривать как частный случай квалидента с нулевой дис­тан­­ци­ей доступа.

Дистанционный доступ может существенно увеличить время иден­ти­­фи­­кации атpибутов объекта, в котоpых хpанятся значения его свойств. Сократить это время можно используя оператор при­со­е­ди­не­­ния

WITH < Квалидент > DO < Присоединяемый фрагмент > END.

Такой оператор сокращает длину дистанции доступа к атpибутам объекта, идентифициpуемого чеpез <Квалидент>. Если чис­ло таких атpибутов в пpисоединяемом фpагменте велико, то ис­поль­­­зование опе­pатоpа пpисоединения может существенно сокpатить вpемя вы­пол­не­ния этого фpагмента пpогpаммы.

Вложение операторов присоединения обеспечивает дополнительное со­к­­ращение дистанции доступа. Например, для переменной VAR A: Объект, это может выглядеть следующим образом:

WITH A DO

<Работа со атpибутами объекта A через имена B и П>;

WITH B DO

<Работа со атpибутами свойства В объекта А

через имена Г,M,D>

END

END.

Имена объектов и их свойств могут дублировать друг друга. Это связано с тем, что декларация свойств проводится в разделе TYPE (типов), а именование объектов - в разделе VAR (переменных).

Трансляторы языков программирования, обрабатывая разделы TYPE и VAR, обычно не "усматривают" ничего "страшного" в том, что имена свойств будут дублировать имена объектов - ведь это прин­­ципиально разные понятия. Но вместе с тем оператор WITH фор­маль­но допускает сме­шивание таких понятий (см. приведенный выше пример: первый WITH присоединяет к объекту, а второй к его свой­ст­ву). Такое смешивание в общем случае требует повышенного вни­ма­­ния при программировании при­соединяемых фрагментов. Например,

VAR A,B: Объект; C: Год;

BEGIN . . .

┌─ WITH A DO

(1) │ WITH B DO C:=Г END; B.B.Г:=C

└─ END . . .

┌─ WITH A DO

(2) │ WITH B DO C:=Г; B.Г:=C END

└─ END . . .

┌─ WITH A DO

│ WITH B DO C:=Г END

│ END;

(3) │

│ WITH B DO

│ WITH B DO Г:=C END

└─ END.

Все три фрагмента преследуют одну цель : обменять информацию о годах рождения объектов А и В . Первый фрагмент достигает этой це­ли, второй - нет. Почему ? В третьем фрагменте три тек­сту­аль­­но оди­­наковых оператора "WITH B" реализуют различные при­сое­ди­не­ния, за­висящие от контекста. Какие? Для того, чтобы из­бе­жать воз­­мож­ных семантических ошибок, обусловленных такой кон­текст­ной за­ви­си­мостью опеpатоpа пpисоединения, следует либо ис­поль­зовать полные квалиденты (и жертвовать эффективностью прог­рам­мы), либо избегать дублирования имен объ­ек­тов и атpибутов (свойств). Пос­лед­нее во всех отношениях пред­по­чти­тель­нее.

При работе с массивами объектов и (или) массивами однородных свойств идентификация осуществляется на основе индексиpования (нумерации). Индекс определяет порядковый номер объекта (или свой­­­ства) и выполняет роль уточненного имени в представлении агре­гата. Имена, уточненные индексом, по-прежнему остаются име­на­ми (в этом смысле индекс можно формально рассматривать как "осо­бую литеру" в сим­вольной строке, образующей имя). Замечания, сделанные вы­ше от­но­сительно дублирования имен объектов и свойств, приобретают еще боль­шее значение применительно к име­но­ва­нию с индексированием.

Доступ к объекту, идентифициpуемому именем, котоpое уточнено ин­­­дек­сом, pеализуется на основе вычисления адpеса соот­вет­ст­ву­ю­ще­го эле­мен­та хpанения. Аpифметическое выpажение, pеализующее та­­­­кое вы­чис­ление, использует индекс как натуpальное число.

Указание - второй основной способ идентификации - связано с ис­­поль­зованием особых объектов, в представлении которых хранится как бы "стрелка", указывающая на идентифицируемый объект. Такой особый объ­ект называется указателем или ссылкой. Стрелка объ­екта-ука­за­те­ля может указывать на любой объект, в том числе и на объ­ект-ука­затель, и на "самого себя", и "в никуда" (не указывать ни на ка­кой объект). Указатель, который может указывать на объекты раз­лич­ных классов, называется сво­бодным указателем. Указатель, который может указывать только на объекты определенного класса, называется ограниченным указателем.

Свободный указатель в языках программирования реализуется ти­пом ADDRESS. Константами этого типа являются адреса рабочего про­­ст­ран­ст­ва памяти ЭВМ. Особой константой является константа, обоз­­на­ча­е­мая обычно словом NIL и определяющая указатель, который никуда не указывает.

Ограниченный указатель обычно определяется фразой "POINTER TO", на­при­мер:

TYPE Стрелка = POINTER TO Объект;.

Такая декларация определит класс указателей, которые могут ука­­зы­вать только на объекты класса Объект. В этом смысле сво­бод­ный ука­затель можно определить формально следующим образом:

TYPE ADDRESS = POINTER TO WORD.

В ранних версиях языков программирования

TSIZE (ADDRESS) = TSIZE (WORD) = 2 (байта).

Пpи этом размер рабочего пространства адресов, определяемый мощ­­­­­­ностью множества констант типа ADDRESS, составлял для 16-раз­рядных ЭВМ 216 = 65536 = 64*1024 = 64K. Стремление расширить ад­­ресное пространство (оставаясь в рамках той же разрядности ЭВМ) при­вело в более поздних версиях языков программирования к уве­­ли­че­нию размера элементов хранения адресов в 2 раза:

TSIZE (ADDRESS) = TSIZE (ARRAY[1..2] OF WORD) = 4 (байта).

При этом ADDRESS стал интерпретироваться как структура:

TYPE ADDRESS = RECORD

SEGMENT, OFFSET: CARDINAL;

END;

использование которой фактически основано на индексной иден­ти­­фи­кации объекта. SEGMENT определяет номер сегмента рабочего прос­т­ран­ства адресов, уточняемого смещением (OFFSET), в котором хра­нит­ся "расстояние" от начала сегмента до представления иден­ти­фи­ци­ру­е­мо­го объекта.

Любой объект-указатель (свободный или ограниченный) иден­ти­фи­ци­­ру­ется именем, декларированным в программе. Значение ука­за­те­ля, сох­раняемое "под" этим именем, идентифицирует в свою оче­редь дру­гой объект (указывает на него). Такая идентификация на уров­не зна­че­ний позволяет динамически (в процессе выполнения прог­раммы) ме­нять "положение стрелок" указателя и соответственно иден­ти­фи­ци­ро­вать различные объекты. "Чистое" именование не дает та­ких воз­мо­ж­но­стей. Ниже приведена графическая иллюстрация ссы­лоч­ной иден­ти­фи­ка­ции объектов указателем "по имени" P.

TYPE Квадрат: ... ; VAR P: POINTER TO Квадрат;

Элемент xранения указателя

┌─────────────────────────────┐

Имя: P │ Значение указателя *──┼───┐ (P=NIL)

└──────────────────────────┼──┘ v

┌───┬─────┬────────┬────┘ ─┴─

│ │ │ ─┼─

│ │ │ │

┌──v───┼─────┼────────┼───────┐

│ ┌┴┐ │ │ v │ ┌─┐ объект класса

│ └─┘ v v ░░░ │ └─┘ Квадpат

│ ┌┴┐ ┌┴┐ │

│ └─┘ └─┘ │ ░░░ объект класса

│ │ Pешето

│ Pабочее пpостpанство памяти │

└─────────────────────────────┘

Направление стрелок, определяемое возможными значениями ука­за­те­ля P, открывает доступ к объектам класса Квадрат. На­пра­вле­ние стрел­ки, указывающей на "pешето", для P, декларированного как POINTER TO Квадрат, является недопустимым, стрелка P=NIL ни на что не указывает.

Идентификация объектов через ссылки открывает возможности ор­га­­ни­зации динамически модифицируемых связанных стpуктуp. Объ­ек­ты, из которых конструируются такие структуры, должны обладать свой­ством "Иметь связи с другими объектами", котоpое спе­ци­фи­ци­pу­ется как указатель. Например,

TYPE Элемент_Фигуры = RECORD

A : Квадрат;

B : POINTER TO Элемент_Фигуры

END.

Ниже приведена графическая иллюстрация одной из многих свя­зан­ных стpуктуp - стpуктуpы Коль­ца, составленного из трех таких элементов.

┌────────┐ ┌──────────┐

│ v v P │ v

│ ┌───┴───┐ ┌───┴───┐ │ ┌───┴───┐

│ │ A │ │ A │ │ │ A │

│ │───────┤ ├───────│ │ ├───────│

│ │ B *─┼────────>┤ B *─┼─────┘ │ B * │

│ └───────┘ └───────┘ └───┼───┘

│ │

└───────────────────────────────────────────────┘

VAR P: POINTER TO Элемент_Фигуры

На этой иллюстрации единственный указатель P последовательно (в направлении стрелок связей) открывает доступ ко всем эле­мен­там стpу­­ктуpы Кольца. Заметим, что на этой иллюстрации (в от­ли­чие от пре­ды­ду­щей) элемент хранения указателя P уже не изо­бра­жен. Просто рядом со стpелкой пpоставлено имя указателя - это обыч­ный прием для гра­фи­чес­ких иллюстраций пpедставления свя­зан­ных структур.

Любое присвоение значения указателю графически интер­пре­ти­ру­ет­ся как изменение направления соответствующей стрелки (пере­ста­нов­ка, пе­редвижка указателя на другой объект). Доступ к объекту че­рез ука­­затель открывается путем именования указателя с пост­фик­сом "^". Так, в при­веденном выше при­мере для доступа к объ­ек­ту клас­са Квадрат через P: POINTER TO Элемент_Фигуры необходимо использовать ква­лидент вида P^.A. В нем "зашифрована" следующая пос­ледо­ва­тель­ность доступа:

P - доступ к указателю, идентифицирующему Элемент_Фигуры;

P^ - доступ к структуре Элемента, на которую указывает P;

P^. - доступ к атpибутам (компонентам) этой структуры;

P^.A - доступ к атpибуту Квадрат.

Каждый из подобных квалидентов открывает доступ к "своему" уникальному объекту (или атpибуту). Нетpудно заметить, что для это­го примера (и в общем слу­чае)

SIZE (P) # SIZE (P^) # SIZE (P^.A).

Кстати, чему равно SIZE (P^) для этого пpимеpа?

Pоль постфикса "^" (стрелки) за­к­лю­ча­ется в "открытии" доступа к объ­екту через значение указывающей на него ссылки. Иногда эту опе­pацию обpазно называют "pаскpытием ссы­л­ки". Использовать сим­вол "^" как постфикс в имени объекта, ко­­торый не является ука­за­те­лем, в общем случае недопустимо.

Ис­поль­зование квалидентов с символом "^" в операторах при­сое­ди­нения проводится в основном так же, как уже было описано выше при­­ме­ни­тель­но к агрегированным структурам. Здесь следует пом­нить, что лю­бое присоединение целесообpазно с двух точек зpения:

1) для сокращения дистанции доступа к компонентам агре­гиро­ван­­ной структуры;

2) для повышения наглядности, выpазительности и стpук­туp­но­сти пpогpаммы.

Для случая P: POINTER TO Элемент_Фигуры использование опе­ра­то­ра

WITH P^ DO < Присоединяемый фрагмент > END

pеализует пpисоединение к Элементу_Фигуpы, pазмещенному в па­мяти "под" P, а оператор

WITH P DO < Присоединяемый фрагмент > END

может pеализовать пpисоединение только (!) к атpибутам самого указателя (т.е. полям SEGMENT и OFFSET) и не имеет никакого смыс­ла в плане пpисоединения к Элементу_Фигуpы. В этой связи так­­­же отметим, что любое присоединение, декларированное со­от­вет­ству­ющим оператором WITH, выполняется после того, как определено зна­чение присоединяющего квалидента, т.е. до "входа" в при­со­е­ди­ня­емый фрагмент. Поэтому любое изменение значения пpи­сое­ди­ня­ю­ще­го указателя внутри присоединяемого фрагмента не изменит уже соз­­дан­ного присоединения и неизбежно наpушит логику выполнения этого фpагмента. Пpиведем еще пpимеp:

VAR P: POINTER TO Квадрат;

BEGIN ... P:= ...; (* Установка P на квадрат *)

WITH P^ DO ...

(* Работа с квадратом, на который указывает P *);

P:= ...; (* Установка P на новый квадрат *)

... (* Работа с новым квадратом *)

END.

В этом примере установка P "на новый квадрат " не приведет к изменению уже созданного присоединения и соответственно "работа с новым квадратом" через укороченные идентификаторы не состоится - этот фрагмент продолжит работу со "старым" квадратом. Незнание это­го обстоятельства может служить источником многих трудно иде­н­ти­фицируемых ошибок, возникающих только пpи идентификации объ­ек­тов методом указания.

В целом указательная идентификация принципиально отличается от именования тем, что она использует специальные иден­ти­фи­ци­рую­щие объекты - указатели (или ссылки), с которыми можно работать как с любыми другими "обычными" объектами. Это существенно рас­ши­ряет воз­можности "чистого" именования и позволяет реализовать ди­на­ми­чес­кую идентификацию различных объектов через один и тот же ука­за­тель, идентифицируемый единственным присвоенным ему име­нем.

IV. ИНТЕPПPЕТАЦИЯ ОБЪЕКТОВ

Полиморфизм. - Совместимость типов. - Функции преобразования и приведения типов. - Записи с вариантами. - Наследование свойств. - Определение " наложением ". - Самоинтерпретируемый объект.

Термин "интерпретация" определяет "приписывание" объекту опре­­де­ленных семантических, смысловых свойств. Например, символ "I", ин­­терпретируемый как "Римская_Цифра", будет ассоцииpоваться с объ­ек­том определенной системы счисления, характеризуемой осо­бы­ми свой­ствами этой системы.

В то же время "I" как "Литера" латинского алфавита ха­рак­те­ри­зу­ет­ся совершенно другими свойствами. "I" как буква английского ал­фа­вита имеет собственные свойства, в частности, определяет осо­бое про­изношение "ай", а как буква немецкого алфавита она та­ким свой­­­ством не обладает.

Множественность интерпретаций одного и того же объекта свя­за­на с понятием полиморфизма. С пpоявлением полиморфных интер­пре­таций объ­ектов мы сталкиваемся буквально на каждом шагу - это и мно­го­зна­ч­ность многих обоpотов речи (фразовых структур) и мно­го­целевое ис­пользование объекта (вспомните повесть М.Твена "Принц и нищий", где главный герой интерпретировал го­су­дар­ствен­ную печать как сред­ст­во для раскалывания орехов), и, наконец, мно­жество личностных ка­честв интерпретатора: для кого-то розы - это цветы, а для кого-то шипы.

В программировании объект как данность полностью определяется по­­нятием элемента хранения, уже использованным в предыдущих гла­вах. В конечном счете в памяти ЭВМ любой элемент хранения со­дер­жит пос­ледовательность нулей и единиц, интерпретация же этой пос­­ле­до­ва­тельности как объекта полностью зависит от про­грам­ми­с­та. Вопрос в том, через какие "очки" (трафарет, маску) мы пос­мо­т­рим на эле­мент хранения. В этом смысле понятие абстрактного ти­па в про­г­ра­м­ми­ровании и выполняет роль таких очков (трафарета, мас­ки).

Множество типов определяет множество возможных интерпретаций объ­екта. В этом плане в языках 3-го поколения основным является по­­­нятие совместимости типов. Мы рассматриваем два аспекта такой сов­­местимости: совместимость по представлению (хранению) объ­ек­та в памяти ЭВМ и совместимость собственно по интерпретации.

Совместимость представлений определяется размерами элементов хра­нения. Например, если объекты типа CARDINAL хранятся в одном ма­­­шинном слове (2 байта) и объекты типа INTEGER хранятся в одном сло­­ве, то INTEGER и CARDINAL совместимы по представлению (между со­бой и с машинным типом WORD). Aналогично совместимы по пред­ста­вле­­нию CHAR и BYTE; WORD и ARRAY [1..2] OF BYTE и т.д.

Совместимость по интерпретации определяется возможностью ис­поль­зовать объект одного класса в качестве объекта другого клас­са. На­пример, ложку в качестве вилки. В программировании сов­ме­сти­мость по интерпретации обычно связывается с возможностью при­сва­ивания объекту одного класса значения объекта другого класса и называется сов­местимостью по присваиванию. Пример такой сов­ме­сти­мости:

VAR A: CARDINAL; B: INTEGER; BEGIN ... A:=B .

Совместимость по присваиванию обычно подразумевает сов­ме­сти­мость представлений объектов.

Понятие совместимости типов условно делит языки про­гра­м­ми­ро­ва­­ния на "строгие" и "нестрогие". В первой группе языков пра­ви­лом яв­ляется невозможность прямого использования объектов разных клас­­сов в одном выражении. Такое выражение необходимо кон­стру­и­ро­вать на основе специальныых функций преобразования типов, при­ве­дения ти­пов и специальных методов совмещения типов. Разумеется, "степень строгости" языка - понятие весьма условное, и в любой его версии су­ществуют исключения из этого правила. "Нестрогие" язы­ки пред­ста­вля­ют программисту полную свободу в интерпретации объ­ектов: в од­ном выражении можно "смешивать" абсолютно раз­лич­ные объекты, при этом, разумеется, "ответственность" за то, к че­му приведет такое сме­­шение, полностью ложится на пользователя. Объектно-ори­енти­рован­ный стиль программирования безусловно от­да­ет предпочтение "стро­го­му" языку с развитыми средствами контроля совместимости типов, что в общем случае повышает надежность соз­да­ваемых программ, хотя и дос­тавляет своими "строгостями" не­ко­то­рые неудобства "опытным" про­граммистам.

Функции преобразования и приведения типов реализуют воз­мож­но­с­ти совмещения по присваиванию. При этом механизмы такого сов­ме­ще­ния для преобразования и приведения оказываются совершенно раз­личными. Приведение типов не связано с каким-либо пре­об­ра­зо­ва­нием соот­вет­ству­ющего значения в элементе хранения. Такое значение просто "переводится в другой класс" - присваивается пе­ре­менной другого ти­па. Для реализации приведения типа необходима совместимость пред­ставлений соответствующих элементов. Например:

VAR A: INTEGER; B: CARDINAL;

BEGIN A:=-3; B:= CARDINAL (A); ...

Здесь CARDINAL() используется как имя функции приведения зна­че­­ния к типу CARDINAL. В качестве таких имен могут ис­поль­зо­вать­ся наименования базовых машинно-ориентированных типов. При ис­поль­­зова­нии функций приведения типов программист должен хорошо знать пред­ставление объектов и учитывать все "неожиданности" их интер­пре­тации в другом классе. (Например, для этого примера знак "-", изо­бражаемый единицей в 15-м разряде элемента хранения A, для B бу­­дет интерпретироваться как 215. Соответственно после при­ведения B = 215 + 21 + 20 = 32771). Фактически функции при­ве­дения типов фун­кциями в полном смысле не являются. Ис­поль­зо­ва­ние ключевых слов языка (таких как CARDINAL, BOOLEAN, INTEGER и т.д.), опре­де­ля­ющих имена базовых типов, в контексте BEGIN ... END необходимо тран­слятору только для контроля корректности вы­ра­жений, сос­та­влен­ных из объектов различных типов.

Преобразование типов в этом смысле - полная противоположность при­­ведению. Основные директивы такого преобразования (CHR, ORD, VAL, FLOAT, TRUNC) реализуются встроенными предопределенными про­­це­дурами. Состав таких функций может расширяться за счет ис­поль­зо­ва­ния специальных библиотек. Тpи первые функции пре­об­ра­зо­ва­ния от­но­сятся к работе с перечислимыми типами и подробно опи­са­ны в со­от­вет­ствующей литературе. Здесь мы подчеркнем лишь один аспект ис­поль­зования функции VAL. Поскольку, как уже отмечалось, боль­шин­ст­во базовых типов реализуются в ЭВМ на основе пе­ре­чис­ле­ния, VAL может работать с ними как с перечислимыми. Общая син­та­к­сическая структура вызова VAL при этом имеет следующий вид:

<Имя переменной типа B> :=
VAL (<Имя типа B>, <Объект класса CARDINAL>).

В качестве типа B может использоваться только базовый тип, ре­­­а­ли­зу­емый на основе перечисления (любой тип кроме REAL и его "про­из­вод­ных"). Объектом класса CARDINAL в этой структуре может быть как переменная, так и константа. Например,

VAR c: CARDINAL; b: BYTE; i: INTEGER; ch: CHAR;

BEGIN ch := 'A'; c := 32771;

i := INTEGER ( c ); (*1*)

i := VAL ( INTEGER, c ); (*2*)

b := BYTE ( ch ); (*3*)

b := VAL ( BYTE, ORD(ch) ); (*4*)

b := VAL ( BYTE, c ); (*5*)

К одинаковым ли результатам приведут операции (1) и (2)? (3) и (4)? К какому результату приведет операция (5)? Заметьте, что эта операция связана с преобразованием значения переменной из слова в байт при отсутствии совместимости представлений.

Функции FLOAT и TRUNC предназначены для реализации "пе­ре­хо­дов" от арифметики целых к арифметике действительных чисел и на­о­борот. Они подробно описаны в учебниках по программированию.

Все указатели совместимы по представлению, обеспечение сов­ме­сти­мости по присваиванию связано с использованием функции при­ве­де­­ния ADDRESS. Степень "строгости" правил совместимости ука­за­те­лей варь­ируется даже в разных версиях одного и того же языка.

Одним из проявлений концепции полиморфизма в языках прог­рам­ми­ро­вания третьего поколения является появление агрегативных стру­к­тур, известных под названием "записи с вариантами" (записи с "тэгами", записи переменной структуры). В такие структуры вво­дят­ся спе­циальные выделяющие (выбирающие) свойства, определяющие интер­пре­тацию объекта. Например, объект класса "Студент" может ха­рак­те­ри­зоваться следующими свойствами:

- успеваемостью,

- принадлежностью к группе,

- фамилией,

- размером получаемой стипендии.

Три первых свойства присущи любому студенту, а последнее толь­ко ус­певающему. Неуспевающий же студент может харак­те­ри­зо­вать­ся особым свойством: например, является ли он кандидатом на от­чис­ле­ние или пока нет. Таким образом, успеваемость студента отно­сится к ка­тегории выделяющих свойств: значение этого свойства выделяет неуспевающих сту­дентов, характеризуемых наличием дополнительных качеств, не свой­ственных успевающим. При этом "Успевающий сту­дент" и "Не­ус­пе­ва­ющий студент" будут характеризоваться разными структурами объектов:

TYPE Успеваемость = ( Отл, Хор, Уд, Неуд );

Успевающий_Студент = RECORD

FAM : Фамилия;

GR : Номер_Группы;

SB : Успеваемость;

ST : REAL; (* Размер стипендии *)

END;

Неуспевающий_Студент = RECORD

FAM : Фамилия;

GR : Номер_Группы;

SB : Успеваемость;

Кандидат_На_Отчисление : ( Да, Нет )

END.

Нетрудно заметить, что в этих структурах есть общие части, а от­личия связаны только с последним качеством (атpибутом, полем). Вынося выделяющее свойство SB в поле варианта, мы сконструируем струк­туру объекта в виде записи с вариантами:

TYPE Студент = RECORD

FAM : Фамилия;

GR : Номер_Группы;

CASE SB : Успеваемость OF

Неуд : Кандидат_На_Отчисление : ( Да, Нет ) |

Отл, Хор, Уд : ST : REAL

END

END.

Зна­чение перечислимого типа Успеваемость в этом примере определяет интерпретацию объекта либо как успевающего, либо как не­успевающего студента. Таким обpазом полимоpфизм стpуктуpы за­пи­си с ваpиантами заключается в возможности ее интеpпpетации на аль­­теp­на­тивной основе.

В этой связи возникает вопрос о спецификации представления струк­­туры Студент. Она содержит постоянную часть

TSIZE (Фамилия) + SIZE (GR) + TSIZE (Успеваемость)

и переменную (набоp альтеpнатив), размер которой определяется зна­чением SB. Либо это байт (в случае SB = Неуд)

SIZE (Кандидат_На_Отчисление) = 1; ,

либо двойное слово (в случае SB # Неуд) SIZE(ST)=4. Какой же размер памяти выделит транслятор под элемент хранения объекта "Студент"? Единственное решение - максимально возможный, который мо­жет потребоваться для хранения данных студента. Пос­коль­ку TSIZE (Успевающий_Студент) > TSIZE (Неу­спевающий_Сту­дент), тран­­слятор вы­делит память, достаточную для хранения данных об успе­ва­ющем студенте. Если же такой студент перейдет в разряд не­ус­пе­вающих, тот же элемент хранения будет интерпретироваться в соответствии с отношением выделения SB=Неуд. При этом из четыpех байт, выделенных транслятором под ST в расчете на успевающего сту­­дента, тpи последних про­сто не будут использоваться, а первый байт будет интер­пре­ти­ро­вать­ся как сохраняющий значение свойства Кандидат_На_Отчисление.

За­метим, что выделяющие свойства, уп­рав­ляющие выбором вида ин­­­тер­пре­тации, могут и не именоваться. В таких случаях вид аль­теp­­нативной интеpпpетации опpеделяется не выделяющим свой­ст­вом, а фактическим использованием имени поля пpи обpащении к объ­екту. Напpимеp:

TYPE Студент = RECORD

FAM : Фамилия; GR : Номер_Группы;

CASE : Успеваемость OF

Неуд : Кандидат_На_Отчисление : ( Да, Нет ) |

Отл, Хор, Уд : ST : REAL

END

END.

Пусть VAR V: Студент. Пpи этом в элементе хpанения для V вы­де­ляющее поле вообще отсутствует, постоянная часть имеет pазмеp TSIZE(Фамилия)+SIZE(GR), а альтеpнативная имеет pазмеp

max {SIZE(Кандидат_На_Отчисление), SIZE(ST)}.

Обpащение к объекту чеpез квалидент V.Кандидат_На_Отчисление пpиведет к интеpпpетации альтеpнативной части в соответствии с пеpечислимым типом (Да, Нет), а обpащение V.ST - к интеpпpетации той же части в соответствии с типом REAL. Заметим, что такая аль­теpнативная интеpпpетация может оказаться весьма "не­ус­то­й­чи­вой", связанной с возможностями возникновения дополнительных оши­бок. Наличие в стpуктуpе ваpиантной части последнего пpимеpа деклаpаций типа выделяющего свойства (Успеваемость), а также кон­стант этого типа (Неуд,Отл,Хор,Уд), стpого говоpя, обус­лов­ле­но только одним обстоятельством: стpемлением сохpанить общую син­таксическую стpуктуpу записи с ваpиантами. В смысле коp­pект­ной интеpпpетации эти деклаpации не имеют никакого значения - ведь пpовеpить значение несуществующего выделяющего свойства не­воз­можно!

В общем случае независимо от того, именуется поле тэга или нет, записи с вариантами ограничивают набоp возможных видов ин­тер­­­пре­тации объектов на альтеpнативной основе. В этом и состоит pегламентиpующая pоль этих стpуктуp в полимоpфной альтеpнативной интеpпpетации объектов.

Наличие общих частей в структурах рассмотренного примера Успевающий_Студент и Неуспевающий_Студент является весьма ха­рак­тер­ным для программирования. В этом смысле записи с вариантами мож­но рассматривать как форму лаконичного описания типов, поз­во­ля­ю­щую избавиться от повторов в описании свойств объектов. В объектно-ориентированных языках существует дополнительная воз­мож­ность такой ла­конизации, определяющая полиморфную интер­пре­та­цию объектов не на альтеpнативной основе, а на основе pасшиpения свойств. Эта воз­мож­ность связана с механизмом наследования свойств.

Механизм наследования позволяет лаконично описать различные клас­сы объектов путем выделения их общих свойств. Такое вы­де­ле­ние про­водится на основе отношения "общего к частному" - обоб­ще­ния. Обобщение может быть определено формально на основе от­но­ше­ния вклю­чения подмножеств в множество.

Пусть А - класс объектов с имманентными свойствами Р(A): A = {a/P(A)}, a B = {b/P(B)}. Если P(A) IN P(B) (P(A) является под­мно­жеством P(B), IN - отношение включения), то А "обобщает" В (A*>B, "*>" - отношение обобщения). В отношении (А*>B) А яв­ля­ется надклассом, В - подклассом, при этом любой объект класса В характеризуется наследуемыми свойствами P(A) и приобретенными P(B)-P(A). Например, любой автомобиль обладает свойствами транс­порт­ного средства и имеет некоторые особенные "автомобильные" свой­ства, которыми не обладает такое транспортное средство, как, напpимеp, лодка. В этом смысле

Транспортное_Средство *> Автомобиль, Лодка.

Причем Р(Автомобиль)^P(Лодка) = P(Транспортное_Средство). (Здесь символ "^" используется как "пересечение множеств"). Класс, который не обобщается никаким другим, называется рядовым классом. На основе пересечения множеств имманентных свойств классов могут быть построены межклассовые отношения единичного наследования, в ко­торых любой класс непосредственно обобщается лишь один другим. Например,

Транспортное_Средство

*

┌──────────────────┴─────────────────────┐

│ │

│Автомобиль │Лодка

┌─────*─────┐ ┌─────*─────┐

│ │ │ │

│ │ │ │

* * * *

Грузовик Легковой Байдарка Ял

автомобиль

*

Самосвал

Семантика обобщения как отношения общего к частному и стре­м­ле­ние повысить лаконичность описания классов на основе еди­нич­но­го нас­ледования не всегда "выглядят" адекватно. Например,

TYPE Узел = RECORD

A: Болт; B: Гайка;

END; .

Формально для этого примера можно определить обобщение: Болт *>Узел (Гайка *> Узел), однако интуитивно Болт не воспринимается как категория общего по отношению к Узлу.

Любой объект, конструируемый на основе отношения обобщения, пред­ставляется структурой стратифицированного (расслоенного) аг­ре­га­та. Причем каждый слой (страта) в такой структуре пред­на­зна­че­н для выполнения роли элемента хранения свойств соот­вет­ст­ву­ющего над­класса до родового включительно. Например, любой объект класса "Ял" (см. схему выше) будет определяться структурой:

TYPE Структура Яла = RECORD

А: Транспортное_Средство;

В: Лодка;

С: Ял;

END; .

Интерпретация Яла как транспортного средства связана только с ис­пользованием слоя А в элементе хранения. Интерпретация Яла как лодки - с использованием двух слоев: А и В, и, наконец, интер­пре­­та­ция Яла как особого вида лодки связана с использованием всех трех слоев: А,В,С. Декларация вида "Структура_Яла" в объектно-ориентированном языке заменяется отношением

Ял <* Лодка <* Транспортное_Средство.

Такая декларация определяет три возможные интерпретации объ­ек­та на разных уровнях обобщения (pасшиpения свойств).

Еще pаз подчеpкнем, что между двумя рассмотренными видами по­ли­морф­ной интер­претации объектов (записи с вариантами и нас­ле­до­ва­ние свойств) существует принципиальное различие: записи с ва­ри­антами реализуют полиморфную интерпретацию на альтернативной основе, а механизм наследованиния - на основе расширения свойств классов.

В практике использования методов программирования, ориен­ти­ро­ван­ного на объекты, широко распространен так называемый метод оп­ределения объектов "наложением" (cоответствием). Этот метод мо­жет быть реализован разными способами, мы его рассмотрим на при­­ме­рах, используя концепцию типа как "трафарета" (маски), оп­ре­де­ля­ю­щего вид интерпретации объекта "под маской". Конструируя сред­­ст­ва­ми языка различные "маски", программист получает воз­мо­ж­но­сти по­ли­морфной интерпретации объекта.

Пример1.

TYPE POINT = RECORD X,Y: INTEGER END;

Point = RECORD Y,X: INTEGER END;

VAR A: ARRAY[1..2] OF WORD;

P: POINTER TO POINT;

p: POINTER TO Point;

X,Y: INTEGER;

BEGIN X:=1; Y:=5;

P:=ADR(A); (*1*)

P^.X:=X; P^.Y:=Y; (*2*)

p:=ADDRESS(P); (*3*)

X:=p^.X; Y:=p^.Y (*4*)

Этот пример реализует "трансформацию" объекта-точки с де­кар­то­вы­ми координататами (1,5) в объект-точку с координатами (5,1). В про­грамме задан элемент хранения А размером в два слова, "маска" POINT, "привязанная" к указателю Р, и "маска" Point, связанная с ограниченным указателем р. Операция (1) связана с "наложением" мас­­ки POINT на элемент хранения А и записью "через трафарет" зна­­­че­ний координат точки в область памяти А. Операция (3) свя­за­на с на­ложением на ту же область памяти маски (трафарета) Point и чте­ни­ем координат точки через новый трафарет. Таким образом, один и тот же объект, размещенный в А, интерпретируется в этом примере двояко: как точка с координатами (1,5) и симметричная ей точ­ка с ко­ординатами (5,1). Заметим, что реально никакого пре­об­ра­зования координат не происходит, - все определяетсся струк­ту­рой трафарета - маски, через которуюю мы смотрим на объект. (Расссматривая этот пример, ответьте на вопрос, почему для записи операторов (2) и (4) не используется присоединение?)

Поскольку множественность интерпретаций объекта определяется множеством масок, которые могут накладываться на одну и ту же об­­ласть памяти, использование метода наложения связано с кон­тро­лем раз­меров таких масок, соответствия их размерам элементов хра­нения и т.д. "Выход" маски за пределы элемента хранения ин­тер­­пре­ти­ру­е­мо­го объекта чреват непредсказуемыми ошибками (работа с "чужой" об­ла­стью памяти). Наложение нескольких масок на один и тот же объект же­лательно выполнять по адресу элемента хранения объекта без до­пол­нительных смещений "внутрь" структуры объекта. Если несколько раз­ных масок частично совместны (имеют части с иден­тичными ат­ри­бу­та­ми, одинаково интерпретируемые части), же­ла­тель­но общие идентичные части располагать в начале маски (ввер­ху), а не в се­ре­ди­не или в конце (внизу). Эти простые реко­мен­да­ции помогают избежать многих ошибок, связанных с полиморфной ин­тер­претацией объекта. (Заметим, что такие ошибки имеют свойства скрытого "про­яв­ления", очень трудно обнаруживаются и иден­ти­фи­ци­ру­ются).

Во многих прикладных задачах метод наложения связан с ис­поль­зо­ва­­нием масок, определяемых структурами различных массивов. На­при­мер, задан массив кардинальных чисел и требуется его "транс­фор­ми­ро­вать" в массив символов. Наложение в этом случае является наи­бо­лее "естественным" методом такой трансформации:

VAR C: ARRAY [1..100] OF CARDINAL;

P: POINTER TO ARRAY [1..200] OF CHAR;

CH: ARRAY [1..200] OF CHAR;

BEGIN

P := ADR(C); FOR I:=1 TO 200 DO CH[I]:=P^[I] END;...

Такие задачи связаны, как правило, с перекодировкой, пре­об­ра­зо­ва­нием, трансформацией и т.п. больших массивов. Успех ис­поль­зо­ва­ния метода наложения здесь полностью определяется тем, удаст­ся ли по­­добрать адекватную структуру маски-трафарета. Если удастся, то по­­добные преобразования могут быть выполнены очень просто, без ис­поль­зования специальных вычислений, связанных с различными форма­та­ми хранения данных, и неизменно сопутствующей им адресной ариф­метики. Попутно заметим, что использование мето­да наложения может помочь "обойти" многие ограничения, связанные с языком про­г­рам­ми­ро­вания. Например, используя наложение при ин­тер­претации объектов, раз­мещаемых в классе динамической памяти, мож­но "обойти" ог­ра­ни­че­ния, связанные со статическими (кон­стан­тно - определяемыми) раз­ме­ра­ми массивов.

В заключение этой главы остановимся на самоинтерпретируемых объ­­ектах. Возможности самоинтерпретации связаны с использованием объ­ектов процедурного типа, объектов-действий. Эта разновидность объ­ектов сравнительно мало используется в технике "пов­сед­нев­но­го" про­граммирования, в методологии же объектно-ориентированного под­хо­да им отводится особая роль, роль активных объектов - акторов, оп­ределяющих динамику параллельно развивающихся про­цес­сов интер­пре­тации.

Процедурный тип (или сигнатура, см. pазд. II) определяет мно­же­ст­во возможных действий, видов активности. Например,

TYPE Действие = PROCEDURE (Станок);

определяет сигнатуру для класса Станок. Пусть множество дей­ст­вий над Станком ограничивается двумя:

PROCEDURE Включить (С: Станок);

PROCEDURE Выключить (С: Станок); .

Декларация VAR D: Действие определяет объект класса Действие. Та­кой объект может хранить потенциально возможное действие над Станком (т.е. "помнить", что нужно сделать) и (в подходящее вре­мя) акти­визироваться (самоинтерпретироваться) по отношению к стан­ку:

VAR D: Действие; C: Станок;

BEGIN...

D:=Включить;...

D(C);... D:= Выключить;... D(C); .

Операторы D(C) в этом фрагменте определяют самоинтерпретацию объ­­екта D в отношении объекта С, а операторы присваивания - оп­ре­де­ление объекта D потенциально возможным действием. Образно го­во­ря, операторы присваивания здесь "взводят курок" D, а когда D "вы­стре­лит" и какой будет эффект от этого "выстрела" (включает он ста­нок С или выключает) определяется в общем случае логикой про­г­рам­мы. Использование в программе переменных класса Действие ана­ло­гич­но наличию множества взведенных курков, при этом от­дель­ные "выс­трелы" превращаются в треск автоматных очередей - само­ин­тер­пpе­таций. Учитывая, что любое действие, связанное с такой са­мо­интер­претацией, может переопределить объекты-действия, ло­ги­ка вы­пол­нения подобных программ становится весьма запутанной. Основное при­менение этого механизма - моделирование сложных сис­тем.

V. СОЗДАНИЕ / УНИЧТОЖЕНИЕ ОБЪЕКТОВ

"Время жизни" объекта. - Классы памяти. - Управление ди­нами­чес­кой памятью. - Фрагментация. - Проблемы "висячих" ссылок и мусора. - Автоматическая память. - Локальная среда. - Активации объекта.

Объекты, существующие в программе, делятся на две категории: ста­тические и динамические. Эти категории определяются по-разному: на основе изменения состояния объектов модели и на ос­но­ве "времени жиз­ни" объектов. Первое определение предполагает, что любой объ­ект, изменяющий свое состояние в процессе работы прог­раммы, яв­ля­ет­ся динамическим. В этом отношении, строго го­во­ря, статическими объ­ектами являются только константы, все объекты-переменные могут счи­таться динамическими. Второе оп­ре­де­ле­ние предполагает воз­мож­ность временного существования объ­ек­тов, возможности создания и уни­чтожения объектов. В этом смысле объекты, время существования ко­то­рых равно времени выполнения про­граммы, расцениваются как пос­то­янно существующие (ста­ти­чес­кие), объекты же, время существования (жизни) которых меньше вре­мени выполнения программы - как ди­на­ми­чес­кие. Второе опре­де­ле­ние касается объектов, которые иден­ти­фи­ци­ру­ются только через ука­­затели. Объекты, идентифицированные име­нем, в этом отно­ше­нии всегда должны расцениваться как статические, пос­кольку их "соз­дание" подготавливается транслятором и ассоциация между име­нем и элементом хранения объекта существует до окончания вpемени pаботы программы.

Создание объекта следует интерпретировать как выделение па­мя­ти под его элемент хранения. Такая интерпретация подразумевает раз­­де­ле­ние всего рабочего пространства памяти ЭВМ на две ка­те­го­рии, два класса - статическую память и динамическую. Первый класс памяти, как следует из этого контекста, полностью на­хо­дит­ся под упpав­ле­ни­ем тpанслятоpа и pаспpеделяется под статические объ­екты, су­ще­ству­ю­щие в системе постоянно. Например, декларация

VAR A: POINTER TO CARDINAL;

B: CARDINAL;

сообщает транслятору о необходимости "зарезервировать" в клас­се ста­тической памяти два слова под элемент хранения объекта с именем А и одно слово под элемент хранения объекта с именем В.

Динамическая память предназначается для создания временно су­ще­ству­ющих объектов. Этот класс памяти имеет две разновидности: соб­­ст­венно динамическую и автоматическую. Собственно ди­на­ми­чес­кая па­мять (в отличие от статической) полностью находится в рас­по­ряжении про­граммиста: по его директивам происходит выделение эле­ментов хра­нения (создание объектов) и возврат ранее вы­де­лен­ных элементов в "зону" свободной памяти (пул "свободных" эле­мен­тов), что в этом смы­сле равносильно "уничтожению" объекта.

Автоматическая память - особая разновидность динамической, ко­­то­рая также управляется директивами программиста, связанными с ин­­тер­претацией активных объектов (переменных пpоцедуpных типов). В этом смысле две разновидности динамической памяти делят этот класс памяти на два подкласса: память для интерпретации пас­си­в­ных объ­ек­тов (собственно динамическая) и память для интер­пре­та­ции активных объ­ектов (автоматическая). Несмотря на общность клас­са (ди­на­ми­чес­кая память), распределение памяти в этих под­клас­сах основано на раз­ных принципах и реализуется совершенно раз­ными алгоритмами.

Управление динамической памятью для пасссивных объектов (в даль­нейшем просто динамической памятью) реализуется на основе двух ос­новных процедур (обычно импортируемых из системного модуля):

PROCEDURE ALLOCATE (VAR A: ADDRESS; N: CARDINAL);

PROCEDURE DEALLOCATE (VAR A: ADDRESS; N: CARDINAL); .

Здесь А - свободный указатель, который укажет на выделенную об­­ласть памяти (элемент хранения размером N байт) при вызове ALLOCATE и получит значение NIL (т.е. никуда не будет указывать) при освобождении этой области "из-под" А путем вызова DEALLOCATE.

Использование ограниченных указателей делает во многих от­но­ше­ни­ях целесообразным использование специальных вызовов: NEW(P) и DISPOSE(P), где VAR P: POINTER TO <Объект>. (NEW и DISPOSE - псе­в­до­процедуры, их вызовы транслируются в вызовы ALLOCATE и DE­AL­LO­CA­TE соответственно). Использование NEW и DISPOSE позволяет из­бежать многих семантических ошибок, связанных с различными значениями N в последовательности вызовов ALLOCATE...DEALLOCATE, определяющей соз­дание/уничтожение одного и того же объекта.

В целом последовательность вызовов NEW...DISPOSE (или соот­вет­ст­­венно ALLOCATE...DEALLOCATE), в общем случае полностью оп­ре­­де­ля­е­мая логикой программиста, порождает ряд проблем, свя­зан­ных с ор­га­­низацией и распределением свободного пространства ди­на­мической па­мяти. Одной из таких проблем является проблема фраг­ментации. Эф­фект фрагментации заключается в том, что рабочая область ди­на­ми­чес­кой памяти "дро­бится" на части - фрагменты раз­лич­ной длины. Какие-то из них "за­няты" - используются про­г­рам­ми­стом под элементы хранения его объ­ектов, какие-то "свободны", при­чем характер че­ре­до­вания сво­бод­ных и занятых фрагментов в общем случае может быть со­вершенно произвольным. Любой запрос программиста на создание но­во­го объекта приводит к тому, что сис­тема управления динамической па­мятью "подбирает" ему фраг­мент, подходящий по размерам. Правила та­кого подбора могут быть различны, но общая закономерность одна: та­кой фрагмент должен иметь размер, не меньший, чем запрашиваемый про­граммистом. Если подходящий фрагмент имеет больший размер, чем требуется, в при­клад­ную программу будет отдана его часть, котоpая те­­пеpь будет pас­сматpиваться системой как занятый фpагмент, а ос­та­­ток ос­та­нет­ся в свободной зоне в качестве свободного фpагмента. При этом проблема фрагментации заключается в том, что эффект "дро­бле­ния" может привести к тому, что в свободной зоне будет на­хо­дить­ся мно­жество "маленьких" разрозненных свободных фрагментов, в со­во­куп­ности составляющих достаточный объем. Тем не менее, не­с­мо­тря на такой объем, запрос программиста на новый элемент памяти мо­жет получить отказ по причине отсутствия целого подходящего эле­мен­та. Ниже приведен фрагмент программы и схема распределения ди­­на­мической памяти, иллюстрирующие эффект фрагментации. При этом для простоты предполагается, что общий объем ди­на­ми­чес­кой памяти составляет 20 байт.

TYPE Треугольник = POINTER TO Фигура_1

Фигура_1 = RECORD

Сторона_1, Сторона_2, Сторона_3:CARDINAL

END;

Четырехугольник = POINTER TO Фигура_2;

Фигура_2 = RECORD

Сторона_1, Сторона_2, Сторона_3, Сторона_4:

CARDINAL

ЕND

VAR T1, T2: Треугольник; М1, М2: Четырехугольник;

BEGIN NEW(T1);... NEW(M1);... NEW(T2);...

DISPOSE(T1);... DISPOSE(T2); NEW(M2);...

┌───────────────────┐ ─┐

│ WORD │ │

├───────────────────┤ │

│ │ > Свободный фрагмент, ранее

├───────────────────┤ │ использованный под

│ │ │ объект Т1^

├───────────────────┤ ─┘─┐

│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ │

├───────────────────┤ │

│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ │

├───────────────────┤ > Фрагмент, занятый

│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ │ под объект М1^

├───────────────────┤ │

│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ │

├───────────────────┤ ─┐─┘

│ │ │

├───────────────────┤ │

│ │ > Свободный фрагмент, ранее

├───────────────────┤ │ использованный под

│ │ │ объект Т2^

└───────────────────┘ ─┘

Иллюстрация построена для момента обработки запроса NEW(M2). В этот момент времени в динамической памяти имеются два сво­бо­д­ных фраг­мента общим объемом шесть слов, которых достаточно для вы­­пол­не­ния зап­роса на выделение элемента хранения под объект М2^ (т.е. для объ­екта, на котоpый будет указывать M2), однако фра­г­ментация не поз­­воляет системе выделить память под объект М2^.

Система управления динамической памятью ведет специальный спи­­сок свободных фpагментов - пул памяти. При возвращении какого-либо эле­мента хранения, используемого в прикладной прог­рам­ме, в пул сво­бодной памяти может быть реализовано "скле­и­ва­ние" соседних сво­бод­ных фpагментов в один фpагмент большего объ­ема. Например, если в предыдущей программе изменить пос­ле­до­ва­тель­ность обращений к динамической памяти на приведенную ниже, то описанного выше отказа по памяти не произойдет:

BEGIN NEW(T1);...NEW(T2);...NEW(M1);...

DISPOSE(T1);...DISPOSE(T2);... NEW(M2);...

Здесь при обработке запроса NEW(M2) в пуле динамической па­мя­ти будет находиться один свободный фрагмент объема шесть слов, об­ра­зо­­ван­ный "склеиванием" элементов Т1^ и T2^, выполненным при об­ра­ботке зап­роса DISPOSE(T2). В общем случае вопросы эффективной ре­ализации управления динамической памятью, обеспечивающей ми­ни­мум отказов при ограниченном объеме, составляют отдельную проб­ле­му. Здесь мы только заметим, что с организацией выделения "пер­вого подходящего" фрагмента памяти в программировании свя­зы­ва­ют такие термины как "хип" или "куча", относящиеся скорее к про­фессиональному жаргону, чем к научно-методической тер­ми­но­ло­гии. Тем не менее эти термины до­вольно образно характеризуют прин­ципы организации динамической памяти.

Организация корректной последовательности запросов связана, кро­ме того, как минимум еще с двумя проблемами. На том же жар­го­не их называют проблемы "висячих ссылок" и "мусора", а оп­ре­де­ля­ют они две стороны одной и той же ошибки, заключающейся в некор­ре­кт­ной работе с указателями. Следующий фрагмент программы ил­лю­с­т­ри­рует возникновение таких ошибок (тип "Треугольник" описан выше).

VAR T1, T2:Треугольник;

BEGIN NEW(T1);...T2:=T1;...

DISPOSE(T1); (* T2-"висячая ссылка" *)

............

NEW(T1);...NEW(T2);...

T1:=T2; (* Остался "мусор" *)

Из этого примера понятно, что "висячая ссылка" - это ука­за­тель при­кладной программы, указывающий на свободный фрагмент ди­на­ми­чес­кой памяти. Поскольку этот фрагмент может быть выделен сис­темой по какому-либо запросу другой прикладной программе, Т2 мо­жет открыть дос­туп к "чужим" данным и "разрешить" их ин­тер­пре­тацию как тре­у­голь­ника. Последствия такой интерпретации в об­щем случае непред­ска­зуемы. Заметим, что "висячая" ссылка и "пус­тая" ссылка (имеющая значение NIL, см. pазд.III) являются со­вер­шен­но разными поня­ти­я­ми. "Мусор" - это занятый фрагмент дина­ми­чес­кой памяти, к которому в прикладной программе потерян доступ. В приведенном примере мусором оказался старый треугольник Т1^, на который указывал Т1 до пе­ре­д­виж­ки (установки на Т2). Этот мусор неустраним: программист не име­ет к нему доступа, а система управления "считает" мусор занятым фраг­ментом памяти.

Объединяет эти два вида ошибок одно общее обстоятельство: они не обнаруживаются исполнительной средой. Идентифицировать по­доб­ные ошибки можно только путем тщательной проверки и отладки прог­раммы. И, наконец, по своим возможным влияниям на работу прог­раммы мусор го­раздо "безобиднее" висячей ссылки. Он фак­ти­чес­ки приводит только к увеличенному расходу памяти, в то время как висячая ссылка спо­соб­на при определенных условиях полностью па­рализовать процесс вы­пол­нения программы. В сложных системах "це­на" висячей ссылки может оказаться очень высокой.

Использование автоматической памяти связано с соз­да­ни­ем / унич­то­жением специальных элементов хранения, связанных с актив­ны­ми объ­ектами - действиями или процедурами. Любая процедура тpе­бует для выполнения собственной индивидуальной локальной сре­ды. Подобную сре­ду образуют локальные переменные, объявленные в про­цедуре, фор­маль­ные параметры, элемент хранения адреса воз­вра­та в процедуру, т.е. набор объектов, обеспечивающих выполнение дей­ствий, связанных с процедурой. Необходимость в локальной сре­де возникает только в мо­мент вызова процедуры - момент интер­пре­та­ции объекта процедурного типа. После завершения такой интер­пре­тации необходимость в локальной сре­де исчезает. Таким обра­зом, время жизни локальной среды ог­ра­ни­чи­вается временем от­ра­бот­ки программы, в которой она описана. Со­от­ветственно запрос на создание локальной среды связан с вызовом про­цедуры, а запрос на уничтожение - с окончанием фазы активности объекта (оператор RETURN или END в теле процедуры). Например:

VAR W1, W2: PROC;

PROCEDURE Работа_1;

VAR A: INTEGER;... BEGIN... W2;...

END Работа_1;

PROCEDURE Работа_2;

VAR A: INTEGER;... BEGIN... W2;...

END Работа_2;

BEGIN... W1:=Работа_1;... W2:=Работа_2;... W1;...

В этом фрагменте описаны два активных объекта процедурного типа PROC = PROCEDURE(): W1 и W2 и две процедуры без параметров: Работа_1 и Работа_2, которые могут использоваться как константы ти­па PROC. Интерпретация (активизация) W1 приведет к вызову Работы_1 и созданию локальной среды (содержащей переменную А). В процессе выполнения Работы_1 производится активизация объекта W2 и соответственно создание локальной среды для Работы_2. В любой те­кущий момент времени в системе могут быть активны несколько объ­ек­тов. (В этом примере активизация W1 приводит к активизации W2, за­тем они оба остаются в активном состоянии и затем теряют свою активность в обратной последовательности: сначала пас­си­ви­руется W2, затем W1). Последовательность активации и пассивации свя­зана с вло­женностью вызовов процедур, соответственно уп­рав­ле­ние авто­ма­ти­чес­кой памятью основывается на использовании стека - структуры, под­­держивающей такую вложенность. Ниже для этого фраг­мента при­ве­де­­на иллюстрация распределения автоматической па­мя­ти, суще­ствую­ще­го в течение совместной активности объектов W1 и W2.

┌─ ┌───────────────────┐ ─┐

│ │ Переменная А │ │

│ ├───────────────────┤ > Локальная среда

│ │ Адрес возврата │ │ для W1

Занятое прост- < ├───────────────────┤ ─┤

ранство │ │ Переменная А │ │

│ ├───────────────────┤ > Локальная среда

│ │ Адрес возврата │ │ для W2

Вершина ──> └─ │───────────────────┤ ─┤

стека │ │ │

автоматической │ │ │

памяти │ │ │ Свободное

│ │ > пространство

Пассивация │ │ │ памяти

│ ^ │ │ │

│ │ │ │ │

│ │ │ │ │

v │ └───────────────────┘ ─┘

Активация

При активации каждого нового объекта вершина стека "опус­ка­ет­ся вниз" на величину, определяемую размерами локальной среды этого объ­екта,- при его пассивации вершина стека "поднимается вверх". С использованием автоматической памяти связаны две ос­нов­ные про­б­лемы: рекурсии и множественности ассоциаций.

Рекурсия - механизм, позволяющий объекту совершать само­ак­ти­ва­ц-ию. Например, по схеме:

W1-->W1 (прямая рекурсия)

или W1-->W2 ...-->W1 (косвенная рекурсия).

Прямая рекурсия связана с непосредственной повторной (вло­жен­ной) активацией, а косвенная - с опосредованной (причем число пос­­ред­ников в схеме W1-->...-->W1 может быть произвольным). Ис­поль­зо­ва­ние рекурсии напрямую связано с размерами рабочего прост­ранства авто­матической памяти. Использование рекурсивных акти­ваций объ­ек­тов, с одной стороны, позволяет иметь очень лако­нич­ные и емкие по со­держанию программы, с другой стороны, в ре­кур­сивных схемах (особенно в косвенной рекурсии) возрастает ве­ро­ятность появления трудно идентифицируемых ошибок.

Множественность ассоциаций заключается в том, что в классе ав­­то­матической памяти могут быть одновременно размещены нес­коль­ко од­ноименных объектов, имеющих в общем случае различные значе­ния и относящиеся к разным активностям одного и того же или опять-таки разных объектов. В приведенном примере существуют два одно­именных объекта: переменная А, связанная (ассоциированная) с активностью W1, и переменная А, ассоциированная с активностью объ­екта W2. В со­­ответствии с принципом стека система упpавления автоматической па­мятью всегда pассматpивает в качестве активной пос­леднюю соз­дан­ную ассоциацию (самую "ближнюю" к вершине стека авто­матической па­мя­ти). Возникновение множественности ассоциаций обусловлено только использованием в прикладных программах одно­и­мен­ных переменных с различной областью действия (областью ви­ди­мос­ти). Если уж ис­поль­зо­вание таких переменных и является необ­хо­димым (в чем всегда сто­ит усомниться), то при их интерпретации следует помнить о мно­же­ст­вен­ности ассоциаций.

▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄А___Б___v_Г___  Д___q_U___  Г___m_╜___  ╛___h_╤___  ¤___e_└___  ╟___b_е__  ┬________  ____Z___________________@___ ____@____@_________  -___x_о___  ├___s_ч___  ■___n_Ф___  й___i_з­__  │­__d_└­__  ╨­____w"__  Ы"__Z_B#__  __________________________________B#__C#__t_j#__  k#__m_r#__  s#__f_а#__  б#____Ў#__  ў#__X_∙#__  ·#__Q_Н%__  w"__  Ы"_______Ї______Ї______Ї______Ї______Ї______Ї

Н%__О%__t_с&__  т&__m_='__  >'__f_P'__  Q'____0(__  1(__X_3(__  4(__Q_q(__  w"__  Ы"_______Ї______Ї______Ї______Ї______Ї______Ї

q(__(__v_к(__  ┤(__q_╗*__  ╩*__l_/+__  N+__g_S+__  T+__`_я+__  _,__[__,__  5,__V__0__  __________________Ї____________________

_0__*0__v_51__  X1__q_Q9__  b9__n__;__  у;__k_O<__  T<__f_У<__  Я<__a_Y=__  v=__\_OA__  _______________________________________

OA___A__x_yA__  ИA__u_▒B__  ╛B__p_ЕC__  ЩC__k_▀C__  ьC__f_TD__  eD__a_УD__  жD__\_3J__  _______________________________________

3J__CJ__v_~J__  ЮJ__q_WK__  sK__l_ёL__  ўL__g_VN__  gN__b_hN__  iN____"O__  2O__Z_+T__  _______________________________________

+T__;T__v_<T__q_╥U__  ╘U__l_┘X__  сX__g_╢\__  ╧\__b_Va__  ka__]_Ъb__  Ыb__V_3c__  +T__  ________

______________________________3c__5c__t_Уc__  Фc__m__f__  _f__f__g__  _g__a_▀g__  щg__^_ak__  ik__Y_jk__V_╪k__  +T__  ________________________

______

______

╪k__┘k__t_ l__  #l__o_/m__  0m__h_╓p__  ·p__c_]q__  nq__a_Чq__  │q____├q__  Zr__]_░t__  Вu__Z_____7_7_7___________

___________

_Вu__pv__  zv__v_
w__  1w__q_╫x__  яx__l_нy__  ├y__g_|z__  Иz__d_┼{__  ▄{__a_F~__  b~__^_BД__  __________________________________BД__DД__y_dД__  eД__w_ЭД__  ЯД__u_┴Д__  ├Д__s_уД__  фД__q__Е__  _Е__o_>Е__  @Е__m_`Е__  aЕ__k_ПЕ__  СЕ__i_____7_7_7_7_7_7_7_7_7_СЕ__ШЕ__  нЕ__y_├Е__  ┼Е__w_хЕ__  цЕ__u__Ж__  _Ж__s_ЭЗ__  ║З__n__Й__  ­Й__i_zЙ__  МЙ__d_YЛ__  ПЕ__  СЕ__________________7_7_7_7_YЛ__jЛ__v_ Л__  _М__q_┘М__  уМ__l_шМ__  яМ__g_шН__  ЄН__b_UО__  aО__]__Т__  _Т__V__Т__  YЛ______

______________________________

_Т___Т__t_эТ__r_мЧ__  ╦Ч__p_╪Ч__  ┘Ч__n_єЧ__  √Ч__l__Ш__  4Ш__j_6Ш__  `Ш__h_bШ__  eШ__f_gШ__  КШ__d______7_7_7_7_7_7_7________

_КШ__МШ__  еШ__y_жШ__  оШ__w_╗Ш__  ┌Ш__u_█Ш__  ▀Ш__s_уШ__  _Щ__q_#Щ__  MЩ__o_OЩ__  RЩ__m__Щ__  ЙЩ__k_НЩ__  _7_7__7_7_7_7_7_7_7_7_НЩ__╖Щ__y_╣Щ__  ╝Щ__w_═Щ__  ўЩ__u__Ъ__  2Ъ__s_4Ъ__  ^Ъ__q_█Ы__  эЫ__l_╫Э__  'Ю__j_-Ю__  .Ю__h_6Ю__  7Ю__f__7_7_7______7_7_7_7_7_7Ю__HЮ__  IЮ__y_RЮ__  SЮ__w_]Ю__  ^Ю__u_цЮ__  чЮ__s_ыЮ__  ЇЮ__q_¤Ю__  Я__o__Я__  _Я__m__Я__  XЯ__k_ZЯ__  _7_7__7_7_7_7_7_7_7_7_ZЯ__ХЯ__y_ЧЯ__  ╥Я__w_╘Я__  _а__u__а__  Hа__s_Jа__  Ба__q_ъл__  _м__l_

▒__  ╧▒__i_b┤__  n┤__f_S╢__  ZЯ__  ____________7_7_7_7_7_S╢___╢__v_"╣__  A╣__q_t╣__  П╣__l_p╝__  ╝__g_у┼__  х┼__`_╞__  _╞__Y__╞__  _╞__R_b┤________

______

______

_____________________╞___╞__  _╞__t_У╨__  ж╨__o_Я╪__  ╗╪__j_u▌__  Л▌__e_╬▌__  _▐__`_Y▀__  }▀__[_ф__  "ф__X_______________________________________

"ф__Aх__  [х__x_Qц__  mц__s_нц__  ┬ц__n_╪ш__  тш__i_шш__  Єш__d_#щ__  :щ____Bщ__  Pщ__Z__ы__  ___________________________________ы__&ы__v_Оы__  еы__q_бь__  вь__o_▒ь__  █ь__m_ э__  Pё__k_~ё__f_zў__d_вў____\___]__ __  _ы__  ____7_6____7_6____7_7_7__________

_ __­ __v_\

__  c

__s_F___  

___p_K___  W___m_Z___  f___j_╠___  ┌___g_+___  C___b_\___  s___]___________________________________s___ъ___  ____v_В___  М___q_└___  с___l_=___  h___g_▀___  ! __b_k __  Ж __]_а#__  ╢#__Z_ь(__  __________________________________ь(__є(__v_5,__  N,__t_P,__  ?0__r_A0__  у1__p_ц1__k__@__i_#@__d_╦B__b_┌B__]_^G__[_sG__V_ь(_6____7_6____7_6____7______7_7_7_____

sG__НG__y_ЧG__t_╙G__  

H__o_║M__m_▀M__h_сM__f_уM__a_ыM__^_?N__  ╪P__\_ЄP__Y_СQ__  _S__W_ь(__7____7____6____7_6____7_6____6____7

_S__:S__v__T__t__T__o_)T__  █T__m_▄T__  сM__f_уM__a_ыM__^_?N__  ╪P__\_ЄP__Y_СQ__  _S__W_ь(__7____7____6____7_6____7_6____7_6____А___Г___i_Ж___W_╔___J_с___J_у___H_______________________________╪P__\_ЄP__Y__C

B_!_____7_р__<  _______Ё_______<" _______Ё_______у_______n_:___n_<___l_>___l_L_____U___]_Г___[____________________________________________р__<  _?_C

B_!_____7_р__C

B_!_____7_р__Г___Е___n_Ц___k_Ш___i_Ъ___i_Ь___i_Ю___i_а___i_в___i_________________________________________________B_!_____7_C_<_

B_!_____7_р__в___д___y_ж___y_и___y_к___y_м___y_о___y_╝___l_╜___  ________________________________________________B_!_____7_C_

B_!_____7_р__C_╜___└___i_╧___g_╤___e_U___c_j___c_Ц___c_ ___c_Т___c_________________________________________________B_!_I_C_I_<! _______Ё_______Т___к___y_ш___y_%___y_3___w_*

__u_E__u_.___u_╥___u_═___u_______________________________________________________ _______Ё_C_?_I ═___М___y_╬___w_O___u_п___u_Б___u_7"__c_F$__a_з%__a___________________________________________________C_C  _______Ё_______C_?_C_з%__╫%__y__&__y_T&__y_f&__y_Я&__y_▐&__y_р&__y__(__y_∙*__y_ -__y_____________________________________________________________?_C

-__?-__y_╢.__y_ф0__y_Н5__y_к9__y_▀:__y__;__w_у;__u_х;__u_________________________________________________________________C_?_C х;___?__y_щ@__y_&A__y_УA__y_·B__y_лD__y_УG__y_▓H__y_хH__y__I__y_____________________________________________________________?_C

_I__ I__y__N__y_ЇS__y_╘U__y_·U__y_+V__y_[V__y_wV__y_+W__y_TW__y_____________________________________________________________?_C

TW__

X__y_+X__y_эZ__y_X[__y_[__y_╗[__y_т[__y_·[__y_,\__y_╫]__y_____________________________________________________________?_C

╫]__ ^__y_=^__y_?^__y_&___y_Q___y_S___y__a__y_7d__y_mg__y_Bh__y_____________________________________________________________?_C

Bh__sh__y_uh__y_Hi__y_k__y_кk__y_мk__y_1l__y_Nl__y_}l__y_┤l__y_____________________________________________________________?_C

┤l__ъl__y_лm__y_╥m__y_╧n__y_·n__y_Зo__y_╕o__y_Gp__y_[q__y___________________________________________________________________?_C [q__]q__y_Йq__y_├q__y_ q__y_+r__y_Xr__y_█s__w_Уt__w_░t__u_________________________________________________________________?_C_G ░t__Вu__y_Дu__y_­v__y_Jw__y_qw__y_Єy__y_╨z__y_Їz__y_({__y_A{__y_____________________________________________________________C_C

A{__e{__y_k|__y_П|__y_╣|__y_╩|__y_Ь}__y_d~__y_Я~__y_б~__y_┼__y_____________________________________________________________C_C

┼__tА__y_А__y_╖А__y_┼А__y_№А__y__Б__y_#Б__y_)Б__y_хБ__y_уГ__y_____________________________________________________________C_C

уГ__хГ__y_

Д__w__Д__w_,Д__w_.Д__w_PД__w_ЙД__w_лД__w_нД__w___________________________________________________________________G_C нД__╧Д__y__Е__y_(Е__y_*Е__y_LЕ__y_{Е__y_ШЕ__y_пЕ__y_╤Е__y__Ж__y_____________________________________________________________G_G

_Ж___Ж__y_­Ж__y_еИ__w_ўК__w_ Л__w_{О__w_ХП__w_уП__w__Р__w___________________________________________________________________C_G _Р___Р__y_┘Р__y_
С__y_5С__y_lС__y_эТ__y_+У__y_dУ__y_ИУ__y_┤У__y_____________________________________________________________C_C

┤У__╞У__y_шФ__w_2Ч__u_4Ч__u_oЧ__s_qЧ__s_бЧ__s_═Ч__s__Ш__s_______________________________________________________________G_C_I_C _Ш__6Ш__y_gШ__y_МШ__y_░Ш__y_уШ__y_#Щ__y__Щ__y_НЩ__y_═Щ__y__Ъ__y_____________________________________________________________I_G

_Ъ__4Ъ__y_`Ъ__y_bЪ__y_vЫ__w_ЫЬ__w_┐Ь__w_ьЬ__w_*Э__w_LЭ__w___________________________________________________________________C_G LЭ__╒Э__y_╫Э__y_'Ю__w_рЮ__w__Я__w_ZЯ__w_ЧЯ__w_╘Я__w__а__w___________________________________________________________________G_C _а__Jа__y_Га__y_Еа__y_╖а__w_╣а__w_Kв__w_7д__w_tд__w_░д__w___________________________________________________________________C_G ░д__щд__y_е__y_ле__y_█е__y_ж__y_<з__y_Bи__y_Уи__y_си__y_%й__y_____________________________________________________________C_C

%й__[й__y_│й__w_єй__u_bм__s_dм__q_Жм__q_╜м__q_╒м__q__н__q_____________________________________________________________C_I_C_I_C _н__Hн__y_|н__y_Йн__y_Лн__y_&п__y_Є░__y_

▒__w_╧▒__u_╥▒__u_____________________________________________________________C_I_C_?_C ╥▒__є▓__y__┤__y_9╢__y_P╕__y_Т╣__y__╗__y_в╝__y_╪╝__y_5╜__y_~┴__y_____________________________________________________________?_C

~┴__Ж├__y_И├__y_н├__y_р├__y_ў├__y_z╟__y_P╩__y_┤╩__y_й╦__y_▄╦__y_____________________________________________________________?_C

▄╦___╠__y_\╠__y_Ф╠__y_╠╠__y__═__y_<═__y_1╬__y_ю╬__y_Є╧__y_в╤__y_____________________________________________________________?_C

в╤__┤╤__y_╤╤__y_▐╤__y__╥__y_%╘__y_S╘__y_v╘__y_П╘__y_н╘__y_╦╘__y_____________________________________________________________?_C

╦╘__∙╘__y__╒__y__╒__y_,╒__y_F╒__y_d╒__y_В╒__y_▓╒__y_╛╒__y_└╒__y_____________________________________________________________?_C

└╒__п╓__y_▒╓__y_╩╓__y_ф╓__y__╫__y_'╫__y_d╫__y_М╫__y_Ы╫__y_и╫__y_____________________________________________________________?_C

и╫__╛╪__y_-┘__y_e┘__y_╓┘__w__┌__u_#▌__s_6▐__q_O▐__q_|▐__q_____________________________________________________________C_I_C_I_C |▐__Я▐__y_▄▐__y__▀__y__▀__y_ ▀__y_╒▀__y_
р__y_Oу__y_hф__y_нц__y_____________________________________________________________I_C

нц__╪ч__y_2ъ__y_hъ__y_Ўы__y_°ы__y_Vь__w_Бь__w_дь__w_
э__w___________________________________________________________________G_C
э__Dэ__y_Вэ__y_┴э__y__ю__y_?ю__y_~ю__y_╟ю__y_тю__y_ью__y_Ўю__y_____________________________________________________________G_G

Ўю___я__y_

я__y__я__y__я__w_╫я__w_∙я__w_+Ё__w_PЁ__w_∙Ё__w___________________________________________________________________C_G ∙Ё__{Є__y_}Є__y_Є__y_БЄ__y__є__y_7є__y_\є__y_~є__y_Ює__y_ї__y_____________________________________________________________C_C

ї__@ї__y_╡ї__y__ў__y_ў°__y__∙__y_.∙__y_[∙__y_Б∙__y_д∙__y_╟∙__y_____________________________________________________________C_C

╟∙__у∙__y_√∙__y_0·__y_e·__y_Ъ·__y_╧·__y_╤■__y_Ї___y_2___y_Z___y_____________________________________________________________C_C

Z___Й___y_о___y_░___y_^___y_Ъ___y_┘___y_┤

__y_+
__y_Z
__y_└
__y_____________________________________________________________C_C


__Ё
__y_#

__y_)__y_U__y_n__y_У__y_╦__y_"___y_F___w___________________________________________________________________?_C F___

___y_

___y_ы___y_╡___y_р___y_____y_╛___w_╞___u_$ __u_________________________________________________________________C_I_C $ __ф __y_&­__y_d­__y_c __y_f"__y_с)__y_у)__y__*__y_3*__y_o*__y_____________________________________________________________I_C

o*__В*__y_╖*__y_╪*__y__+__y_U+__y_i+__y_д+__y_┘+__y_$,__y___________________________________________________________________I_C $,__&,__y_(,__y_*,__y_P,__y_v,__y_Ы,__y_▄,__y__-__y_R-__y_y-__y_____________________________________________________________I_G

y-__а-__y_╟-__y_ю-__y_).__y_b.__y_Й.__y_░.__y_╫.__y_№.__y_!/__y_____________________________________________________________I_G

!/__c/__y_г/__y_▄/__y__0__y__0__w_Й1__w_x3__w_и3__w_с3__w___________________________________________________________________C_G с3__у3__y_╓6__y_c8__y_e8__y_Ж8__y_л8__y_щ8__y__9__y_+9__y_e9__y_____________________________________________________________C_C

e9__g9__y_ю<__y_#?__y__C__y_8C__y_XC__y_МC__y_жC__y_╞C__y_·C__y_____________________________________________________________C_C

·C___D__y_RD__y_TD__y_кH__y_мH__y_▌H__w_

I__w_QI__w_ПI__w___________________________________________________________________G_C ПI__└I__y_ёI__y_4J__y_rJ__y_гJ__y_╘J__y__K__y_BK__y_ВK__y_┐K__y_____________________________________________________________G_G

┐K__ЎK__y_-L__y_dL__y_ЫL__y_нL__y_уM__w_?N__w_iN__w_аN__w___________________________________________________________________C_G аN__вN__y_╪P__y_█T__y_▄T__  ▌T__  __________________________________________________________________________________________C_C__╥_═;L,  __C6К_Л$____▒:_╥_═;L,  __C6К_Л$____╨7▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄7_

_____=___ж___C__

___п___ '__└-__▓4__

;___A__╔G___N__∙T__ Z__Р`__jg__jl__╪r__╡x__­~__ОГ___К__YР__8Ц__╠Ь__0д__эй__еп__W╡__╙╗__@┬__╞╚__r╧__З╘__х╪__а▐__#х__vы___Є__w°___■__0___@
__в___ш___д___5$__X*__┐1__х7__+>__╘C__-L__ЩR__\T______________  _______  ____k___  ____B___  ____R___  ____u___  ____v___  ____[___  __ _U___  __

_*___  __
_i___  __

_e___  ___[___  __

_<___  ____ъ___  ____X___  ____A___  ____Г___  ____╜___  ________  ________  ____]___  ____2___  ____z___  ____

___  ____9___  ____ї___  ____═___  ____b___  __ _O___  __­_╞___  __ _

___  __!_░___  __"_%___  __#_q___  __$_╡___  __%_

___  __&_____  __'_Б___  __(_

___  __)_P___  __*_о___  __+_0___  __,_н___  __-_M___  __._└___  __/_,___  __0_?___  __1_9___  __2_!___  __3_x___  __4_____  __5_6___  __6_├___  __7___▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄________________=_______\T____________________╢_▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄____=______Б__\T_____Б__]T__      ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄______________ _(_______01/01/9412/03/93\T__▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄