Прототип - это явное объявление функции, которое предшествует определению
функции. Тип возвращаемого значения при объявлении функции должен
соответствовать типу возвращаемого значения в определении функции.
Если прототип функции не задан, а встретился вызов функции, то строится
неявный прототип из анализа формы вызова функции. Тип возвращаемого значения
создаваемого прототипа 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) ); }Хотя компилятор языка СИ не ограничивает число рекурсивных вызовов функций,
это число ограничивается ресурсом памяти компьютера и при слишком большом числе
рекурсивных вызовов может произойти переполнение стека.