9. Структурные типы. Кроме простых типов (предопределенные типы, тип, задаваемый перечислением и ограниченный тип), в языке Pascal существуют структурные типы, которые конструируются из простых. Структурные типы отличаются от простых тем, что переменные структурных типов имеют более чем одну компоненту. Каждая компонента структурного типа является переменной либо простого, либо структурного типа. На самом нижнем уровне компоненты структурного типа имеют простые типы, которым могут присваиваться значения и которые могут присутствовать в выражениях также как и простые типы. 9.2. Тип массива. Массив – это упорядоченный набор компонент одинакового типа. Тип компонент, из которых состоит массив, называется базовым типом массива. Каждая компонента массива может быть явно обозначена с помощью имени переменной массива, за которым в квадратных скобках следуют индексы этой компоненты. Их тип называется типом индексов. Типом индексов может быть любой порядковый скалярный тип. Компоненты массива могут быть любого типа. Компонента массива имеет те же свойства, что и переменная базового типа. Определение типа массива задает как тип компонент, так и тип индексов. Рассмотрим сначала описание массива в разделе переменных: Var <имя массива>: Array [<тип индекса>] Of <тип элементов массива> Например: Var A: Array [1 .. 100] Of Real; Здесь A – имя массива, элементы которого имеют базовый тип Real. Тип индексов – отрезок целого типа от 1 до 100. Так как типом индекса может быть любой скалярный порядковый тип, то правильным будет и такое описание массива: Var B: Array [‘a’..‘t’] Of Integer; Здесь B – имя массива, элементы которого имеют базовый тип Integer. Тип индексов – отрезок символьного типа от ‘a’ до ‘t’. Если несколько массивов имеют одинаковый тип индексов и одинаковый базовый тип, то допускается в описании объединять массивы в список: Var: C,D,E: Array [1 .. 50] Of Char; Здесь объявлено три массива C, D, E символов, каждый из которых содержит 50 элементов. Нельзя путать понятия «индекс» и «тип индексов». Тип индекса используется только в разделе описания массива. Индекс указывается в разделе операторов для обозначения конкретных элементов массива. В качестве индекса может быть выражение, частным случаем которого является константа или переменная. При этом тип выражения должен совпадать с типом индексов, который введен в разделе описаний массива. Например, A[7], A[I], A[I+1]. Здесь переменная I и выражение I+1 должны быть целого типа. Элементы массива по-другому называются переменными с индексами. В математике переменные с индексом обозначаются A7, Ai, Ai+1. Элементы массива могут стоять как в левой части оператора присваивания, так и в выражениях: A[5] := X +1; y := A[1] + A[8]; A[10] := A[8] + A[2]; B[‘c’] := A + B; Над элементами массива можно производить те же операции, которые допускаются для данных его базового типа. Например, для элементов массива B допускаются операции Div и Mod, как и для целых чисел. С другой стороны, эти операции недопустимы для элементов массива A, т.к. они имеют вещественный тип. Для ввода и вывода числовых значений массива используют циклы. Например, цикл: For I := 1 to 100 do Read (A[I]) организует ввод ста элементов массива A. Цикл For I := 1 to 100 do Write (A[I]) организует вывод значений элементов этого массива. Пример. Дан массив из 15 целых чисел. Найти сумму элементов массива. Program Summa; Const n = 15; Type Index = 1..n; Var A: Array[Index] of Integer; I: Index; Sum : Integer; Begin For I := 1 to n do Read (A[I]); Sum := 0; For I := 1 to n do Sum := Sum + A[I]; Writeln(‘ Сумма = ‘, Sum: 6) End. В языке Pascal помимо явного описания массивов в разделе переменных имеется другая форма, состоящая из двух этапов. Сначала в разделе описания типов Type определяется тип массива, затем в разделе описания переменных Var перечисляются массивы, относящиеся к указанному типу. Такое описание массивов является необходимым при использовании массивов в качестве параметров процедур и функций. Форма объявления массива в этом случае имеет вид: Type <имя типа> = Array [ <тип индекса>] Of <тип элементов массива>; Var <имя массива> : <имя типа>; Например: Type Massive = Array [1 .. 10 ] Of Real; Var A, B, C : Massive; 9.3. Многомерные массивы. Массивы, каждый элемент которых имеет только один индекс, называют одномерными. Часто необходимо использовать многомерные массивы, т.е. массивы массивов. Особенно широкое распространение получили двумерные массивы или матрицы. Например: 1 2 3 4 5 6 7 8 9 10 11 12 матрица 3х4, т.е. состоящая из трех строк целых чисел, в каждой из которых находится четыре числа. Если всю матрицу обозначить одним именем, например A, то каждый элемент матрицы обозначается с двумя индексами, например, A[I, J]. Здесь I – означает номер строки, а J – номер столбца на пересечении которых находится элемент. В математике это соответствует переменной с индексами Aij. Такую матрицу можно описать следующими способами: 1) Type T:Array[1..3,1..4]Of Integer; Var A:T; 2) Type T:Array[1..3]Of Array[1..4 ]Of Integer; Var A:T; Первый способ при описании двумерных массивов предпочтительней. Ввод и вывод двумерных массивов также осуществляется с использованием циклических конструкций. Ввод: For I := 1 to 3 do For J := 1 to 4 do Read (A[I,J]); Вывод в виде матрицы: For I := 1 to 3 do Begin For J := 1 to 4 do Write(A[I,J]); Writeln End; Пример. Дан двумерный массив целых чисел размерностью 3х4. Заменить третий столбец массива нулями. Program mass; Const n=3; m=4; Type Index1=1..n; Index2=1..m; Var A:Array[Index1,Index2]Of Integer; I:Inex1; J:Index2; Begin For I:=1 to n do For J:=1 to m do Readln(A[I,J]); For I:=1 to n do For J:=1 to m do If J=3 then A[I,J]:=0; For I := 1 to 3 do Begin For J := 1 to 4 do Write(A[I,J]); Writeln End; End. 9.4. Решение задач с использованием массивов Задачи с использованием массивов можно условно разделить на три типа: 1. 2. 3. Задачи на поиск одной или нескольких числовых характеристик массивов. Например: a. поиск максимального и/или минимального элементов массива и их индексов; b. поиск произведения/суммы элементов массива или его частей (элементов на главной/ побочной диагоналях и др.); Задачи на преобразование массивов. Например: a. упорядочить элементы массива по возрастанию или убыванию, b. поменять местами определенные строки или столбцы массива, c. поменять местами максимальный и минимальный элементы массива и др. Задачи на программное заполнение массива заданным образом: a. целочисленный одномерный массив заполнить квадратами индексов элементов массива и т.д. Задачи первого и второго типов решаются в три этапа: 1. 2. Заполнение массива значениями; Поиск заданных характеристик массива или преобразование массива; Эти этапы программируются с использованием циклических конструкций. 3. Вывод найденных характеристик массива или преобразованного массива. В последнем случае также используются циклы. Задачи третьего типа программируются в два этапа: 1. 2. Программное заполнение массива заданным образом; Вывод заполненного массива. Оба этапа используют циклы. Пример. Одномерный массив целых чисел размерностью 20 заполнить элементами, равными квадрату индекса элемента. Program mass; Var X:array[1..20] of Integer; I: Byte; Begin For I:=1 to 20 do X[I]:=sqr(I); For I:=1 to 20 do Write(X[I]) End. 9.5. Заполнение массива случайными числами Массив можно заполнять числами не только с клавиатуры, но и автоматически с помощью генератора случайных чисел. Для этого используется функция random. Эта функция без параметров возвращает случайное вещественное число в диапазоне 0<=x<1. Если функция используется с параметром random(n), где n – целое число, то она возвращает случайное целое число в диапазоне 0<=x<n. Но прежде чем пользоваться функцией random надо выполнить процедуру randomize, которая инициализирует генератор случайных чисел. Пример. Заполнить массив размерностью 100 целых чисел случайными числами в диапазоне 0<=x<1000. Program mass; Var A: array[1..100] of integer; i: 1..100; Begin Randomize; For i:=1 to 100 do A[i]:=random(1000) End. 9.6. Строки символов Символьный тип данных позволяет программисту работать с отдельными символами текста. Для обработки более крупных текстовых единиц — строк в Турбо-Паскале введен особый тип данных, который называется STRING (строка). Значениями этого типа являются строки любых символов, заключенные в одинарные кавычки, например: ‘Колледж’, ‘PASCAL’, ‘Курить — вредно, ‘4567’. Переменные строки должны быть описаны предложением VAR <имя_переменной> : STRING; Это описание задает строку длиной 255 символов. Если надо описать строку длиной меньше 255 символов, используют следующую форму: VAR <имя_переменной> : STRING[21]; Здесь 21 – длина строки. Строки можно присваивать, сравнивать всеми возможными способами, вводить, выводить и соединять. Соединение обозначается знаком «+». Вот примеры некоторых операций (справа — результат операции). 'стол' <= 'столик' true 'АВС' < 'ABDA' true '12' < '2' false 'Харь' + 'ков' 'Харьков' То есть строки сравниваются посимвольно на основе кодов символов, входящих в строки. Ввод строки S: Readln(S); Вывод строки S: Writeln(S); Среди всевозможных значений строк есть пустая строка. Она изображается двумя одинарными кавычками, между которыми ничего нет. Кавычка служит ограничителем строки. Чтобы не лишиться возможности иметь этот символ в составе строки, договорились повторять ее там дважды. Например, оператор write ('им"я') выведет на экран: им'я Турбо-Паскаль не только вводит дополнительный тип данных string, но и обеспечивает программиста готовыми операторами для работы со строками. Concat(s1,s2,...,sN) — соединяет последовательно строки s1, s2,..., sN и возвращает полученное значение; Пример: S1:=’желез’; S2:=’но’; S3:=’дорожник’; S:=Concat(S1,S2,S3); В результате строковая переменная S примет значение ‘железнодорожник’. Copy(S,I,C) — выделяет из строки S подстроку длиной в C символов, начиная с позиции I; Пример: S1:=’железнодорожник’; S:=Copy(S1,6,2); В результате переменная S примет значение ‘но’. Length(S) — возвращает длину строки; Пример: S1:=’железнодорожник’; K:=Length(S1); В результате переменная K (целого типа) примет значение 15. Pos(S1,S) — возвращает позицию, с которой подстрока S1 первый раз встречается в строке S. Если такой подстроки нет в строке – возвращается нулевое значение. Пример: X:= 'Сила есть — ума не надо'; S1:= copy (X, pos(' ', X) + 1, 18); В результате S1 = ‘есть – ума не надо’. Рассмотренные операторы на самом деле являются функциями и могут использоваться только в правой части оператора присваивания. Рассмотрим операторы-процедуры, которые вызываются по их имени по аналогии с процедурой WRITE. Delete(S,I,C) — удаляет из строки S C символов, начиная с позиции I; Пример: X:= 'Сила есть — ума не надо'; Delete(X,5,5); Результат: строка X примет значение ‘Сила – ума не надо’. Insert(S1,S,I) — вставляет подстроку S1 в строку S, начиная с позиции I; Пример: S:= 'Сила есть — ума не надо'; S1:=’много ’; Insert(S1,S,16); Результат: строка S примет значение ‘Сила есть – ума много не надо’. Программисту доступны и отдельные символы переменной строки. С ними работают так, как если бы описатель STRING был равносилен описателю ARRAY [0..255] OF CHAR. Другими словами, если описана переменная X: STRING, то Х[1] — это символьная переменная со значением первого символа строки, Х[2] — символьная переменная со значением второго символа и т.д. У Х[0] особая роль — хранить длину строки. Значением Х [0] является символ, код которого равен количеству символов в строке. Работая с отдельными символами строки, надо соблюдать осторожность, т.к. Х [i] лишь тогда означает iй символ строки, когда сама строка длиннее, чем i. Пример. Дано предложение. Определить, сколько раз в предложении встречается символ ‘a’. Program predl; Var X,X1: String; K,I,L: Byte; Begin Writeln(‘Введите предложение:’); Readln(X); L:=Length(X); K:=0; For I:=1 to L do Begin X1:=Copy(X,I,1); If X1=’a’ then K:=K+1 End; Writeln(‘Символ ‘’a’’ встречается ‘, K, ‘ раз’) End. 9.7. Тип множества В математике под множеством понимается некоторый набор элементов. Например, множество фигур на плоскости. К множествам применимы следующие операции: 1. Объединение множеств C = A ∪ B : каждый элемент множества C является элементом либо множества A, либо множества B. 2. Пересечение множеств C = A ∩ B : каждый элемент множества C является элементом множеств A и B одновременно. 3. Разность двух множеств C = A \ B : каждый элемент множества C является элементом множества A, но не является элементом множества B. В языке Pascal под множеством понимается ограниченный неупорядоченный набор различных элементов одинакового типа. Всему множеству дается имя. Тип элементов, входящих в множество, называется базовым типом. В качестве базового типа может использоваться любой простой тип. Множества должны быть объявлены либо в разделе описания переменных, либо с использованием раздела описания типов. Объявление множеств в разделе переменных имеет вид: VAR <имя множества>:SET OF <базовый тип>; Например: VAR Год: SET OF 1980..2000; С: SET OF Char; Объявление множеств с использованием раздела типов имеет вид: TYPE <имя типа> = SET OF <базовый тип>; VAR <имя множества>: <имя типа>; Примером такого описания может служить множество Радиосхема, элементами которого являются данные типа Радиодеталь: TYPE Радиодеталь = (резистор, конденсатор, индуктивность,транзистор, диод); Радиосхема = SET OF Радиодеталь; VAR контур,детектор,усилитель: Радиосхема; Значение переменных и констант множества задается в разделе операторов с помощью так называемого конструктора. Конструктор – это список элементов базового типа, заключенный в квадратные скобки. Например: контур:=[конденсатор,индуктивность]; Пустые квадратные скобки означают пустое множество: детектор := [ ]; В языке Pascal имеются следующие операции над множествами: + - объединение множеств; * - пересечение множеств; =, <> - вычитание множеств; - проверка множеств на равенство или неравенство. Множество A равно множеству B, если каждый элемент множества A является элементом множества B и, наоборот, каждый элемент множества B является элементом множества A. В противном случае множества A и B не равны друг другу. <=, >= - проверка множеств на включение. Множество A включено в множество B (A <= B или B >= A), если все элементы множества A являются также элементами множества B. При этом результат операции, записанной в скобках, будет True. IN - проверка на принадлежность элемента множеству. Например, оператор C IN A служит для проверки: принадлежит ли элемент базового типа C множеству A. Пример. Из множества целых чисел 1 .. 20 выделить: 1) множество чисел, делящихся на 6 без остатка; 2) множество чисел, делящихся или на 2 или на 3. Program Krat; Const n=20; { размерность множества } Var n2,n3,n6,n23:SET OF Integer; k:Integer; Begin n2:=[ ]; n3:=[ ]; For k:=1 to n do Begin If k mod 2 = 0 Then n2:=n2 + [k]; If k mod 3 = 0 Then n3:=n3 + [k] End; n6:=n2*n3; n23:=n2+n3; Writeln(‘на 6 делятся:’); For k:=1 To n do If k IN n6 Then Write(k:3); Writeln(‘на 2 или 3 делятся:‘); For k:=1 To n do If k IN n23 Then Write(k:3) End. Элементы множеств не выводятся на экран с помощью оператора Write и не вводятся с клавиатуры с помощью оператора Read. Вывод представлен в предыдущем примере. Ввод можно организовать так: Var n:SET OF Integer; k:Integer; Begin N:=[]; Readln(k); N:=N+[k] End. 9.8. Тип записей Во многих экономических и информационных задачах обрабатываются ведомости, каталоги, списки. При этом появляется необходимость объединять данные различного типа в одну группу. Для работы с группами данных в языке Pascal введено понятие записи. Запись – это совокупность ограниченного числа данных различного типа. Рассмотрим понятие записи на примере ведомости списка учащихся с их оценками. № 1 2 3 Ф.И.О. Иванов И.И. Петров П.П. Сидоров С.С. Оценки 5 4 3 5 5 5 4 4 3 Каждая строка в этой ведомости состоит из отдельных данных различного типа. Порядковый номер – целое десятичное число; Ф.И.О. – строка символов или массив символов; оценки – массив целых чисел. Эти данные можно объединить в одну группу и считать записью. То есть отдельно взятая строка в указанной таблице и есть запись. Запись в целом и ее элементы обозначаются именами. Введем, например, следующие имена: B - имя всей записи; N - порядковый номер; ФИО - фамилия, имя, отчество; ОЦЕНКИ - оценки. Обращение к элементу записи в программе выполняется с помощью составного имени. Составное имя содержит имя всей записи и имя элемента, и записывается в следующем виде: <имя записи>.<имя элемента> Например: B.N, B.ФИО, B.ОЦЕНКИ. Записи, как и другие данные, объявляются в разделе описаний и используются в разделе операторов. Объявление записей можно делать как в разделе переменных Var, так и в разделе типов Type. Объявление записи в разделе переменных: Var <имя записи>: RECORD <имя элемента 1>:<тип>; <имя элемента 2>:<тип>; ……………………… <имя элемента N>:<тип> END; Если есть несколько записей с одинаковыми элементами, то их имена можно указывать списком через запятую. Элементы записи вместе с их описанием называются полями записи. Для рассмотренной ведомости объявление записи выглядит так: Var B: Record N:Integer; ФИО: String[30]; Оценки: Array[1..3] of Integer End; Более универсальным является объявление записи в разделе типов. Оно имеет вид: <имя типа> = Record <имя элемента 1>:<тип>; <имя элемента 2>:<тип>; ……………………… <имя элемента N>:<тип> END; Var <имя записи>:<имя типа>; Type Таким образом, нашу ведомость можно описать так: Type Успеваемость=Record N:Integer; ФИО: String[30]; Оценки: Array[1..3] of Integer End; Var B: Успеваемость; Элемент записей используется в программе в том же самом смысле, как и простая переменная того же самого типа. Т.е. элемент записи может стоять как в левой, так и в правой части оператора присваивания, и над ним можно выполнять допустимые для его типа операции. Например: B.N:=2; S:=B.ОЦЕНКИ[1] + B.ОЦЕНКИ[2]; Readln(B.ФИО); Writeln(B.ФИО, В.N); Обращение к записи в целом, а не только к ее элементам допускается лишь в операторе присваивания. При этом должны использоваться имена записей одинакового типа. Например, если есть описание: Var B,C: Успеваемость; то в разделе операторов возможен оператор присваивания B:=C; Если сама запись входит в состав более сложной структуры, эта структура отражается и на строении имени записи. Например, для описания ведомости успеваемости 100 учеников необходимо определить следующие данные: Type Успеваемость=Record N:Integer; ФИО: String[30]; Оценки: Array[1..3] of Integer End; Var Ведомость: Array[1..100] of Успеваемость; В этом случае для изменения данных 29-й записи используется следующая группа операторов: Ведомость[29].N:=29; Read(Ведомость[29].ФИО); Read(Ведомость[29].ОЦЕНКИ[1]); Read(Ведомость[29].ОЦЕНКИ[2]); Read(Ведомость[29].ОЦЕНКИ[3]); 9.9. Оператор присоединения Как было сказано, обращение к элементам записи происходит с помощью уточненного имени. Оператор присоединения позволяет упростить обращение к элементу записи. Имя записи выносится в заголовок оператора присоединения, а в блоке используются только имена элементов записи. Общая форма оператора присоединения: WITH <имя записи> DO Begin <операторы, содержащие имена элементов записи> End; Например, пять операторов для изменения данных 29-й записи заменятся на: With Ведомость[29] Do Begin N:=29; Read(ФИО); Read(ОЦЕНКИ[1]); Read(ОЦЕНКИ[2]); Read(ОЦЕНКИ[3]); End; Пример. В списке успеваемости 100 учеников найти фамилии учеников, имеющих максимальный средний балл. Успеваемость=Record N:Integer; ФИО: String[30]; Оценки: Array[1..3] of Integer End; Var Ведомость: Array[1..100] of Успеваемость; M: Array[1..100] of real; Max: Real; Type I: 1..100; Begin For i:=1 to 100 do With Ведомость[i] do Begin N:=I; Readln(ФИО); Readln(Оценки[1], Оценки[2], Оценки[3]); End; For i:=1 to 100 do With Ведомость[i] do M[i]:=(Оценки[1]+Оценки[2]+Оценки[3])/3; Max:=M[1]; For i:=2 to 100 do If M[i]>Max then Max:=M[i]; For i:=1 to 100 do If Max=M[i] then writeln(Ведомость[i].ФИО) End.