Теоретический материал (Паскаль)

Комбинированный тип данных. Запись. Описание записи. Доступ к полям записи. Оператор With. Примеры решения задач

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

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

Приведем пример описания переменной, имеющей структуру записи:

Var
  Address : Record
    HouseNumber : Integer;
    StreetName : String[20];
    CityName : String[20];
    PeopleName : String;
  End;

Отметим, что поля StreetName и CityName имеют одинаковый тип: String[20]. Поскольку в описании эти поля могут располагаться в любом порядке, то можно сократить описание записи с полями одинакового типа. Сокращенное описание записи Address выглядит следующим образом:

Var
  Address : Record
    HouseNumber : Integer;
    StreetName, CityName : String[20];
    PeopleName : String;
  End;

Каждый компонент записи называется полем. В переменной записи Address поле с именем HouseNumber является переменной типа Integer, поле StreetName - двадцатисимвольной строкой и т.д.

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

Оператор, который присваивает полю HouseNumber значение 45, выглядит так:

Address.HouseNumber := 45;

Таким же образом присваиваются значения другим полям записи Address :

Address.StreetName := 'Профсоюзная';
Address.CityName := 'Сургут';
Address.PeopleName := 'Петрова Алла Ивановна';

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

Предположим, имеется следующее описание:

Type
  Date = Record
    Day : 1..31;
    Month : (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec);
    Year : Integer;
  End;
Var
  HisBirth, MyBirth : Date;

После приведенного описания переменные HisBirth и MyBirth имеют тип записи Date. Помимо действий над отдельными полями записей HisBirth и MyBirth можно выполнять операции над всей записью. Следующий оператор присваивания устанавливает равенство значений записей HisBirth и MyBirth :

HisBirth := MyBirth;

Это присваивание эквивалентно следующей последовательности операторов:

HisBirth.Day := MyBirth.Day;
HisBirth.Month := MyBirth.Month;
HisBirth.Year := MyBirth.Year;

Для переменных одного типа можно проверить выполнение отношения равенства или неравенства ("=", "<>"). После выполнения приведенных выше присваиваний следующее булево выражение будет иметь значение True:

HisBirth = MyBirth;

Рассмотрите пример описания процедуры, которая получает запись в качестве параметра-значения и печатает дату в сокращенной стандартной форме, используя формат (MM-DD-YYYY).

Procedure WriteDate(OneDate : Date);
Begin
  Write(Ord(OneDate.Month)+1);
  Write('-');
  Write(OneDate.Day:2);
  Write('-');
  Write(OneDate.Year:4);
End;

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

Var
  Birthdays : Array [1..Persons] of Date;

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

Например, следующий оператор печатает содержимое поля Year записи Birthdays[3]:

Write(Birthdays[3].Year);

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

Задание. Рассмотрите следующие описания:

Type
  Date = Record
    Day : 1..31;
    Month : 1..12;
    Year : 1..9999
  End;
Reminder = Record
    Message : Array [1..5] of String;
    Event : Date
End;
Var
  Today : Date;
  Memos : Array [1..100] of Reminder;
  Calendar : Array [1..365] of Date;

Ответьте, какой тип, если он определен, имеют следующие идентификаторы:

а) Today.Year
б) Memos [2]
в) Memos [4].Month
г) Calendar [200]
д) Memos [16].Message[2]
е) Memos [16].Message[1],[2]
ж) Calendar[1].Date
з) Memos [10].Event

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

Приведем описание массива, компоненты которого являются записями:

Var
  Payroll : array [1..Workers] of
    record
      FirstName, LastName : string;
      Residence : record
        HouseNumber : real;
        StreetName, CityName : string;
        StateName : Array [1..2] of char;
        ZipCode : integer;
      end;
      Phone : record
        AreaCode, Exchange : 1..999;
        Line : 1..9999;
      end;
      PayScale : 'A' ..'G';
  end;

Отметим, что два поля: Residence и Phone являются записями. Как выполнить обращение к полям этих записей? Как распечатать почтовый индекс рабочего № 7? Поскольку это поле располагается во вложенной записи, то следует указать как имя самой записи, так и имя записи, в которую данная запись входит.

write (Payroll[7].Residence.ZipCode);

Аналогично, приведенное присваивание корректирует региональный код рабочего № 23:

Payroll[23].Phone.AreaCode :=804;

Оператор if, представленный ниже, выполняет проверку инициала рабочего № 58:

if Payroll[58].LastName[1] in ['T'..'Z'] Then ...

Соблюдение всех правил перечисления индексов и имен полей при составлении ссылок является довольно утомительным занятием, часто приводящим к ошибкам. В некоторых программах, содержащих большое количество обращений к одному и тому же полю, такое положение приводит к однообразному повторению. Чтобы облегчить выполнение многократных ссылок на поля структур, вводится оператор With (в переводе с английского - предлог "с").

Общая форма записи:

with <имя переменной> do <оператор>

В рамках оператора, расположенного внутри оператора With, к полям указанной переменной можно обращаться просто по имени. Например,

with Payroll[7].Residence do
  ZipCode := 2345;
for i := 1 to Workers do
  with Payroll[i] do
    if PayScale < 'G'
      then
        PayScale := Succ(PayScale);

Оператор with позволяет более компактно представлять часто используемые переменные. Посмотрите это на примере фрагмента программы, печатающего адрес рабочего № 14:

with Payroll[14].Residence do
  begin
    writeln(HouseNumber,' ',StreetName);
    writeln(CityName);
  end;

В рамках составного оператора, следующего за with, каждое обращение к имени поля автоматически связывается с записью Payroll[14].Residence.

Печать адресов всех рабочих выполняется при помощи следующего оператора цикла:

for i := 1 to Workers do
  with Payroll[i].Residence do
    begin
      writeln(HouseNumber,' ',StreetName);
      writeln(CityName);
    end;

Операторы with могут быть вложенными. Приведенные ниже три оператора эквивалентны друг другу:

  1. Payroll[i].Residence.HouseNumber := 50;

  2. with Payroll[i].Residence do
      HouseNumber := 50;

  3. with Payroll[i] do
      with Residence do
        HouseNumber := 50;

Однако, недопустимым является использование вложенных операторов With, в которых возникает неоднозначность конструкции. Например:

with Payroll[5] do
  with Payroll[17]do
    PayScale :='A';

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

Рассмотрите решение задачи.

Задача. В массиве хранятся данные об учащихся: школа, фамилия, класс. Вывести список учеников, которые учатся в восьмом классе.

Program LipovsevM;
Uses
  Crt;
Type
Uchenik=record
      Shkola : integer;
      Fam : string[15];
      Klass : integer;
    end;
Var
  I, n : integer;
  Massiv : array[1..100] of Uchenik;
Рrocedure Poisk;
Begin
  for i:=1 to n do
    if massiv[i].klass=8
      then
        with massiv[i] do
          writeln(Shkola:5, Fam:16, klass:6);
End;
Begin
  ClrScr;
  writeln('Введите число учеников ');
  write('->');
  read(n);
  for i:=1 to n do
    begin
      writeln('Введите через пробел номер школы и фамилию ученика ');
      write('->');
      with massiv[i] do
        begin
          readln(Shkola,Fam);
          write('Введите класс ученика (только число) ->');
          read(Klass);
        end;
    end;
  writeln('Ученики 8-ых классов:');
  writeln('Школа', 'Фамилия':16, 'Класс':6);
  writeln('---------------------------------');
  Poisk;
  ReadKey;
End.