Чтобы понять, как работают указатели на функции, надо иметь в виду следующее. По мере компиляции функций исходный код, записанный по правилам языка С++, преобразуется в объектный и устанавливается точка входа, к которой происходит обращение при вызове функции.
Указатель на функцию в программе определяется следующим образом:
тип_функции (*имя_ указателя) (список_параметров);
Например,
float (*PFun) (int, int);
определяет указатель PFun на функцию с двумя целочисленными параметрами, возвращающую одно вещественное значение. Если приведённое выше объявление записать без первых круглых скобок, то есть в виде
float *PFun (int, int);,
то компилятор воспримет его как прототип функции с именем PFun с двумя целочисленными параметрами, возвращающей значение указателя типа float *. Другими словами, это не указатель на функцию.
Второй пример. Объявление char *(*PFun2 (char *, float); определяет указатель Pfun2 на функцию с параметрами типа указатель на char и типа float, возвращающую значение типа указатель на char.
При использовании указателя на функцию тип возвращаемого значения, типы, количество и последовательность параметров должны совпадать с соответствующими типами, количеством и последовательностью параметров тех функций, адреса которых (их имена) предполагается присваивать вводимому указателю при инициализации или с помощью оператора присваивания.
|
|
Определим обычным образом, как делали в первом семестре, конкретную функцию от двух целочисленных параметров, возвращающую вещественное значение. Например, в качестве такой функции может быть функция, возвращающая среднее значение двух целых чисел:
float Aver (int x, int y)
{ return (x+y)/2.;
}
Тогда объявленный выше указатель на функцию PFun можем определить с помощью следующего присваивания:
PFun=Aver;
При таком присваивании необходимо соблюдать соответствие типов возвращаемых значений функций (в примере float), количеством (2), порядком следования и типами (int x, int y)параметров для указателей правой и левой частей оператора присваивания. Почему такое присваивание будет корректным? PFun мы объявили как указатель на функцию. По аналогии с массивом имя функции (Aver) без последующих скобок и параметров без дополнительного объявления выступает в качестве указателя на эту функцию, и его значением служит адрес размещения функции в памяти. Хотя функция — это не переменная, она всё равно имеет физическое местоположение в памяти, которое может быть присвоено другому указателю.
Адрес функции является входной точкой функции. Поэтому указатель на функцию может использоваться для её вызова следующим, например, образом:
float Res; Res= (*PFun) (5, 2);
|
|
С помощью операции разыменования * обеспечивается обращение по адресу к этой функции. Заметим. что будет ошибкой записать вызов функции без первых скобок в виде:
Res= *PFun (5, 2);
Дело в том, что круглые скобки имеют более высокий приоритет, чем операция обращения по адресу *. Поэтому сначала будет сделана попытка обратиться к функции PFun. А операция разименования будет отнесена к результату этой функции. Но у нас нет функции PFun! Это указатель на функцию, что не одно и то же.
При объявлении указатель на функцию может быть проинициализирован. Требования к функции те же, что и при присваивании. Например, пусть определена та же функция Aver. Тогда объявить указатель на функцию можем так:
float *PFun (int, int)=Aver;
В качестве конкретного практического использования указателя на функцию рассмотрим следующий пример.
Пример 1. (+) Составить функцию, которая по формуле Симпсона вычисляет значение определённого интеграла от произвольной функции одной переменной. Функцию проверить для вычисления двух интегралов:
Формула Симпсона:
n – чётное.
Функция Integral для вычисления определённого интеграла от произвольной функции по формуле Симпсона имеет следующие параметры: пределы интегрирования a и b; количество точек, на которые разбивается отрезок [a, b], котороеобъявлено с типом float, чтобы исключить преобразования типов при реализации алгоритма; указатель на функцию, для которой вычисляется определённый интеграл.
float Integral (float, float, float, float (*Fun)(float));
// Две тестовые функции, для которых вычисляется интеграл.
float Function1(float x){ return (sqrt(1-x*x));
};
float Function2(float x){ return (exp(-x*x));
};
void main()
{ float a,b,n,I;
// Вычисление первого интеграла.
a=-1; b=1; n=100; I=Integral(a,b,n,Function1); cout<<I;
// Вычисление второго интеграла.
a=0; b=1; n=100; I=Integral(a,b,n,Function2); cout<<endl<<I;
getch();
}
// Функция для вычисления интеграла по формуле Симпсона.
float Integral(float a,float b,float n,float (*Fun)(float))
{ float h,s1=0,s2=0;
h=(b-a)/n;
for(int i=2;i<=(n-2);i+=2)
s1+=(*Fun)(a+i*h);
for(int i=1;i<=(n-1);i+=2)
s2+=(*Fun)(a+i*h);
return ((b-a)/(3*n)*((*Fun)(a)+2*s1+4*s2+(*Fun)(b)));
}
§3. Массив указателей на функции.
Указатели на функции могут быть объединены в массивы. Например,
float (*FunArray[5]) (char, char);
описывает массив фиксированной размерности из пяти указателей на функции, каждая из которых имеет два параметра типа char и возвращает значение типа float. Как для обычных массивов, индексация начинается с нуля. Поэтому для обращения к третьей из этих функций потребуется, например, такой оператор:
float f= (*FunArray[2]) (‘A’, ‘B’);
Массив указателей на функции удобно использовать при разработке программ, управление которыми выполняется с помощью меню. Для этого действия, предлагаемые на выбор пользователю программы (например, создание файла, чтение и анализ, корректировка файла), оформляются в виде функций, адреса которых помещаются в массив указателей на функции. Пользователь вводит номер выбираемого пунктаи по нему из массива выбирается соответствующий адрес функции. Обращение к функции по этому адресу обеспечивает выполнение требуемых действий. Благодаря такому методу программирования нет необходимости выбирать соответствующие функции с помощью оператора switch. Общая схема реализации такого подхода иллюстрирует следующая программа:
Пример 1. Статический массив указателей на функции.
void Create (int);
void Read(int);
void Append(int);
void main()
{ const n=3;
// Объявляем массив из трёх указателей на функции
void (*far[n])(char *)= { Create, Read, Append };
int Numf;
char NameOfFile[30];
cout<<”\n 1- CREATE”;
cout<<”\n 2- READ”;
cout<<”\n 3- APPEND”;
cout<<”\n 4- EXIT”;
while (1)
{ while (1)
{ cout<<”\n Item of menu”; cin>>Numf
if (Numf>=1 && Numf<=4) break;
cout<<” \n Error! Repeat/ “;
}
if (Numf==4) break;
else
{cout<<”\n Name of file “;
gets(NameOfFile);
(*far[Numf-1])(NameOfFile);
}
}
}
void Create (int x)
{ cout << " Creating of file"<<endl;
}
void Read (int x)
{ cout << " Reading of file"<<endl;
}
void Append (int x)
{ cout << " Appending of file"<<endl;
|
|
}
В этом учебном, искусственном примере объявили массив far из трёх указателей на функции типа void, каждая из которых имеет один входной параметр (физическое имя обрабатываемого файла) типа указателя на строку (char *). Функция ничего не возвращает, а только выводит текст в зависимости от выполняемых над файлом действий. При объявлении массива выполнена инициализация тремя конкретными, определёнными после main(), функциями Create, Read и Append. В цикле вводим номер функции (1, 2, 3 или 4 для выхода) и выполняем одну из трёх функций. В качестве параметра в конкретную функцию передаётся её реальный номер в массиве (1 для Create, 2 для Read, и 3 для Append). Если введём в качестве номера число 4 выходим из внешнего цикла.
Пример 2. (+) Динамический массив указателей на функции. Для x=0, 0.2, …, 1 вывести таблицу значений трёх функций.
// Тексты двух функций. Третья функция – стандартная cos(x).
double MyExp (double x){
return (exp(x));
};
double Myq(double x){
return x*x;
};
void main()
{ int n; cin>>n;
/* Объявляем и создаём массив указателей на функции с одним вещественным параметром. */
double (*(*fun))(double)= new (double (*[n])(double));
// Элементам созданного массива присваиваем имена функций.
fun[0]= cos;
fun[1]=MyExp;
fun[2]=Myq;
// Цикл для изменения x
for (float x=0; x<1; x+=0.2)
{ printf("%5.1f",x);
/* Цикл для вывода значений всех функций для одного фиксированного значения x. */
for(int j=0; j<n; j++)
printf (" %20.6f", fun[j](x));
cout<<endl;
}
getch(); }