Прототип - это явное объявление функции, которое предшествует определению

функции. Тип возвращаемого значения при объявлении функции должен

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

Если прототип функции не задан, а встретился вызов функции, то строится

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

создаваемого прототипа int, а список типов и числа параметров функции

формируется на основании типов и числа фактических параметров используемых при

данном вызове.

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

1. Функция возвращает значение типа, отличного от int.

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

как эта функция будет определена.

Наличие в прототипе полного списка типов аргументов параметров позволяет

выполнить проверку соответствия типов фактических параметров при вызове функции

типам формальных параметров, и, если необходимо, выполнить соответствующие

преобразования.

В прототипе можно указать, что число параметров функции переменно, или что

функция не имеет параметров.

Если прототип задан с классом памяти static, то и определение функции должно

иметь класс памяти static. Если спецификатор класса памяти не указан, то

подразумевается класс памяти extern.

Вызов функции имеет следующий формат:

адресное-выражение ([список-выражений])

Поскольку синтаксически имя функции является адресом начала тела функции,

в качестве обращения к функции может быть использовано адресное-выражение (в

том числе и имя функции или разадресация указателя на функцию), имеющее

значение адреса функции.

Список-выражений представляет собой список фактических параметров,

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

скобок обязательно.

Фактический параметр может быть величиной любого основного типа, структурой,

объединением, перечислением или указателем на объект любого типа. Массив и

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

использовать указатели на эти объекты.

Выполнение вызова функции происходит следующим образом:

1. Вычисляются выражения в списке выражений и подвергаются обычным

арифметическим преобразованиям. Затем, если известен прототип функции, тип

полученного фактического аргумента сравнивается с типом соответствующего

формального параметра. Если они не совпадают, то либо производится

преобразование типов, либо формируется сообщение об ошибке. Число выражений в

списке выражений должно совпадать с числом формальных параметров, если только

функция не имеет переменного числа параметров. В последнем случае проверке

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

ей не требуются параметры, а при вызове они указаны, формируется сообщение об

ошибке.

2. Происходит присваивание значений фактических параметров соответствующим

формальным параметрам.

3. Управление передается на первый оператор функции.

4. Выполнение оператора return в теле функции возвращает управление и

возможно, значение в вызывающую функцию. При отсутствии оператора return

управление возвращается после выполнения последнего оператора тела функции, а

возвращаемое значение не определено.

Адресное выражение, стоящее перед скобками определяет адрес вызываемой

функции. Это значит что функция может быть вызвана через указатель на функцию.

Пример:

int (*fun)(int x, int *y);

Здесь объявлена переменная fun как указатель на функцию с двумя параметрами:

типа int и указателем на int. Сама функция должна возвращать значение типа int.

Круглые скобки, содержащие имя указателя fun и признак указателя *,

обязательны, иначе запись

int *fun (intx,int *y);

будет интерпретироваться как объявление функции fun возвращающей указатель

на int.

Вызов функции возможен только после инициализации значения указателя fun и

имеет вид:

(*fun)(i,&j);

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

указатель fun используется операция разадресации * .

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

этом разадресация происходит во время вызова функции, на которую ссылается

указатель на функцию. Присвоить значение указателю на функцию можно в операторе

присваивания, употребив имя функции без списка параметров.

Пример:

double (*fun1)(int x, int y); double fun2(int k, int l); fun1=fun2; /* инициализация указателя на функцию */ (*fun1)(2,7); /* обращение к функции */

В рассмотренном примере указатель на функцию fun1 описан как указатель на

функцию с двумя параметрами, возвращающую значение типа double, и также

описана функция fun2. В противном случае, т.е. когда указателю на функцию

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

Рассмотрим пример использования указателя на функцию в качестве параметра

функции вычисляющей производную от функции cos(x).

Пример:

double proiz(double x, double dx, double (*f)(double x) ); double fun(double z); int main() { double x; /* точка вычисления производной */ double dx; /* приращение */ double z; /* значение производной */ scanf("%f,%f",&x,&dx); /* ввод значений x и dx */ z=proiz(x,dx,fun); /* вызов функции */ printf("%f",z); /* печать значения производной */ return 0; } double proiz(double x,double dx, double (*f)(double z) ) { /* функция вычисляющая производную */ double xk,xk1,pr; xk=fun(x); xk1=fun(x+dx); pr=(xk1/xk-1e0)*xk/dx; return pr; } double fun( double z) { /* функция от которой вычисляется производная */ return (cos(z)); }

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

функции fun или использовать при вызове функции proiz имя другой функции. В

частности, для вычисления производной от функции cos(x) можно вызвать функцию

proiz в форме

z=proiz(x,dx,cos);

а для вычисления производной от функции sin(x) в форме

z=proiz(x,dx,sin);

Любая функция в программе на языке СИ может быть вызвана рекурсивно, т.е.

она может вызывать саму себя. Компилятор допускает любое число рекурсивных

вызовов. При каждом вызове для формальных параметров и переменных с классом

памяти auto и register выделяется новая область памяти, так что их значения из

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

значения текущего вызова.

Переменные, объявленные с классом памяти static, не требуют выделения новой

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

течение всего времени выполнения программы.

Классический пример рекурсии - это математическое определение факториала n! :

n! = 1 при n=0; n*(n-1)! при n>1 .

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

long fakt(int n) { return ( (n==1) ? 1 : n*fakt(n-1) ); }

Хотя компилятор языка СИ не ограничивает число рекурсивных вызовов функций,

это число ограничивается ресурсом памяти компьютера и при слишком большом числе

рекурсивных вызовов может произойти переполнение стека.

Наши рекомендации