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

книги / Язык Си

..pdf
Скачиваний:
6
Добавлен:
20.11.2023
Размер:
7.64 Mб
Скачать

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

June

int func(int a, float b, float c) ;

и

int func(int, float, float);

одинаковы.

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

#include<stdio.h>

#include<conio.h>

float min(float,float); void max(float,float);

main()

{float a=4.5,b=-6.7; printf("MIN=% f\nH,min(a,b)); max(a,b);

getch(); return 0;

}

float min(float x,float y)

{

return x<=y?x:y;

}

void max(float x,float y)

{

printf("MAX=% f",x>=y?x:у);

}

Если закомментировать один из прототипов:

//float min(float,float);

то компилятор выдаст сообщение об ошибке: «Вызов неопреде­ ленной функции min». Так происходит потому, что первый вызов функции min находится внутри функции main

printf("MIN=%f\n",min(a,b));

а сама функция min в коде программы находится ниже main и компилятор о ней теперь ничего не знает (тип возвращаемого зна­ чения, имя функции, количество и тип аргументов функции).

9.3. Фактические и формальные параметры

Фактические параметры - это параметры при вызове функ­

ции.

Формальные параметры - это параметры при определении функции.

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

Пример. Функция для обмена значениями двух переменных.

#include<stdio.h>

#include<conio.h>

void swap(float,float); //прототип функции swap main()

{float a=4rb=-7;

swap(a,b); //фактические параметры a,b printf("a=%f\nb=%f\nM,a,b);

getch(); return 0;

}

void swap(float x,float у) //формальные //параметры x,y

{float z; z=x; x=y; y=z;

}

При вызове функции swap в стеке будет выделено место под формальные параметры х, у, в которые будут скопированы значе­ ния фактических параметров а, 6, поэтому имена фактических и формальных параметров не обязательно должны быть одинаковы­ ми. Далее внутри функции swap переменные х, у обменяются зна­ чениями, но при выходе из swap стек будет очищен и измененные значения не поступят в переменные а, Ъ функции main, поэтому в результате работы программы на экране мы увидим

а=4.000000 Ь=-7.000000

вместо ожидаемого

а=-7.000000 Ь=4.000000

Данный способ передачи параметров в функцию называется

передачей по значению.

Для того чтобы при выходе из функции результаты ее работы не пропали, нужно передавать в функцию не значение переменной, а ее адрес. Такой способ передачи параметров называется переда­ чей по ссылке. Взять адрес можно с помощью операции «ампер­ санд» (&), а идентификатор, к которому применена эта опера­ ция, называется ссылкой.

Заменим в последнем примере формальные параметры функ­ ции на ссылки. Прототип функции примет вид

void swap(float &,float &);,

а определение функции:

void swap (float &x,float &y) {float z;

z=x;

x=y; y=z ;

}

Теперь x ссылается на переменную а в функции main, ay на b, и при выполнении обмена между х и у обмен значений на самом

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

а=-7.000000 Ь=4.000000

Кроме значения и ссылки в функцию можно передавать указатель (см. лекцию 10). Такой способ называется передачей параметров по указателю. В этом случае работа будет вестись не со значениями, расположенными в стеке, а со значениями, распо­ ложенными по переданным в функцию адресам, поэтому очистка стека не повлияет на измененные значения.

9.4. Передача в функцию массивов

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

Прототип функции с одномерным массивом в качестве пара­ метра может иметь вид

function(int а [10]);

function(int а []);

Оба способа приведут к одному и тому же результату.

При вызове функции в скобках указывается лишь имя массива:

function(а);

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

function(int а[][5]);

Пример. Найти произведение двух матриц.

#include<stdio.h>

#include<conio.h>

void

mult(int

 

a [][n],int b [][n],int re 2 [][П] )

{int

i,j,k;

i

<

n;

i++)

for

(i

= 0;

for

(j

= 0 ;

j

<

n;

j++)

{ rez[i][j]=0;

k <

n;

k++)

 

 

 

for

(k =

0;

 

 

 

 

rez [i] [j]+=

 

a[i] [k]*b[k] [j];

 

}

 

 

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

main()

 

=

Id,

1,

1},

12,

2,

2}

{int

a [][n]

int

b[] [n]

=

 

13,

3,

3}} ;

2,

2}

{{3,

3,

3),

{2,

int

rez[n][n] r

{1,

1,

D l ;

 

 

 

 

 

 

 

 

 

int

i/ j ;

 

 

 

 

 

 

 

 

m u l t ( a , b , r e z ) ;

//вызов

функции

 

f o r ( i = 0 ; i < n ; i + + )

 

 

 

 

 

{f o r ( j = 0 ; j <n; j ++)

 

 

 

 

 

printf("%3d",rez[i][j]);

 

 

printf("\n");

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

g e t c h ( );

 

 

 

 

 

 

 

 

r e t u r n

0;

 

 

 

 

 

 

 

 

Обратите внимание, что функция mult имеет тип возвращае­ мого значения void, поэтому вызывается в отдельной строке.

9.5. Аргументы по умолчанию

Обычно при вызове функции в нее передается конкретное значение каждого аргумента. В языке Си++ появилась возмож­ ность задавать аргументу функции значение по умолчанию. Оно

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

Аргументы по умолчанию должны быть указаны при первом упоминании имени функции - обычно в прототипе.

#include<stdio.h>

#include<conio.h>

void print(int, int =10);//второй аргумент по //умолчанию равен 10

main ()

{print(3); //второй аргумент при вызове //не задан, по умолчанию будет //использовано значение 10

print(12,-4);

print(-5); //второй аргумент при вызове //не задан, по умолчанию будет //использовано значение 10

getch() ; return 0;

}

void print(int i, int j)

{

printf("%3d%3d\nn,i,j );

}

В результате программа напечатает

3 10

12-4

-5 10

Аргументы по умолчанию можно задавать только в конце списка аргументов:

int

f(int,int =0,char*

=0)

//правильно

int

g(int

=0,int=0,char*)

//ошибка

int

h(int

=0,int,char*

=0)

//ошибка

Аргумент по умолчанию не может быть повторен или изме­ нен в последующих объявлениях в той же области видимости:

void

f(int

х=7);

void f(int

=7); //ошибка: нельзя повторять

void f(int

//аргумент по умолчанию

=8);//ошибка: нельзя изменять

void

g()

//значение аргумента по умолчанию

x=9);//правильно: это объявление

{void f(int

 

 

//видно только внутри функции g

}

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

9.6. Рекурсивные функции

Функция, которая вызывает саму себя, называется рекурсив­ ной. Такая рекурсия называется прямой:

void f()

{.

f О ;

Когда две или более функций вызывают последовательно друг друга, рекурсия называется косвенной:

void f1()

{.

f2 () ;

}

void f2()

{.

f1 () ;

}

Чаще всего рекурсивные функции применяются для описания какого-то набора повторяющихся однотипных действий. Напри­ мер, программирование рядов, сортировка массивов, прорисовка однотипных узоров:

о

Классическим примером прямой рекурсии является вычислен ние факториала:

#include<stdio.h>

#include<conio.h>

int fact(int n) {if(n==0)

return 1; //факториал 0 равен 1

return n*fact(n-1); //функция снова вызывает //саму себя

}

main()

{printf("%d",fact(4)); //напечатать факториал 4 getch();

return 0;

}

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

fact (4) У

_______L

fact (2)

fact (1) / '

fact (0)

fact (3)

<

/

3*fact (2) ;

<

/ /

<

/

{

4*fact (3) ;

2*fact (1) ;

l*fact(0);

1;

} *--- ----- -1- 1 -------

.1. f---- — -L

1 *3 = 1

}

4*6=24

3*2=6

2*1=2

 

 

-----

новый

вызов функции;

----

 

возврат

значения в

точку

вызова

 

 

 

 

 

До рекурсивного вызова должна стоять проверка на воз­

врат из рекурсии.

Если в предыдущем примере убрать строки if(n==0) return 1; то функция туJact будет вызывать саму себя до тех пор, пока стек не переполнится, что приведет к ошибке.

Рекурсию удобно использовать там, где есть однотипное по­ вторение каких-то действий.

Пример. Дано натуральное число слагаемых п. Вычислить

^ 2 + ^ 2 + V2 ... +

Решение через цикл

#include<conio.h>

#include<stdio.h>

#include<math.h> main( )

{int i,n=7; double S;

S=0;

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

S=sqrt(2+S);

printf S) ; getch();

return 0;

}

Решение через рекурсивную функцию

#include<conio.h>

#include<stdio.h>

#include<math.h>

double f(int n)

{if(n==0) //проверка на возврат из рекурсии return 0;

return sqrt(2+f(n-1));

}

main( )

{ p r i n t f f (7)); //7 членов ряда getch();

return 0;

}

Если в задаче постоянно требуется вычислять ряд

^2 +\l2 +j2... + j 2 , то использовать вызов f(n) намного удобнее,

чем каждый раз прописывать

S=0;

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

S=sqrt(2+S);