Каждая функция в языке С/С++ характеризуется типом возвращаемого значения, именем и сигнатурой. Сигнатура определяется количеством, порядком следования и типами формальных параметров. Иногда говорят, что сигнатурой функции называется список типов ее формальных параметров.
При использовании имени функции без последующих скобок и параметров имя функции выступает в качестве указателя на эту функцию, а его значением служит адрес размещения функции в памяти. Это значение адреса может быть присвоено другому указателю, и затем уже этот новый указатель можно применять для вызова функции. Однако в определении нового указателя должен быть тот же тип, что и возвращаемое функцией значение, и та же сигнатура.
Таким образом, указатель на функцию определяется следующим образом:
тип_функции (*имя_указателя)(спецификация_параметров);
Например:
int (*func) (char); // определение указателя func на функцию с параметром
// типа char, возвращающую значение типа int.
Если приведенную синтаксическую конструкцию записать без первых круглых скобок, т.е. в виде
int *funс (char);
то компилятор воспримет ее как прототип некой функции с именем funс и параметром типа char, возвращающей значение указателя типа int *.
В определении указателя на функцию тип возвращаемого значения и сигнатура (типы, количество и последовательность параметров) должны совпадать с соответствующими типами и сигнатурами тех функций, адреса которых предполагается присваивать вводимому указателю при инициализации или с помощью оператора присваивания. В качестве простейшей иллюстрации сказанного приведем программу с указателем на функцию:
//Ex_1.СРР – определение и использование указателей на функции.
#include <iostream.h> // Для ввода-вывода.
void f1(void) // Определение f1().
{
cout << "nВыполняется f1()";
}
void f2(void) // Определение f2().
{
cout << "n Выполняется f2()";
}
void main()
{
void (*ptr)(void); // ptr – указатель на функцию.
ptr = f2; // Присваивается адрес f2().
(*ptr)(); // Вызов f2() по ее адресу.
ptr = f1; // Присваивается адрес f1().
(*ptr)(); // Вызов f1() по ее адресу.
ptr(); // Вызов эквивалентен (*ptr)();
}
Результат выполнения программы:
Выполняется f2()
Выполняется f1()
Выполняется f1()
В программе описан указатель ptr на функцию, и ему последовательно присваиваются адреса функций f2() и f1(). Заслуживает внимания форма вызова функции с помощью указателя на функцию:
(*имя_указателя)(список_фактических_параметров);
Здесь значением имя_указателя служит адрес функции, а с помощью операции разыменования (*) обеспечивается обращение по адресу к этой функции. Однако будет ошибкой записать вызов функции без скобок в виде *ptr( );. Дело в том, что операция «скобки» имеет более высокий приоритет, нежели операция обращения по адресу (*). Следовательно, в соответствии с синтаксисом будет вначале сделана попытка обратиться к функции ptr(). И уже к результату будет отнесена операция разыменования, что будет воспринято как синтаксическая ошибка.
Приведем пример вызова функций через указатель:
// Ex_2.СРР – вызов функций по адресам через указатель.
#include <iostream.h>
// Функции одного типа с одинаковыми сигнатурами:
int add(int n, int m) { return n + m; }
int div(int n, int m) { return n/m; }
int mult(int n, int m) { return n * m; }
int subt(int n, int m) { return n – m; }
void main()
{
int (*par)(int, int); // Указатель на функцию.
int a = 6, b = 2;
char c = ‘+';
while (c != ‘ ‘)
{
cout << "n Аргументы: a = " << a <<", b = " << b;
cout << ". Результат для с = ‘" << c << "’" <<
" равен ";
switch (c)
{
case ‘+': par = add; c = ‘-‘; break;
case ‘-‘ : par = subt; c = ‘*'; break;
case ‘*’ : par = mult; c = ‘/'; break;
case ‘/’ : par = div; c = ‘ ‘; break;
}
cout << (a = (*par)(a,b)); // Вызов по адресу.
}
}
Иногда полезен массив указателей на функции. Например, можно реализовать систему меню для редактора с вводом, управляемым мышью, используя массив указателей на функции, реализующие команды. Дадим самый общий его набросок:
typedef void (*PF)();
PF edit_ops[ ] = { // команды редактора
&cut, &paste, &search
};
PF file_ops[ ] = { // управление файлом
&open, &close, &write
};
Далее надо определить и инициализировать указатели, с помощью которых будут запускаться функции, реализующие выбранные из меню команды. Выбор происходит нажатием клавиши мыши:
PF* button2 = edit_ops;
PF* button3 = file_ops;
Для настоящей программы редактора надо определить большее число объектов, чтобы описать каждую позицию в меню. Например, необходимо где-то хранить строку, задающую текст, который будет выдаваться для каждой позиции. При работе с системой меню назначение клавиш мыши будет постоянно меняться. Частично эти изменения можно представить как изменения значений указателя, связанного с данной клавишей. Если пользователь выбрал позицию меню, которая определяется, например, как позиция 1 для клавиши 2, то соответствующая команда реализуется вызовом:
(*button2[1])();
Чтобы полностью оценить мощность конструкции указатель на функцию, стоит попробовать написать программу без нее. Меню можно изменять в динамике, если добавлять новые функции в таблицу команд. Довольно просто создавать в динамике и новые меню.