Чтобы сделать подпрограмму более гибкой и обеспечить ее общность, данные в подпрограмму передаются через список формальных параметров. Формальные параметры задают не какие-то конкретные данные программы, а лишь условно определяют объекты подпрограммы. Это позволяет применять подпрограммы к различным объектам программы, конкретизируя при каждом обращении ее данные в списке фактических параметров. Все объекты подпрограммы можно разделить на входные данные, промежуточные и выходные. В список формальных параметров необходимо вводить только те объекты, которые изменяются при вызове подпрограммы, т.е. входные и выходные данные.
Формальные параметры в Си задаются в списке формальных параметров перечислением через запятую спецификаций отдельных параметров. Спецификация каждого параметра имеет вид:
<спецификация типа> <имя параметра>
Список фактических параметров задается перечислением через запятую фактических данных, над которыми функция должна выполнять действия при данном вызове. При обращении к функции формальные параметры заменяются на фактические. Между списком формальных и списком фактических параметров устанавливается соответствие по их взаимному расположению в списках: первому формальному параметру соответствует первый фактический параметр и т. д. При этом соблюдается соответствие параметров по типам.
Связь между фактическими и формальными параметрами может устанавливаться двумя способами:
– связь по значению – значение фактического параметра присваивается формальному. Действия в подпрограмме выполняются над формальной переменной, получившей значение фактического параметра. Такой способ используется, прежде всего, для передачи входных данных в подпрограмму;
– связь по адресу (ссылке) – фактический параметр заменяет в теле подпрограммы формальный и подпрограмма получает доступ к фактическому параметру. Если говорить более точно, то формальному параметру присваивается адрес (ссылка) фактического параметра. Значение такого формального параметра при его использовании в операциях рассматривается как адрес того объекта, над которым надо выполнить эту операцию. Такой способ используется как для передачи входных данных, так, и, прежде всего, для выходных данных (результатов).
В Си связь между формальными и фактическими параметрами устанавливается по значению. Фактический параметр в этом случае – выражение того же типа, что и соответствующий ему формальный. В момент вызова функции вычисляется значение фактического параметра и присваивается формальному параметру.
Пример определения функции с параметрами:
int MAX (int a, int b)
{ return a > b ? a : b ; }
Функция MAX выполняет действия над формальными переменными a и b (входные данные функции). Переменные a и b получают значения фактических параметров при обращении к MAX (т.е. связь между параметрами устанавливается по значению). Результат функции возвращается в точку вызова при выполнении оператора return. Обращение к функции MAX – операнд выражения, например : x = MAX (n+1, m*m ); Переменной x будет присвоено значение функции MAX, т.е. наибольшее из значений выражений n+1 и m*m.
Если через список формальных параметров должен передаваться результат функции, то формальный параметр должен быть типа указатель, а соответствующий ему фактический параметр это выражение, значение которого адрес переменной результата.
Пример определения функции с входными и выходными параметрами:
void MAX (int a, int b, int *c)
{ *c = a > b ? a : b ; }
Функция MAX выполняет действия над формальными переменными a , b и с. Переменные a , b и c получают значения фактических параметров при обращении к MAX (т.е. связь между параметрами устанавливается по значению). Результат функции передается через формальный параметр типа указатель - с. Фактический параметр, соответствующий с – это адрес переменной, которой надо присвоить результат. При выполнении оператора присваивания, в левой части которого стоит выражение *c, происходит обращение к области памяти (к переменной), адрес которой является значением с. И туда помещается вычисленное значение правой части оператора присваивания. Обращение к функции MAX – оператор вызова функции, например : MAX ( n+1, m*m, y); Переменная y получает значение наибольшего из выражений n+1 и m*m.
Связь по адресу в Си используется только для передачи в функцию параметра типа массив и для передачи в качестве параметра функции.
Пример1. Программа с функцией, параметры которой типа массив
# include <stdio.h>
# define n 15
# define n1 10
# define n2 20
void MULT (float a[ ][n1], float b[ ][n2], float[ ][n2], int n, int m, int p)
{ int i, j, k ;
float s;
for( i=0; i < n; i++ )
for( j=0; j < p; j++ )
{ for( s=k=0; k < m; k++ )
s += a[i][k] * b[k][j];
c[i][j] = s; }
}
void main ( )
{ float x[n] [n1], y[n1] [n2], z [n] [n2];
int i, j, k, str, stb1, stb2 ;
printf (“\n введи размерность матриц \n”);
scanf (“%d%d%d”, &str, &stb1,&stb2);
printf (“\n введи первую матрицу \n”);
for( i=0; i < str; i++ )
for( j=0; j < stb1; j++ ) scanf (“%f ”, &x[i][j] );
printf (“\n введи вторую матрицу \n”);
for( i=0; i < stb1; i++ )
for( j=0; j < stb2; j++ ) scanf (“%f ”, &y[i][j] );
MULT(x, y, z, str, stb1, stb2);
printf (“\n произведение матрицы x на матрицу y \n”);
for( i=0; i < str; i++ )
{ for( j=0; j < stb2; j++ ) printf (“%f ”, z[i][j] );
printf (“\n”); }
}
Здесь a, b и c – формальные параметры типа массив функции MULT. Они являются входными данными функции (массивы a и b) и выходными (массив c). Формальные параметры целого типа n, m, p – входные данные функции MULT – размерности матриц. При обращении к MULT из главной функции формальному параметру a соответствует фактический массив x, формальному b соответствует фактический y, параметру c – фактический массив z. Связь между параметрами устанавливается по адресу. Это значит, что формальному параметру a присваивается адрес фактического массива x, формальному b присваивается адрес фактического массива y, а формальному c адрес фактического z. Таким образом, формальные параметры a, b, c на самом деле параметры типа указатель на объекты типа float. Поэтому размерность формального массива по первому измерению можно опускать (остальные размерности не опускаются, т.к. они необходимы для вычисления адреса элемента массива). При выполнении действий над этими параметрами из них извлекается адрес фактического массива и, действия выполняются над элементами фактического массива. Таким образом, при присваивании c[ i,j] значения переменной s происходит обращение к элементу z[ i,j] фактического массива, который и получает значение s.
Формальным параметрам n, m, p ставятся в соответствие фактические str, stb1, stb2. Связь между этими параметрами устанавливается по значению. Это значит, что формальному параметру n присваивается значение фактической переменной str, формальной m присваивается значение фактической переменной stb1, а формальной p значение фактической stb2. Таким образом, в функции действия выполняются над формальными переменными n, m, p, получившими значения фактических переменных.
Так как имени массива соответствует адрес начала массива, то результирующий массив можно передавать в вызывающую функцию, возвращая указатель на него.
Пример 2. Функция с параметрами и возвращаемым результатом типа указатель
int *max_vect ( int n, int *x, int *y )
{ static int z [nmax ], i;
for (i=0; i < n; i++)
z [i] = x[ i] > y[ i] ? x[ i] : y[ i];
return ( &z ); }
Здесь nmax именованная внешняя константа, а возвращаемый результат - адрес локального массива z функции max_vect. Входные параметры x, y – указатели на величины типа int. Обратиться к функции можно, например, из оператора p = max_vect (10, a, b); или p = max_vect ( 10, &a[0], &b[0]). В первом случае a и b имена фактических массивов и связь между формальными параметрами x и y и фактическими массивами a и b устанавливается по адресу. Во втором случае все фактические параметры – выражения, способ связи между формальными и фактическими параметрами – по значению. Переменная p – это переменная типа указатель на величину типа int.
Строгое согласование по типам между формальными и фактическими параметрами требует, чтобы до первого обращения к функции, появилось либо ее определение (см. пример 1.) либо ее описание, содержащее сведения о типе возвращаемого результата и о типах всех параметров. Описание функции называется прототипом функции. Синтаксис прототипа почти полностью совпадает с заголовком функции в ее определении:
<cпецификация типа результата функции> <имя функции> ( [ <cписок формальных параметров> ] )
| void <имя функции> ( [ <cписок формальных параметров> ] )
Основные отличия: 1) в конце прототипа ставится точка с запятой, 2) имена формальных параметров можно опускать.
Пример 3. Программа с определением и прототипом функций
# include <stdio.h>
int MAX (int a, int b) //Определение до вызова функции
{ return a > b ? a : b ; }
void main ( )
{ float cube ( float x); //Прототип до определения и вызова функции
int Max, p = 2; scanf (“%d ”, &Max );
Max = MAX (( int) cube ( (float) p), Max); //Явные преобразования типов фактических параметров
printf (“\n наибольшее равно %d ”, Max );
}
float cube ( float x) //Определение функции
{ return x*x*x ; }
Преобразования типов фактических параметров при обращении к функции cube - (float) p и функции MAX (первый параметр) требуются для согласования типов формальных и фактических параметров. Прототип функции cube необходим, так как ее определение размещено после обращения к ней.
В программе с многоуровневой структурой возникает проблема взаимодействия объектов, описанных в разных частях и модулях программы. В языках с блочной структурой принят следующий принцип локализации объектов: объекты, вводимые в употребление в блоке, могут использоваться только в пределах данного блока. В подпрограмме могут действовать три вида объектов:
– локальные,
– формальные,
– глобальные.
Локальные объекты – это объекты, описанные в блоке. Их область действия – блок подпрограммы: от точки их определения до конца блока. При вложенности блоков внутри блока доступны и объекты, определенные во внешних блоках. Повторное определение имени во внутреннем блоке (т.е. повторение имени во внутреннем блоке) изменяет видимость объекта с таким же именем из внешнего блока: внутри блока сфера действия имен, определенных во внешних блоках сохраняется до точки их повторного описания. После выхода из блока восстанавливается видимость внешних объектов, переопределенных внутри блока.
Формальные объекты – это объекты, описанные в заголовке определения подпрограммы. Область действия этих объектов – тело подпрограммы. Формальные параметры это также локальные объекты подпрограммы.
Область действия формальных параметров, описанных в прототипе функции – прототип функции.
Глобальные объекты – это объекты, описанные вне всех функций (сама функция тоже является глобальным объектом). Сфера действия глобальных имен - файл (модуль) с текстом программы от точки описания имени до конца файла. Внутри каждой функции они имеют один и тот же смысл. Повторное определение глобального объекта в теле функции изменяет его видимость. Внутри функции сфера действия глобальных имен сохраняется до точки их повторного описания. После выхода из функции (или внутреннего блока, содержащего повторное описание глобального имени) восстанавливается видимость глобальных объектов, переопределенных внутри функции. Если глобальный объект определен позже своего первого использования, то он должен быть описан в той функции, где он используется с атрибутом extern.
Пример 4. Программа с переопределенными именами объектов
# include <stdio.h>
сhar st [ ] = “СТРОКА 1”; //Определение глобального объекта
void f1 ( void )
{ printf ( “%s”, st ) ; //Вывод значения глобальной st
сhar st [ ] = “СТРОКА 2”; //Определение локального объекта в блоке 1
printf ( “%s”, st ) ; //Вывод значения локальной st блока 1
scanf ( “%s”, &st ) ; //Ввод нового значения локальной st блока 1
{ сhar st [ ] = “СТРОКА 3”; //Определение локального объекта в блоке 2
printf ( “%s”, st ) ; //Вывод значения локальной st блока 2
} //Конец блока 2
printf ( “%s”, st ) ; //Вывод значения локальной st блока 1
}
void f2 ( char *st )
{ printf ( “%s”, st ) ; //Вывод значения формальной st
{сhar st [ ] = “СТРОКА 4”; //Определение локального объекта в блоке 3
printf ( “%s”, st ) ; //Вывод значения локальной st блока 3
} //Конец блока3
printf ( “%s”, st ) ; //Вывод значения формальной st
}
void main ( )
{ printf ( “%s”, st ) ; //Вывод значения глобальной st
scanf ( “%s”, &st ) ; //Ввод нового значения глобальной st
extern int p; //Описание глобальной переменной p
if ( p ) f1( ); else f2 ( st );
printf ( “%s”, st ) ; //Вывод значения глобальной st
}
int p = 5; //Определение с инициализацией глобальной переменной p
Имя переменной st типа строка определено вне функций и переопределяется в функциях f1 и f2. Комментарии показывают видимость имени st в разных блоках программы. Таким образом, глобальные объекты позволяют осуществлять непосредственную связь подпрограмм по данным.
Контрольные вопросы
1. Понятие нисходящего проектирования.
2. Понятие подпрограммы.
3. Понятие функции.
4. Определение функции на языке Си.
5. Способы передачи данных в подпрограмму.
6. Установление связи между формальными и фактическими параметрами.
7. Обращение к функции.
8. Понятие локального, формального, глобального объектов подпрограммы.
9. Принципы взаимодействия объектов, определенных в блоках, в функциях, в модуле.
10.Рекурсивные подпрограммы.