Практическая работа №3 «Указатели на функции»
1. Указатели при вызове функций.
До сих пор мы рассматривали функцию как минимальный исполняемый модуль программы, обмен данными с которым происходит через набор параметров функции и с помощью значений, возвращаемых функцией в точку вызова. Теперь перейдем к вопросу о том, почему в языке Си функция введена как один из производных типов. Необходимость в таком типе связана, например, с задачами, в которых функция (или ее адрес) должна выступать в качестве параметра другой функции или в качестве значения, возвращаемого другой функцией. Вызов функции:
обозначение_функции (список_фактических_параметров)
где обозначение_функции (только в частном случае это идентификатор) должно иметь тип "указатель на функцию, возвращающую значение конкретного типа".
В соответствии с синтаксисом языка указатель на функцию -это выражение или переменная, используемые для представления адреса функции. По определению, указатель на функцию содержит адрес первого байта или первого слова выполняемого кода функции (арифметические операции над указателями на функции запрещены).
Самый употребительный указатель на функцию - это ее имя (идентификатор). Именно так указатель на функцию вводится в ее определении:
тип имя_функции (спецификация_параметров) тело_функции
Прототип
тип имя_функции (спецификация_параметров);
также описывает имя функции именно как указатель на функцию, возвращающую значение конкретного типа.
Имя_функции в ее определении и в ее прототипе - указатель-константа. Он навсегда связан с определяемой функцией и не может быть "настроен" на что-либо иное, чем ее адрес. Для идентификатора имя_функции термин "указатель" обычно не используют, а говорят об имени функции.
Указатель на функцию как переменная вводится отдельно от определения и прототипа какой-либо функции. Для этих целей используется конструкция:
тип (*имя_указателя) (спецификация_параметров);
где тип - определяет тип возвращаемого функцией значения;
имя_указателя - идентификатор, произвольно выбранный программистом;
спецификация_параметров - определяет состав и типы параметров функции.
Например, запись
int (*point) (void);
определяет указатель-переменную с именем point на функции без параметров, возвращающие значения типа int.
Важнейшим элементом в определении указателя на функции являются круглые скобки. Если записать
int * funct (void);
то это будет не определением указателя, а прототипом функции без параметров с именем funct, возвращающей значения типа int*.
В отличие от имени функции (например, funct) указатель point является переменной, т.е. ему можно присваивать значения других указателей, определяющих адреса функций программы. Принципиальное требование - тип указателя-переменной должен полностью соответствовать типу функции, адрес которой ему присваивается.
Мы уже говорили, что тип функции определяется типом возвращаемого значения и спецификацией ее параметров. Таким образом, попытка выполнить следующее присваивание
point=funct; /* Ошибка - несоответствие типов */
будет ошибочной, так как типы возвращаемых значений для point и funct различны.
Неконстантный указатель на функцию, настроенный на адрес конкретной функции, может быть использован для вызова этой функции. Таким образом, при обращении к функции перед круглыми скобками со списком фактических параметров можно помещать: имя_функции (т.е. константный указатель); указатель-переменную того же типа, значение которого равно адресу функции; выражение разыменования такого же указателя с таким же значением. Следующая программа иллюстрирует три способа вызова функций.
#include "stdafx.h"
#include "stdio.h"
#include "conio.h"
#include "locale.h"
void f1 (void)
{
printf("\n Выполняется f1( ) ");
}
void f2 (void)
{
printf ("\n Выполняется f2( )");
}
int _tmain(int argc, _TCHAR* argv[])
{
setlocale(LC_ALL,"Russian");
void (*point) (void); /*point - указатель-переменная на функцию */
f2 (); /* Явный вызов функции f2()*/
point=f2; /* Настройка указателя на f2()*/
(*point)(); /* Вызов f2() по ее адресу с разыменованием указателя */
point=f1; /* Настройка указателя на f1()*/
(*point)(); /* Вызов f1() по ее адресу с разыменованием указателя */
point (); /* Вызов f1() без разыменования указателя */
_getch();
return 0;
}
Результат выполнения программы:
Выполняется f2( )
Выполняется f2( )
Выполняется f1( )
Выполняется f1( )
Все варианты обращения к функциям с использованием указателей-констант (имен функций) и указателей-переменных (неконстантных указателей на функции) в программе продемонстрированы и снабжены комментариями. Приведем общий вид двух операций вызова функций с помощью неконстантных указателей:
(*имя_указателя) (список_фактических_параметров) имя_указателя (список_фактических_параметров)
В обоих случаях тип указателя должен соответствовать типу вызываемой функции, и между формальными и фактическими параметрами должно быть соответствие. При использовании явного разыменования обязательны круглые скобки, т.е. в нашей программе будет ошибочной запись
*point (); /* Ошибочный вызов */
Это полностью соответствует синтаксису языка. Операция '()' - "круглые скобки" имеет более высокий приоритет, чем операция разыменования '*'. В этом ошибочном вызове вначале выполнится вызов point(), а уж к результату будет применена операция разыменования.
При определении указателя на функции он может быть инициализирован. В качестве инициализирующего выражения должен использоваться адрес функции того же типа, что и тип определяемого указателя, например:
int fic (char); /* Прототип функции */
int (*pfic) (char)=fic; /* pfic - указатель на функцию */