logo search
Языки программирования

5.1. Записи

Значение типа запись (record) состоит из набора значений других типов, назы­ваемых компонентами (componentsAda), членами (membersС) или полями (fields —Pascal). При объявлении типа каждое поле получает имя и тип. Следу­ющее объявление в языке С описывает структуру с четырьмя компонентами: одним — типа строка, другим — заданным пользователем перечислением и двумя компонентами целого типа:

typedef enum {Black, Blue, Green, Red, White} Colors;

C

typedef struct {

char model[20];

Colors color;

int speed;

int fuel;

} Car_Data;

Аналогичное объявление в языке Ada таково:

type Colors is (Black, Blue, Green, Red, White);

Ada

type Car_Data is

record

Model: String(1..20);

Color: Colors:

Speed: Integer;

Fuel: Integer;

end record;

После того как определен тип записи, могут быть объявлены объекты (пере­менные и константы) этого типа. Между записями одного и того же типа до­пустимо присваивание:

C

Car_Data c1,c2;

с1 =с2;

а в Ada (но не в С) также можно проверить равенство значений этого типа:

С1, С2, СЗ: Car_Data;

Ada

if C1=C2then

С1 =СЗ;

end if;

Поскольку тип — это набор значений, можно было бы подумать, что всегда можно обозначить* значение записи. Удивительно, но этого вообще нельзя сделать; например, язык С допускает значения записи только при инициали­зации. В Ada, однако, можно сконструировать значение типа запись, называе­мое агрегатом (aggregate), просто задавая значение правильного типа для каж­дого поля. Связь значения с полем может осуществляться по позиции внутри записи или по имени поля:

Ada

if С1 = (-Peugeot-, Blue, 98, 23) then ...

С1 := (-Peugeot-, Red, C2.Speed, CS.Fuel);

C2 := (Model=>-Peugeot", Speed=>76,

Fuel=>46, Color=>White);

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

Ada


Ada С1.Model :=-Peugeot-;

--Забыли С1.Color

С1.Speed := C2.Speed;

С1.Fuel := CS.Fuel;

Можно выбрать отдельные поля записи, используя точку и имя поля:

C

с1. speed =c1.fuel*x;

Будучи выбранным, поле записи становится обычной переменной или значе­нием типа поля, и к нему применимы все операции, соответствующие этому типу.

Имена полей записи локализованы внутри определения типа и могут повторно использоваться в других определениях:

typedef struct {

float speed; /* Повторно используемое имя поля */

C

} Performance;

Performance p;

Car_Data с;

p.speed = (float) с.speed; /* To же самое имя, другое поле*/

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

Реализация

Значение записи представляется некоторым числом слов в памяти, достаточ­ным для того, чтобы вместить все поля. На рисунке 5.1 показано размещение записи Car_Data. Поля обычно располагаются в порядке их появления в опре­делении типа записи.

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

load R1.&C1 Адрес записи

load R2,20(R1) Загрузить второе поле

load R3,24(R1) Загрузить третье поле

Так как для поля иногда нужен объем памяти, не кратный размеру слова, компилятор может

«раздуть» запись так, чтобы каждое поле заведомо находи­лось на границе слова, поскольку доступ к не выровненному на границу сло­ву гораздо менее эффективен. На 16-разрядном компьютере такое определе­ние типа, как:

typedef struct {

C

char f 1; /* 1 байт, пропустить 1 байт */

int f2; /* 2 байта*/

char f3; /* 1 байт, пропустить 1 байт */

int f4; • /* 2 байта*/

};

может привести к выделению четырех слов для каждой записи таким образом, чтобы поля типа int были выровнены на границу слова, в то время как следу­ющие определения:

typedef struct { [с]

C

int f2; /* 2 байта*/

int f4; /* 2 байта*/

charfl ; /Мбайт*/

char f3; /* 1 байт */

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