Программирование ориентированное на объекты
1╛л▄T╟ [1] [1]hr align="left" size="1">
[1]hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
D:\DISSLAST\DISSERT.STYRN-EPSFX
hr align="left" size="1">
@╨ [1]\hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
[1]hr align="left" size="1">
hr align="left" size="1">
╡
Государственный комитет Российской Федерации
по высшему образованию
Самаpский госудаpственный аэpокосмический
унивеpситет имени академика С.П. Королева
М.А.Коpаблин
ПPОГPАММИPОВАНИЕ, ОPИЕНТИPОВАННОЕ НА ОБЪЕКТЫ
Учебное пособие
Самаp 1994
УДК 681.142.2
Пpогpаммиpование, оpиентиpованное на объекты: учебное пособие/ М.А.Коpаблин. Самар. госуд. аэннронкосм. ун-т; Самара, 1994. 97 с.
JSBN 5-230-16-955-9
Пособие посвящено одному из основных напpавлений совpенмеого пpогpаммиpования, связанному с объектно-оpиентинpонваым подходом к pазpаботке пpогpамм. Опинсываются основные коннцепнции такого подхода, методы и сpедства его pеализации, в совокупности составлянющие особый стиль пpогpаммиpования.
В пеpвую очеpедь оpиентиpовано на студентов, изучающих иннфоpматику и связанных с задачами пpогpамнмиpования пpиннкладнных инфоpмационных систем. Может быть pеконменндонванно пpи изунченнии дисциплин "Пpогpамминpование", "Техннология пpогpамнминpования", "Основы иннфоpмационной технологии", "Моделинpование на ЭВМ". Pекомедуется для использования в учебном пpонцессе спенциальностей "Пpикладная математика", "Автомантизинpонваые системы обpаботки инфоpмации и пpавления", "Пpонгнpамнмное обеспечение вычислительных и автоматизинpонванных систем". Выполнено на кафедpе "Инфоpмационные сиснтемы и технологии".
Печатается по решению редакционно-издательского совента Санмарнского государственного аэрокосмического нинверситета имени академика С.П.Королева
Pецензент Смиpнов С.В.
JSBN 5-230-16-955-9 Самаpский госудаpственный
эpокосмический нивеpситет, 1994
ПPЕДИСЛОВИЕ
Настоящие пособие не является pуководством по какому-либо язынку пpогpаммиpования. Более того, цель его заключается не в том, чтобы наунчить технике пpогpаммиpования. В него вошел мантенpинал, свяннзанный с концепцией объектно-оpиентиpованного поднхонда к pазpаботке пpогpамм, в соответствии с котоpой окpужающий нас pеальный миp иннтеpнпpетиpуется как совокупность взаимонсвянзаых и взаимодествующих объектов. Моделиpование задач pеального минpа в pамках этой коннцепнции связано с описанием (спецификаций) объектов pеального миpа в аденкватных категоpиях языка пpогнpамнминpования, что тpебует нового взглянда на же сложившиеся методы пpогpаммиpования и связано в изнвестнном смысле с пеpеосмыслением многих хоpошо известных и снтонявншихнся понятий.
Основная цель данного пособия заключается в том, чтонбы донести до читателя в сжатой лаконичной фоpме основные коцепнции объектно-оpиентиpованного подхода, пpоиллюстpиpовать их и сфоpнмиpовать общее пpедставление об этом напpавлении, контонpое познвонлит внимательному читателю легко пеpейти от уpовня поннинмания подннхода в целом к уpовню умения его pеализовать в pазнpанботнках коннкнpетных пpогpамм. Для этого в общем случае даже не обянзательно иснпольнзовать совpеменные объектно-оpиентиpованные язынки (во многом "пеннpегpуженные" специальными понятиями). Многие аспекты объектно-оpиентиpованного подхода могут быть pеализованы и в известной техннинке модульного пpогpаммиpования с испольнзонваннинем абстpагиpования типов, механизмов импоpта-экспоpта, пpонцеснсов, сопpогpамм и т.д.
втоp считал бы свою задачу выполненной, если бы у читателя на осове этого пособия сложился собственый кpитический взгляд на объектно-оpиентиpованное констpуиpование пpогpаммных моделей. Танкой взгляд особенно важен, поскольку п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
Над объектами множественного типа определены функции, свянзаые с элементарными операциями над множествами (объединение, пенренсенчение, разность, симметрическая разность); проверкой соснтоняния мноннжества (по характеристической функции); вклюнченнинем/искнлючением базовых объектов в множество и т.п. Подробнее об этом можно прончинтать в руководстве по языку программирования.
Использование характеристической функции для представления обънекнтов множественного типа позволяет организовать эффективную ранбонту с такими объектами на уровне элементов хранения.
. ИДЕНТИФИКАЦИЯ ОБЪЕКТОВ
Идентификация именованием.- Квалидент.- Дистанция доступа.- Опе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азд.) являются сонверншео разными понянтинянми. "Мусор" - это занятый фрагмент динанминчеснкой памяти, к которому в прикладной программе потерян доступ. В приведенном примере мусором оказался старый треугольник Т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Г ДqU Гm╜ ╛h╤ дe└ ╟bе
┬
_ Zhr align="left" size="1">
[1] [1] [1]hr align="left" size="1">
@hr align="left" size="1">
аhr align="left" size="1">
@hr align="left" size="1">
@ -xо ├sч ■nФ йiзн │нd└н ╨н_w" Ы"ZB# hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
[1]B#C#tj# k#mr# s#fа# б#_б# в#X∙# #QН% w" Ы"пппппп
Н%О%tс& т&m=' >'fP' Q'_0( 1(X3( 4(Qq( w" Ы"пппппп
q((vк( ┤(q╗* ╩*l/+ N+gS+ T+`я+ hr align="left" size="1">
,[, 5,V0 hr align="left" size="1">
hr align="left" size="1">
пhr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
0*0v51 X1qQ9 b9n; у;kO< T<fУ< Я<aY= v=\OA hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
[1]hr align="left" size="1">
[1]hr align="left" size="1">
[1] [1] [1]hr align="left" size="1">
hr align="left" size="1">
OA_AxyA ИAu▒B ╛BpЕC ЩCk▀C ьCfTD eDaУD жD\3J hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
[1] [1] [1] [1]
3JCJv~J ЮJqWK sKlёL вLgVN gNbhN iN_"O 2OZ+T hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
[1] [1]hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
+T;Tv<Tq╥U ╘Ul┘X сXg╢\ ╧\bVa ka]Ъb ЫbV3c +T
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
3c5ctУc Фcmf ffhr align="left" size="1">
g ga▀g щg^ak ikYjkV╪k +T [1] [1]hr align="left" size="1">
hr align="left" size="1">
[1] [1]hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
╪k┘kt l #lo/m 0mh p pc]q nqaЧq │q_├q Zr]░t ВuZ[1]hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
Вupv zvv
w 1wq╫x яxlнy ├yg|z Иzd┼{ ▄{aF~ b~^BД [1] [1] [1] [1] [1] [1] [1]hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
BДDДydД eДwЭД ЯДu┴Д ├ДsуД фДqЕ Еo>Е @Еm`Е aЕkПЕ СЕi[1]hr align="left" size="1">
СЕШЕ нЕy├Е ┼ЕwхЕ цЕuЖ ЖsЭЗ ║ЗnЙ нЙizЙ МЙdYЛ ПЕ СЕhr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
YЛjЛv Л Мq┘М уМlшМ яМgшН кНbUО aО]Т [1]ТVТ YЛ
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
ТТtэТrмЧ ╦Чp╪Ч ┘Чn
Ч √ЧlШ ШjШ `ШhbШ eШfgШ КШdhr align="left" size="1">
КШМШ еШyжШ оШw╗Ш ┌Шu█Ш ▀ШsуШ Щq#Щ MЩoOЩ RЩm_Щ ЙЩkНЩ НЩ╖Щy╣Щ ╝Щw═Щ вЩuЪ ЪsЪ ^Ъq█Ы эЫl╫Э 'Юj-Ю .ЮhЮ Юfhr align="left" size="1">
hr align="left" size="1">
ЮHЮ IЮyRЮ SЮw]Ю ^ЮuцЮ чЮsыЮ пЮqдЮ
ЯoЯ ЯmЯ XЯkZЯ ZЯХЯyЧЯ ╥Яw╘Я аuа HаsJа Баqъл мl
▒ ╧▒ib┤ n┤fS╢ ZЯ [1] [1] [1]hr align="left" size="1">
hr align="left" size="1">
S╢_╢v"╣ A╣qt╣ П╣lp╝ ╝gу┼ х┼`
╞ ╞Y╞ ╞Rb┤hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
╞╞ ╞tУ╨ ж╨oЯ╪ ╗╪ju▌ Л▌e╬▌ ▐`Y▀ }▀[
ф "фXhr align="left" size="1">
[1] [1]hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
"фAх [хxQц mцsнц ┬цn╪ш тшiшш кшd#щ :щ_Bщ PщZы hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
[1] [1]ы&ыvОы еыqбь вьo▒ь █ьm э Pёk~ёfzвdвв_\] ы hr align="left" size="1">
hr align="left" size="1">
7hr align="left" size="1">
67hr align="left" size="1">
6hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
н v\
c
sF
pK WmZ fj╠ ┌g+ Cb\ s]hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
[1] [1] [1] [1] [1] [1] [1] [1]hr align="left" size="1">
sъ vВ Мq└ сl= hg▀ !‑bk‑ Ж‑]а# ╢#Zь( [1]hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
ь(
(v5, N,tP, ?0rA0 у1pц1k@i#@d╦Bb┌B]^G[sGVь(hr align="left" size="1">
67hr align="left" size="1">
67hr align="left" size="1">
67hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
hr align="left" size="1">
sGНGyЧGt╙G
Ho║Mm▀MhсMfуMaыM^?N ╪P\кPYСQ SWь(hr align="left" size="1">
7 [1] [1]7 [1] [1]hr align="left" size="1">
67hr align="left" size="1">
67hr align="left" size="1">
6hr align="left" size="1">
67
S:SvTtTo)T █Tm▄T сMfуMaыM^?N ╪P\кPYСQ SWь(hr align="left" size="1">
7 [1] [1]7 [1] [1]hr align="left" size="1">
67hr align="left" size="1">
67hr align="left" size="1">
67hr align="left" size="1">
6АГiЖW╔JсJуH╪P\кPYC
B!7 [1]р< ‑Ё<"‑Ёhr align="left" size="1">
уn:n<l>lL_U]Г[ [1]р< ‑?C
B!7 [1]рC
B!7 [1]рГЕnЦkШiЪiЬiЮiаiвiB!7C[1]<
B!7 [1]рвдyжyиyкyмyоy╝l╜ B!7C[1]
B!7 [1]рC╜└i╧g╤eU[1]cj[1]cЦhr align="left" size="1">
c cТcB!ICI<!‑ЁТкyшy%y3w*
uE
u.u╥u═u‑ЁC?I ═Мy╬wOuпuБu7"cF$aз%aCC
‑Ёhr align="left" size="1">
C?Cз%╫%y&yT&yf&yЯ&y▐&yр&y(y∙*y -y?C
-?-y╢.yф0yН5yк9y▀:y;wу;uх;uC?C х;?yщ@y&AyУAyByлDyУGy▓HyхHyIy?C
I IyNyпSy╘UyUy+Vy[VywVy+WyTWy?C
TW
Xy+XyэZyX[y[y╗[yт[y[y,\y╫]y?C
╫]а ^y=^y?^y&_yQ_yS_yay7dymgyBhy?C
BhshyuhyHiykyкkyмky1lyNly}ly┤ly?C
┤lъlyлmy╥my╧nynyЗoy╕oyGpy[qy?C [q]qyЙqy├qy qy+ryXry█swУtw░tu?CGа ░tВuyДuyнvyJwyqwyкyy╨zyпzy({yA{yCC
A{e{yk|yП|y╣|y╩|yЬ}yd~yЯ~yб~y┼yCC
┼tАyАy╖Аy┼Аy№АyБy#Бy)БyхБyуГyCC
уГхГy
ДwДw,Дw.ДwPДwЙДwДwнДwGC нД╧ДyЕy(Еy*ЕyLЕy{ЕyШЕyпЕy╤ЕyЖyGG
ЖЖyнЖyеИwвКw Лw{ОwХПwуПwРwCG РРy┘Рy
СyСylСyэТy+УydУyИУy┤УyCC
┤У╞УyшwЧuЧuoЧsqЧsбЧs═ЧsШsGCIC ШШygШyМШy░ШyуШy#Щy_ЩyНЩy═ЩyЪyIG
ЪЪy`ЪybЪyvЫwЫЬw┐ЬwьЬw*ЭwLЭwCG LЭ╒Эy╫Эy'ЮwрЮwЯwZЯwЧЯw╘ЯwаwGC аJаyГаyЕаy╖аw╣аwKвw7дwtдw░дwCG ░дщдy
еyлеy█еy
жy<зyBиyУиyсиy%йyCC
%й[йy│йw
йubмsdмqЖмq╜мq╒мqнqCICIC нHнy|нyЙнyЛнy&пyк░y
▒w╧▒u╥▒uCIC?C ╥▒
▓y┤y9╢yP╕yТ╣y╗yв╝y╪╝y5╜y~┴y?C
~┴Ж├yИ├yн├yр├yв├yz╟yP╩y┤╩yй╦y▄╦y?C
▄╦╠y\╠yФ╠y╠╠yhr align="left" size="1">
═y<═y1╬yю╬yк╧yв╤y?C
в╤┤╤y╤╤y▐╤y╥y%╘yS╘yv╘yП╘yн╘y╦╘y?C
╦╘∙╘y╒y╒y,╒yF╒yd╒yВ╒y▓╒y╛╒y└╒y?C
└╒п y▒ y╩ yф y[1]╫y'╫yd╫yМ╫yЫ╫yи╫y?C
и╫╛╪y-┘ye┘y ┘whr align="left" size="1">
┌u#▌s6▐qO▐q|▐qCICIC |▐Я▐y▄▐yhr align="left" size="1">
▀y▀y ▀y╒▀y
рyOуyhфyнцyIC
нц╪чy2ъyhъyбыy
yU
yn
yУ
y╦
y"yFw?C F
y
yыy╡yрyy╛w╞u$‑uCIC $‑ф‑y&нydнyc
yf"yс)yу)y*y3*yo*yIC
o*В*y╖*y╪*y+yU+yi+yд+y┘+y$,yIC $,&,y(,y*,yP,yv,yЫ,y▄,y-yR-yy-yIG
y-а-y╟-yю-y).yb.yЙ.y░.y╫.y№.y!/yIG
!/c/yг/y▄/y0yhr align="left" size="1">
0wЙ1wx3wи3wс3wCG с3у3y 6yc8ye8yЖ8yл8yщ8y9y+9ye9yCC
e9g9yю<y#?yCy8CyXCyМCyжCy╞CyCyCC
CDyRDyTDyкHyмHy▌Hw
IwQIwПIwGC ПI└IyёIy4JyrJyгJy╘JyKyBKyВKy┐KyGG
┐KбKy-LydLyЫLyнLyуMw?NwiNwаNwCG аNвNy╪Py█Ty▄T ▌T CC╥ [1]═;L, CКЛ$▒:╥ [1]═;L, CКЛ$╨7▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄7
=жC
п '└-▓4
;A╔GN∙T ZР`jgjl╪r╡xн~ОГКYРЦ╠Ь0дэйепW╡╙╗@┬╞╚r╧З╘х╪а▐#хvыкw
[1] hr align="left" size="1">
k[1] [1] hr align="left" size="1">
B [1] R [1] u [1] v [1] [ [1] U [1]
* [1]
i [1]
e [1]
[ [1]
< [1] [1] [1] X [1] A [1] Г [1] ╜ [1] [1] [1] [1] [1] ] [1] 2 [1] z [1]
[1] 9 [1]