Указатель функции
Указатель функции , также называемый указателем подпрограммы или указателем процедуры , представляет собой указатель, ссылающийся на исполняемый код, а не на данные. Разыменование указателя функции дает указанную функцию , которую можно вызывать и передавать аргументы так же, как при обычном вызове функции. Такой вызов также известен как «косвенный» вызов, поскольку функция вызывается косвенно через переменную, а не напрямую через фиксированный идентификатор или адрес.
Указатели функций позволяют выполнять различный код во время выполнения. Их также можно передать в функцию, чтобы включить обратные вызовы .
Указатели функций поддерживаются третьего поколения языками программирования (такими как PL/I , COBOL , Fortran , [1] dBASE dBL и C ) и объектно-ориентированные языки программирования (такие как C++ , C# и D ). [2]
Простые указатели функций [ править ]
Простейшая реализация указателя функции (или подпрограммы) — это переменная, содержащая адрес функции в исполняемой памяти. Старые языки третьего поколения, такие как PL/I и COBOL , а также более современные языки, такие как Pascal и C, обычно реализуют указатели на функции таким образом. [3]
Пример на C [ править ]
Следующая программа на языке C иллюстрирует использование двух указателей на функции:
- func1 принимает один параметр двойной точности (double) и возвращает другой двойной точности, который присваивается функции, которая преобразует сантиметры в дюймы.
- func2 принимает указатель на постоянный массив символов, а также целое число, и возвращает указатель на символ, а также назначается функции обработки строк C , которая возвращает указатель на первое вхождение данного символа в массив символов.
#include <stdio.h> /* for printf */
#include <string.h> /* for strchr */
double cm_to_inches(double cm) {
return cm / 2.54;
}
// "strchr" is part of the C string handling (i.e., no need for declaration)
// See https://en.wikipedia.org/wiki/C_string_handling#Functions
int main(void) {
double (*func1)(double) = cm_to_inches;
char * (*func2)(const char *, int) = strchr;
printf("%f %s", func1(15.0), func2("Wikipedia", 'p'));
/* prints "5.905512 pedia" */
return 0;
}
Следующая программа использует указатель на функцию для вызова одной из двух функций ( sin
или cos
) косвенно из другой функции ( compute_sum
функции , вычисляя аппроксимацию интегрирования Римана ). Программа работает, имея функцию main
функция вызова compute_sum
дважды, передав ему указатель на библиотечную функцию sin
первый раз и указатель на функцию cos
второй раз. Функция compute_sum
в свою очередь вызывает одну из двух функций косвенно, разыменовывая аргумент указателя функции. funcp
несколько раз, складывая значения, возвращаемые вызванной функцией, и возвращая полученную сумму. Две суммы записываются в стандартный вывод с помощью main
.
#include <math.h>
#include <stdio.h>
// Function taking a function pointer as an argument
double compute_sum(double (*funcp)(double), double lo, double hi) {
double sum = 0.0;
// Add values returned by the pointed-to function '*funcp'
int i;
for (i = 0; i <= 100; i++) {
// Use the function pointer 'funcp' to invoke the function
double x = i / 100.0 * (hi - lo) + lo;
double y = funcp(x);
sum += y;
}
return sum / 101.0 * (hi - lo);
}
double square(double x) {
return x * x;
}
int main(void) {
double sum;
// Use standard library function 'sin()' as the pointed-to function
sum = compute_sum(sin, 0.0, 1.0);
printf("sum(sin): %g\n", sum);
// Use standard library function 'cos()' as the pointed-to function
sum = compute_sum(cos, 0.0, 1.0);
printf("sum(cos): %g\n", sum);
// Use user-defined function 'square()' as the pointed-to function
sum = compute_sum(square, 0.0, 1.0);
printf("sum(square): %g\n", sum);
return 0;
}
Актеры [ править ]
Функторы или функциональные объекты подобны указателям на функции и могут использоваться аналогичным образом. Функтор — это объект типа класса, который реализует оператор вызова функции , позволяя использовать объект в выражениях, используя тот же синтаксис, что и вызов функции. Функторы более мощны, чем простые указатели на функции, поскольку они могут содержать собственные значения данных и позволяют программисту эмулировать замыкания . Они также используются в качестве функций обратного вызова, если необходимо использовать функцию-член в качестве функции обратного вызова. [4]
Многие «чистые» объектно-ориентированные языки не поддерживают указатели на функции. Однако нечто подобное можно реализовать и в таких языках, используя ссылки на интерфейсы , определяющие один метод (функцию-член). Языки CLI, такие как C# и Visual Basic .NET, реализуют типобезопасные указатели функций с делегатами .
В других языках, поддерживающих первоклассные функции , функции рассматриваются как данные и могут передаваться, возвращаться и создаваться динамически непосредственно другими функциями, что устраняет необходимость в указателях на функции.
Широкое использование указателей функций для вызова функций может привести к замедлению работы кода на современных процессорах, поскольку предсказатель ветвления может быть не в состоянии определить, куда перейти (это зависит от значения указателя функции во время выполнения), хотя этот эффект можно переоценить, поскольку он часто полностью компенсируется значительно меньшим количеством запросов к неиндексированным таблицам.
Указатели методов [ править ]
C++ включает поддержку объектно-ориентированного программирования , поэтому классы могут иметь методы (обычно называемые функциями-членами). Нестатические функции-члены (методы экземпляра) имеют неявный параметр ( указатель this ), который является указателем на объект, над которым они работают, поэтому тип объекта должен быть включен как часть типа указателя функции. Затем метод используется для объекта этого класса с помощью одного из операторов «указателя на член»: .*
или ->*
(для объекта или указателя на объект соответственно). [ сомнительно – обсудить ]
Хотя указатели на функции в C и C++ могут быть реализованы как простые адреса, так что обычно sizeof(Fx)==sizeof(void *)
Указатели на члены в C++ иногда реализуются как « толстые указатели », обычно в два или три раза превышающие размер указателя на простую функцию, чтобы иметь дело с виртуальными методами и виртуальным наследованием. [ нужна ссылка ] .
В C++ [ править ]
В C++, помимо метода, используемого в C, также можно использовать шаблон класса стандартной библиотеки C++. std::function , экземплярами которого являются функциональные объекты:
#include <iostream>
#include <functional>
using namespace std;
static double derivative(const function<double(double)> &f, double x0, double eps) {
double eps2 = eps / 2;
double lo = x0 - eps2;
double hi = x0 + eps2;
return (f(hi) - f(lo)) / eps;
}
static double f(double x) {
return x * x;
}
int main() {
double x = 1;
cout << "d/dx(x ^ 2) [@ x = " << x << "] = " << derivative(f, x, 1e-5) << endl;
return 0;
}
Указатели на функции-члены в C++ [ править ]
Именно так C++ использует указатели на функции при работе с функциями-членами классов или структур. Они вызываются с помощью указателя объекта или вызова this. Они типобезопасны, поскольку вы можете вызывать члены этого класса (или его производных) только с помощью указателя этого типа. В этом примере также показано использование typedef для указателя на функцию-член, добавленного для простоты. Указатели функций на статические функции-члены выполняются в традиционном стиле C, поскольку для этого вызова не требуется указатель объекта.
#include <iostream>
using namespace std;
class Foo {
public:
int add(int i, int j) {
return i+j;
}
int mult(int i, int j) {
return i*j;
}
static int negate(int i) {
return -i;
}
};
int bar1(int i, int j, Foo* pFoo, int(Foo::*pfn)(int,int)) {
return (pFoo->*pfn)(i,j);
}
typedef int(Foo::*Foo_pfn)(int,int);
int bar2(int i, int j, Foo* pFoo, Foo_pfn pfn) {
return (pFoo->*pfn)(i,j);
}
typedef auto(*PFN)(int) -> int;
// C++ only, same as: typedef int(*PFN)(int);
int bar3(int i, PFN pfn) {
return pfn(i);
}
int main() {
Foo foo;
cout << "Foo::add(2,4) = " << bar1(2,4, &foo, &Foo::add) << endl;
cout << "Foo::mult(3,5) = " << bar2(3,5, &foo, &Foo::mult) << endl;
cout << "Foo::negate(6) = " << bar3(6, &Foo::negate) << endl;
return 0;
}
Альтернативный синтаксис C и C++ [ править ]
Приведенный выше синтаксис C и C++ является каноническим, используемым во всех учебниках, но его сложно читать и объяснять. Даже вышеперечисленное typedef
примеры используют этот синтаксис. Однако каждый компилятор C и C++ поддерживает более понятный и краткий механизм объявления указателей на функции: используйте typedef
, но не сохраняйте указатель как часть определения. Обратите внимание, что единственный способ такого рода typedef
на самом деле можно использовать с указателем, но это подчеркивает его указательность.
C и C++ [ править ]
// This declares 'F', a function that accepts a 'char' and returns an 'int'. Definition is elsewhere.
int F(char c);
// This defines 'Fn', a type of function that accepts a 'char' and returns an 'int'.
typedef int Fn(char c);
// This defines 'fn', a variable of type pointer-to-'Fn', and assigns the address of 'F' to it.
Fn *fn = &F; // Note '&' not required - but it highlights what is being done.
// This calls 'F' using 'fn', assigning the result to the variable 'a'
int a = fn('A');
// This defines 'Call', a function that accepts a pointer-to-'Fn', calls it, and returns the result
int Call(Fn *fn, char c) {
return fn(c);
} // Call(fn, c)
// This calls function 'Call', passing in 'F' and assigning the result to 'call'
int call = Call(&F, 'A'); // Again, '&' is not required
// LEGACY: Note that to maintain existing code bases, the above definition style can still be used first;
// then the original type can be defined in terms of it using the new style.
// This defines 'PFn', a type of pointer-to-type-Fn.
typedef Fn *PFn;
// 'PFn' can be used wherever 'Fn *' can
PFn pfn = F;
int CallP(PFn fn, char c);
С++ [ править ]
В этих примерах используются приведенные выше определения. В частности, отметим, что приведенное выше определение для Fn
может использоваться в определениях указателей на функции-члены:
// This defines 'C', a class with similar static and member functions,
// and then creates an instance called 'c'
class C {
public:
static int Static(char c);
int Member(char c);
} c; // C
// This defines 'p', a pointer to 'C' and assigns the address of 'c' to it
C *p = &c;
// This assigns a pointer-to-'Static' to 'fn'.
// Since there is no 'this', 'Fn' is the correct type; and 'fn' can be used as above.
fn = &C::Static;
// This defines 'm', a pointer-to-member-of-'C' with type 'Fn',
// and assigns the address of 'C::Member' to it.
// You can read it right-to-left like all pointers:
// "'m' is a pointer to member of class 'C' of type 'Fn'"
Fn C::*m = &C::Member;
// This uses 'm' to call 'Member' in 'c', assigning the result to 'cA'
int cA = (c.*m)('A');
// This uses 'm' to call 'Member' in 'p', assigning the result to 'pA'
int pA = (p->*m)('A');
// This defines 'Ref', a function that accepts a reference-to-'C',
// a pointer-to-member-of-'C' of type 'Fn', and a 'char',
// calls the function and returns the result
int Ref(C &r, Fn C::*m, char c) {
return (r.*m)(c);
} // Ref(r, m, c)
// This defines 'Ptr', a function that accepts a pointer-to-'C',
// a pointer-to-member-of-'C' of type 'Fn', and a 'char',
// calls the function and returns the result
int Ptr(C *p, Fn C::*m, char c) {
return (p->*m)(c);
} // Ptr(p, m, c)
// LEGACY: Note that to maintain existing code bases, the above definition style can still be used first;
// then the original type can be defined in terms of it using the new style.
// This defines 'FnC', a type of pointer-to-member-of-class-'C' of type 'Fn'
typedef Fn C::*FnC;
// 'FnC' can be used wherever 'Fn C::*' can
FnC fnC = &C::Member;
int RefP(C &p, FnC m, char c);
См. также [ править ]
- Делегирование (вычисления)
- Функциональный объект
- Функция высшего порядка
- Процедурный параметр
- Закрытие
- Анонимные функции
Ссылки [ править ]
- ^ Эндрю Дж. Миллер. «Примеры Фортрана» . Проверено 14 сентября 2013 г.
- ^ «Учебные пособия по указателям функций» . логотип. Архивировано из оригинала 16 мая 2011 г. Проверено 13 апреля 2011 г.
Указатели функций — это указатели, то есть переменные, которые указывают на адрес функции.
- ^ «Учебные пособия по указателям функций» . логотип. Архивировано из оригинала 16 мая 2011 г. Проверено 13 апреля 2011 г.
Важное примечание: указатель функции всегда указывает на функцию с определенной сигнатурой! Таким образом, все функции, которые вы хотите использовать с одним и тем же указателем функции, должны иметь одинаковые параметры и тип возвращаемого значения!
- ^ «Экспертиза: средний уровень языка: C++: использование функтора для обратных вызовов в C++» . DevX.com. 31 января 2005 г. Проверено 13 апреля 2011 г.
Если вы хотите использовать функцию-член в качестве функции обратного вызова, то функция-член должна быть связана с объектом класса, прежде чем ее можно будет вызвать. В этом случае вы можете использовать функтор [с примером на этой странице].
Внешние ссылки [ править ]
- Часто задаваемые вопросы по указателям на функции , чего следует избегать при использовании указателей на функции, некоторая информация об использовании объектов-функций
- Учебные пособия по указателям функций, заархивированные 30 июня 2018 г. на Wayback Machine , руководство по указателям на функции C/C++, обратным вызовам и объектам функций (функторам).
- Указатели функций-членов и самые быстрые делегаты C++ , статья CodeProject Дона Клагстона
- Учебные пособия по указателям, заархивированные 5 апреля 2009 г. на Wayback Machine , документация и учебные пособия по C++.
- Объяснение указателей C. Архивировано 9 июня 2019 г. на Wayback Machine, визуальное руководство по указателям на C.
- Безопасный указатель функции и обратные вызовы в программировании под Windows , статья Р. Сельвама на CodeProject
- Книга C , Указатели функций в C от "The C Book"
- Указатели функций в dBASE dBL , Указатели функций в dBASE dBL