Указатель на функцию , которая также называется указателем подпрограммы или указатель процедура , является указателем , который указывает на функцию. В отличие от ссылки на значение данных, указатель функции указывает на исполняемый код в памяти. Разыменование указателя функции дает указанную функцию , которую можно вызывать и передавать аргументы так же, как при обычном вызове функции. Такой вызов также известен как «косвенный» вызов, потому что функция вызывается косвенно через переменную, а не напрямую через фиксированный идентификатор или адрес.
Указатели функций могут использоваться для упрощения кода, обеспечивая простой способ выбора функции для выполнения на основе значений времени выполнения.
Указатели функций поддерживаются языками программирования третьего поколения (такими как PL / I , COBOL , Fortran , [1] dBASE dBL и C ) и объектно-ориентированными языками программирования (такими как C ++ и D ). [2]
Указатели на простые функции
Самая простая реализация указателя функции (или подпрограммы) - это переменная, содержащая адрес функции в исполняемой памяти. Старые языки третьего поколения, такие как PL / I и COBOL , а также более современные языки, такие как Pascal и C, обычно реализуют указатели на функции таким образом. [3]
Пример на C
Следующая программа на C иллюстрирует использование двух указателей на функции:
- func1 принимает один параметр двойной точности (double) и возвращает другой двойной, и назначается функции, которая преобразует сантиметры в дюймы.
- func2 принимает указатель на массив константных символов, а также целое число и возвращает указатель на символ, и назначается функции обработки строки C, которая возвращает указатель на первое вхождение данного символа в массив символов.
#include / * для printf * /#include / * для strchr * /двойной cm_to_inches ( двойной см ) { return cm / 2.54 ; }// "strchr" является частью обработки строки C (т. е. не требует объявления) // См. https://en.wikipedia.org/wiki/C_string_handling#Functionsint main ( void ) { двойной ( * func1 ) ( двойной ) = cm_to_inches ; char * ( * func2 ) ( const char * , int ) = strchr ; printf ( "% f% s" , func1 ( 15.0 ), func2 ( "Википедия" , 'p' )); / * выводит "5.905512 pedia" * / return 0 ; }
Следующая программа использует указатель функции для вызова одной из двух функций ( sin
или cos
) косвенно из другой функции ( compute_sum
вычисляя приближение интегрирования Римана функции ). Программа работает, имея функцию main
вызова функции compute_sum
дважды, передавая ей указатель на библиотечную функцию sin
в первый раз и указатель на функцию cos
во второй раз. Функция, compute_sum
в свою очередь, вызывает одну из двух функций косвенно, разыменовывая аргумент указателя функции funcp
несколько раз, складывая значения, возвращаемые вызванной функцией, и возвращая полученную сумму. Две суммы записываются в стандартный вывод с помощью main
.
#include #include // Функция принимает указатель на функцию в качестве аргументаdouble compute_sum ( double ( * funcp ) ( double ), double lo , double hi ) { двойная сумма = 0,0 ; // Добавляем значения, возвращаемые указанной функцией '* funcp' int i ; for ( i = 0 ; i <= 100 ; i ++ ) { // Используйте указатель функции 'funcp' для вызова функции двойной x = i / 100.0 * ( hi - lo ) + lo ; двойной y = funcp ( x ); сумма + = у ; } сумма возврата / 101.0 * ( привет - низ );}двойной квадрат ( двойной х ) { вернуть х * х ;}int main ( void ) { двойная сумма ; // Использовать стандартную библиотечную функцию sin () как указанную функцию сумма = compute_sum ( sin , 0,0 , 1,0 ); printf ( "сумма (грех):% г \ п " , сумма ); // Использовать стандартную библиотечную функцию 'cos ()' как указанную функцию сумма = compute_sum ( cos , 0,0 , 1,0 ); printf ( "сумма (соз):% г \ п " , сумма ); // Использование пользовательской функции square () в качестве функции, на которую указывает сумма = compute_sum ( квадрат , 0,0 , 1,0 ); printf ( "сумма (квадрат):% g \ n " , сумма ); возврат 0 ;}
Функторы
Функторы или функциональные объекты похожи на указатели на функции и могут использоваться аналогичным образом. Функтор - это объект типа класса, который реализует оператор вызова функции , что позволяет использовать объект в выражениях с использованием того же синтаксиса, что и вызов функции. Функторы более мощные, чем простые указатели на функции, они могут содержать собственные значения данных и позволяют программисту имитировать замыкания . Они также используются как функции обратного вызова, если необходимо использовать функцию-член в качестве функции обратного вызова. [4]
Многие «чистые» объектно-ориентированные языки не поддерживают указатели на функции. Что-то подобное можно реализовать в этих языках, используя ссылки на интерфейсы, которые определяют единственный метод (функцию-член). Такие языки интерфейса командной строки , как C # и Visual Basic .NET, реализуют типобезопасные указатели на функции с делегатами .
В других языках, поддерживающих функции первого класса , функции рассматриваются как данные и могут передаваться, возвращаться и создаваться динамически непосредственно другими функциями, что устраняет необходимость в указателях на функции.
Широкое использование указателей функций для вызова функций может привести к замедлению выполнения кода на современных процессорах, потому что предсказатель ветвления может не понять, куда перейти (это зависит от значения указателя функции во время выполнения), хотя это Эффект можно переоценить, поскольку он часто полностью компенсируется значительным сокращением поиска в неиндексированной таблице.
Указатели методов
C ++ включает поддержку объектно-ориентированного программирования , поэтому классы могут иметь методы (обычно называемые функциями-членами). Нестатические функции-члены (методы экземпляра) имеют неявный параметр ( указатель this ), который является указателем на объект, над которым он работает, поэтому тип объекта должен быть включен как часть типа указателя функции. Затем метод используется для объекта этого класса с помощью одного из операторов «указатель на член»: .*
или ->*
(для объекта или указателя на объект, соответственно).
Хотя указатели на функции в C и C ++ могут быть реализованы как простые адреса, поэтому обычно sizeof(Fx)==sizeof(void *)
указатели на элементы в C ++ иногда реализуются как «жирные указатели», обычно в два или три раза превышающие размер простого указателя на функцию, чтобы иметь дело с виртуальными методы и виртуальное наследование [ ссылка ] .
В C ++
В C ++, помимо метода, используемого в C, также можно использовать шаблон класса стандартной библиотеки C ++ std :: function , экземпляры которого являются объектами функций:
#include #include <функциональный>статическая двойная производная ( const std :: function < double ( double ) > & f , double x0 , double eps ) { double eps2 = eps / 2 ; двойной lo = x0 - eps2 ; двойной привет = x0 + eps2 ; return ( f ( привет ) - f ( lo )) / eps ; }статический двойной е ( двойной х ) { вернуть х * х ; }int main () { двойной x = 1 ; std :: cout << "d / dx (x ^ 2) [@ x =" << x << "] =" << производная ( f , x , 1e-5 ) << std :: endl ; возврат 0 ; }
Указатели на функции-члены в C ++
Вот как C ++ использует указатели на функции при работе с функциями-членами классов или структур. Они вызываются с помощью указателя на объект или вызова this. Они безопасны по типу в том смысле, что вы можете вызывать только члены этого класса (или производные), используя указатель этого типа. В этом примере также демонстрируется использование typedef для указателя на функцию-член, добавленную для простоты. Указатели функций на статические функции-члены выполнены в традиционном стиле «C», поскольку для этого вызова не требуется указатель на объект.
#include используя пространство имен std ;class Foo {общедоступные : 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 я , int j , Foo * pFoo , int ( Foo :: * pfn ) ( int , int )) { return ( pFoo -> * pfn ) ( я , j ); }ЬурейиЙ INT ( Foo :: * Foo_pfn ) ( INT , INT );int bar2 ( int я , int j , Foo * pFoo , Foo_pfn pfn ) { return ( pFoo -> * pfn ) ( я , j ); }typedef int ( * PFN ) ( интервал );int bar3 ( int я , PFN pfn ) { return pfn ( я ); }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 ; возврат 0 ; }
Альтернативный синтаксис C и C ++
Приведенный выше синтаксис C и C ++ является каноническим, используемым во всех учебниках, но его трудно читать и объяснять. Даже в приведенных выше typedef
примерах используется этот синтаксис. Однако каждый компилятор C и C ++ поддерживает более ясный и лаконичный механизм объявления указателей на функции: используйте typedef
, но не сохраняйте указатель как часть определения. Обратите внимание, что единственный способ использования этого типа typedef
- указатель, но это подчеркивает его указатель.
C и C ++
// Объявляется 'F', функция, которая принимает 'char' и возвращает 'int'. Определение находится в другом месте. int F ( символ c );// Это определяет 'Fn', тип функции, которая принимает 'char' и возвращает 'int'. typedef int Fn ( символ 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 ); } // Вызов (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 );
C ++
В этих примерах используются приведенные выше определения. В частности, обратите внимание, что приведенное выше определение для 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 ', // указатель-на-член-of-'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 );
Смотрите также
- Делегирование (вычисления)
- Функциональный объект
- Функция высшего порядка
- Процедурный параметр
Рекомендации
- ^ Эндрю Дж. Миллер. «Примеры Фортрана» . http://www.esm.psu.edu/~ajm138/fortranexamples.html . Проверено 14 сентября 2013 .CS1 maint: location ( ссылка )
- ^ "Учебные пособия по указателям на функции" . http://www.newty.de/ : логотип . Проверено 13 апреля 2011 .
Указатели функций - это указатели, то есть переменные, которые указывают на адрес функции.
- ^ "Учебные пособия по указателям на функции" . http://www.newty.de/ : логотип . Проверено 13 апреля 2011 .
Важное примечание: указатель функции всегда указывает на функцию с определенной сигнатурой! Таким образом, все функции, которые вы хотите использовать с одним и тем же указателем на функцию, должны иметь одинаковые параметры и возвращаемый тип!
- ^ «Опыт: Промежуточный язык: C ++: используйте функтор для обратных вызовов в C ++» . http://www.devx.com/ : DevX.com. 2005-01-31 . Проверено 13 апреля 2011 .
Если вы хотите использовать функцию-член в качестве функции обратного вызова, тогда эта функция-член должна быть связана с объектом класса, прежде чем ее можно будет вызвать. В этом случае вы можете использовать функтор [с примером на этой странице].
Внешние ссылки
- Часто задаваемые вопросы по указателям на функции , чего следует избегать с указателями на функции, некоторая информация об использовании объектов функций
- Учебники по указателям на функции , руководство по указателям на функции C / C ++, обратным вызовам и объектам функций (функторам)
- Указатели на функции-члены и самые быстрые из возможных делегатов C ++ , статья Дона Клагстона на CodeProject
- Учебники по указателям , документация по C ++ и учебные пособия
- Указатели C объяснили визуальное руководство указателей в C
- Указатель на безопасную функцию и обратные вызовы в программировании под Windows , статья Р. Селвама на CodeProject
- Книга C , указатели на функции в C от "The C Book"
- Указатели функций в dBASE dBL , указатели функций в dBASE dBL