Практика программирования (Бейсик, Си, Паскаль)

       

Массивы в качестве параметров процедур и функций


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

SUBROUTINE MATMULT(A,B,C,N)

REAL A(N,N),B(N,N),C(N,N),D

DO 1 J=1,N

DO 1 K=1,N D=0

DO 2 L=1,N

2 D=D+A(J,L)*B(L,K)

1 C(J,K)=D

END

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

Массивы-параметры в подпрограммах и функциях QBasic

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

Программа 4_01.bas



DECLARE SUB ADDMAT(A%(),B%(),C%(),N%) DEFINT A-Z CLS

DIM A1(2,2), A2(2,2),A3{2,2)

DIM Bl(3,3),B2(3,3),B3(3,3)

FOR J=0 TO 2 : FOR K=0 TO 2

A1(J,K)=J+K : A2(J,K)=J*K

NEXT К : NEXT J

CALL ADDMAT(Al(),A2(),A3() ,2) 'Так можно обратиться к подпрограмме

FOR J=0 TO 3 : FOR K=0 TO 3 B1(J,K)=J+K :

B2(J,K)=J*K

NEXT К : NEXT J

ADDMAT Bl(),B2(),B3(),3 'И так можно обратиться к подпрограмме END

SUB ADDMAT (A%(),В%(),С%(),N%)

DEFINT A-Z

FOR Q=0 TO N : FOR S=0 ТО N

C(Q,S)=A(Q,S)+B(Q,S)

NEXT S : NEXT Q

END SUB

Как в заголовке подпрограммы, так и в операторах обращения к ней массив-параметр сопровождается пустыми круглыми скобками, независимо от числа индексов, приписанных фактически передаваемым массивам. Никаких переобъявлений типа DIM A(N,N) в теле подпрограммы не нужно. Более того, попытка вставить такой оператор приведет к фиксации ошибки, сопровождаемой сообщением о повторном объявлении массива. Обратите внимание на то, что в заголовке подпрограммы использованы имена А%, B%, C%, N%, а в тексте подпрограммы вместо них выступают имена А, в, с, N. Своей эквивалентностью они обязаны оператору DEFINT A-Z, объявляющему все переменные внутри подпрограммы целочисленными.




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

Массивы-параметры в процедурах и функциях Паскаля

Первое ограничение, с которым сталкиваются программисты в Паскале, звучит следующим образом — если формальным параметром является массив, то его нельзя описать явным образом. Например, ошибочным является заголовок:

function Max(a:array [1..100] of integer):integer;

Правильным считается предварительное объявление типа в головной программе и его использование в заголовке функции:

type

mas100=array [1..100] of integer;

function Max(a:mas100):integer;

Однако такая функция обрекает нас на определение максимального элемента только в массиве типа masioo. А если в нашей программе понадобится обрабатывать массивы и с другим количеством элементов? Конечно, можно добавить еще один параметр — количество обрабатываемых элементов:

function Max(a:mas100; n:integer):integer;

Это позволит ограничиться первыми n элементами массива, но размерность фактического параметра может быть только mas100.

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

function Max(var a; n:integer):integer;

type

b=array [1.. Maxlnt] of integer;

var

m,k:integer;

begin

m:=b(a}[1];

for k:=2 to n do

if m<b(a)[k] then m:=b(a)[k] ;

Max:=m;

end;

Несколько необычная запись b(а) означает, что нетипизированный параметр а приводится к типу целочисленного массива, каковым он на самом деле и является. Только функция мах об этом "не знает". Может показаться немного странным выбор верхней границы индекса в типе "b". Однако он позволяет избежать неприятностей, когда фактическое значение параметра n достаточно велико (при этом может возникнуть аварийная ситуация, вызванная выходом индекса за границы диапазона). Каким бы большим не было значение п, оно не может превысить величину Maxint, ибо массивы в Паскале не могут быть более 64 Кбайт.



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

function Max(var a; n:integer):integer;

var

c:array[l.. Maxlnt] of integer absolute a;

m,k:integer;

begin

m:=c[l];

for k:=2 to n do

if m<c[k] then m:=c[k];

Max:=m;

end;

Конструкция с использованием служебного слова absolute означает, что массив с, под который дополнительное место не выделяется, расположен в памяти, начиная с того же адреса, что и массив а.

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

function Max(pa:pointer; n:integer):integer;

var

m,k:integer;

pb:^integer;

begin

pb:=ptr(зед(ра^) , ofs(ра^) ) ;

m:=рb^;

for k:=2 to n do

begin

pb:=ptr(seg(ра^),ofs(ра^)+k*2);

if m < рb^ then m:=рb^;

end;

Max:=m;

end;

Для вызова такой функции при определении максимального элемента в массиве q необходимо задавать адрес аргумента:

max_q:=Max(&q,100);

Этот пример демонстрирует работу с адресами и указателями в Паскале. В нем использована функция ptr, вычисляющая адрес элемента данных по сегменту (seg) и смещению (ofs). Адрес сегмента, задающий начало массива, у локачьного указателя ь совпадает с адресом массива a (seg(pb^)=seg(pa^)), а смещение очередного элемента вычисляется в цикле путем прибавления к смещению начального элемента ofs (ра^) приращения 2*k.

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

function Max(a:array of integer):integer;

var

m,k:integer;

begin

m:=a[0];

for k:=l to High(a) do

if m < a[k] then m:=a[k];

Max:=m; end;

Для открытых массивов появилась системная функция High, позволяющая узнать максимальный индекс. При использовании открытых массивов вы должны включить соответствующее указание компилятору — {$P+}.



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

Распространим некоторые идеи передачи параметров на двумерные массивы в задаче суммирования квадратных матриц. Первый пример использует нетипизированные переменные:

procedure sum+mat(var a,b,c; n:integer);

var

x:array [0..0] of integer absolute a;

y:array [0..0] of integer absolute b;

z:array [0..0] of integer absolute c;

j,k:integer;

begin {$R-}

for j :=0 to n-1 do

for k:=0 to n-1 do

z[j*n+k]:=x[j*n+k]+y[j*n+k];

{$R+} end;

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

Второй пример использует вариант с указателями:

procedure sum_mat(pa, pb, pc:pointer; n:integer);

var

a,b,с:^integer;

j,k:integer;

begin

for j:=0 to n-1 do

for k:=0 to n-1 do

begin

a:=ptr(seg(ра^),ofs(ра^)+(j*n+k)*2) ;

b:=ptr(seg(pb^),ofs(рb^)+(j*n+k)*2);

c:=ptr(seg(рс^),ofs(рс^)+(j*n+k)*2);

с^:=а^+b^;

end;

end;

Массивы-параметры в функциях Си

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

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

int Max(int *a, int n) {



int m, k;

m=a[0];

for(k=l; k<n; k++)

if (m < a[k]) m=a[k];

return m; }

Предлагаемый вариант очень напоминает работу с открытым массивом с той лишь разницей, что функции типа High в Си нет. Поэтому список параметров дополнен еще и количеством элементов в массиве а.

Абсолютно эквивалентным является вариант с использованием арифметики указателей:

int Max(int *a, int n) {

int m,k;

m=*a;

for(k=l; k<n; k++)

if (m < *(a+k) m=*(a+k);

return m; }

Следующий пример демонстрирует сложение квадратных матриц с приведением адресов элементов двумерного массива к их одномерным аналогам:

void sum_mat(int *a, int *b, int *c, int n) {

int j,k,m;

for(j=0; j < n; j++)

for(k=0; k < n; k++) {

m=j*n+k;

* (c+m)=* (a+m) + *(b+m) ; } }




Содержание раздела