Программирование в среде Турбо Паскаль - Дмитрий Поляков 10 стр.


типы Type1 и Type2 не будут идентичными, поскольку конструкции Array...of..., хотя и одинаковы, но не являются идентификаторами, т.е. обособленными именами. Переменные типов Type1 и Type2 не смогут в последнем случае обмениваться значениями.

2. Типы Type1 и Type2 описаны как эквивалентные. Это означает, что, например, при описании

| TYPE

| Type1 = Array [1..2] of Boolean;

| Type2 = Type1;

| Type3 = Type2;

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

VAR

x1, x2, xЗ : Type1;

то они совместимы. Если Type1 - идентификатор типа, а не конструкция, то совместимость сохранится и при объявлении вида

| VAR

| x1 : Type1;

| x2 : Type1;

| x3 : Type2;

Здесь Type2 идентичен типу Type1, но будут несовместимы переменные x1 и x2:

| VAR

| x1 : Array [1..2] of Real;

| x2 : Array [1..2] of Real;

Ограничения на совместимость только по идентичным типам было бы слишком жестким. Поэтому совместимость в Турбо Паскале трактуется несколько шире. Так, типы считаются совместимыми, если:

- оба типа являются одинаковыми;

- оба типа являются вещественными типами;

- оба типа являются целочисленными;

- 87 -

- один тип является поддиапазоном другого;

- оба типа являются поддиапазонами одного и того же базового типа;

- оба типа являются множественными типами с совместимыми базовыми типами;

- один тип является строковым, а другой тип - строковым или символьным типом;

- один тип является указателем (Pointer), а другой - указателем или ссылкой.

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

Существует еще один вид совместимости: совместимость по присваиванию, т.е. правила присваивания значения V2 (собственно значение, переменная или выражение) переменной V1. Они действительны только для операций присваивания и являются немногим более широкими, чем правила совместимости по типам. Значение V2 типа Type1 может быть присвоено переменной V1 типа Type2, если выполняется одно из условий:

1. Type1 и Type2 - тождественные типы, и ни один из них не является файловым типом или структурным типом, содержащим компонент с файловым типом.

2. Type1 и Type2 - совместимые перечислимые типы, и значения типа Type2 попадают в диапазон возможных значений Type1.

3. Type1 и Type2 - вещественные типы, и значения типа Type2 попадают в диапазон возможных значений Typel.

4. Typel - вещественный тип, а Type2 - целочисленный тип.

5. Type1 и Type2 - строковые типы.

6. Type1 - строковый тип, а Type2 - символьный тип.

7. Type1 и Type2 - совместимые множественные типы, и все члены значения множества типа Type2 попадают в диапазон возможных значений Type1.

8. Type1 и Type2 - совместимые адресные типы.

9 Тип объекта Type2 совместим по присваиванию с типом объекта Type1, если Type2 находится в области типа объекта Type1.

10. Тип ссылки Ptr2, указывающий на тип объекта Type2, совместим по присваиванию с типом ссылки Ptr1, указывающим на тип объекта Type1, если Type2 находится в области типа объекта Type1.

- 88 -

Последние два правила, относящиеся к данным типа "объект", не слишком очевидны. Более подробное их описание приводится в гл. 13 "Объектно-ориентированное программирование".

Нарушение правил совместимости типов и значений обнаруживается, как правило, на этапе компиляции программы.

С вопросом совместимости очень тесно связан вопрос о типе результатов арифметических выражений. Например, можно ли заранее сказать, какой будет тип у результата выражения справа?

| VAR

| B :Byte;

| W : Word;

| I : Integer;

| R : Real;

...

| R := В*I+W;

На этот счет существуют четкие правила внутреннего преобразования типов значений - участников операций:

1. В случае бинарной операции, использующей два операнда, оба операнда преобразуются к их общему типу перед тем, как над ними совершается действие. Общим типом является встроенный целочисленный тип с наименьшим диапазоном, включающим все возможные значения обоих типов. Например, общим типом для целого и целого длиной в байт является целое, а общим типом для целого и целого длиной в слово является длинное целое. Действие выполняется в соответствии с точностью общего типа, и типом результата является общий тип. Если же один из операндов - вещественный, а второй - целочисленный, то результатом операции может быть только значение вещественного типа.

2. Выражение справа в операторе присваивания вычисляется независимо от размера переменной слева.

Если результат выражения "не вписывается" в тип переменной слева от знака ":=", то может возникнуть ошибка переполнения. В случае вещественной переменной слева при переполнении возникнет ошибка счета 205 (Floating Point overflow). Но если слева стоит целочисленная переменная, то при режиме компиляции {$R+} возникнет ошибка нарушения диапазона 201 (Range Check Error), а при {$R-} программа не прервется, но значение в переменной будет "обрезано" ее диапазоном и перестанет соответствовать выражению справа. Последний случай чреват труднодиагностируемыми ошибками в результатах счета программы.

- 89 -

Другим примером опасности может стать вывод значений выражений оператором Write (или в общем случае подстановка выражений в вызовы процедур или функций):

| VAR

| A, B : Word;

| BEGIN

| A:= 55000;

| B:= A-256;

| Write(A+B);

| END.

Эта программа должна вычислить значение A+B и вывести его на экран. Но она этого не сделает. В режиме компиляции {$R+} запуск программы даст ошибку 201, поскольку общий для A и B тип Word не вмещает их сумму (его "потолок" равен 65535). В режиме {$R-} программа напечатает заведомую ложь.

Выход из подобных ситуаций прост. Надо объявлять хотя бы одного участника выражения более длинным (емким) типом. Так, если описать A как LongInt, то общим для A и B типом станет LongInt, и в нем уместится достаточно большое значение суммы. Можно даже просто, не изменяя объявления переменной, переписать последний оператор в виде

Write(LongInt(A) + B);

используя описываемое ниже приведение типа значения.

5.4. Изменение (приведение) типов и значений

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

ИмяТипа( ПеременнаяИлиЗначение )

Задаваемое имя типа, в который происходит преобразование, должно быть известно в программе. Примеры приведения типов:

- 90 -

| TYPE

| Arr4Byte = Array[1..4] of Byte; { массив из 4-х байтов }

| Arr2Word = Array[1..2] of Word; { массив из двух слов }

| RecType = RECORD

| Word1, Word2 : Word { запись из двух слов }

| END;

| VAR

| L : LongInt; { четырехбайтовое целое со знаком }

| S : ShortInt; { однобайтовое целое со знаком }

| В : Byte; { однобайтовое целое без знака }

| W : Word; { двухбайтовое целое без знака }

| a4 : Arr4Byte; { массив из четырех байтов }

| a2 : Arr2Word; { массив из двух слов по два байта }

| Rec : RecType; { запись из двух слов по два байта }

| BEGIN

| L := 123456; { некое значение переменной L }

| S := -2; { некое значение переменной S }

| a2 := arr2Word( L ); { два слова L перешли в массив a2 }

| a4 := arr4Byte( L ); {четыре байта L перешли в a4 }

| W := RecType( L ).Word1; { доступ к L по частям

| W := arr2Word( L )[ 1 ];

| RecType(L).Word1 := 0; { обнуление первого слова в L }

| B := Byte( S ); { если S=-2, то B станет 254 }

| B := Arr4Byte( a2 )[1]; { запись в B значения первого }

{полуслова массива a2 }

| END.

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

- 91 -

Аналогично изменению типа переменных можно изменять тип значений как таковых, а также результирующий тип выражений, т.е. разрешены такие преобразования:

Integer( 'Y' ) { код символа 'Y' в формате Integer }

Boolean( 1 ) { это логическое значение True }

LongInt( 1 ) { значение 1, размещенное в четырех байтах}

Char ( 130-1 ) { символ с кодом ASCII номер 129 }

В этом случае происходит трактование значения в скобках как значения другого типа. Например, символ 'Y' хранится как код 89 и занимает один байт. Но конструкция Integer ('Y') представляет собой значение 89 в формате целого числа со знаком (в двух байтах). Как видно, при преобразовании типа значений соблюдение размера уже не нужно. Новый тип может быть шире, а может быть и короче, чем естественный тип значения.

При приведении значения в более широкий тип (например, LongInt(1)) значения будут целиком записаны в младшие (наименее значащие) байты, что сохранит само значение. В противоположном случае, когда значение приводится к более короткому типу, от него берутся уже не все, а лишь самые младшие байты. При этом старшие байты игнорируются, и приведенное значение не равно исходному. Например, выражение Byte( 534 ) равно 22, поскольку значение 534 кодируется в тип Word и раскладывается на младший и старший байты как 22 + 2*256. Младший байт (22) мы получим, а старший (со значением 2) проигнорируем.

Преобразование типа значения внешне похоже на преобразование типа переменной. Но эффект от него несколько иной (за счет возможности изменения размера), и ограничения на применение другие. В частности, как задаваемый тип, так и тип значения (выражения) должны быть перечислимыми (это все целочисленные типы, тип Char, Boolean и вводимые перечисления) или адресными (адресные значения хранятся как значения LongInt). По понятным причинам преобразование значения не может появиться в левой части присваивания.

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

Приведение типов как переменных, так и значений - нетривиальная операция. Она подразумевает достаточно высокий уровень знаний технических подробностей языка. Например, нужно знать, как хранятся структуры (массивы, записи, множества), адреса, числа, строки в памяти, какие размеры им отводятся. Приведение

- 92 -

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

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

- 93 -

Глава 6. Управляющие структуры языка

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

Предварительно необходимо ввести такие базовые понятия, как простой и составной операторы.

6.1. Простой и составной операторы

Оператор в программе - это единое неделимое предложение, выполняющее какое-либо действие. Типичный простой оператор - оператор присваивания. Другим примером может служить вызов какой-либо процедуры в программе. Важно, что под любым оператором подразумевается действие (присваивание, сравнение величин, вызов подпрограммы, переход по программе и т.п.). Блоки описания типов, переменных, констант, меток и составляющие их предложения не являются в этом смысле операторами.

Два последовательных оператора обязательно должны разделяться точкой с запятой ";". Этот символ имеет смысл конца оператора, и он же разделяет операторы при записи в одну строку, например:

a:=11; b:=a*a; Write(a,b);

Отсюда вовсе не следует, что можно не закрывать символом ";" единственные в строке операторы.

Если какое-либо действие мыслится как единое (например, присваивание явно значений ряду элементов массива), но реализуется несколькими различными операторами, то последние могут быть представлены как составной оператор.

Составной оператор - это последовательность операторов, перед которой стоит слово BEGIN, а после - слово END. Между любыми двумя операторами должна стоять точка с запятой. Она сама по себе не является оператором и поэтому может отсутствовать между оператором и словом END. Зарезервированное слово BEGIN тоже не является оператором (как и все остальные зарезервированные слова), и после него точка с запятой не ставится. Так, чтобы оформить

- 94 -

три приведенных выше оператора в один, но составной, нужно как бы заключить их в операторные скобки BEGIN...END:

| BEGIN

| a:=11;

| b:=a*a;

| Write(a,b)

| END;

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

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

6.2. Условный оператор (IF...THEN...ELSE)

Условный оператор IF...THEN...ELSE (если...то...иначе) имеет структуру

If Условие THEN Оператор1 ELSE Оператор2;

и служит для организации процесса вычислений в зависимости от какого-либо логического условия. Под условием понимается логическое значение True (истинно) или False (ложно), представленное константой, переменной или логическим выражением, например:

IF True THEN ...; { крайний и бесполезный случай условия }

IF LogicalVar THEN ...; { условие - логическая переменная }

IF not LogicalVar THEN ...; {условие - логическое выражение}

IF x > 5 THEN ...; { условие - результат операции сравнения}

Если условие представлено значением True, то выполняется оператор (простой или составной), следующий за словом THEN. Но если условие не выполняется, т.е. представлено значением False, то будет выполняться оператор (может быть простым или составным), следующий за словом ELSE. Например:

- 95 -

| IF x>5

| THEN { ветвь при x>5 - истинно }

| BEGIN

| x:=x+5; y:=1 { некий составной оператор }

| end

| ELSE { ветвь при x>5 - ложно }

| y:=-1; { простой оператор }

В примере между ключевыми словами нет точек с запятой. Более того, их появление было бы ошибкой, что будет показано ниже. Но точка с запятой в конце всего оператора (после завершения ветви ELSE) обязательна. Она отделяет условный оператор от остальных, следующих за ним по тексту. Альтернативную ветвь ELSE можно опускать, если в ней нет необходимости. В таком "усеченном" условном операторе в случае невыполнения условия ничего не происходит, и выполняется следующий за условным оператор. Имеет "право на жизнь" условный оператор с ветвями, содержащими пустые операторы, например такой:

IF LogicFunc(x) THEN ;

Он полезен в случаях, когда условием является возвращаемое значение какой-либо логической функции, имеющей побочный эффект. Например, известны библиотеки подпрограмм (Turbo Power Toolbox), где для создания окна на экране дисплея имеются функции, собственно строящие окно и возвращающие логическое значение в зависимости от результата построения. Приведенная выше конструкция позволяет проявиться побочному эффекту, игнорируя возвращаемое значение.

Условные операторы могут быть вложенными друг в друга:

IF Условие

Назад Дальше