7.2. Тип «запись» (Record) и оператор присоединения With

We use cookies. Read the Privacy and Cookie Policy

Для компактного представления комбинаций разнотипных данных их можно объединять в структуры-записи. Каждая запись состоит из объявленного числа полей. Тип «запись» определяется конструкцией

RECORD

Поле1 : ТипПоля1;

Поле2 : ТипПоля2;

...

ПолеN : ТипПоляN

END;

Если тип нескольких полей совпадает, то имена полей могут быть просто перечислены, например:

| TYPE

| PointRecType = RECORD

| x,y : Integer

| END;

После объявления в программе переменной типа «запись»

| VAR

| Point : PointRecType;

к каждому ее полю можно обратиться, указав сначала идентификатор переменной-записи, а затем через точку — имя поля: Point.x и Point.y — значения полей записи (но просто Point — уже комбинация двух значений).

- 137 -

Независимо от количества объявленных переменных данного типа, поля каждой из них будут называться одинаково, как они названы в описании типа. Поскольку имена полей «скрыты» внутри типа, они могут дублировать «внешние» переменные и поля в других описаниях записей, например:

| TYPE

| PointRecType = RECORD

| X,Y : Integer

| END;

| ColorPointRecType = RECORD

| X,Y : Integer; Color:Word

| END;

| VAR

| X, Y : Integer;

| Point : PointRecType;

| ColorPoint : ColorPointRecType;

В программе X, PointX и ColorPoint.X — совершенно разные значения. Поле записи может иметь практически любой тип (массив, другая запись, множество). Доступ к вложенным элементам таких структур осуществляется по тем же правилам, что и обычно:

Переменная_СложнаяЗапись.ЕеПоле_Запись.ПолеПоляЗаписи

или

Переменная_С_полем_массивом.ПолеМассив[i]

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

2 ( это X, тип Integer) + 2 ( Y, тип Integer) + 2 (для Color типа Word).

Запись может иметь вариантную часть. Это означает, что можно задать в пределах одного типа несколько различных структур. Непосредственный выбор структуры будет определяться контекстом или каким-либо сигнальным значением. Вариантные поля указываются после того, как перечислены поля фиксированные. Вариантные поля и оформляются особым образом. Рассмотрим пример описания типа VRecType — записи с вариантами.

| TYPE

| VRecType = RECORD { тип записи с вариантами }

| Number : Byte; { номер измерения длины }

| case Measure : Char of { признак единицы длины }

| 'д','Д' (inches : Word); {длина в дюймах}

| 'с','С' (cantimeters : LongInt); { длина в см }

| '?' (Comment1, Comment2 : String[16]) { тексты }

| END;

- 138 -

В данном случае в записи имеется обычное фиксированное поле Number. Другое фиксированное поле — Measure. Оно всегда присутствует в структуре записи, но параллельно с этим выполняет роль селектора (иногда его называют полем тега от английского «Tag Field»). Поле-селектор обрамляется словами CASE и OF, и за ним следует перечисление вариантов третьего поля, взятых в круглые скобки. Какой именно вариант поля будет принят при работе с записью, обозначается содержимым селектора (Measure в приведенном примере). Значения селектора, указывающие на тот или иной вариант, записываются перед соответствующими вариантами (аналогично тому, как это происходит в операторе выбора CASE). Пусть объявлена переменная VRec типа VRecType. В зависимости от содержимого поля-селектора Measure будут корректно работать поля записи, показанные в табл. 7.1.

Таблица 7.1

Measure =

'д' или 'Д'

'с' или 'С'

'?'

прочие

Поля Vrec, к которым обращение будет корректным.

Number

Measure

inches

Number

Measure

cantimeters

Number

Measure

Comment1

Comment2

Number

Measure

Важно понимать, что в любое время доступны поля только одного из всех возможных вариантов, описанных в типе (или ни одно из них). Все варианты располагаются в одном и том же месте памяти при хранении, а размер этого места определяется самым объемным из вариантов. Так, запись типа VRecType будет храниться как 36 байт (1 байт — Number, 1 байт — Measure и 2*(16+1) байт на самый длинный вариант — с типом поля String[16]) независимо от выбора варианта.

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

| VAR

| VRec : VRecType;

| BEGIN

| VRec.Measure := 'Д'; { выбираем дюймы }

| VRec.Inches := 32; { запишем 32 дюйма }

{ Теперь, не изменяя значения поля Comment1, опросим его: }

| WriteLn(VRec.Comment1);

| WriteLn(VRec.Comment1);

| END.

- 139 -

Результатом будет печать значения числа 32 в формате String. Подобные ошибки никак не диагностируются, и вся ответственность ложится на программиста. Хуже того, поле-селектор — не более чем указание, какое поле соответствует его значению. Можно, игнорируя поле-селектора, обращаться к любому из полей-вариантов. Но поскольку значение для всех вариантов одно, оно будет трактоваться по-разному согласно типу каждого поля.

Несмотря на подобные неприятности, использование записей с вариантами и полем-селектором иногда очень удобно (если, конечно, не ошибаться). Например, если есть набор изделий с параметрами в различных системах измерения, то его можно представить в программе как массив записей с типом, подобным VRecType. Корректно заполнив поле каждой записи в массиве, мы можем потом легко опрашивать их: сначала значение поля селектора, а затем в зависимости от его значения один из вариантов хранения длины или комментария, например для массива AVRec из 100 записей типа VRecType:

| for i:=1 to 100 do

| case AVRec[i].Measure of

| 'д','Д' :WriteLn('Длина в дюймах ', AVRec[i].inches);

| 'с','С' :WriteLn('Длина в см ', AVRec[i].cantimeters);

| '?' :WriteLn('Нет данных из-за', AVRec[i].Comment1);

| end; {case}

Если не нужен явный указатель на использование определенного варианта, можно обойтись без поля селектора, заменив его именем любого перечислимого типа. Нужно лишь, чтобы этот тип имел элементов не меньше, чем будет вариантов. Запись VRec может быть описана иным способом:

| TYPE

| VRecType = RECORD { тип записи с вариантами }

| Number : Byte; { номер измерения длины )

| case Byte of { признак единицы длины }

| 1 : (inches : Word); { длина в дюймах}

| 2 : (cantimeters : LongInt); { длина в см }

| 3 : (Comment1, Comment2 : String[16]) { тексты }

| END;

Поля-варианты по-прежнему разделяют общую область памяти, а имя поля, записанное в программе, определяет, в каком типе будут считаны данные, т.е., как и в предыдущем случае, можно считать значение, записанное ранее в другом формате. И это никак не будет продиагностировано. Значения констант перед вариантами в задании

- 140 -

типа — чистая условность. Они могут быть любыми, но только не повторяющимися. Обычно для двух вариантов в CASE вписывают тип Boolean (значения True и False), для большего числа вариантов — любой целочисленный тип.

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

Обычно записи с вариантами, но без поля-селектора используются для обеспечения разнотипного представления одних и тех же данных. Например;

| TYPE

| CharArrayType = Array [1..4] of Char;

| VAR

| V4 : RECORD

| case Boolean of

| True : ( С : CharArrayType );

| False : ( B1, B2, B3, B4 : Byte );

| END;

Размер переменной V4 — четыре байта (оба варианта равны). Обращение к V4.C — это обращение к массиву из четырех символов к V4.C[1] — к первому элементу этого массива. Но одновременно можно обратиться и к ASCII-кодам элементов V4.C[1], V4.C[2], .... V4.C[4], используя поля V4.B1, V4.B2 V4.B4.

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

WITH ИмяПеременной_Записи DO Оператор;

Внутри оператора (он может быть и составным) обращение к полям записи уже производится без указания идентификатора самой переменной:

| VAR

| DemoRec : RECORD X,Y : Integer END;

| ...

| WITH DemoRec DO

| BEGIN

| X:=0; Y:=120

| END; {with}

Внутри области действия оператора WITH могут указываться и

- 141 -

переменные, не имеющие отношения к записи. Но в этом случае надо следить, чтобы они не совпадали по написанию с полями записи (рис. 7.1).

| PROGRAM MAIN;

| VAR

| X, Y : Integer;

| RecXY : RECORD X,Y: Integer END;

| BEGIN

| X:=10; Y:=20; { значения переменных X и Y }

| WITH RecXY DO BEGIN { работаем с записью RecXY }

| X := 3.14*X; { Где какой X и Y ? }

| Y := 3.14*Y

| END; {with}

| ...

| END.

Рис. 7.1

На рис. 7.1 действия внутри оператора WITH проводятся только над полями записи RecXY. Чтобы сохранить оператор WITH и «развязать» имена X и Y, надо к переменным X и Y приписать так называемый квалификатор — имя программы или модуля (UNIT), в которой они объявлены (для этого программа должна иметь заголовок). Так, оператор присоединения с рис. 7.1 можно исправить следующим образом:

| WITH RecXY DO

| BEGIN

| X := 3.14*Main.X;

| Y := 3.14*Main.Y

| END;

и проблема исчезнет.

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

WITH ИмяЗаписи, Поле_3апись Do

BEGIN

Обращения к именам полей Поля_3аписи,

т.е. к тем, которым предшествовала конструкция

ИмяЗаписи.Поле_3апись.

END; {with}

- 142 -

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