Указатели на функции


В языке программирования C функция тоже имеет адрес и может иметь указатель. Указатель на функцию представляет собой выражение или переменную, которые используются для представления адреса функции. Указатель на функцию содержит адрес первого байта в памяти, по которому располагается выполняемый код функции.

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

Но также указатель на функцию мы можем определять в виде отдельной переменной с помощью следующего синтаксиса:

1
тип (*имя_указателя) (параметры);
Здесь тип представляет тип возвращаемого функцией значения.

имя_указателя представляет произвольно выбранный идентификатор в соответствии с правилами о наименовании переменных.

И параметры определяют тип и название параметров через запятую при их наличии.

Например, определим указатель на функцию:

1
void (*message) (void);>
Здесь определен указатель, который имеет имя message. Он может указывать на функции без параметров, которые возвращают тип void (то есть ничего не возвращают).

Применим этот указатель на функцию:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>

void hello(void)
{
printf("Hello, World \n");
}
void goodbye()
{
printf("Good Bye, World \n");
}
int main(void)
{
void (*message) (void);

message=hello;
message();
message = goodbye;
message();
return 0;
}
Указателю на функцию можно присвоить функцию, которая соответствует указателю по возвращаемому типу и спецификации параметров:

1
message=hello;
То есть в данном случае указатель message теперь хранит адрес функции hello. И посредством обращения к указателю мы можем вызвать эту функцию:

1
message();
Впоследствии мы можем присвоит указателю адрес другой функции, как в данном случае. В итоге результатом данной программы будет следующий вывод:

Hello, World
Good Bye, World
При определении указателя стоит обратить внимание на скобки вокруг имени. Так, использованное выше определение

1
void (*message) (void);
НЕ будет аналогично следующему определению:

1
void *message (void);
Во втором случае определен не указатель на функцию, а прототип функции message, которая возвращает указатель типа void*.

Рассмотрим еще один указатель на функцию:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>

int add(int x, int y)
{
return x+y;
}
int subtract(int x, int y)
{
return x-y;
}
int main(void)
{
int a = 10;
int b = 5;
int result;
int (*operation)(int a, int b);

operation=add;
result = operation(a, b);
printf("result=%d \n", result); // result=15

operation = subtract;
result = operation(a, b);
printf("result=%d \n", result); // result=5

return 0;
}
Здесь определен указатель operation, который может указывать на функцию с двумя параметрами типа int, возвращающую также значение типа int. Соответственно мы можем присвоить указателю адреса функций add и subtract и вызвать их, передав при вызове в указатель нужные значения для параметров.

Массивы указателей на функции
Кроме одиночных указателей на функции мы можем определять их массивы. Для этого используется следующий формальный синтаксис:

1
тип (*имя_массива[размер]) (параметры)
Например:

1
double (*actions[]) (int, int)
Здесь actions представляет массив указателей на функции, каждая из которых обязательно должна принимать два параметра типа int и возвращать значение типа double.

Посмотрим применение массива указателей на функции на примере:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>

void add(int x, int y)
{
printf("x+y=%d \n", x+y);
}
void subtract(int x, int y)
{
printf("x+y=%d \n", x-y);
}
void multiply(int x, int y)
{
printf("x*y=%d \n", x*y);
}
int main(void)
{
int a = 10;
int b = 5;
void (*operations[3])(int, int) = {add, subtract, multiply};

// получаем длину массива
int length = sizeof(operations)/sizeof(operations[0]);

for(int i=0; i<length;i++)
{
operations[i](a, b); // вызов функции по указателю
}

return 0;
}
Здесь массив operations содержит три функции add, subtract и multiply, которые последовательно вызываются в цикле через перебор массива в функции main.