declare

 

 

1. Особенности роста и развития современного человека.

2. Здоровье и факторы риска.

3. Элементы экологии внутренней среды человека.

4. Трансформирующие агенты биосферы.

5. Деградация генофонда человечества.

6. Здоровый образ жизни граждан как основа устойчивого развития общества.

 

Type

indextype = 1. .100; {ограниченный тип}

Var

count: integer;

index : indextype;

 

Если бы в Pascal использовалась совместимость имен типов, то переменные count и index не были бы совместимы.

Совместимость структур типов более гибка, но сложнее реализуется. При определении совместимости имен типов сравниваются только имена двух типов, а при использовании совместимости структур типов – структуры двух типов. Выполнить второе сравнение не всегда лег­ко. (Рассмотрите, например, структуру данных, ссылающуюся на собственный тип – связный список.) При этом могут возникать дополнительные вопросы. Являются ли, например, два структурных типа совместимыми, если они имеют одинаковую структуру, но разные имена полей? Совместимы ли два одномерных масси­ва в программе на языке Pascal или Ada, если они содержат элементы одного типа, но различаются областью значений индекса: 0..10 и 1..11?

Еще одной трудностью, связанной с совместимостью структур типов, является то, что она не признает различий между типами, имеющими одинаковую структуру. Рассмотрим следующее объявление, которое могло бы появиться в программе на языке Pascal:

Type

celsius = real;

fahrenheit = real;

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

В исходном определении языка Pascal явно не устанавливается, когда должна использоваться совместимость структур типов, а когда – совместимость их имен. Это вредно для мобильности программ, поскольку программа, корректная в одной системе реализации языка, может оказаться некорректной в другой. Стандарт язы­ка Pascal, созданный Международной организацией по стандартизации, явно устанавливает правила совместимости типов для данного языка, частично – по имени, частично – по структуре. В большинстве случаев используется структура, а имя типа приме­няется для формальных параметров и в некоторых других ситуациях. Рассмотрим, на­пример, следующие объявления:

Type

typel = array [1..10] of integer;

type2 = array [1..10] of integer;

type3 = type2;

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

В языке Ada применяется совместимость имен типов, но при этом имеются две кон­струкции – подтипы и производные типы, которые позволяют устранить возни­кающие проблемы. Производным называется новый тип, основанный на некотором ранее определенном типе, с которым он несовместим, несмот­ря на то, что они имеют идентичную структуру. Производные типы наследуют все свой­ства родительских типов. Рассмотрим следующий пример:

type celsius is new FLOAT;

type fahrenheit is new FLOAT;

Переменные типов celsius и fahrenheit несовместимы, хотя и имеют идентичную структуру. Более того, переменные этих типов несовместимы ни с каким другим типом чисел с плавающей точкой. Исключением из правила являются только литеральные константы. Литеральная константа, например, 3.0 имеет тип универсальных действительных чисел и совместима с любым типом чисел с плавающей точкой. Производные типы также могут содержать огра­ничения диапазона родительского типа, наследуя при этом все его операции.

Подтип в языке Ada – версия существующего типа с, возможно, ограничен­ным диапазоном. Подтип совместим с породившим его типом. Рассмотрим следующее объявление:

subtype SMALL_TYPE is INTEGER range 0..99;

Переменные, имеющие тип SMALL_TYPE, совместимы с переменными типа INTEGER.

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

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

В языке C++ используется эквивалентность имен типов. При этом отметим, что оператор typedef языков C и C++ не вводит новый тип. Он просто определяет новое имя для уже сущест­вующего типа.

Во многих языках переменные могут объявляться без использования имен типа, при этом соз­даются безымянные типы. Рассмотрим следующий пример из языка Ada:

A : array (1..10) of INTEGER;

В этом случае переменная A имеет безымянный, но неоднозначно определенный, тип. После объявления

В : array (1..10) of INTEGER;

 

переменные A и B будут принадлежать к безымянным, различным и несовместимым типам, хотя они имеют идентичную структуру. Множественное объявление

С, D : array (1..10) of INTEGER;

создаст два безымянных типа: один для переменной C, другой – для переменной D, не­совместимых между собой. Фактически эти объявления можно рассматривать как сле­дующие два объявления:

С : array (1..10) of INTEGER;

D : array (1..10) of INTEGER;

Однако в объявлении

type LIST__10 is array (1..10) of INTEGER;

C, D : LIST_10;

переменные C и D будут совместимыми.

Очевидно, что в языках, не позволяющих пользователям определять и называть типы, например FORTRAN и COBOL, эквивалентность имен применяться не может.

Возникновение таких объектно-ориентированных языков как C++, Java, C# подняло вопрос о новой концепции совместимости типов – совместимости объектов и ее связи с иерархией наследования.

 

3.8. Область видимости

 

Важной характеристикой переменных является область видимости – совокупность операторов, в которых пе­ременная доступна.

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

Переменная является локальной в программной единице или блоке, если она там объявлена. В данном разделе программными единицами считаются главный программный модуль или подпрограммы. Единицы, подобные клас­сам языков C++, Java, C# здесь не рассмотрены. Нелокальными переменными программной единицы или блока называются переменные, которые видимы в этой программной единице или блоке, но не объявляются в них.

 

3.8.1. Статическая область видимости

 

В языке ALGOL 60 был введен метод связывания имен с нелокальными переменными, названный статическим обзором данных. Позже этот метод был позаимст­вован большинством императивных, а также и многими неимперативными языками. Исполь­зование статического обзора данных получило свое название из-за возможности статиче­ского (то есть до периода выполнения) определения области видимости любой переменной.

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

В концепциях многих языков подпрограммы создают собственные области видимости. Во многих распространенных языках со статическим обзором данных, за исключением C-подобных языков и FORTRAN, подпрограммы могут вкладываться в другие подпрограммы, что создает в программе иерархию областей видимости.

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

Рассмотрим следующую процедуру языка Pascal:

procedure big;

var x : integer;

procedure subl;

begin { subl }

... x ...

end; { subl }

procedure sub2;

var x : integer;

begin { sub2 }

…x…

end; { sub2 }

begin { big }

end; { big }

При статическом обзоре данных ссылка на переменную x подпро­граммы sub1 относится к переменной x, объявленной в процедуре big. Поиск объявления переменной x начался в той процедуре, в кото­рой встречается ссылка (sub1), но объявления этой переменной в ней найдено не было. Далее поиск продолжился в статическом родителе подпрограммы sub1 (подпрограмме big), в котором и было найдено объявление переменной х.

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

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

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

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

 

Блоки

Многие языки позволяют создавать новые статические области видимости во время выполнения программы. Эта мощная концепция, впервые появившаяся в языке ALGOL 60, позволяет фрагменту программы иметь собственные локальные переменные с минимизированной областью видимости. Такие переменные, как правило, являют­ся автоматическими, так что память выделяется им в начале выполнения фрагмента про­граммы, а освобождается по окончании его выполнения. Подобный фрагмент программ­ного кода получил название блока.

В языке Ada блоки задаются оператором declare:

declare

TEMP : integer;

Begin

TEMP := FIRST; FIRST := SECOND; SECOND := TEMP;

end;

От термина «блок» произошло выражение язык с блочной структурой. Хотя языки Pascal и Modula-2 называются языками с блочной структурой, они не имеют непроцедурных блоков.

В языках C, С++, Java, C# любой составной оператор (последовательность операторов, заключенная в фигурные скобки) может содержать объявления и таким образом опреде­лять новую область видимости. Напри­мер, если list – массив целых чисел, то можно написать следующий код:

if (list [i] < list [j])

{

int temp = list [i];

1ist [j] = temp;

}

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

В языках C++, Java, C# определять переменные можно в любом месте блока. Если определение появляется не в начале, то область видимости данной переменной начинается с оператора определения и заканчивается концом блока.

Оператор for языков C++, Java, C# позволяет определять переменные в выражениях, инициализирующих счетчики цикла. Область видимости таких переменных ограничена заголовком и телом цикла for.

Определения класса и метода в объектно-ориентированных языках программирова­ния также порождают вложенные статические области видимости.

Применение статических областей видимости представляет метод нелокаль­ного доступа, хорошо работающий во многих ситуациях. Однако у этого метода есть и недостатки.

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

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

Для решения проблем, связанных со статическим обзором данных, во многих новых языках программирования используется концепция инкапсуляции,то есть возможности сокрытия данных и процедур (фактически – сужения областей видимости).

 

3.8.2. Динамическая область видимости

 

Область видимости переменных в таких языках как APL, SNOBOL4 и ранние версии языка LISP является динамической. Динамический обзор данных опирается на последовательность вызова подпрограмм, а не на их пространственную взаимосвязь. Следовательно, область видимости можно определить только во время вы­полнения программы.

Рассмотрим повторно процедуру big из п. 3.8.1. Предположим, что правила динамического обзора данных применимы к нелокальным ссылкам. Значение идентификатора x, к которому обращается подпрограмма sub1, – динамическое, оно не может определяться во время компиляции. В зависимости от последовательности вызовов это значение может относиться к переменной из предыдущего объявления переменной х.

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

Рассмотрим две различные последовательности вызовов процедуры sub1 в указанном примере. Предположим, что в первом случае процедура big обращается к процедуре sub2, которая вызывает процедуру subl. При этом поиск перейдет от локальной процедуры subl к вызывающей ее процедуре sub2, в которой находится объявление переменной x. Таким образом, в данном случае обращение к переменной x в процедуре subl будет обращением к переменной x процедуры sub2. Пусть теперь во втором случае про­цедура subl вызывается непосредственно из процедуры big. При этом динамическим родителем процедуры subl является процедура big, и обращение будет направлено к переменной x, объявленной в процедуре big.

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

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

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

Использование динамического обзора данных также затрудняет чтение программ, по­скольку для определения нелокальных переменных, на которые имеются ссылки, должна быть известна последовательность вызова подпрограмм. Если программу читает человек, то определить последнее практически невозможно.

Также отметим, что обращение к нелокальным переменным в языках с дина­мическим обзором данных происходит значительно медленнее, чем обращение в языках со статическим обзором.

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

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