Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Основы программирования. Учебное пособие

.pdf
Скачиваний:
0
Добавлен:
12.08.2026
Размер:
955 Кб
Скачать

6.5. ФУНКЦИИ С ПАРАМЕТРАМИ

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

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

<спецификация типа> <имя параметра>

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

Связь между формальными и фактическими параметрами может устанавливаться двумя способами:

связь по значению – значение фактического параметра присваивается формальному. Действия в подпрограмме выполняются над формальной переменной, получившей значение фактического параметра;

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

51

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

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

int MAX2 (int a, int b)

{ return a > b ? a : b ; }

Функция MAX2 выполняет действия над формальными переменными a и b (входные данные функции). Переменные a и b получают значения фактических параметров при обращении к MAX2 (т. е. связь между параметрами устанавливается по значению). Результат функции возвращается в точку вызова при выполнении оператора return. Обращение к функции MAX2 – операнд выражения, например: x = MAX2 ( n+1, m*m ); Переменной x будет присвоено значение функции MAX2, т. е. наибольшее из значений выражений n+1 и m*m.

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

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

ным параметрами (задача нахождения наибольшего значения из двух переменных):

void MAX3 (int a, int b, int *c) { *c = a > b ? a : b ; }

Функция MAX3 выполняет действия над формальными переменными a , b и с. Переменные a , b и c получают значения фактических параметров при обращении к MAX3 (т. е. связь между параметрами устанавливается по значению). Результат функции передается через формальный параметр типа указатель – с. Фактический параметр, соответствующий с – это адрес переменной, которой надо присвоить результат. При выполнении оператора присваивания, в левой части которого стоит выражение *c (здесь * – это операция разыменования указа-

52

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

MAX3 ( n+1, m*m, &y );

(здесь & – это операция получения адреса переменной). Переменная y получает значение наибольшего из выражений n+1 и m*m.

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

int MAXMIN (int x, int y, int z, int *max ) { int min;

if (x > y) if (x > z) {*max =x;

if (y < z) min = y; else min = z;} else { *max = z; min = y;}

else if (y > z) { *max = y;

if (x < z) min = x; else min = z;} else {*max = z; min = x;}

return min ; }

Один результат это наименьшее значение из трех величин x, y, z, он возвращается как основной результат функции. Второй результат – наибольшее из x, y, z, для его возврата используется формальный параметр max. Вызов подпрограммы: int r = MAXMIN (a, b, c, &x);

6.6. ФУНКЦИИ С ПАРАМЕТРАМИ ТИПА МАССИВ

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

Пример определения процедуры с параметром типа массив (за-

дача возведения в квадрат элементов массива):

вариант 1:

void sqrarray (float a[ ], int n) {int i;

for (i = 0; i < n; i++) a[i] *=a[i]; }

53

вариант 2:

void sqrarray (float *a, int n) { int i;

for (i = 0; i < n; i++) a[i] *=a[i]; }

вариант 3:

void sqrarray (float *a, int n) {int i;

for (i = 0; i < n; i++) *(a + i) *= *(a+ i);}

вариант 4:

void sqrarray (float *a, int n) {int i;

for (i = 0; i < n; i++) *a *= *a++;}

Все четыре варианта равнозначны, но в первом варианте формальный параметр a задан более наглядно, он имеет тип массив. Во 2–4 вариантах используется то, что имя массива является указателем на начало массива, поэтому формальный параметр задан как параметр типа указатель. В 4-м варианте изменение параметра a (a++) приводит к вычислению указателя (адреса) на очередной элемент массива. Вызов подпрограммы sqrarray:

1) const int n = 20; float x [n]; sqrarray (x, n);

Все 20 элементов массива x будут возведены в квадрат.

2)const int n = 20; float x [n]; sqrarray (x, n / 2);

Первые 10 элементов x будут возведены в квадрат.

3)const int n = 20; float x [n]; sqrarray (&x[10], 10);

Последние 10 элементов x будут возведены в квадрат.

Пример определения процедуры с параметром типа двумерный массив (задача поиска строки квадратной матрицы с наибольшей суммой элементов):

const int N =25;

void matr (int a[ ][N], int n, int b[ ]) { int i, j, k, s, max;

for ( s = 0, i = 0; i < n; i++ ) s += a[0][i];

54

max = s; k = 0;

for ( i = 0; i < n; i++ ) { s = 0;

for ( j = 0; j < n; j++ ) s += a[i][j]; if (s > max){max = s; k = i;}

}

for ( i = 0; i < n; i++ ) b[i] = a[k][i];

}

 

Вызов процедуры:

 

main ( )

 

{int d [N][N], c[N], m;

 

. . .

 

matr (d, m, c);

// поиск строки среди всех строкматрицы

matr (d[2], m-2, c);

// поиск строки среди строк матрицы с

 

// номерами от 2 до m – 1

matr (&d[m/2][0], m/2, c);

// поиск строки среди строк матрицы с

 

// номерами от m/2 до m-1

}

 

Пример программы с функциями, параметры которых типа

массив (задача вычисления произведения матриц Zn×n2

#include <stdio.h>

#include <conio.h > const int n = 15; const int n1= 10; const int n2 = 20;

void MULT (float a[ ][n1], float b[ ][n2], float c[ ][n2], { 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; }

}

= Xn×n1·Ynn2)

int n, int m, int p)

55

void input (float a[ ][m], int n, int m) { int i, j;

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

for ( j = 0; j < m; j++ ) scanf ("%f ", &a[i][j] );

}

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"); input (x, str, stb1);

printf ("\n введите вторую матрицу \n"); input (y, stb1, stb2);

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"); }

getch ( );

}

Здесь 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. Поэтому размерность формального массива по первому измерению можно опускать (остальные размерности не опускаются, т.к. они необходимы для вычисления адреса элемента массива). При выполнении действий над этими параметрами из них извлекается адрес фактического массива и, действия выполняются над элементами фактиче-

56

ского массива. Таким образом, при присваивании c[i][j] значения переменной s происходит обращение к элементу z[i][j] фактического массива, который и получает значение s.

Формальным параметрам n, m, p ставятся в соответствие фактические str, stb1, stb2. Связь между этими параметрами устанавливается по значению. Это значит, что формальному параметру n присваивается значение фактической переменной str, формальной m присваивается значение фактической переменной stb1, а формальной p значение фактической stb2. Таким образом, в функции действия выполняются над формальными переменными n, m, p, получившими значения фактических переменных.

6.7. ПРОТОТИП ФУНКЦИИ

Строгое согласование по типам между формальными и фактическими параметрами требует, чтобы до первого обращения к функции, появилось либо ее определение (см. пример программы в п. 6.6), либо

ееописание, содержащее сведения о типе возвращаемого результата и

отипах всех параметров. Описание функции называется прототипом функции. Синтаксис прототипа почти полностью совпадает с заголовком функции в ее определении:

<тип результата функции> <имя функции> ( [ <cписок формальных параметров> ] );

| void <имя функции> ( [ <cписок формальных параметров> ] );

Основные отличия: 1) в конце прототипа ставится точка с запятой, 2) имена формальных параметров можно опускать.

Пример программы с определением и прототипом функций

#include <stdio.h>

#include <stdio.h>

#include <conio.h > const int n = 15; const int n1= 10; const int n2 = 20;

int MAX (int a, int b)

//Определение функции MAX до ее вызова

{ return a > b ? a : b ; }

 

float cube ( float x);

//Прототип функции cube

 

57

void main ( )

{

int max, p = 2; scanf ("%d ", &max );

max = MAX (( int) cube ( (float) p), max); //Вызов функции MAX

// и функции cube.

printf ("\n наибольшее равно %d ", max ); getch ( );

}

float cube ( float x) //Определение функции cube { return x * x * x ; }

Явные преобразования типов фактических параметров при обращении к функции cube – (float) p и функции MAX (первый параметр) требуются для согласования типов формальных и фактических параметров. Прототип функции cube необходим, так как ее определение размещено после обращения к ней.

6.8. РЕКУРСИВНЫЕ ПОДПРОГРАММЫ

Рекурсия позволяет наглядно и в простых задачах достаточно коротко описать решение задачи. Но рекурсию надо использовать только в тех случаях, когда, либо природа обрабатываемых данных рекурсивна, либо способ решения задачи является перебором возможных вариантов решения задачи. Рекурсивное решение состоит как минимум из двух частей: 1) часть, задающая непосредственное решение задачи и тем самым определяющая возможность завершения рекурсии. Непосредственное решение возможно в каком-то частном или простом с точки зрения размера задачи случае; 2) часть, задающая решение всей задачи в целом. Эта часть является сведением решения общей задачи к частной, т. е. к непосредственному ее решению, как правило, путем снижения размера задачи.

Механизм реализации рекурсивного решения состоит из: 1) подпрограмма явно или косвенно вызывает саму себя (и называется рекурсивная подпрограмма); 2) при каждом вызове подпрограммы создаются экземпляры локальных переменных подпрограммы, которые доступны только на том уровне рекурсии (вызове подпрограммы), на котором были созданы. Этим обеспечивается передача данных с одного уровня рекурсии на другой.

58

Пример рекурсивного решения задачи вычисления n! для нату-

рального n:

 

1, если n

1

n!

 

n 1

n(n 1)!, если

Определение рекурсивной функции для вычисления n!:

int fact ( int n)

{ if (n == 1) return 1;

if (n > 1) return fact ( n - 1) * n; }

Пример рекурсивного решения задачи вычисления xn для вещественного x и натурального n:

x,

если n 1,

 

 

если n 1 и n нечетно,

xn xn 1 x,

 

если n 1

и n четно

(xn/2 )2 ,

Определение рекурсивной функции для вычисления xn:

float step (float x, int n) { float r;

if (n == 1) return x;

if (n % 2) return step (x, n - 1) * x; {r = step (x, n / 2); return r * r;}

}

Пример определения рекурсивной процедуры (задача – напеча-

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

void print ( )

{ char x; scanf ("%c", &x );

if ( x >= ′0′ && x <= ′9′){ printf ("%c ", x ); print ( ); } else if (x != ′.′ ) print ( );

}

59

6.9.ОБЪЕКТЫ ПРОГРАММЫ И ИХ ВЗАИМОДЕЙСТВИЕ

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

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

Формальные объекты – это объекты, описанные в заголовке определения подпрограммы. Область действия этих объектов – тело подпрограммы. Формальные параметры это также локальные объекты подпрограммы.

Область действия формальных параметров, описанных в прототипе функции – прототип функции.

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

60

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]