Указатель функции , также называемый указателем подпрограммы или указателем процедуры , является указателем, ссылающимся на исполняемый код, а не на данные. Разыменование указателя функции дает указанную функцию , которую можно вызывать и передавать аргументы так же, как при обычном вызове функции. Такой вызов также известен как «косвенный» вызов, потому что функция вызывается косвенно через переменную, а не напрямую через фиксированный идентификатор или адрес.
Указатели функций позволяют выполнять различный код во время выполнения. Их также можно передавать в функцию для включения обратных вызовов .
Указатели функций поддерживаются языками программирования третьего поколения (такими как PL/I , COBOL , Fortran , [1] dBASE , dBL [ требуется пояснение ] и C ), а также объектно-ориентированными языками программирования (такими как C++ , C# и D ). [2]
Простейшая реализация указателя функции (или подпрограммы) — это переменная, содержащая адрес функции в исполняемой памяти. Старые языки третьего поколения, такие как PL/I и COBOL , а также более современные языки, такие как Pascal и C, обычно реализуют указатели функций таким образом. [3]
Следующая программа на языке C иллюстрирует использование двух указателей функций:
#include <stdio.h> /* для printf */ #include <string.h> /* для strchr */ двойной см_в_дюймы ( двойной см ) { вернуть см / 2,54 ; } // "strchr" является частью обработки строк C (т.е. нет необходимости в объявлении) // См. https://en.wikipedia.org/wiki/Function_pointer/C_string_handling#Functionsint main ( void ) { double ( * func1 )( double ) = cm_to_inches ; char * ( * func2 )( const char * , int ) = strchr ; printf ( "%f %s" , func1 ( 15.0 ), func2 ( "Wikipedia" , 'p' )); /* печатает "5.905512 pedia" */ return 0 ; }
Следующая программа использует указатель на функцию для вызова одной из двух функций ( sin
или cos
) косвенно из другой функции ( , вычисляя приближение интегрирования Риманаcompute_sum
функции ). Программа работает, дважды вызывая функцию , передавая ей указатель на библиотечную функцию в первый раз и указатель на функцию во второй раз. Функция , в свою очередь, вызывает одну из двух функций косвенно, разыменовывая свой аргумент указателя на функцию несколько раз, складывая значения, которые возвращает вызванная функция, и возвращая полученную сумму. Две суммы записываются в стандартный вывод с помощью .main
compute_sum
sin
cos
compute_sum
funcp
main
#include <math.h> #include <stdio.h> // Функция, принимающая указатель на функцию в качестве аргументадвойная сумма_компьютера ( двойная ( * funcp )( двойная ), двойная нижняя , двойная верхняя ) { двойная сумма = 0,0 ; // Добавить значения, возвращаемые указанной функцией '*funcp' целое число i ; для ( я = 0 ; я <= 100 ; я ++ ) { // Используйте указатель функции 'funcp' для вызова функции двойной x = i / 100,0 * ( хай - лоу ) + лоу ; двойной y = funcp ( x ); сумма += у ; } возврат суммы / 101.0 * ( хай - лоу ); }двойной квадрат ( двойной икс ) { вернуть х * х ; }int main ( void ) { двойная сумма ; // Использовать стандартную библиотечную функцию 'sin()' как указанную функцию сумма = вычислить_сумму ( sin , 0.0 , 1.0 ); printf ( "сумма(sin): %g \n " , сумма ); // Использовать стандартную библиотечную функцию 'cos()' в качестве указываемой функции сумма = вычисляемая_сумма ( потому что , 0,0 , 1,0 ); printf ( "сумма(cos): %g \n " , сумма ); // Использовать пользовательскую функцию 'square()' как указанную функцию сумма = вычислить_сумму ( квадрат , 0,0 , 1,0 ); printf ( "сумма(квадрат): %g \n " , сумма ); вернуть 0 ; }
Функторы, или функциональные объекты, похожи на указатели функций и могут использоваться аналогичным образом. Функторы — это объекты класса, реализующие оператор вызова функции , что позволяет использовать объект в выражениях с использованием того же синтаксиса, что и вызов функции. Функторы более эффективны, чем простые указатели функций, поскольку они могут содержать собственные значения данных и позволяют программисту эмулировать замыкания . Они также используются в качестве функций обратного вызова, если необходимо использовать функцию-член в качестве функции обратного вызова. [4]
Многие «чистые» объектно-ориентированные языки не поддерживают указатели функций. Однако в таких языках можно реализовать нечто подобное, используя ссылки на интерфейсы , определяющие один метод (функцию-член). Языки CLI, такие как C# и Visual Basic .NET, реализуют указатели функций с безопасной типизацией с делегатами .
В других языках, поддерживающих функции первого класса , функции рассматриваются как данные и могут передаваться, возвращаться и создаваться динамически непосредственно другими функциями, что устраняет необходимость в указателях функций.
Широкое использование указателей функций для вызова функций может привести к замедлению кода на современных процессорах, поскольку предсказатель ветвлений может оказаться не в состоянии определить, куда следует перейти (это зависит от значения указателя функции во время выполнения), хотя этот эффект может быть преувеличен, поскольку он часто с лихвой компенсируется значительным сокращением неиндексированных поисков в таблицах.
C++ включает поддержку объектно-ориентированного программирования , поэтому классы могут иметь методы (обычно называемые функциями-членами). Нестатические функции-члены (методы экземпляра) имеют неявный параметр ( указатель this ), который является указателем на объект, с которым он работает, поэтому тип объекта должен быть включен как часть типа указателя функции. Затем метод используется на объекте этого класса с использованием одного из операторов «указатель на член»: .*
или ->*
(для объекта или указателя на объект соответственно). [ dubious – discussion ]
Хотя указатели функций в C и C++ могут быть реализованы как простые адреса, sizeof(Fx)==sizeof(void *)
указатели членов в C++ иногда реализуются как « толстые указатели », обычно в два или три раза превышающие размер простого указателя функции, для работы с виртуальными методами и виртуальным наследованием [ требуется ссылка ] .
В C++, в дополнение к методу, используемому в C, также можно использовать шаблон класса стандартной библиотеки C++ std::function , экземплярами которого являются объекты функций:
#include <iostream> #include <functional> с использованием пространства имен std ; статическая двойная производная ( константная функция < 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 ; } статический двойной f ( двойной x ) { return x * x ; } int main () { double x = 1 ; cout << "d/dx(x ^ 2) [@ x = " << x << "] = " << производная ( f , x , 1e-5 ) << endl ; return 0 ; }
Вот как C++ использует указатели функций при работе с функциями-членами классов или структур. Они вызываются с помощью указателя на объект или вызова this. Они являются типобезопасными, поскольку вы можете вызывать членов этого класса (или производных) только с помощью указателя этого типа. Этот пример также демонстрирует использование typedef для указателя на функцию-член, добавленного для простоты. Указатели функций на статические функции-члены сделаны в традиционном стиле 'C', поскольку для этого вызова не требуется указатель на объект.
#include <iostream> с использованием пространства имен std ; класс Фу { 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++, то же самое, что: 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++, приведенный выше, является каноническим, используемым во всех учебниках, но его трудно читать и объяснять. Даже приведенные выше typedef
примеры используют этот синтаксис. Однако каждый компилятор C и C++ поддерживает более понятный и лаконичный механизм объявления указателей функций: используйте typedef
, но не сохраняйте указатель как часть определения. Обратите внимание, что единственный способ, которым этот тип typedef
может быть фактически использован, — это указатель, но это подчеркивает его указательность.
// Это объявляет 'F', функцию, которая принимает 'char' и возвращает 'int'. Определение в другом месте. int F ( char c ); // Это определяет 'Fn', тип функции, которая принимает 'char' и возвращает 'int'. typedef int Fn ( char c ); // Это определяет 'fn', переменную типа указатель на 'Fn', и присваивает ей адрес 'F'. Fn * fn = & F ; // Обратите внимание, что '&' не требуется, но он подчеркивает то, что делается. // Это вызывает 'F' с помощью 'fn', присваивая результат переменной 'a' int a = fn ( 'A' ); // Это определяет 'Call', функцию, которая принимает указатель на 'Fn', вызывает ее и возвращает результат int Call ( Fn * fn , char c ) { return fn ( c ); } // Call(fn, c) // Это вызывает функцию 'Call', передавая 'F' и присваивая результат 'call' int call = Call ( & F , 'A' ); // Опять же, '&' не требуется // УСТАРЕНИЕ: Обратите внимание, что для поддержания существующих баз кода можно по-прежнему сначала использовать указанный выше стиль определения; // затем исходный тип можно определить в его терминах, используя новый стиль.// Это определяет 'PFn', тип указателя на тип-Fn. typedef Fn * PFn ; // 'PFn' можно использовать везде, где можно использовать 'Fn *' PFn pfn = F ; int CallP ( PFn fn , char c );
В этих примерах используются приведенные выше определения. В частности, обратите внимание, что приведенное выше определение for Fn
может использоваться в определениях указателей на функции-члены:
// Это определяет 'C', класс с похожими статическими и функциями-членами, // а затем создает экземпляр с именем 'c' class C { public : static int Static ( char c ); int Member ( char c ); } c ; // C // Это определяет 'p', указатель на 'C' и присваивает ему адрес 'c' C * p = & c ; // Это присваивает указатель на 'Static' переменной 'fn'. // Поскольку 'this' отсутствует, 'Fn' — правильный тип; и 'fn' можно использовать, как указано выше. fn = & C :: Static ; // Это определяет 'm', указатель на член класса 'C' с типом 'Fn', // и присваивает ему адрес 'C::Member'. // Вы можете читать его справа налево, как и все указатели: // "'m' — это указатель на член класса 'C' типа 'Fn'" Fn C ::* m = & C :: Member ; // Здесь используется 'm' для вызова 'Member' в 'c', результат присваивается 'cA' int cA = ( c . * m )( 'A' ); // Здесь используется 'm' для вызова 'Member' в 'p', результат присваивается 'pA' int pA = ( p ->* m )( 'A' ); // Это определяет 'Ref', функцию, которая принимает ссылку на 'C', // указатель на член 'C' типа 'Fn' и 'char', // вызывает функцию и возвращает результат int Ref ( C & r , Fn C ::* m , char c ) { return ( r . * m )( c ); } // Ref(r, m, c) // Это определяет 'Ptr', функцию, которая принимает указатель на 'C', // указатель на член 'C' типа 'Fn' и 'char', // вызывает функцию и возвращает результат int Ptr ( C * p , Fn C ::* m , char c ) { return ( p ->* m )( c ); } // Ptr(p, m, c) // УСТАРЕНИЕ: Обратите внимание, что для поддержания существующих баз кода можно по-прежнему сначала использовать указанный выше стиль определения; // затем исходный тип можно определить в его терминах, используя новый стиль.// Это определяет 'FnC', тип указателя на член класса 'C' типа 'Fn' typedef Fn C ::* FnC ; // 'FnC' можно использовать везде, где можно использовать 'Fn C::*' FnC fnC = & C :: Member ; int RefP ( C & p , FnC m , char c );
Указатели функций — это указатели, т.е. переменные, которые указывают на адрес функции.
Важное примечание: указатель на функцию всегда указывает на функцию с определенной сигнатурой! Таким образом, все функции, которые вы хотите использовать с одним и тем же указателем на функцию, должны иметь одинаковые параметры и возвращаемый тип!
Если вы хотите использовать функцию-член в качестве функции обратного вызова, то функцию-член необходимо связать с объектом класса, прежде чем ее можно будет вызвать. В этом случае вы можете использовать функтор [с примером на этой странице].