Функциональные значения

Как мы уже говорили, указатели на функции являются равноправными объектами в языке 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 &sum;

case '-': return &sub;

case '*': return &mult;

case '/': return &div;

}

}

и может быть применена как

((*get_binop)('+'))(7,8)

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

typedef float (* unary_operation)(float);

unary_operation get_incr(float k)

{

float plus(float x)

{

return x + k;

}

return &plus;

}

которая, будучи применена к аргументу k, должна возвращать функцию, которая, будучи применена к x, возвращает x+k. То есть вызывать мы её должны как, например,

(*get_incr(7))(8)

Проблема здесь заключается в том, что локальный объект, соответствующий формальному параметру k, удалится после завершения вызова процедуры get_incr, и когда дело дойдёт во вызова функции plus, он уже не будет существовать, хотя и используется внутри plus. Поэтому чаще всего языки, допускающие вложенные процедуры, считают такие трюки некорректными. Обобщая, можно сказать, что некорретным будет возвращение в качестве результата, либо присваивание в нелокальную переменную любой ссылки на локальный объект, будь то переменная, процедура или метка.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: