Типы данных


Любые данные, т.е. константы, переменные, значения функций или выражения, в Турбо Паскале характеризуются своими типами. Тип определяет множество допустимых значений, которые может иметь тот или иной объект, а также множество допустимых операций, которые применимы к нему. Кроме того, тип определяет также и формат внутреннего представления данных в памяти ПК.
Турбо Паскаль характеризуется разветвленной структурой типов данных ( 1).
В Турбо Паскале предусмотрен механизм создания новых типов данных, благодаря чему общее количество типов, используемых в программе, может быть сколь угодно большим.
В этой главе приводится подробное описание всех типов, за исключением файлов и указателей, которые рассматриваются в следующих двух главах, а также процедурных типов, которые рассматриваются в гл.8, и объектов (гл.10).
 
Простые типы
К простым типам относятся порядковые и вещественные типы. Порядковые типы отличаются тем, что каждый из них имеет конечное число возможных значений. Эти значения можно определенным образом упорядочить (отсюда -название типов) и, следовательно, с каждым из них можно сопоставить некоторое целое число - порядковый номер значения.
Вещественные типы, строго говоря, тоже имеют конечное число значений, которое определяется форматом внутреннего представления вещественного числа. Однако количество возможных значений вещественных типов настолько велико, что сопоставить с каждым из них целое число (его номер) не представляется возможным.
 
Порядковые типы
К порядковым типам относятся (см.  1) целые, логический, символьный, перечисляемый и тип-диапазон. К любому из них применима функция ORD(X), которая возвращает порядковый номер значения выражения X. Для целых типов функция ORD(X) возвращает само значение X, т.е. ORD(X) = X для X, принадлежащего любому шелому типу. Применение ORD(X) к логическому, символьному и перечисляемому типам дает положительное целое число в диапазоне от 0 до 1 (логический тип), от 0 до 155 (символьный), от 0 до 65535 (перечисляемый). Тип-диапазон сохраняет все свойства базового порядкового типа, поэтому результат применения к нему функции ORD(X) зависит от свойств этого типа.
К порядковым типам можно также применять функции:
PRED (X) - возвращает предыдущее значение порядкового типа (значение, которое соответствует порядковому номеру ORD(X)- 1), т.е.
ORD(PRED(X)) = ORD(X) - 1;
SUCC (X) - возвращает следующее значение порядкового типа, которое соответствует порядковому номеру ORD(X) +1, т.е.
ORD(SUCC(X)) = ORD(X) + 1.
Например, если в программе определена переменная
var
с : Char; 
begin
с := '5' ; 
end.
то функция PRED(C) вернет значение '4', а функция SUCC(C) - значение '6'.
Если представить себе любой порядковый тип как упорядоченное множество значий, возрастающих слева направо и занимающих на числовой оси некоторый отрезок, то функция PRED(X) не определена для левого, a SUCC(X) - для правого конца этого отрезка.
Целые типы. Диапазон возможных значений целых типов зависит от их внутреннего представления, которое может занимать один, два или четыре байта. В табл. 4.1 приводится название целых типов, длина их внутреннего представления в байтах и диапазон возможных значений.
Таблица 4.1

При использовании процедур и функций с целочисленными параметрами следует руководствоваться «вложенностью» типов, т.е. везде, где может использоваться WORD, допускается использование BYTE (но не наоборот), в LONGINT «входит» INTEGER, который, в свою очередь, включает в себя SHORTINT.
Перечень процедур и функций, применимых к целочисленным типам, приведен в табл.4.2. Буквами b, s, w, i, l обозначены выражения соответственно типа BYTE, SHORTINT, WORD, INTEGER и LONGINT, x - выражение любого из этих типов; буквы vb, vs, vw, vi, vl, vx обозначают переменные соответствующих типов. В квадратных скобках указывается необязательный параметр.
Таблица 4.2

При действиях с целыми числами тип результата будет соответствовать типу операндов, а если операнды относятся к различным целым типам, - типу того операнда, который имеет максимальную мощность (максимальный диапазон значений). Возможное переполнение результата никак не контролируется, что может привести к недоразумениям, например:
var
а : Integer; 
х, у : Real; 
begin
а := 32767; {Максимально возможное значение типа INTEGER}
х := а + 2; {Переполнение при вычислении этого выражения!}
у := LongInt(а)+2; {Переполнения нет после приведения переменной к более мощному типу}
WriteLn(x:10:0, у:10:0) 
end.
В результате прогона программы получим
-32767      32769
Логический тип. Значениями логического типа может быть одна из предварительно объявленных констант FALSE (ложь) или TRUE (истина). Для них справедливы правила:
ord(False) = 0; 
ord(True) = 1; 
False < True; 
succ(False)= True; 
pred(True) = False.
Поскольку логический тип относится к порядковым типам, его можно использовать в операторе счетного типа, например:
var
1 : Boolean; 
begin
for 1 := False to True do ....
Символьный тип. Значением символьного типа является множество всех символов ПК. Каждому символу приписывается целое число в диапазоне 0...255. Это число служит кодом внутреннего представления символа, его возвращает функция ORD.
Для кодировки используется код ASCII (American Standard Code for Information Interchange - американский стандартный код для обмена информацией). Это 7-битный код, т.е. с его помощью можно закодировать лишь 128 символов в диапазоне от 0 до 127. В то же время в 8-битном байте, отведенном для хранения символа в Турбо Паскале, можно закодировать в два раза больше символов в диапазоне от 0 до 255. Первая половина символов ПК с кодами 0...127 соответствует стандарту ASCII (табл. 4.3). Вторая половина символов с кодами 128...255 не ограничена жесткими рамками стандарта и может меняться на ПК разных типов (в прил.2 приведены некоторые распространенные варианты кодировки этих символов).
Таблица 4.3

Символы с кодами 0...31 относятся к служебным кодам. Если эти коды используются в символьном тексте программы, они считаются пробелами. При использовании их в операциях ввода-вывода они могут иметь следующее самостоятельное значение:

К типу CHAR применимы операции отношения, а также встроенные функции: СНR(В) - функция типа CHAR; преобразует выражение В типа BYTE в символ и возвращает его своим значением;
UPCASE(CH) - функция типа CHAR; возвращает прописную букву, если СН -строчная латинская буква, в противном случае возвращает сам символ СН, например:
var
cl,c2: Char; 
begin
cl := UpCase('s') ; 
c2 := UpCase ('Ф') ;
WriteLn(cl,'  ',c2) 
end.
Так как функция UPCASE не обрабатывает кириллицу, в результате прогона этой
программы на экран будет выдано
S     ф
Перечисляемый тип. Перечисляемый тип задается перечислением тех значений, которые он может получать. Каждое значение именуется некоторым идентификатором и располагается в списке, обрамленном круглыми скобками, например:
typе
colors =(red, white, blue);
Применение перечисляемых типов делает программы нагляднее. Если, например, в программе используются данные, связанные с месяцами года, то такой фрагмент программы:
type
ТипМесяц=(янв,фев,мар,апр,май,июн,июл,авг,сен,окт,ноя,дек); 
var
месяц : ТипМесяц; 
begin
.......
if месяц = авг then WriteLn('Хорошо бы поехать к морю!');
.......
end.
был бы, согласитесь, очень наглядным. Увы! В Турбо Паскале нельзя использовать кириллицу в идентификаторах, поэтому мы вынуждены писать так:
type
TypeMonth=(jan,feb,mar,may,jun,jul,aug,sep,oct,nov,dec); 
var
month: TypeMonth; 
begin
.......
if month = aug then WriteLn('Хорошо бы поехать к морю!');
.......
end.

Соответствие между значениями перечисляемого типа и порядковыми номерами этих значений устанавливается порядком перечисления: первое значение в списке получает порядковый номер 0, второе - 1 и т.д. Максимальная мощность перечисляемого типа составляет 65536 значений, поэтому фактически перечисляемый тип задает некоторое подмножество целого типа WORD и может рассматриваться как компактное объявление сразу группы целочисленных констант со значениями О, 1 и т.д.
Использование перечисляемых типов повышает надежность программ благодаря возможности контроля тех значений, которые получают соответствующие переменные. Пусть, например, заданы такие перечисляемые типы:
type
colors = (black, red, white);
ordenal= (one, two, three);
days = (monday, tuesday, Wednesday);
С точки зрения мощности и внутреннего представления все три типа эквивалентны:
ord(black)=0, ..., ord(white)=2,
ord(one)=0, ...ord(three)=2,
ord(monday)=0, ...ord(Wednesday)=2.
Однако, если определены переменные
var
col :colors; num :ordenal;
day : days;
то допустимы операторы
col := black;
num := succ(two);
day := pred(tuesday);
но недопустимы
col := one; 
day := black;
Как уже упоминалось, между значениями перечисляемого типа и множеством целых чисел существует однозначное соответствие, задаваемое функцией ORD(X). В Турбо Паскале допускается и обратное преобразование: любое выражение типа WORD можно преобразовать в значение перечисляемого типа, если только значение целочисленного выражения не превышает мощное1™ перечисляемого типа. Такое преобразование достигается применением автоматически объявляемой функции с именем перечисляемого типа (см. п. 4.4). Например, для рассмотренного выше объявления типов эквивалентны следующие присваивания:
col := one;
col := colors(0);
Разумеется, присваивание
col := 0;
будет недопустимым.
Переменные любого перечисляемого типа можно объявлять без предварительного описания этого типа, например:
var
col: (black, white, green);
Тип-диапазон. Тип-диапазон есть подмножество своего базового типа, в качестве которого может выступать любой порядковый тип, кроме типа-диапазона. Тип-диапазон задается границами своих значений внутри базового типа:
<мин.знач.>..<макс.знач.>
Здесь <мин.знач. > - минимальное значение типа-диапазона; 
<макс.знач.> - максимальное его значение.
Например:
type
digit = '0'..'9'; 
dig2= 48..57;
Тип-диапазон необязательно описывать в разделе TYPE, а можно указывать непосредственно при объявлении переменной, например:
var
date : 1..31; 
month: 1. .12; 
Ichr : 'A'..'Z';.
При определении типа-диапазона нужно руководствоваться следующими правилами:

  • два символа «..» рассматриваются как один символ, поэтому между ними недопустимы пробелы;
  • левая граница диапазона не должна превышать его правую границу. Тип-диапазон наследует все свойства своего базового типа, но с ограничениями, связанными с его меньшей мощностью. В частности, если определена переменная

type
days = (mo,tu,we,th,fr,sa,su);
WeekEnd = sa .. su; 
var
w : WeekEnd; 
begin
.....
w := sa;
.....
end;
то ORD(W) вернет значение 5 , в то время как PRED(W) приведет к ошибке.
В стандартную библиотеку Турбо Паскаля включены две функции, поддерживающие работу с типами-диапазонами:
НIGН(Х) - возвращает максимальное значение типа-диапазона, к которому принадлежит переменная X;
LOW(X) -возвращает минимальное значение типа-диапазона.
Следующая короткая программа выведет на экран строку
-32768...32767
var 
k: Integer; 
begin
WriteLn(Low(k),'..',High(k))
end.
 
Вещественные типы
В отличие от порядковых типов, значения которых всегда сопоставляются с рядом целых чисел и, следовательно, представляются в ПК абсолютно точно, значения вещественных типов определяют произвольное число лишь с некоторой конечной точностью, зависящей от внутреннего формата вещественного числа.
Таблица 4.4

Как видно из табл.4.4, вещественное число в Турбо Паскале занимает от 6 до 10 смежных байт и имеет следующую структуру в памяти ПК:

  Здесь s - знаковый разряд числа; е - экспоненциальная часть; содержит двоичный порядок; m - мантисса числа.
Мантисса m имеет длину от 23 (для SINGLE) до 63 (для EXTENDED) двоичных разрядов, что и обеспечивает точность 7.. .8 для SINGLE и 19.. .20 для EXTENDED десятичных цифр. Десятичная точка (запятая) подразумевается перед левым (старшим) разрядом мантиссы, но при действиях с числом ее положение сдвигается влево или вправо в соответствии с двоичным порядком числа, хранящимся в экспоненциальной части, поэтому действия над вещественными числами называют арифметикой с плавающей точкой (запятой).
Как видим, Турбо Паскаль характеризуется богатой гаммой вещественных типов, однако доступ к типам SINGLE, DOUBLE и EXTENDED возможен только при особых режимах компиляции. Дело в том, что эти типы рассчитаны на аппаратную поддержку арифметики с плавающей точкой и для их эффективного использования в состав ПК должен входить арифметический сопроцессор. Компилятор Турбо Паскаля позволяет создавать программы, работающие на любых ПК (с сопроцессором или без него) и использующие любые вещественные типы. Необходимая для этого настройка компилятора описана в прил.1. В процессе запуска Турбо Паскаль проверяет состав аппаратных средств и выявляет наличие или отсутствие сопроцессора.
В некоторых случаях бывает необходимо отключить автоконтроль. Для этого перед запуском Турбо Паскаля следует дать такую команду ДОС:
set 87=N
команда
set 87=Y
напротив, включает автоконтроль - эта команда активна по умолчанию.
Отметим, что арифметический сопроцессор всегда обрабатывает числа в формате EXTENDED, а три других вещественных типа в этом случае получаются простым усечением результатов до нужных размеров и применяются в основном для экономии памяти.
Например, если «машинное эпсилон» (см. пример 2.6 в гл.2) вычисляется с помощью такой программы:
{$N+,E+} 
type
RealType = Real: 
var
epsilon : RealType; 
begin
epsilon := 1;
while 1+epsilon/2 > 1 do 
epsilon := epsilon/2;
WriteLn(epsilon) 
end.
то независимо от объявления типа REALTYPE (он может быть SINGLE, REAL, DOUBLE или EXTENDED) на печать будет выдан результат
1.08420217248550Е-0019
что соответствует типу EXTENDED. Происходит это по той причине, что все операнды вещественного выражения 1 + epsilon/2 в операторе WHILE, перед вычислением автоматически преобразуются к типу EXTENDED. Чтобы получить правильный результат (например, для типа REALTYPE = REAL он будет 9 . 09494701772928Е-0013), программу необходимо изменить следующим образом:
{$N+,E+} 
type
RealType= Real;
var
epsilon, epsl:RealType;
begin
epsilon := 1; 
repeat
epsilon := epsilon/2; 
epsl := 1 + epsilon 
until epsl = 1; 
WriteLn(2*epsilon) 
end.
Следует учесть, что тип REAL оптимизирован для работы без сопроцессора. Если Ваш ПК оснащен сопроцессором, использование типа REAL приведет к дополнительным затратам времени на преобразование REAL к EXTENDED. Поэтому никогда не используйте REAL на ПК с сопроцессором, т.к. дополнительные затраты времени на преобразование типов могут свести на нет все преимущества сопроцессора. При разработке программ, критичных ко времени счета, следует заменять его типами SINGLE или DOUBLE: по сравнению с типом REAL скорость вычислений на машинах с сопроцессором в этом случае увеличивается в 2...3 раза. Если в ПК нет арифметического сопроцессора, скорость обработки данных всех вещественных типов приблизительно одинакова.
Особое положение в Турбо Паскале занимает тип СОМР, который трактуется как вещественное число без экспоненциальной и дробной частей. Фактически, СОМР - это «большое» целое число со знаком, сохраняющее 19...20 значащих десятичных цифр (во внутреннем представлении СОМР занимает 8 смежных байт). В то же время в выражениях СОМР полностью совместим с любыми другими вещественными типами: над ним определены все вещественные операции, он может использоваться как аргумент математических функций и т.д. Наиболее подходящей областью применения типа СОМР являются бухгалтерские расчеты: денежные суммы выражаются в копейках или центах и действия над ними сводятся к операциям с достаточно длинными целыми числами.
Для работы с вещественными данными могут использоваться встроенные математические функции, представленные в табл. 2.5. В этой таблице REAL означает любой вещественный тип, INTEGER - любой целый тип.
Таблица 4.5

 
Сьруктурированные типы
Любой из структурированных типов (а в Турбо Паскале их четыре: массивы, запиcи, множества и файлы) характеризуется множественностью образующих этот тип элементов, т.е. переменная или константа структурированного типа всегда имеет неcколько компонентов. Каждый компонент, в свою очередь, может принадлежать cтруктурированному типу, что позволяет говорить о возможной вложенности типов. В Турбо Паскале допускается произвольная глубина вложенности типов, однако суммарная длина любого из них во внутреннем представлении не должна превышать -5520 байт.
В целях совместимости со стандартным Паскалем в Турбо Паскале разрешается черед описанием структурированного типа ставить зарезервированное слово PACKED, предписывающее компилятору, по возможности, экономить память, отводимую под объекты структурированного типа; но компилятор фактически игнорирует это указание: «упаковка» данных в Турбо Паскале осуществляется автоматически зезде, где это возможно.
 
Массивы
Массивы в Турбо Паскале во многом схожи с аналогичными типами данных в других языках программирования. Отличительная особенность массивов заключается в том, что все их компоненты суть данные одного типа (возможно, структурированного). Эти компоненты можно легко упорядочить и обеспечить доступ к любому из них простым указанием его порядкового номера, например:
type
digit =array[0..9] of Char ;
matrix =array[Byte] of Single; 
var
m : matrix;
d : digit;
i : Integer; 
begin
.......
m[17] := ord(d[i-l])/10;
.......
end. 
Описание типа массива задается следующим образом:
<имя типа> = ARRAY [ <сп.инд.типов> ] OF <тип>
Здесь <имя типа> - правильный идентификатор;
ARRAY, OF - зарезервированные слова (массив, из);
<сп.тд.типов> - список из одного или нескольких индексных типов, разделенных запятыми; квадратные скобки, обрамляющие список, - требование синтаксиса;
<тип> - любой тип Турбо Паскаля.
В качестве индексных типов в Турбо Паскале можно использовать любые порядковые типы, кроме LONGINT и типов-диапазонов с базовым типом LONGINT.
Определить переменную как массив можно и непосредственно при описании этой переменной, без предварительного описания типа массива, например:
var
а,b : array [1..10] of Real;
Обычно в качестве индексного типа используется тип-диапазон, в котором задаются границы изменения индексов. Так как тип <тип>, идущий за словом OF, - любой тип Турбо Паскаля, то он может быть, в частности, и другим массивом, например:
type
mat = array [0..5] of array [-2..2] of array [Char] of Byte;
Такую запись можно заменить более компактной:
type
mat = array [0..5,-2..2,Char] of Byte;
Глубина вложенности структурированных типов вообще, а следовательно, и массивов - произвольная, поэтому количество элементов в списке индексных типов (размерность массива) не ограничено, однако суммарная длина внутреннего представления любого массива, как уже говорилось, не может быть больше 65520 байт. В памяти ПК элементы массива следуют друг за другом так, что при переходе от младших адресов к старшим наиболее быстро меняется самый правый индекс массива. Если, например,
var
а : array[1. .2,1. .2] of Byte;
begin
a [1,1]:=1; 
a [2,1]:=2; 
a [l, 2]:=3; 
a [2,2]:=4; 
end.
то в памяти последовательно друг за другом будут расположены байты со значениями 1,3,2, 4 . Это обстоятельство может оказаться важным при использовании стандартной процедуры копирования памяти MOVE.
В Турбо Паскале можно одним оператором присваивания передать все элементы одного массива другому массиву того же типа, например:
var
а,b:array [1..5] of Single;
begin
.....
a := b;
.....
end.
После этого присваивания все пять элементов массива А получат те же значения, что и в массиве В. Однако над массивами не определены операции отношения. Нельзя, например, записать
if a = b then ... 
Сравнить два массива можно поэлементно, например:
var
a,b:array [1..5] of Single;
eq:Boolean;
i:Byte;
begin
.....
eq := True; for i := 1 to 5 do 
if a[i] <> b[i] then
eq := False; 
if eq then
.....
end.
 
Записи
Запись - это структура данных, состоящая из фиксированного числа компонентов, называемых полями записи. В отличие от массива, компоненты (поля) записи могут быть различного типа. Чтобы можно было ссылаться на тот или иной компонент записи, поля именуются.
Структура объявления типа записи такова:
<имя типа> = RECORD <сп.полей> END
Здесь <имя типа> - правильный идентификатор;
RECORD, END - зарезервированные слова (запись,конец); <сп.полей> - список полей; представляет собой последовательность разделов записи, между которыми ставится точка с запятой.
Каждый раздел записи состоит из одного или нескольких идентификаторов полей, отделяемых друг от друга запятыми. За идентификатором (идентификаторами) ставится двоеточие и описание типа поля (полей), например:
type
BirthDay = record 
day,month : Byte; 
year : Word 
end; 
var
a,b : Birthday;
.......
В этом примере тип BIRTHDAY (день рождения) есть запись с полями DAY, MONTH и YEAR (день, месяц и год); переменные А и В содержат записи типа BIRTHDAY.
Как и в массиве, значения переменных типа записи можно присваивать другим переменным того же типа, например
а := b;
К каждому из компонентов записи можно получить доступ, если использовать составное имя, т.е. указать имя переменной, затем точку и имя поля:
а.day := 27; 
b.year := 1939;
Для вложенных полей приходится продолжать уточнения:
type
BirthDay = record 
day,month: Byte; 
year : Word 
end; 
var
с : record
name : String; 
bd : BirthDay 
end; 
begin
.....
if c.bd.year = 1939 then ... 
end.
Чтобы упростить доступ к полям записи, используется оператор присоединения WITH:
WITH <переменная> DO <оператор>
Здесь WITH, DO - ключевые слова (с, делать);
<переменная> - имя переменной типа запись, за которым, возможно, следует список вложенных полей; <оператор> - любой оператор Турбо Паскаля.
Например:
with c.bd do month := 9;
Это эквивалентно
with с do with bd do month := 9;
или
with c,bd do month := 9;
или
c.bd.month := 9;
Турбо Паскаль разрешает использовать записи с так называемыми вариантными полями, например:
type
Forma = record 
Name: String; 
case Byte of
0: (Birthplace: String [40]); 
1: (Country : String [20]; 
EntryPort : String [20]; 
EntryDate : 1. . 31; 
ExitDate : 1..31) 
end;
В этом примере тип FORMA определяет запись с одним фиксированным полем NAME и вариантной частью, которая задается предложением CASE... OF. Вариантная часть состоит из нескольких вариантов (в примере - из двух вариантов: 0 и 1). Каждый вариант определяется константой выбора, за которой следует двоеточие и список полей, заключенный в круглые скобки. В любой записи может быть только одна вариантная часть, и, если она есть, она должна располагаться за всеми фиксированными полями.
Замечательной особенностью вариантной части является то обстоятельство, что все заданные в ней варианты «накладываются» друг на друга, т.е. каждому из них выделяется одна и та же область памяти. Это открывает дополнительные возможности преобразования типов, например:
var
mem4 : record case Byte of
0 : (by : array'[0..3] of Byte);
1 : (wo : array [0..1] of Word);
2 : (lo : longint); 
end;
В этом примере запись МЕМ4 имеет три варианта, каждый из которых занимает в памяти один и тот же участок из 4 байт. В зависимости от того, к какому полю записи мы обращаемся в программе, этот участок может рассматриваться как массив из 4 байт (поле ВТ), массив из двух целых типа WORD (поле WO) или, наконец, как одно целое число типа LONGINT (поле LO). Например, этой записи можно сначала присвоить значение как длинному целому, а затем проанализировать результат по байтам или словам:
var
х : Word; 
xb: Byte; 
x1: Longint; 
begin
.....
with m do 
begin
lo := trunc(2*pi*x); 
if wo[1] = 0 
then if by[l] = 0 then
xb := x[0] 
else
x := wo[0] 
else
x1 := lo 
end;
.....
end.
Предложение CASE... OF, открывающее вариантную часть, внешне похоже на соответствующий оператор выбора, но на самом деле лишь играет роль своеобразного служебного слова, обозначающего начало вариантной части. Именно поэтому в конце вариантной части не следует ставить END как пару к CASE... OF. (Поскольку вариантная часть - всегда последняя в записи, за ней все же стоит END, но лишь как пара к RECORD). Ключ выбора в предложении CASE... OF фактически игнорируется компилятором: единственное требование, предъявляемое к нему Турбо Паскалем, состоит в том, чтобы ключ определял некоторый стандартный или предварительно объявленный порядковый тип. Причем сам этот тип никак не влияет ни на количество следующих ниже вариантных полей, ни даже на характер констант выбора. В стандартном Паскале в качестве ключа выбора необходимо указывать некоторую переменную порядкового типа, причем в исполняемой части программы можно присваивать значение этой переменной и таким образом влиять на выбор полей. В Турбо Паскале также можно в поле ключа выбора указывать переменную порядкового типа и даже присваивать ей в программе значение, что однако не влияет на выбор поля: значения констант выбора в Турбо Паскале могут быть произвольными, в том числе повторяющимися, например:
type
reel = record
a : Byte;
b : Word; 
end; 
rec2 = record
с : longint;
case x : Byte of
1 : (d : Word);
2 : (e : record
case Boolean of
3 :( freel);
3 :( g Single);
'3':( с Word);
end)
end; 
var
r : rec2; 
begin
r.x := 255;

if r.e.g = 0 then
WriteLn('O.K. ')  
else
WriteLn(r.e.g) 
end.
В этом примере предложение
case Boolean of
в записи, определяемой в поле Е, объявляет ключом выбора логический тип, который, как известно, имеет лишь два значения - TRUE и FALSE. Константы же выбора следующих далее вариантов не только содержат совершенно не свойственные этому типу значения, но и две из них повторяются, а общее количество вариантов - три, а не два, как следовало бы ожидать.
Имена полей должны быть уникальными в пределах той записи, где они объявлены, однако, если записи содержат поля-записи, т.е. вложены одна в другую, имена могут повторяться на разных уровнях вложенности (см. поле С в последнем примере).
 
Множества
Множества - это наборы однотипных логически связанных друг с другом объектов. Характер связей между объектами лишь подразумевается программистом и никак не контролируется Турбо Паскалем. Количество элементов, входящих в множество, может меняться в пределах от 0 до 256 (множество, не содержащее элементов, называется пустым). Именно непостоянством количества своих элементов множества отличаются от массивов и записей.
Два множества считаются эквивалентными тогда и только тогда, когда все их элементы одинаковы, причем порядок следования элементов в множестве безразличен. Если все элементы одного множества входят также и в другое, говорят о включении первого множества во второе. Пустое множество включается в любое другое.
Пример определения и задания множеств:
type
digitChar= set of '0'..'9';
digit = set of 0. .9; 
var
sl,s2,s3 :digitChar;
s4,s5,s6 :digit;
begin
.....
s1:=['1','2','3'];
s2:=['3','2','1'];
s3:=['2','3'];
s4:=[0..3,6];
s5:=[4,5];
s6:=[3..9];
.....
end.
В этом примере множества S1 и S2 эквивалентны, а множество S3 включено в S2 , но не эквивалентно ему.
Описание типа множества имеет вид:
<имя типа> = SET OF <баз.тип>
Здесь <имя типа> - правильный идентификатор;
SET, OF - зарезервированные слова (множество, из);
<баз.тип> - базовый тип элементов множества, в качестве которого может
использоваться любой порядковый тип, кроме WORD, INTEGER, LONGINT.
Для задания множества используется так называемый конструктор множества: список спецификаций элементов множества, отделяемых друг от друга запятыми; список обрамляется квадратными скобками (см. предыдущий пример). Спецификациями элементов могут быть константы или выражения базового типа, а также - тип-диапазон того же базового типа.
Над множествами определены следующие операции:
*  пересечение множеств; результат содержит элементы, общие для обоих множеств; например, S4*S6         содержит [3], S4*S5 - пустое множество (см. выше);
+ объединение множеств; результат содержит элементы первого множества, дополненные недостающими  элементами из второго множества:
  S4+S5 содержит [0,1,2,3,4,5,6]; 
  S5+S6 содержит [3,4,5,6,7,8,9];
- разность множеств; результат содержит элементы из первого множества, которые не принадлежат второму:
  S6-S5 содержит [3,6,7,8,9]; 
  S4-S5 содержит [0,1,2,3,6];
= проверка эквивалентности; возвращает TRUE, если оба множества эквивалентны;
<> проверка неэквивалентности; возвращает TRUE, если оба множества неэквивалентны;
<= проверка вхождения; возвращает TRUE, если первое множество включено во второе;
>= проверка вхождения; возвращает TRUE, если второе множество включено в первое;
IN проверка принадлежности; в этой бинарной операции первый элемент - выражение, а второй -   множество одного и того же типа; возвращает TRUE , если выражение имеет значение, принадлежащее множеству:
  3 in s6 возвращает TRUE; 
  2*2 in s1 возвращает FALSE.
Дополнительно к этим операциям можно использовать две процедуры. INCLUDE - включает новый элемент во множество. Обращение к процедуре:
INCLUDE (S,I)
Здесь S - множество, состоящее из элементов базового типа TSetBase;
          I - элемент типа TSetBase, который необходимо включить во множество.
EXCLUDE - исключает элемент из множества. Обращение:
EXCLUDE(S,I)
Параметры обращения - такие же, как у процедуры INCLUDE.
В отличие от операций + и -, реализующих аналогичные действия над двумя множествами, процедуры оптимизированы для работы с одиночными элементами множеcтва и поэтому отличаются высокой скоростью выполнения.
В примере 4.1, иллюстрирующем приемы работы с множествами, реализуется алгоритм выделения из первой сотни натуральных чисел всех простых чисел. В его основе лежит прием, известный под названием «решето Эратосфена». В соответствии с этим алгоритмом вначале формируется множество BEGINSET, состоящее из всех целых чисел в диапазоне от 2 до N. В множество PRIMERSET (оно будет содержать искомые простые числа) помещается 1. Затем циклически повторяются следующие действия:

  • взять из BEGINSET первое входящее в него число NEXT и поместить его в PRIMERSET;
  • удалить из BEGINSET число NEXT и все другие числа, кратные ему, т.е.2*NEXT, 3*NEXT и т.д. 

Цикл повторяется до тех пор, пока множество BEGINSET не станет пустым.
Эту программу нельзя использовать для произвольного N, так как в любом множестве не может быть больше 256 элементов.
Пример 4.1
Program Primer_numbers_detect;
{Выделение всех простых чисел из первых N целых}
const
N = 255; {Количество элементов исходного множества}
type
SetOfNumber = set of 1..N;
var
n1,next,i : Word; {Вспомогательные переменные} 
BeginSet, {Исходное множество} 
PrimerSet : SetOfNumber; {Множество простых чисел} .
begin
BeginSet := [2. .N] ; {Создаем исходное множество}
PrimerSet:= [1]; {Первое простое число} 
next:= 2; {Следующее простое число}
while BeginSet <> [] do {Начало основного цикла} 
begin
n1 := next;{n1-число,кратное очередному простому (next)} 
{Цикл удаления из исходного множества непростых чисел:} 
while n1 <= N do 
begin
Exclude(BeginSet,nl);
n1 := n1+next {Следующее кратное} 
end; {Конецциклаудаления} 
Include(PrimerSet,next);
{Получаем следующее простое, которое есть первое невычеркнутое из исходного множества} 
repeat
inc(next)
until (next in BeginSet) or (next > N) 
end; {Конец основного цикла} 
{Выводим результат:} 
for i := 1 to N do
if i in PrimerSet then Write(i:8); 
WriteLn 
END.
Перед тем как закончить рассмотрение множеств полезно провести небольшой эксперимент. Измените описание типа SETOFNUMBER следующим образом:
type
SetOf Number = set of 1. . 1 ;
и еще раз запустите программу из предыдущего примера. На экран будет выведено
1   3   5   7
Множества BeginSet и PrimerSet состоят теперь из одного элемента, а программа сумела поместить в них не менее семи! Секрет этого прост: внутреннее устройство множества таково, что каждому его элементу ставится в соответствие один двоичный разряд (один бит); если элемент включен во множество, соответствующий разряд имеет значение 1, в противном случае - 0. Минимальной единицей памяти является один байт, содержащий 8 бит. Компилятор выделил множествам по одному байту, в результате мощность каждого из них стала равна 8 элементов. Максимальная мощность множества - 256 элементов. Для таких множеств компилятор выделяет по 16 смежных байт.
И еще один эксперимент: измените диапазон базового типа на 1.256. Хотя мощность этого типа составляет 256 элементов, при попытке компиляции программы компилятор сообщит:
Error 23: Set base type out of range.
(Ошибка 23: Базовый тип множества выходит за допустимые границы.)
Компилятор разрешает использовать в качестве базового типа целочисленный тип-диапазон с минимальной границей 0 и максимальной 255 или любой перечисляемый тип не более чем с 256 элементами (максимальная мощность перечисляемого типа -5536 элементов).
 
Строки
Тип STRING (строка) в Турбо Паскале широко используется для обработки текстов. Он во многом похож на одномерный массив символов ARRAY[O..N] OF CHAR, однако, в отличие от последнего, количество символов в строке-переменной может меняться от 0 до N, где N - максимальное количество символов в строке. Значение N определяется объявлением типа STRING [N] и может быть любой константой порядкового типа, но не больше 255 . Турбо Паскаль разрешает не указывать N, в этом случае длина строки принимается максимально возможной, а именно N=255 .
Строка в Турбо Паскале трактуется как цепочка символов. К любому символу в строке можно обратиться точно так же, как к элементу одномерного массива ARRAY [0..N] OF CHAR, например:
var 
st : String; 
begin
.....
if st[5] = 'A' then... 
end.
Самый первый байт в строке имеет индекс 0 и содержит текущую длину строки, первый значащий символ строки занимает второй байт и имеет индекс 1. Над длиной троки можно осуществлять необходимые действия и таким способом изменять длину. Например, удалить из строки все ведомые пробелы можно следующим образом:
var
st : String; 
i : Byte;
begin
i := ord(st [0] ) ; {i - текущаядлинастроки
while (i <> 0) and (st[i] = ' ') do 
begin 
dec(i);
st[0] := chr(i) 
end;
.....
end.
Значение ORD(st[0]) , т.е. текущую длину строки, можно получить и с помощью функции LENGTH(st), например:
while (Length(st)<>0) and (st[Length(st)]=' ') do 
st[0] := chr(Length(st)-1)
К строкам можно применять операцию «+» - сцепление, например:
st := 'а1 + 'b'; 
st := st + 'с'; {st содержит "abc"}
Если длина сцепленной строки превысит максимально допустимую длину N, то «лишние» символы отбрасываются. Следующая программа, например, напечатает символ 1:
var
st: String [1] ; 
begin
St:='123'; 
WriteLn(st) 
end.
Все остальные действия над строками и символами реализуются с помощью описываемых ниже стандартных процедур и функций.
CONCAT(S1 [,S2, ... , SN] ) - функция типа STRING; возвращает строку, представляющую собой сцепление строк-параметров SI, S2, ..., SN.
COPY(ST, INDEX, COUNT) - функция типа STRING; копирует из строки ST COUNT символов, начиная с символа с номером INDEX.
DELETE (ST, INDEX, COUNT) - процедура; удаляет СОUNT символов из строки ST, начиная с символа с номером INDEX.
INSERT (SUBST, ST, INDEX) - процедура; вставляет подстроку SUBST в строку ST, начиная с символа с номером INDEX.
LENGTH (ST) - функция типа INTEGER; возвращает длину строки ST.
POS (SUBST, ST) - функция типа INTEGER; отыскивает в строке STпервое вхождение подстроки SUBST и возвращает номер позиции, с которой она начинается; если подстрока не найдена, возвращается ноль.
STR(X [; WIDTH [: DECIMALS] ], ST) - процедура; преобразует число X любого вещественного или целого типов в строку символов ST так, как это делает процедура WRITELN перед выводом; параметры WIDTH и DECIMALS, если они присутствуют, задают формат преобразования: WIDTH определяет общую ширину поля, выделенного под соответствующее символьное представление вещественного или целого числа X, a DECIMALS - количество символов в дробной части (этот параметр имеет смысл только в том случае, когда Х- вещественное число).
VAL(ST, X, CODE) - процедура; преобразует строку символов ST во внутреннее представление целой или вещественной переменной X, которое определяется типом этой переменной; параметр CODE содержит ноль, если преобразование прошло успешно, и тогда в X помещается результат преобразований, в противном случае он содержит номер позиции в строке ST, где обнаружен ошибочный символ, и в этом случае содержимое Х не меняется; в строке ST могут быть ведущие пробелы, однако ведомые пробелы недопустимы; например, обращение val (' 123',k,i) пройдет успешно: k получит значений 123, в i будет помещен 0, в то время как обращение val (' 123 ' , k, i) будет ошибочным: значение k не изменится, a i будет содержать 4.
UPCASE (СН) - функция типа CHAR; возвращает для символьного выражения СН, которое должно представлять собой строчную латинскую букву, соответствующую заглавную букву; если значением СН является любой другой символ (в том числе строчная буква русского алфавита), функция возвращает его без преобразования.
Примеры:
var
х : Real; 
у : Integer; 
st,st1: String; 
begin
st := concat('12','345'); {строка st содержит 12345} 
st1 := copy(st,3,Length(st)-2); {st1 содержит 345}
insert('-',st1,2); {строка st1 содержит 3-45} 
delete(st,pos('2',at),3); {строка st содержит 15} 
str(pi:6:2,st); {строка st содержит 3.14} 
val(''3,1415' ,x,y) ; {у содержит 2, х остался без изменения}
end.
Операции отношения =, о, >, <, >=, <= выполняются над двумя строками посимвольно, слева направо с учетом внутренней кодировки символов (см. табл.4.1 и прил.2). Если одна строка меньше другой по длине, недостающие символы короткой строки заменяются значением СНR(0) .
Следующие операции отношения дадут значение TRUE:
'''' < ' . '
'А' > '1'
'Turbo' <' Turbo Pascal'
'Паскаль' >'Turbo Pascal'
 
Совместимость и преобразование типов
Как уже неоднократно отмечалось, Турбо Паскаль - это типизированный язык. Он построен на основе строгого соблюдения концепции типов, в соответствии с которой все применяемые в языке операции определены только над операндами совместимых типов. При обсуждении операций над вещественными данными мы уже затрагивали проблему совместимости вещественных и целых типов. Аналогичные проблемы возникают при операциях над строками разной длины, строками и символами и т.д. Ниже приводится более полное определение совместимости типов.
Два типа считаются совместимыми, если:

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

Совместимость типов приобретает особое значение в операторах присваивания. Пусть T1 - тип переменной, а Т2 - тип выражения, т.е. выполняется присваивание T1 := T2. Это присваивание возможно в следующих случаях:

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

В программе данные одного типа могут преобразовываться в данные другого типа. Такое преобразование может быть явным или неявным.
При явном преобразовании типов используются вызовы специальных функций преобразования, аргументы которых принадлежат одному типу, а значение - другому. Таковыми являются уже рассмотренные функции ORD, TRUNC, ROUND, CHR. В гл. 6 описывается функция PTR, преобразующая четырехбайтный целочисленный аргумент к типу-указателю.
В Турбо Паскале может использоваться и более общий механизм преобразования типов, согласно которому преобразование достигается применением идентификатора имени) стандартного типа или типа, определенного пользователем, как идентификатора функции преобразования к выражению преобразуемого типа (так называемое автоопределенное преобразование типов). Например, допустимы следующие вызовы функций:
type
МуТуре = (а, Ь, с, d);
.....
МуТуре (2) 
Integer ('D') 
pointer (longint(a)+ $FF)
Char (127 mod c) 
Byte (k)
При автоопределенном преобразовании типа выражения может произойти изменение длины его внутреннего представления (длина может увеличиться или уменьшиться).
В Турбо Паскале определен еще один явный способ преобразования данных: в ту область памяти, которую занимает переменная некоторого типа, можно поместить значение выражения другого типа, если только длина внутреннего представления вновь размещаемого значения в точности равна длине внутреннего представления переменной. С этой целью вновь используется автоопределенная функция преобразования типов, но уже в левой части оператора присваивания:
type
byt = array [1..2] of Byte;
int = array [1..2] of Integer;
rec = record
x, у : Integer 
end; 
var
vbyt : byt;
vint : int;
vrec : rec; 
begin
byt(vint[1])[2] := 0;
int(vrec)[1] := 256 
end.
Неявное преобразование типов возможно только в двух случаях:

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

Совмещение данных в памяти может произойти при использовании записей с вариантными полями (см. 4.2.2), типизированных указателей, содержащих одинаковый адрес (см. гл. 6), а также при явном размещении данных разного типа по одному и тому же абсолютному адресу. Для размещения переменной по нужному абсолютному адресу она описывается с последующей стандартной директивой ABSOLUTE, за которой помещается либо абсолютный адрес, либо идентификатор ранее определенной переменной. Абсолютный адрес указывается парой чисел типа WORD, разделенных двоеточием; первое число трактуется как сегмент, второе - как смещение адреса (см. гл. 6). Например:
b : Byte absolute $0000:$0055; w : Longlnt absolute 128:0;
Если за словом ABSOLUTE указан идентификатор ранее определенной переменной, то происходит совмещение в памяти данных разного типа, причем первые байты внутреннего представления этих данных будут располагаться по одному и тому же абсолютному адресу, например:
var
х : Real;
у : array [1..3] of Integer absolute x;
В этом примере переменные X и Y будут размещены, начиная с одного и того же абсолютного адреса. Таким образом, одну и ту же область памяти длиной 6 байт, а следовательно, и размещенные в этой области данные теперь можно рассматривать как данные либо типа REAL, либо как массив из трех данных типа INTEGER. Например, следующая программа выдаст на экран содержимое первых двух байт внутреннего представления вещественного числа п = 3.1415 в виде целого числа:
var
х : Real; у : array[1..3] of Integer absolute x;
begin
х := pi; WriteLn(y[1]) 
end.
На экран будет выдан результат 8578.
Неявные преобразования типов могут служить источником трудно обнаруживаемых ошибок в программе, поэтому везде, где это возможно, следует избегать их.

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г. Яндекс.Метрика