Массивы в качестве параметров процедур и функций
Программисты, работавшие на Фортране, с грустью вспоминают времена, когда передача массива любой размерности в подпрограмму или функцию никаких забот не вызывала. Достаточно было написать нечто похожее на:
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) ; } }