Как мы уже говорили, указатели на функции являются равноправными объектами в языке C. В частности, их можно передавать параметром другим функциям. В таком случае передаваемые функции называются функциями обратного вызова (call-back). Продемонстрируем это понятие на примере алгоритма сортировки массива. В стандартной библиотеке языка C имеется функция qsort, описанная в стандартном включаемом файле stdlib.h как
void qsort(void * base,
size_t num, size_t size,
int (*cmp) (void *,void *));
Реализованный в ней алгоритм сортировки не зависит природы элементов. Первые три параметра задают сортируемый массив: указатель на начало массива base, количество элементов num и их размер size. Последний параметр cmp задаёт функцию сравнения элементов, которая согласно принятым соглашениям вырабатывает положительное значение, если первый аргумент больше второго, отрицательное - если меньше, и ноль - если они равны.
Пусть, например, задан двумерный массив размера N×N
#define N 1000
float M[N][N];
и нам требуется отсортировать его строки довольно специфическим образом, сравнивая их по длине задаваемых ими N-мерных векторов. Для этого достаточно описать функцию veccmp следующим образом:
float veclen(float x[])
{
float sum = 0;
for (int i=0; i<N; i++)
sum += x[i] * x[i];
return sqrt(sum);
}
int veccmp(void *x; void *y)
{
float v = veclen((float*) x) - veclen((float*) y);
return (v==0? 0: v>0? 1: -1);
}
Здесь нам пришлось преодолеть типовый контроль, во-первых, описав параметры функции как void*, чтобы её тип совпал с тем, который требует qsort, и, во-вторых, внутри функции привести универсальные указатели к конкретному типу float*.
Если теперь мы вызовем
qsort(M, N, N*sizeof(float), &veccmp);
то в ходе своего выполнения qsort (многократно) вызовет нашу функцию veccmp. Отсюда и название - обратный вызов.
Функции можно не только передавать в качестве параметров, но и выдавать в качестве результата. В следующем примере функция op преобразует код операции в указатель на реализующую её функцию:
float sum(float x, float y) { return x+y; }
float sub(float x, float y) { return x-y; }
float mult(float x, float y) { return x*y; }
float div(float x, float y) { return x/y; }
typedef float (operation *)(float,float);
operation get_binop(char code)
{
switch (code)
{
case '+': return ∑
case '-': return ⊂
case '*': return &mult;
case '/': return ÷
}
}
и может быть применена как
((*get_binop)('+'))(7,8)
Если возвращаемое функциональное значение ссылается на вложенную функцию, то может возникнуть проблема с несоответствием времени жизни объектов. Рассмотрим, например, функцию
typedef float (* unary_operation)(float);
unary_operation get_incr(float k)
{
float plus(float x)
{
return x + k;
}
return +
}
которая, будучи применена к аргументу k, должна возвращать функцию, которая, будучи применена к x, возвращает x+k. То есть вызывать мы её должны как, например,
(*get_incr(7))(8)
Проблема здесь заключается в том, что локальный объект, соответствующий формальному параметру k, удалится после завершения вызова процедуры get_incr, и когда дело дойдёт во вызова функции plus, он уже не будет существовать, хотя и используется внутри plus. Поэтому чаще всего языки, допускающие вложенные процедуры, считают такие трюки некорректными. Обобщая, можно сказать, что некорретным будет возвращение в качестве результата, либо присваивание в нелокальную переменную любой ссылки на локальный объект, будь то переменная, процедура или метка.