Смекни!
smekni.com

Программирование ориентированное на объекты (стр. 5 из 10)

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 при этом имеет следующий вид: