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

Чтобы понять, как работают указатели на функции, надо иметь в виду следующее. По мере компиляции функций исходный код, записанный по правилам языка С++, преобразуется в объектный и устанавливается точка входа, к которой происходит обращение при вызове функции.

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

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

Например,

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


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: