Запись(record) – наиболее общий сложный тип данных. Переменная типа "запись" объединяет данные разного типа. В этом принципиальное отличие записи от массива - все элементы массива имеют один и тот же тип. Необходимость в таком типе данных вытекает из многих программистских задач. Скажем, нам нужно спроектировать базу данных для хранения информации о сотрудниках фирмы. Для каждого сотрудника надо запомнить его фамилию (текстовая строка), год рождения (целое число), семейное положение (два варианта: женат/замужем – холост/не замужем). Хранение всех этих данных в отдельных переменных типа STRING, WORD и BOOLEAN соответственно – прямой путь к путанице. Гораздо удобнее, когда все данные на одного сотрудника хранятся в одной переменной. Вот тут-то нам и понадобятся записи. Кстати, само название "запись" происходит оттого, что чаще всего переменные такого типа записываются в файлы данных.
Рассмотрим на примере тип данных "запись":
TYPE TDate = RECORD day: 1..31; month:1..12; year:1900..2100; END;
Представленный здесь тип данных Tdate предназначен для хранения календарных дат. Запись состоит из полей. Каждое поле имеет свое уникальное имя (day, month, year) и для каждого поля указывается его собственный тип данных. Для полей, в которых хранятся номера дней в месяце и месяцев в году, правильным будет применение типов-диапазонов, что позволит сразу выявлять ошибочные даты типа 32.13.2002.
А в этом примере создается структура данных для хранения координат точки:
TYPE TPoint = RECORD x:REAL; y: REAL; END;
Как видно, поля могут иметь и одинаковый тип – запись остается записью.
Тот факт, что каждое поле имеет сове уникальное имя, приводит к невозможности перебора полей записи в цикле. В отличие от массива, по полям записи нельзя пройтись в цикле – нужно знать имя каждого поля. Для обращения к полю по имени название поля отделяется от имени переменной-записи точкой:
TYPE TA=RECORD
x:REAL;
y:BYTE;
END; VAR A:TA; … A.x:=10.0; A.y:=5;
Полем записи, в свою очередь, может быть другая запись! Возможен следующий вариант:
TYPE TA1=RECORD
a:REAL;
b:BYTE;
END;
TA2=RECORD
c:TA1;
d:WORD;
END; VAR x:TA2; … Label1.Caption:=x.c.a;
У вложенных записейдля обращения к нужному полю может потребоваться несколько точек.
Еще более сложная структура – записи с вариантами. Внутри записи с вариантами создается несколько наборов полей и специальное поле-селектор. Значение селектора определяет, какой набор полей доступен в настоящий момент.
Как известно, координаты точек на плоскости можно выражать в привычной декартовой системе координат (Х и Y), а можно – в полярной (Рис. 3.1). В полярной системе положение точки задается длиной и углом поворота отрезка, соединяющего эту точку с началом координат.
Рис. 6.1. Полярная система координат.
Создадим структуру данных, которая сможет хранить координаты точек как в декартовой, так и в полярной системах:
TYPE TCoord=(Cartesian, polar); TCrd=RECORD CASE kind:TCoord OF Cartesian: x,y: REAL; polar: r, phi: REAL; END END;
Для различения систем координат вводится перечислимый тип данных Tcoord, содержащий два значения: Cartesian для декартовых координат и polar – для полярных. Поле-селектор kind показывает, что, если в kind записано значение Cartesian, то в записи окажутся доступными поля x,y, а если polar – то поля r, phi.
Попробуем вычислить расстояние между двумя точками, причем координаты каждой из точек могут быть заданы как в полярной, так и декартовой системе. Вспомнив геометрию и формулы преобразования координат, получим следующий код:
CASE a.kind OF Cartesian: CASE b.kind OF Cartesian: d:=sqrt(sqr(a.x-b.x)+sqr(a.y-b.y)); Polar: d:=sqrt(sqr(a.x-b.r*cos(b.phi)+
sqr(a.y-b.r*sin(b.phi)); END; Polar: CASE b.kind OF Cartesian: d:=sqrt(sqr(a.r*cos(a.phi)- b.x)+sqr(a.r*sin(.phi)-b.y)); Polar: d:=sqrt(a.r+sqr(b.r)-2*a.r*cos(a.phi-b.phi)) END;
В записях с вариантами часть полей может существовать всегда, независимо от значения поля-селектора. Например, мы создаем структуру данных для хранения информации о студенте или сотруднике университета. Очевидно, и студент, и профессор имеют имя и возраст. В то же время для студента важен номер его учебной группы, а для профессора – номер кафедры, на которой он работает. Записывается это так:
TYPE Tposition=(student, professor); Tperson=RECORD name: STRING; age: BYTE; CASE position:Tposition OF student: group:STRING; professor: departmentNo: BYTE; END END;
Поля name, age, position есть всегда. Поле group есть, если position=student. Поле DepartmentNo есть, если position=professor.
Для повышения эффективности работы с записямив Паскале (в отличие от большинства других языков, в частности, от языка С) предусмотрен очень удобный оператор WITH. Смысл действия оператора WITH заключается в том, чтобы не писать много раз имя переменной-записи при обращении к ее полям. Например, мы хотим занести дату в переменную рассмотренного выше типа Tdate. Придется написать следующее:
a.Date:=28; a.Month:=2; a.Year:=2002;
Такая программа не только громоздка, но и медлительна: в каждой строчке для обращения к переменной а ее адрес вычисляется заново. Вот как надо было написать этот фрагмент:
WITH a DO BEGIN Date:=28; Month:=2; Year:=2002 END;
Между BEGIN и END действует соглашение: все упоминаемые поля считаются полямипеременной a, указанной в операторе WITH. Адрес переменной a теперь вычисляется только один раз, что ускоряет работу программы.
Записи целиком нельзя ввести с клавиатуры или вывести на экран. Приходится делать это по полям:
VAR d:Tdate; … WITH d DO BEGIN day:=StrToInt(Edit1.Text);
month:=StrToInt(Edit1.Text);
year:=StrToInt(Edit1.Text)
END;
Гораздо чаще приходится сохранять записи в файл. Если создать файл данных, состоящий из записей, то их в него можно будет записывать целиком, а не по полям:
TYPE Ta=RECORD…
VAR a:TA; f:FILE OF Ta;
…
AssignFile(f,’abc.dbf’); Rewrite(f); Write(f,a)
Оперативную память компьютера можно представить как набор пронумерованных ячеек - слов. Размер слова зависит от конкретной модели компьютера. Например, на IBM PC слово имеет размер один байт. Это означает, что байт – минимальная единица памяти, имеющая свой адрес (порядковый номер). На других типах компьютеров размер слова может меняться. У суперкомпьютеров слово может достигать четырех байт.
Все работающие на компьютере программы должны как можно быстрее вычислять адреса отдельных элементов переменных, относящихся к сложным типам данных (скажем, элементов массива или полей записи). В массиве адрес j-го элемента будет равен
j=i0+j´s,
( 3.1)
где i0 – адрес первого элемента массива, s - число слов памяти, занимаемых одним элементом.
Для упрощения вычислений желательно устранить медленную операцию умножения. Поэтому оптимальный случай - s=1 или s=int(s).
Для достижения указанного оптимального варианта применяют выравнивание данных(padding). При выравнивании s округляют до ближайшего большего целого (обозначается ). Предположим, что размер слова на нашем компьютере равен двум байтам, а размер, занимаемый одним элементом массива - пять байт или 2,5 слова. Выравнивание приведет к тому, что под каждый элемент будет выделено 3 слова, а "полслова" останутся незанятыми (Рис. 3.2).
Рис. 6.2. Выравнивание данных.
Очевидное преимущество выравнивания – повышение скорости работы программы, так как умножение на целое число выполняется в сотни раз быстрее, чем на вещественное. Особенно быстро выполняется умножение на числа, являющиеся степенями двойки, так как для них умножение можно заменить поразрядным сдвигом. Недостаток выравнивания – часть памяти остается неиспользуемой и фактически пропадает впустую.
Коэффициент использования памяти u равен
( 3.2)
В идеале u=1 (вся память используется).
Итак, выравнивать данные или нет? Нужно рассмотреть следующие соображения:
1. Выравнивание требует лишней памяти 2. Отсутствие выравнивания требует организации доступа к части слова 3. Доступ к части слова приводит к заметному увеличению объема программы.