Двумерные и многомерные массивы

 

 

МАССИВЫ

 

Массивы представляют собой ограниченную упорядоченную совокупность однотипных величин. Каждая отдельная величина называется компонентой массива. Тип компонент может быть любым, принятым в языке ПАСКАЛЬ, кроме файлового типа. Тип компонент называется базовым типом. Вся совокупность компонент определяется одним именем. Для обозначения отдельных компонент используется конструкция, называемая пере менной с индексом или с индексами:

 

A[5] S[k+1] B[3,5].

 

В качестве индекса может быть использовано выражение. Тип индексов может быть только интервальным или перечисляемым. Действительный и целый типы недопустимы. Индексы интервального типа, для которого базовым является целый тип, могут принимать отрицательные, нулевое и положительные значения.{}

В операторной части

программы один массив может быть присвоен другому, если их типы идентичны, например:

 

R1:=Z.

 

Для ввода или вывода массива в список ввода или вывода помещается переменная с индексом, а операторы ввода или вывода выполняются в цикле.

{}

Первый индекс определяет номер строки, второй - номер столбца. Двумерные массивы хранятся в памяти ЭВМ по строкам.

Инициализация массивов (присвоение начальных значений всем компонентам массивов) осуществляется двумя способами. Первый способ - с использованием типизированных констант, например:

 

type Dim10= Array[1..10] of Real;

 

const

raM10: Dim10 = ( 0, 2.1, 4, 5.65, 6.1, 6.7, 7.2, 8, 8.7, 9.3 );

 

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

 

type Dim3x2= Array[1..3,1..2] of Integer;

 

const

iaM3x2: Dim3x2= ( (1, 2)

(3, 4)

(5, 6) );

 

Второй способ инициализации - использование разновидности процеду ры FillChar:

 

FillChar( var V; NBytes: Word; B: Byte );

 

Эта процедура заполняет участок памяти однобайтовым значением. Напри мер, для обнуления массива A[1..10] of Real можно записать:

 

FillChar(A, 40, 0);

 

или

 

FillChar(A, SizeOf(A), 0);

 

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

I.

Var

A : Array [1..20] Of Array [1..30] Of Integer;

II.

Var

A : Array [1..20,1..30] Of Integer;

 

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

 

Отдельный элемент двумерного массива адресуется, естественно, двумя индексами. Например, ячейка, находящаяся в 5-й строке и 6-м столбце будет называться A[5][6] или A[5,6].

 

Для иллюстрации способов работы с двумерными массивами решим задачу: "Задать и распечатать массив 10X10, состоящий из целых случайных чисел в интервале [1,100]. Найти сумму элементов, лежащих выше главной диагонали."

 

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

 

Program M5;

Var

A : Array[1..10,1..10] Of Integer;

I, K : Byte;

S : Integer;

Begin

S:=0;

For I:=1 To 10 Do

Begin

For K:=1 To 10 Do

Begin

A[I,K]:=Trunc(Random*100)+1;

Write(A[I,K]:6);

If K>I Then S:=S+A[I,K]

End;

Writeln

End;

Writeln('Сумма элементов выше гл. диагонали равнаV',S)

End.

 

Если модель данных в какой-либо задаче не может свестись к линейной или плоской таблице, то могут использоваться массивы произвольной размерности. N-мерный массив характеризуется N индексами. Формат описания такого типа данных:

Type

<Имя типа>=Array[<диапазон индекса1>,<диапазон индекса2>,...

<диапазон индекса N>] Of <тип компонент>;

Отдельный элемент именуется так:

<Имя массива>[<Индекс 1>,<Индекс 2>,...,<Индекс N>]

 

Процедуры и функции

 

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

 

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

 

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

 

Подпрограммы в языке Паскаль могут иметь параметры (значения, передаваемые в процедуру или функцию в качестве аргументов). При описании указываются так называемые формальные параметры (имена, под которыми будут фигурировать передаваемые данные внутри подпрограммы) и их типы. При вызове подпрограммы вместе с ее именем должны быть заданы все необходимые параметры в том порядке, в котором они находятся в описании. Значения, указываемые при вызове подпрограммы, называются фактическими параметрами.

 

Формат описания процедуры:

Procedure <Имя процедуры> (<Имя форм. параметра 1>:<Тип>;

< Имя форм. параметра 2>:<Тип>?);

<Раздел описаний>

Begin

<Тело процедуры>

End;

 

Раздел описаний может иметь такие же подразделы, как и раздел описаний основной программы (описание процедур и функций - в том числе). Однако все описанные здесь объекты "видимы" лишь в этой процедуре. Они здесь локальны также, как и имена формальных параметров. Объекты, описанные ранее в разделе описаний основной программы и не переопределенные в процедуре, называются глобальными для этой подпрограммы и доступны для использования.

 

Легко заметить схожесть структуры программы целиком и любой из ее процедур. Действительно, ведь и процедура и основная программа реализуют некий алгоритм, просто процедура не дает решения всей задачи. Отличие в заголовке и в знаке после End.

 

Формат описания функции:

Function <Имя функции> (<Имя форм. параметра 1>:<Тип>;

< Имя форм. параметра 2>:<Тип>?) : <Тип результата>;

<Раздел описаний>

Begin

<Тело функции>

End;

 

В теле функции обязательно должна быть хотя бы команда присвоения такого вида: <Имя функции>:=<Выражение>;

 

Указанное выражение должно приводить к значению того же типа, что и тип результата функции, описанный выше.

 

Вызов процедуры представляет в программе самостоятельную инструкцию:

<Имя процедуры>(<Фактический параметр 1>, < Фактический параметр 2>?);

 

Типы фактических параметров должны быть такими же, что и у соответсвующих им формальных.

 

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

 

Приведем простейший пример использования подпрограммы.

 

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

Program Fn;

Var

A,B,C :Real;

Function Max(A,B:Real):Real; {Описываем функцию Max с формальными}

Begin {параметрами A и B, которая принимает }

If A>B Then Max:=A {значение максимального из них }

Else Max:=B {Здесь A и B - локальные переменные }

End;

Begin

Writeln('Введите три числа');

Readln(A,B,C);

Writeln('Максимальным из всех является ', Max(Max(A,B),C))

End.

 

Обратите внимание на краткость тела основной программы и на прозрачность действий внутри функции. Формальные параметры A и B, используемые в подпрограмме, не имеют никакого отношения переменным A и B, описанным в основной программе.

 

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

 

Передача параметров по ссылке отличается тем, что при описании подпрограммы перед именем переменной-формального параметра ставится служебное слово Var. Теперь использование в качестве фактических параметров выражений или непосредственных значений уже не допускается - они должны быть именами переменных.

 

Еще один классический пример. Задача: "Расположить в порядке неубывания три целых числа".

 

Program Pr;

Var

S1,S2,S3 :Integer;

Procedure Swap(Var A,B: Integer);{Процедура Swap с параметрами-переменными}

Var C : Integer; {C - независимая локальная переменная}

Begin

C:=A; A:=B; B:=C {Меняем местами содержимое A и B}

End;

Begin

Writeln('Введите три числа');

Readln(S1,S2,S3);

If S1>S2 Then Swap(S1,S2);

If S2>S3 Then Swap(S2,S3);

If S1>S2 Then Swap(S1,S2);

Writeln('Числа в порядке неубывания:V',S1,S2,S3)

End.

 

Работа с файлами

 

Тип-файл представляет собой последовательность компонент одного типа, расположенных на внешнем устройстве (например, на диске). Элементы могут быть любого типа, за исключением самого типа-файла. Число элементов в файле при описании не объявляется. Работа с физическими файлами происходит через так называемые файловые переменные.

 

Для задания типа-файла следует использовать зарезервированные слова File и Of, после чего указать тип компонент файла.

 

Пример:

Type

N = File Of Integer; {Тип-файл целых чисел}

C = File Of Char; {Тип-файл символов}

 

Есть заранее определенный в Паскале тип файла с именем Text. Файлы этого типа называют текстовыми.

 

Введя файловый тип, можно определить и переменные файлового типа:

Var

F1 : N;

F2 : C;

F3 : Text;

 

Тип-файл можно описать и непосредственно при введении файловых переменных:

Var

Z : File Of Word;

 

Файловые переменные имеют специфическое применение. Над ними нельзя выполнять никаких операций (присваивать значение, сравнивать и т.д.). Их можно использовать лишь для выполнения операций с файлами (чтение, запись и т.д.).

 

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

 

Перед тем, как осуществлять ввод-вывод, файловая переменная должна быть связана с конкретным внешним файлом при помощи процедуры Assign.

 

Формат:

Assign(<Имя файловой переменной>,<Имя файла>);

 

Имя файла задается либо строковой константой, либо через переменную типа Sting. Имя файла должно соответствовать правилам работающей в данный момент операционной системы. Если строка имени пустая, то связь файловой переменной осуществляется со стандартным устройством ввода-вывода (как правило - с консолью).

 

После этого файл должен быть открыт одной из процедур:

Reset(<Имя файловой переменной>);

Открывается существующий файл для чтения, указатель текущей компоненты файла настраивается на начало файла. Если физического файла, соответствующего файловой переменной не существует, то возникает ситуация ошибки ввода-вывода.

 

Rewrite(<Имя файловой переменной>);

Открывается новый пустой файл для записи, ему присваивается имя, заданное процедурой Assign. Если файл с таким именем уже существует, то он уничтожается.

 

После работы с файлом он, как правило, должен быть закрыт процедурой Close.

Close(<Имя файловой переменной>);

 

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

 

Теперь рассмотрим непосредственную организацию чтения и записи.

 

Для ввода информации из файла, открытого для чтения, используется уже знакомый вам оператор Read. Правда, в его формате и использовании вы заметите некоторые изменения:

Read(<Имя файловой переменной>, <Список ввода>);

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

 

Вывод информации производит, как можно догадаться оператор Write(<Имя файловой переменной>, <Список вывода>);

 

Данные из списка вывода заносятся в файл, открытый для записи.

Для текстовых файлов используются также операторы Readln и Writeln с соответствующими дополнениями, относящимися к файловому вводу-выводу. Любопытно, что вывод данных на монитор и ввод с клавиатуры в языке Паскаль тоже являются действиями с файлами. Они даже имеют свои предопределенные файловые переменные текстового типа: Output и Input соответственно. Переменная Output всегда открыта для записи, Input - для чтения. Если не указывать файловые переменные в операторах ввода-вывода (придем к формату, рассмотренному в теме "Операторы ввода-вывода"), то в случае записи по умолчанию выбирается файл Output, в случае чтения - Input.

Как вы знаете, любой файл конечен и продолжать чтение из него информации можно лишь до определенного предела. Как этот предел установить? Проверить, окончен ли файл, можно вызовом стандартной логической функции Eof(<Имя файловой переменной>)

Она вырабатывает значение True, если файл окончен, и False - в противном случае.

 

Решим следующую задачу: "Написать программу, которая вводит с клавиатуры список фамилий учащихся, а затем распечатывает его, кроме тех учащихся, у которых фамилия начинается с буквы 'Ш'".

 

Так как заранее количество данных не известно, то для их хранения используем файл. Тип элементов - строковый.

 

Program L;

Var

I,N : Integer;

F : File Of String;

S : String;

Begin

Assign(F,'Spis.lst'); {Связываем переменную F с файлом Spis.lst}

Writeln('Введите количество учащихся');

Readln(N); {Вводим количество учащихся}

Rewrite(F); {Создаем файл для записи в него данных}

For I:=1 To N Do {Для всех учащихся}

Begin

Writeln('Введите фамилию');

Readln(S);

Write(F,S)

End;

Close(F);

Reset(F);

Writeln; Writeln('Список учащихся:');

While Not(Eof(F)) Do

Begin

Read(F,S);

If S[1]<>'Ш' Then

Writeln(S)

End;

Close(F)

End.