Указатель на функцию (function pointer) хранит адрес функции. По сути указатель на функцию содержит адрес первого байта в памяти, по которому располагается выполняемый код функции.
Самым распространенным указателем на функцию является ее имя. С помощью имени функции можно вызывать ее и получать результат ее работы.
Но также указатель на функцию мы можем определять в виде отдельной переменной с помощью следующего синтаксиса:
тип (*имя_указателя) (типы_параметров);
тип
представляет тип возвращаемого функцией значения.
имя_указателя
представляет произвольно выбранный идентификатор в соответствии с правилами о наименовании переменных.
параметры
определяют типы параметров через запятую (при их наличии).
Указатель может указывать только на такую функцию, которая имеет тот же возвращаемый тип и типы параметров, что и определение указателя на функцию.
Например, определим указатель на функцию:
void (*message) ();
В данном случае определен указатель, который имеет имя message. Он может указывать на функции без параметров, которые возвращают тип void (то есть ничего не возвращают).
Используем указатель на функцию:
#include <iostream> void hello(); void goodbye(); int main() { void (*message)(); // определение указателя на функцию message=hello; message(); message = goodbye; message(); } void hello() { std::cout << "Hello, World" << std::endl; } void goodbye() { std::cout << "Good Bye, World" << std::endl; }
Указателю на функцию можно присвоить функцию, которая соответствует указателю по возвращаемому типу и спецификации параметров:
message=hello;
То есть в данном случае указатель message теперь хранит адрес функции hello. И посредством обращения к указателю мы можем вызвать эту функцию:
message();
В качестве альтернативы мы можем обращаться к указателю на функцию следующим образом:
(*message)();
Впоследствии мы можем присвоить указателю адрес другой функции, которая также соответствует определению указателя. В итоге результатом данной программы будет следующий вывод:
Hello, World Good Bye, World
При определении указателя стоит обратить внимание на скобки вокруг имени. Так, использованное выше определение
void (*message) ();
НЕ будет аналогично следующему определению:
void *message ();
Во втором случае определен не указатель на функцию, а прототип функции message, которая возвращает указатель типа void*.
При определении указатель можно сразу инициализировать:
void (*message)() {hello}; // указывает на функцию hello // или так void (*message2)() =hello; // указывает на функцию hello
Можно инициализировать значением nullptr:
void (*message)() { nullptr};
Если указатель при определении инициализируется какой-либо функцией, то можно опустить все определение типа и просто использовать слово auto:
auto message { hello}; // указывает на функцию hello auto message2 = hello; // указывает на функцию hello
Можно подчеркнуть, что переменная является именно указателем, указав после auto
символ звездочки:
auto* message { hello};
Но особой разницы - что со звездочкой, что без звездочки нет.
Стоит отметить, что при присвоении функции мы можем применять операцию получения адреса:
auto message { &hello};
Но в принципе применение такого символа, как и символа звездочки с auto, ни на что не влияет.
Рассмотрим еще один указатель на функцию:
#include <iostream> int sum(int, int); int subtract(int, int); int main() { int a{10}; int b{5}; int (*operation)(int, int) {sum}; // указатель operation указывает на функцию sum int result = operation(a, b); // result = (*operation)(a, b); // альтернативный вариант std::cout << "result = " << result << std::endl; // result = 15 operation = subtract; // указатель operation указывает на функцию subtract result = operation(a, b); std::cout << "result = " << result << std::endl; // result = 5 } int sum(int x, int y) { return x+y; } int subtract(int x, int y) { return x-y; }
Здесь определен указатель operation, который может указывать на функцию с двумя параметрами типа int, возвращающую также значение типа int. Соответственно мы можем присвоить указателю адреса функций add и subtract и вызвать их, передав при вызове указателю некоторые значения для параметров.
Кроме одиночных указателей на функции мы можем определять их массивы. Для этого используется следующий формальный синтаксис:
тип (*имя_массива[размер]) (параметры)
Например:
double (*actions[]) (int, int)
Здесь actions представляет массив указателей на функции, каждая из которых обязательно должна принимать два параметра типа int и возвращать значение типа double.
Посмотрим применение массива указателей на функции на примере:
#include <iostream> void add(int, int); void subtract(int, int); void multiply(int, int); int main() { int a {10}; int b {5}; void (*operations[3])(int, int) = {add, subtract, multiply}; // получаем длину массива unsigned length = std::size(operations); for(unsigned i{}; i < length; i++) { operations[i](a, b); // вызов функции по указателю } } void add(int x, int y) { std::cout << "x + y = " << x + y << std::endl; } void subtract(int x, int y) { std::cout << "x - y = " << x - y << std::endl; } void multiply(int x, int y) { std::cout << "x * y = " << x * y << std::endl; }
Здесь массив operations содержит три функции add, subtract и multiply, которые последовательно вызываются в цикле через перебор массива в функции main.
Консольный вывод программы:
x + y = 15 x - y = 5 x * y = 50