книги / Язык Си
..pdfИмена аргументов в списке параметров функции не играют никакой роли и игнорируются компилятором, поэтому в прототипе функции их можно не указывать. Варианты прототипа функции
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);