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

Каждая функция в языке С/С++ характеризуется типом возвращаемого значения, именем и сигнатурой. Сигнатура определяется количеством, порядком следования и типами формальных параметров. Иногда говорят, что сигнатурой функции называется список типов ее формальных параметров.

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

Таким образом, указатель на функцию определяется следующим образом:

тип_функции   (*имя_указателя)(спецификация_параметров);

Например:

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])();

Чтобы полностью оценить мощность конструкции указатель на функцию, стоит попробовать написать программу без нее. Меню можно изменять в динамике, если добавлять новые функции в таблицу команд. Довольно просто создавать в динамике и новые меню.