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

Сайт: Информатикс
Курс: Типы данных
Книга: Теоретический материал (Паскаль)
Напечатано:: Гость
Дата: Пятница, 27 Июнь 2025, 17:59

Комбинированный тип данных. Запись. Описание записи. Доступ к полям записи. Оператор 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.

Сортировка записей

Рассмотрите решение задачи, наберите ее на компьютере, протестируйте, найдите в программе недостатки и устраните их.

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

Program Ski;
Uses
  Crt;
Type
  inf= record
      num: byte;
      name,strana: string[30];
      rez: real;
    end;
Var
  m: array [1..100] of inf;
  i, j, k : integer;
Procedure Input;
Begin
  write('Введите количество участников соревнований: ');
  readln(k);
  for i:=1 to k do
    with m[i] do
      begin
        write('Введите номер участника: ');
        readln(num);
        write('Введите фамилию: ');
        readln(name);
        write('Какую страну представляет: ');
        readln(strana);
        write('Показанный результат: ');
        readln(rez);
        writeln;
      end;
End;
Procedure Vich;
Var
  o: real;
  n, s: string;
  nm: byte;
Begin
  ClrScr;
  for i:=1 to k-1 do
    for j:=i+1 to k do
      if m[j].rez<m[i].rez
        then
   
      begin
            o:=m[j].rez;{Меняем результаты}
            m[j].rez:=m[i].rez;
            m[i].rez:=o;
            nm:=m[j].num;{Меняем номера}
            m[j].num:=m[i].num;
            m[i].num:=nm;
            n:=m[j].name;{Меняем фамилии}
            m[j].name:=m[i].name;
            m[i].name:=n;
            s:=m[j].strana; {Меняем страны}
            m[j].strana:=m[i].strana;
            m[i].strana:=s;
          end;
End;
Procedure Output;
Begin
  for i:=1 to k do
    with m[i] do
      begin
        writeln(i,'-ое место занял:');
        writeln('участник под номером: ',num);
        writeln('Его фамилия: ',name);
        writeln('Представляет страну : ',strana);
        writeln('Показанный результат: ',rez:3:1);
        writeln;
      end;
End;
Begin
  ClrScr;
  Input;
  Vich;
  Output;
  ReadKey;
End.

Задание. Напишите программу для сортировки записей по выбранному Вами полю и вывода отсортированного массива записей на экран. Сортировку осуществляйте с помощью одного из предложенных способов:

  • сортировка вставкой;
  • сортировка выбором;
  • сортировка методом простого обмена;
  • сортировка с помощью рекурсии.

Записи с вариантами

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

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

Const
  Kol = 1000;
Type
  Entry = Record
      Autor, Title, Publisher, City : String;
      Year : 1..2000;
  End;
Var
  List : Array[1..Kol] of Entry;

Что произойдет, если в части библиографических ссылок надо указать не книги, а журнальные статьи? Если ограничиваться только записями с фиксированными частями, то следует описать различные массивы для каждого вида записей. Использование записей с вариантами позволяет решить задачу по-другому. Опишем новый тип, в котором перечислены различные типы ссылок:

Type
  EntryType = (Book, Magazine);

Теперь можно привести скорректированное описание Entry

Type
  Entry = Record
      Autor, Title : String;
      Year : 1..2000;
      Case EntryType of
        Book : (Publisher, City : String);
        Magazine : (MagName : String,
        Volume, Issue : Integer)
    End;

Это описание делится на две части: фиксированную и вариантную. Поля Autor, Title, Year составляют фиксированную часть. Оставшаяся часть описания Entry образует вариантную часть, структура которой, подобно хамелеону, может меняться в пределах двух альтернативных определений.

Первая строка вариантной части представляет оператор Case, который отличается тем, что в качестве селектора применяется идентификатор типа. Значения EntryType используются в качестве имен двух альтернатив определения записи. Когда эта компонента имеет значение Book, можно обращаться к следующим полям:

Autor, Title, Year, Publisher, City

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

Autor, Title, Year, MagName, Volume, Issue

В такой ситуации возникает естественный вопрос: как программа может хранить информацию о текущем состоянии каждой записи? Другими словами, каким образом можно узнать , что List[3] содержит ссылку на книгу, а List[4] - ссылку на журнал?

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

Type
  Entry = Record
      Autor, Title : String;
      Year : 1..2000;
      Case TAG : EntryType of
        Book : (Publisher, City : String);
        Magazine : (MagName : String,
        Volume, Issue : Integer)
    End;

Поле, названное TAG, является переменной типа EntryType. Когда запись содержит ссылку на книгу, TAG следует присвоить значение Book. Когда запись содержит ссылку на журнал, TAG следует присвоить значение Magazine.

Рассмотрите последовательность операторов, где в RefList[12] помещается ссылка на книгу:

RefList[12].TAG := Book;
RefList[12].Autor := 'Thomas Hobbes';
RefList[12].Title := 'Leviathan';
RefList[12].Year := 1651;
RefList[12].Publisher := 'Andrew Crooke';
RefList[12].City := 'London';

Для определения состояния записи с вариантами достаточно проверить значение поля тега. Рассмотрите процедуру, выводящую на экран переданную ей запись.

Procedure PrintRef(Citation : Entry);
Begin
  Writeln(Citation.Autor);
  Writeln(Citation.Title);
  Writeln(Citation.Year);
  If Citation.TAG = Book
    Then
      Writeln(Citation.Publisher,', ',Citation.City)
    Else
      Begin
        Writeln(Citation.MagName);
        Writeln(Citation.Volume'-',Citation.Issue)
      End;
End;

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

Очевидно, что один и тот же идентификатор поля не может дважды использоваться при описании записи, даже если он применяется в определении различных альтернатив записи с вариантами. Если же это условие не выполняется, то обращение к такому идентификатору приведет к непредсказуемому результату.

Наверное, Вы уже обратили внимание, что описание записи с вариантами имеет единственный закрывающий оператор End. Поскольку любая запись может иметь лишь одну вариантную часть, то End, который является индикатором конца описания записи, служит для обозначения конца и ее вариантной части.

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

а) Напишите процедуру, которая запрашивает и получает значение типа Figure от пользователя.

б) Напишите функцию, которая получает на входе значение типа Figure и вычисляет площадь фигуры.

в) Напишите функцию, которая получает на входе значение типа Figure и вычисляет периметр фигуры.

г) Напишите булеву функцию, которая получает на входе два значения типа Figure и определяет, помещается ли первая фигура внутри второй.

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

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

Program LipovsevM;
Uses
  Crt;
Type
  Uchenik=record
      Name : string[10];
      Fam : string[15];
      Otch : string[15];
      Ulica : string[20];
      Dom : string[5];
      Kvartira : integer;
      case tel : boolean of
        False :();
        True :(Telefon : string[15]);
    end;
Var
  Massiv : array[1..100] of Uchenik;
  I, n : integer;
  Otvet : 0..1;
Begin
  ClrScr;
  TextColor(9);
  write('Введите число учеников->');
  readln(n);
  for i:=1 to n do
    with massiv[i] do
      begin
        write('Введите имя ',i,'-го ученика ->');
        readln(name);
        write('Введите фамилию ',i,'-го ученика ->');
        readln(fam);
        write('Введите отчество ',i,'-го ученика ->');
        readln(otch);
        write('Введите улицу ',i,'-го ученика ->');
        readln(ulica);
        write('Введите дом ',i,'-го ученика ->');
        readln(dom);
        write('Введите квартиру ',i,'-го ученика ->');
        readln(kvartira);
        write('Есть ли у ',i,'-го ученика телефон (0-нет, 1-да->');
        readln(otvet);
        if otvet=1
          then
            begin
              tel:=True;
              write('Введите телефон ',i,'-го ученика ->');
              readln(telefon);
            end
          else
            tel:=False;
        end;
  TextColor(red);
  writeln('Список учеников, до которых нельзя дозвониться:');
  for i:=1 to n do
    with massiv[i] do
      if tel=False
        then
          begin
            writeln('Имя:',name);
            writeln('Фамилия:',fam);
            writeln('Отчество:',otch);
            writeln('Улица:',ulica);
            writeln('Дом:',dom);
            writeln('Квартира:',kvartira);
          end;
  ReadKey;
End.

Задание. Разберите решение предыдущей и следующей задачи.

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

Program SedihA;
Uses
  Crt;
Type
  TypePubl = (Book, Journal, Newspaper);
  Litter = record
      Title : string[50];
      Author : string[50];
      case V : TypePubl of
        Вook : (YearB : integer);
        Journal : (Num : 1..12;
        YearJ : 1900..2000);
        Newspaper : (Day : 1..31;
        Month : 1..12;
        YearN : integer);
    end;
Const
  Count = 10;
Var
  Katalog : array [1..count] of Litter;
  NumArray : 1..count;
  YesLitter : Boolean;
  Vybor : byte;
  Edition : TypePubl;
  CountFind : integer;
Procedure InputData;
Begin
  writeln;
  writeln('Введите данные о литературе ', NumArray,' :');
  write('Введите число, указывающее вид издания: ');
  Write('1-книга, 2-журнал, 3-газета : ');
  readln(Vybor);
  case Vybor of
    1 : Katalog[NumArray].v:=Book;
    2 : Katalog[NumArray].v:=Journal;
    3 : Katalog[NumArray].v:=Newspaper;
  end;
  with katalog[NumArray] do
    begin
      write('Фамилия автора? ');
      readln(Author);
      write('Название? ');
      readln(Title);
      case v of
        Book : begin
          write('Год издания ? ');
          readln(YearB);
        end;
        Journal : begin
          write('Номер ? ');
          readln(Num);
          write('Год издания ? ');
          readln(YearJ);
        end;
        Newspaper : begin
          write('Дата издания: День? ');
          readln(Day);
          write('Месяц? ');
          readln(Мonth);
          write('Год? ');
          readln(YearN);
        end;
      end;
    end;
End;
Procedure WriteData;
Begin
  writeln;
  with Katalog[NumArray] do
    begin
      writeln('Название : ',Тitle);
      writeln('Фамилия автора: ',Аuthor);
      case v of
        Book : writeln('Год издания : ',YearB);
        Journal: begin
          writeln('Номер : ', Num);
          writeln('Год издания : ',YearJ);
        end;
        Newspaper : writeln('Дата издания: День: ',Day,' Месяц: ',Month,'Год: ',YearN);
      end;
    end;
Еnd;
Procedure FindLitter;
Begin
  writeln('Поиск литературы по типу издания: ');
  writeln;
  write('1-книга, 2-журнал, 3-газета: ');
  readln(Vybor);
  case Vybor of
    1 : Edition:=Book;
    2 : Edition:=Journal;
    3 : Edition:=Newspaper;
  end;
  YesLitter:=False;
  CountFind:=0;
  for NumArray:=1 to count do
    if katalog[NumArray].v = edition
      then
        begin
          YesLitter:=True;
          CountFind:=CountFind+1;
          WriteData;
      end;
  if not YesLitter
    then
      writeln('В библиотеке нет такой литературы')
  else
      writeln('Всего в библиотеке ',CountFind,' таких изданий');
End;
Begin
  ClrScr;
  for NumArray:=1 to Count do
    InputData;
  writeln;
  FindLitter;
End.