Передача массивов в качестве параметров в подпрограммы. Открытые массивы

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

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

Пример 1. В процедуру сортировки SORT необходимо передавать целочисленные массивы длины 10. Для этого вначале в вызывающем блоке описывают соответствующий тип int_ar_10, который затем используем в заголовке процедуры:

type int_ar_10 = array [1..10] of integer;

. . .

procedure SORT(ent_mas:int_ar_10; var exit_mas:int_ar_10);

Рассмотренная конструкция проста, однако процедуру SORT можно использовать только для сортировки целочисленных массивов длины 10.

Если необходимо упорядочивать массивы различной длины, например, содержащих от 1 до 100 элементов, простейший выход заключается в описании типа int_ar_100с числом элементов (100) и дополнительной передаче в процедуру еще одного параметра, задающего действительную длину обрабатываемого массива. Данный способ относительно прост, однако он подразумевает наложение ограничения на длину массива (для 101 элемента процедура уже не подходит). Также при обработке коротких массивов нерационально будет использоваться память из-за того, что в соответствующих переменных типа int_ar_100будет использоваться только малая часть элементов.

Требование объявления типа массива в разделе описания typeтакже нарушает требование переносимости подпрограмм, поскольку их корректная работа в этом случае зависит от обязательного выполнения некоторых дополнительных внешних действий.

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

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

Применение открытых массивов не предусматривает передачи длины массива-параметра в подпрограмму в качестве ее отдельного параметра (как при использовании статических массивов избыточной длины). Для отслеживания границ одномерного массива-параметра внутри подпрограммы необходимо использовать стандартные функции Low и High. Их единственным параметром является идентификатор массива, Low возвращает начальное значение его индекса, а High -- конечное. Обычно динамический массив начинается с элемента с номером 0, однако надежнее применять функцию Low.

Пример 2. Рассмотрим использование процедуры сортировки SORT с использованием открытых массивов для обработки целочисленных массивов произвольной длины, которая задается в вызывающем блоке. Для этого: 1) входнойent_mas и выходнойexit_mas массивы процедурыSORT описаны как открытые, без указания длины, 2) для отслеживания границ массивов ent_mas и exit_mas в теле SORT использованы переменные i_first, i_fin(ent_mas) и j_first, j_fin(exit _mas), в которые номер начального и конечного элементов массивов засылаются при помощи функций Low и High, 3) поскольку длины входного и выходного массивов должны быть одинаковы, то дополнительно выполняется проверка (i_fin-i_first) <> (j_fin - j_first). Схема программного кода с комментариями имеет вид:

var a,b:array [1..8] of integer;{описание во внешней программе статических массивов a,b}

procedure SORT(ent_mas: array of integer; var exit_mas: array of integer);

var i_first,i_fin,j_first,j_fin:integer;{описание в SORT переменных для границ массивов}

begin{начало тела процедуры SORT}

i_first:=Low(ent_mas);i_fin:=High(ent_mas);{определение границ массиваent_mas}

j_first:=Low(exit_mas);j_fin:=High(exit_mas);{определение границ массива exit_mas}

if (i_fin-i_first)<>(j_fin-j_first) then begin

wirteln (’ Different lengths of arrays in proc. SORT, EXIT!’); exit end;

. . .

end;{конец тела процедуры SORT}

begin{начало тела внешней программы}

. . .

SORT(a,b); {вызов процедуры SORT с подстановкой в нее статических массивов a,b }

. . .

end. {конец тела внешней программы}

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

Пример 3. Рассмотрим решение задачи из примера 2 для случая, когда размерность входного линейного массива заранее неизвестна и во входном блоке. Она должна вводиться с клавиатуры, после чего также вводятся величины элементов массива. Поскольку в подпрограмме SORT все сохраняется, рассмотрим только код вызывающего блока:

var a,b:array of integer;{описание во внешней программе динамических массивов a,b}

N:integer;

Procedure SORT . . .

begin{начало тела внешней программы}

writeln ('Vvedite razmernost massiva:');read(n);{запрос и ввод размерности n}

SetLength (a,n); SetLength (b,n);{Задание длины nдинамическим массивамaи b}

for i:=1 to n do begin {ввод элементов массиваa с клавиатуры}

writeln('vvedite element a[',i,']=');{запрос на ввод элемента a[i]}

read(a[i]){ввод элемента a[i] в линейный динамическиймассив a }

End;. . .

SORT(a,b); {вызов процедуры SORT с подстановкой в нее динамических массивов a,b }

. . .

SetLength(a,0); SetLength(b,0); {Удаление динамических массивов a и b}

end. {конец тела внешней программы}

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

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

а) ввод с клавиатуры числа строк n и столбцов m целочисленной матрицы M[1..n,1..m],

б) ввод с клавиатуры элементов матрицы M[1..n,1..m],

в) формирование с помощью отдельной процедуры SUM_STRвектора S[1..n], состоящего из сумм элементов в строках матрицы,

г) вывод вектора S[1..n]на экран,

д) удаление всех динамических массивов программы.

Решение. Обозначим номера строк и столбцов матрицы через i и j, динамический линейный массив, соответствующий ей, через LM. Функция k = k (i, j, m)для расчета номеров коэффициентов LM по коэффициентам матрицы и числу столбцов взята из примера 5 п.8.5. Число столбцов матрицы m передается в процедуру суммирования элементов строк SUM_STR по двум причинам: для использования в функции k (i, j, m) и для расчета числа строк n (чтобы не передавать его через заголовок процедуры). При задании номеров элементов необходимо учитывать, что в матрице начало отсчета индексов с 1, а в динамических массивах – с 0.

var LM: array of integer;{ Описание дин. массиваLM}

S: array of integer;{ Описание дин. массиваS}

n,m,i,j,el: integer;

function k(i,j,m:integer):integer; {функция для расчета k = k (i, j, m) = (i-1)m +(j-1) }

begin k:= (i-1)*m + j -1; end;

procedure SUM_STR(L:array of integer; m:integer; var STR_S:array of integer);

{процедура для расчета вектора из сумм элементов в строках матрицы }

var k,n,i_first,i_fin,i,j:integer;

begin{начало тела процедуры SUM_STR }

i_first:=Low(L);i_fin:=High(L);{определение границ массиваL}

n:=(i_fin-i_first+1) div m;{определение количества строкn в матрице, соответствующей динамическому массиву L}

for i:=1 to n do STR_S[i-1]:=0;{начальная инициализация массива STR_S нулями}

for i:=1 to n do{расчет элементов массива STR_S}

for j:=1 to m do

begin k:=(i-1)*m+j-1; STR_S[i-1]:=STR_S[i-1]+L[k] end;

end; {конец тела процедуры SUM_STR }

begin{ начало тела основной программы}

writeln (' Vvedite сhislo strok:');read(n);{запрос и ввод числа строк n}

writeln (' Vvedite сhislo stolbtsov:');read(m);{запрос и ввод числа строк m}

SetLength (LM,n*m);{Создание динамического массиваLMдля ввода матрицыM[1..n,1..m] }

SetLength (S,n);{Создание динамического массиваSдля сумм элементов в столбцах матрицы }

for i:=1 to n do{ввод элементов матрицы с клавиатуры}

for j:=1 to m do begin

writeln('vvedite element M[',i,',',j,']=');{запрос на ввод элемента M[i,j]}

read(el); LM[k(i,j,m)]:=el;{ввод элемента M[i,j] в линейный массив LM }

End;

for i:=1 to n do begin{вывод матрицы M[1..n,1..n] на экран}

for j:=1 to m do write(' M[',i,',',j,']=',LM[k(i,j,m)]:6);{вывод строки}

writeln;{переход на очередную строку}

End;

SUM_STR(LM, m, S); {вызов процедуры SUM_STR }

for i:=0 to n-1 do{Печать динамического массиваS }

writeln(' Sum of elements in stroke ',i+1,'=',S[i]:6);

SetLength(LM,0); SetLength(S,0); //Удаление динамических массивов LM и S

end. {конец тела основной программы}

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

Вопросы для проверки знаний.

1. Как передают в подпрограммы статические массивы постоянного размера с использованием описания их типа в вызывающем блоке ?

2. Как передают в подпрограммы статические массивы ограниченного размера с использованием описания их типа в вызывающем блоке ?

3. Где и с какой целью применяют открытые массивы ?

4. Какие ограничения снимает использование открытых массивов в случае передачи в подпрограммы массивов различного размера ?

5. Какие ограничения снимает передача через открытые массивы динамических массивов по сравнению со статическими ?

6. Можно ли использовать открытые массивы для передачи многомерных динамических массивов переменных размеров ?

Практические задания.

1. Разработать варианты а) - г) программы, в которой производится ввод с клавиатуры двух векторов`a = (a1,...,an) и`b = (b1,...,bn) и последующий расчет их скалярного произведения (`a ,`b) = (a1b1 +...+anbn) с помощью отдельной функции Sc(`a ,`b ):

а) с использованием для векторов`a и`b статических массивов постоянной длины n=3 и опиcания типа массива в вызывающем блоке;

б) с использованием для векторов`a и`b статических массивов переменной длины 1 ≤ n ≤ 100 и опиcания типа массива в вызывающем блоке;

в) с использованием для векторов`a и`b статических массивов переменной длины 1 ≤ n ≤ 100 и открытых массивов в заголовке кода функции Sc(`a ,`b );

г) с использованием векторов`a и`b произвольной длины n, динамических массивов в вызывающем блоке и открытых массивов в заголовке кода функции Sc(`a ,`b ).


 

Пока же рассмотрим примеры решения типовых задач.

1. Заданы вектора A и B, содержащие по 5 элементов. Используя подпрограмму, найти их скалярное произведение по формуле

Поиск скалярного произведения реализуем в виде подпрограммы-функции scal.

type vector=array [1..5] of real;

 

function scal (n:integer;

var a,b:vector):real;

var i:integer;

s:real;

begin

s:=0;

for i:=1 to n do s:=s+a[i]*b[i];

scal:=s;

end;

 

var i:integer;

a,b:vector;

s:real;

begin

writeln ('Вектор 1 из 5 элементов:');

for i:=1 to 5 do read (a[i]);

writeln ('Вектор 2 из 5 элементов:');

for i:=1 to 5 do read (b[i]);

s:=scal(5,a,b);

writeln ('s=',s:10:3);

end.

2. Сформировать по введенному с клавиатуры вектору A размерности n вектор res, компонентами которого являются отклонения элементов A от их арифметического среднего (подобная задача уже решалась выше, расширим ее на случай вектора).

Задача предполагает написание, по меньшей мере, двух подпрограмм: функция Middle будет вычислять арифметическое среднее элементов вектора, а процедура Otkl - формировать по вектору A и ранее найденному среднему mid искомый вектор отклонений b. Компоненты вектора b при этом будут вычисляться по формуле . Поскольку о размерности векторов в задаче ничего не сказано, укажем в разделе type максимальную размерность, равную 100 элементам.

type vector= array [1..100] of real;

 

function Middle (n:integer;

var a:vector):real;

var j:integer;

res:real;

begin

res:=0.0;

for j:=1 to n do res:=res+a[j];

Middle:=res/n;

end;

 

procedure Otkl (n:integer; mid:real;

var a,b:vector);

var j:integer;

begin

for j:=1 to n do b[j]:=abs(a[j]-mid);

end;

 

var a,res: vector;

i,n:integer;

s:real;

begin

write ('Размерность? ');

readln (n);

for i:=1 to n do begin

write ('A[',i,']=');

readln (a[i]);

end;

s:=Middle (n,a);

Otkl (n,s,a,res);

for i:=1 to n do

writeln ('res[',i,']=',res[i]:8:2);

end.

3. Используя подпрограмму, написать и проверить программу перемножения двух матриц.

Как известно, матрица A размерностью n3m может быть умножена на матрицу B размерностью m3p по следующей формуле: где ci,j -- элемент получающейся в результате перемножения матрицы С размерностью n3m. Из формулы видно, что для умножения двух матриц нам понадобится тройной цикл: внешний цикл по i перебирает строки матрицы A, вложенный в него цикл по j выбирает в матрице B очередной столбец, а самый внутренний цикл по l умножает строку матрицы A на столбец матрицы B, получая элемент ci,j. Напишем соответствующую процедуру mmul и тестовую программу для нее:

type matrix=array[1..10,1..10] of real;

 

var a,b,c: matrix;

i,j,n,m,p: integer;

 

procedure mmul (n,m,k:integer;

var a,b,c:matrix);

var i,j,l:integer; s:real;

begin

for i:=1 to n do

for j:=1 to k do begin

s:=0;

for l:=1 to m do s:=s+a[i,l]*b[l,j];

c[i,j]:=s;

end;

end;

 

begin

repeat

writeln;

write ('Введите количество строк ',

'1 матрицы: ');

readln (n);

write ('Введите количество столбцов ',

'1 матрицы: ');

readln (m);

write ('Введите количество столбцов ',

'2 матрицы: ');

readln (p);

until (n>1) and (n<11) and (m>1)

and (m<11) and (p>1) and (p<11);

for i:=1 to n do begin

writeln ('Введите строку ',i,

' матрицы A из',m,'элементов:');

for j:=1 to m do read (a[i,j]);

end;

for i:=1 to m do begin

writeln ('Введите строку ',i,

' матрицы B из',p,'элементов:');

for j:=1 to p do read (b[i,j]);

end;

 

mmul (n,m,p,a,b,c);

for i:=1 to n do begin

writeln;

for j:=1 to p do write (c[i,j]:10:3);

end;

end.

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

5. В качестве еще одного развернутого примера на использование массивов в подпрограммах, разберем следующую задачу.

Имеется N городов, между которыми налажены пассажирские перевозки. Между какими городами самый активный пассажиропоток?

Количество городов обозначим константой cities. После математической формализации задачи, нетрудно заметить, что перевозки из города i в город j могут быть занесены в элемент матрицы ai,j, таким образом, требуется определить величину max(ai,j+aj,i), учитывая перевозки "туда" и "обратно". Для поиска максимального пассажиропотока достаточно двойного цикла с переменной границей по счетчику вложенного цикла. Как и в других программах, выделим в отдельные подпрограммы также типовые задачи ввода и вывода матрицы, а также ввода вещественного значения с контролем допустимых числовых границ ввода.

const cities=10;

type matrix=array [1..cities,1..cities]

of integer;

 

function max1 (n:integer; var a:matrix;

var imax,jmax:integer):integer;

var i,j,m,p:integer;

begin

m:=a[1,2]; imax:=1; jmax:=2;

for i:=1 to n do

for j:=1 to n do

if (i<>j) then begin

p:=a[i,j]+a[j,i];

if p>m then begin

m:=p; imax:=i; jmax:=j;

end;

end;

max1:=p;

end;

 

function readNumber (s:string;

min,max:integer):integer;

var a:integer;

begin

repeat

write (s);

{$I-}readln(a);{$I+}

if IoResult<>0 then

writeln ('Ошибка, введено не число!')

else if (a<min) or (a>max) then

writeln ('Ошибка, введенное число ',

'принадлежит интервалу [',min, ',',

max, ']')

else break;

until false;

readNumber:=a;

end;

 

procedure readMatrix1 (var n:integer;

var a:matrix);

var i,j:integer; s,s1:string;

begin

n:=readNumber ('Введите число строк ',

'и столбцов матрицы: ',2,cities);

for i:=1 to n do

for j:=i+1 to n do begin

s:='A['; str(i,s1); s:=s+s1+',';

str(j,s1); s:=s+s1+']=';

a[i,j]:=readNumber (s,0,maxInt);

end;

end;

 

procedure writeMatrix1 (s:string;

n:integer; var a:matrix);

var i,j:integer;

begin

writeln (s);

for i:=1 to n do begin

for j:=1 to n do write (a[i,j]:7);

writeln;

end;

end;

 

var a:matrix;

n,gorod1,gorod2:integer;

 

begin

readMatrix1 (n,a);

max1 (n,a,gorod1,gorod2);

writeMatrix1 ('A=',n,a);

writeln ('Самый большой пассажиропоток ',

'между городами ',gorod1,' и ',gorod2);

readln;

end.