stringtranslate.com

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

Указатель функции , также называемый указателем подпрограммы или указателем процедуры , представляет собой указатель, ссылающийся на исполняемый код, а не на данные. Разыменование указателя функции дает указанную функцию , которую можно вызывать и передавать аргументы так же, как при обычном вызове функции. Такой вызов также известен как «косвенный» вызов, поскольку функция вызывается косвенно через переменную, а не напрямую через фиксированный идентификатор или адрес.

Указатели на функции позволяют выполнять различный код во время выполнения. Их также можно передать в функцию, чтобы включить обратные вызовы .

Указатели на функции поддерживаются языками программирования третьего поколения (такими как PL/I , COBOL , Fortran , [1] dBASE dBL [ необходимо пояснение ] и C ) и объектно-ориентированными языками программирования (такими как C++ , C# и D ). . [2]

Простые указатели функций

Простейшая реализация указателя функции (или подпрограммы) — это переменная, содержащая адрес функции в исполняемой памяти. Старые языки третьего поколения , такие как PL/I и COBOL , а также более современные языки, такие как Pascal и C , обычно реализуют указатели на функции таким образом. [3]

Пример на языке C

Следующая программа на языке C иллюстрирует использование двух указателей на функции:

#include <stdio.h>  /* для printf */ #include <string.h> /* для strchr */  двойной cm_to_inches ( двойной см ) { return cm / 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 ( "Википедия" , 'p' )); /* печатает «5.905512 педиа» */ return 0 ; }                

Следующая программа использует указатель на функцию для вызова одной из двух функций ( sinили cos) косвенно из другой функции ( , вычисляя приближение интегрированияcompute_sum функции по Риману ). Программа работает, дважды вызывая функцию , передавая ей указатель на библиотечную функцию в первый раз и указатель на функцию во второй раз. Функция, в свою очередь, косвенно вызывает одну из двух функций, несколько раз разыменовывая аргумент указателя функции , суммируя значения, возвращаемые вызванной функцией, и возвращая полученную сумму. Две суммы записываются в стандартный вывод с помощью .maincompute_sumsincoscompute_sumfuncpmain

#include <math.h> #include <stdio.h> // Функция, принимающая указатель на функцию в качестве аргументаdoublecompute_sum ( double ( * funcp )( double ) , double lo , double hi ) {        двойная сумма = 0,0 ;    // Добавляем значения, возвращаемые функцией, на которую указывает '*funcp' ИНТ я ;  для ( я знак равно 0 ; я <= 100 ; я ++ ) {         // Используйте указатель функции funcp для вызова функции двойной х = я / 100,0 * ( привет - ло ) + ло ;            двойной y = funcp ( x );    сумма += у ;   } возвращаемая сумма / 101,0 * ( привет - лоу );       }двойной квадрат ( двойной х ) {    вернуть х * х ;   }int main ( void ) {   двойная сумма ;  // Используем стандартную библиотечную функцию 'sin()' в качестве указываемой функции сумма = вычисляемая_сумма ( грех , 0,0 , 1,0 );     printf ( "sum(sin): %g \n " , sum );  // Используем стандартную библиотечную функцию cos() в качестве указываемой функции сумма = вычисляемая_сумма ( потому что , 0,0 , 1,0 );     printf ( "sum(cos): %g \n " , sum );  // Используем пользовательскую функцию 'square()' в качестве указываемой функции сумма = вычисляемая_сумма ( квадрат , 0,0 , 1,0 );     printf ( "sum(square): %g \n " , sum );  вернуть 0 ; }

Функторы

Функторы или функциональные объекты подобны указателям на функции и могут использоваться аналогичным образом. Функтор — это объект типа класса, который реализует оператор вызова функции , позволяя использовать объект в выражениях, используя тот же синтаксис, что и вызов функции. Функторы более мощны, чем простые указатели на функции, поскольку могут содержать собственные значения данных и позволяют программисту эмулировать замыкания . Они также используются в качестве функций обратного вызова, если необходимо использовать функцию-член в качестве функции обратного вызова. [4]

Многие «чистые» объектно-ориентированные языки не поддерживают указатели на функции. Однако нечто подобное можно реализовать в таких языках, используя ссылки на интерфейсы , определяющие один метод (функцию-член). Языки CLI , такие как C# и Visual Basic .NET, реализуют типобезопасные указатели функций с делегатами .

В других языках, поддерживающих первоклассные функции , функции рассматриваются как данные и могут передаваться, возвращаться и создаваться динамически непосредственно другими функциями, что устраняет необходимость в указателях на функции.

Широкое использование указателей функций для вызова функций может привести к замедлению работы кода на современных процессорах, поскольку предсказатель ветвления может быть не в состоянии определить, куда перейти (это зависит от значения указателя функции во время выполнения), хотя этот эффект можно переоценить, поскольку он часто полностью компенсируется значительным сокращением количества запросов к неиндексированным таблицам.

Указатели методов

C++ включает поддержку объектно-ориентированного программирования , поэтому классы могут иметь методы (обычно называемые функциями-членами). Нестатические функции-члены (методы экземпляра) имеют неявный параметр ( указатель this ), который является указателем на объект, над которым они работают, поэтому тип объекта должен быть включен как часть типа указателя функции. Затем метод используется для объекта этого класса с помощью одного из операторов «указателя на член»: .*или ->*(для объекта или указателя на объект соответственно). [ сомнительнообсудить ]

Хотя указатели на функции в C и C++ могут быть реализованы как простые адреса, поэтому обычно sizeof(Fx)==sizeof(void *)указатели на члены в C++ иногда реализуются как « толстые указатели », обычно в два или три раза превышающие размер простого указателя на функцию, чтобы иметь дело с виртуальными. методы и виртуальное наследование .

На С++

В C++, в дополнение к методу, используемому в C, также можно использовать шаблон класса стандартной библиотеки C++ std::function , экземплярами которого являются функциональные объекты:

#include <iostream> #include <functional> using namespace std ;    статическая двойная производная ( const function < double ( double ) > & f , double x0 , double eps ) { double eps2 = eps / 2 ; двойной lo = x0 - eps2 ; двойной привет = x0 + eps2 ; return ( f ( привет ) - f ( ло )) / eps ; }                                 static double f ( double x ) { return x * x ; }        int main () { двойной x = 1 ; cout << "d/dx(x ^ 2) [@ x = " << x << "] = " << производная ( f , x , 1e-5 ) << endl ; вернуть 0 ; }                     

Указатели на функции-члены в C++

Именно так 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 ; вернуть 0 ; }                                

Альтернативный синтаксис C и C++

Приведенный выше синтаксис C и C++ является каноническим, используемым во всех учебниках, но его сложно читать и объяснять. Даже приведенные выше typedefпримеры используют этот синтаксис. Однако каждый компилятор C и C++ поддерживает более ясный и краткий механизм объявления указателей на функции: используйте typedef, но не сохраняйте указатель как часть определения. Обратите внимание, что единственный способ typedefиспользования такого рода — это использование указателя, но это подчеркивает его указательность.

С и С++

// Здесь объявляется «F», функция, которая принимает «char» и возвращает «int». Определение находится в другом месте. int F ( символ с );  // Здесь определяется «Fn», тип функции, которая принимает «char» и возвращает «int». typedef int Fn ( char c );   // Определяет 'fn', переменную типа указатель на 'Fn', и присваивает ей адрес 'F'. Фн * фн = & Ф ; // Примечание '&' не обязательно, но оно подчеркивает, что делается.    // Это вызывает 'F' с использованием 'fn', присваивая результат переменной 'a' int a = fn ( 'A' );   // Здесь определяется 'Call', функция, которая принимает указатель на 'Fn', вызывает ее и возвращает результат int Call ( Fn * fn , char c ) { return fn ( c ); } // Вызов(фн, с)        // Это вызывает функцию «Call», передавая «F» и присваивая результат «call» int call = Call ( & F , 'A' ); // Опять же, '&' не требуется     // СТАРОСТЬ: Обратите внимание, что для поддержки существующих баз кода сначала можно использовать приведенный выше стиль определения; // тогда исходный тип может быть определен в его терминах с использованием нового стиля.// Здесь определяется 'PFn', тип указателя на тип Fn. определение типа Fn * PFn ;  // 'PFn' можно использовать везде, где можно 'Fn *' PFn pfn = F ; int CallP ( PFn fn , char c );       

С++

В этих примерах используются приведенные выше определения. В частности, обратите внимание, что приведенное выше определение Fnможно использовать в определениях указателей на функции-члены:

// Здесь определяется 'C', класс с похожими статическими функциями и функциями-членами, // а затем создается экземпляр с именем 'c' class C { public : static int Static ( char c ); int Member ( char c ); } С ; // С         // Здесь определяется 'p', указатель на 'C', и присваивается ему адрес 'c' C * p = & c ;   // Это присваивает указатель на «Static» для «fn». // Так как 'this' нет, правильный тип - 'Fn'; и «fn» можно использовать, как указано выше. fn = & C :: Статический ;  // Здесь определяется '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 );         

Смотрите также

Рекомендации

  1. ^ Эндрю Дж. Миллер. «Примеры Фортрана» . Проверено 14 сентября 2013 г.
  2. ^ "Учебные пособия по указателям функций" . логотип. Архивировано из оригинала 16 мая 2011 г. Проверено 13 апреля 2011 г. Указатели функций — это указатели, то есть переменные, которые указывают на адрес функции.
  3. ^ "Учебные пособия по указателям функций" . логотип. Архивировано из оригинала 16 мая 2011 г. Проверено 13 апреля 2011 г. Важное примечание: указатель функции всегда указывает на функцию с определенной сигнатурой! Таким образом, все функции, которые вы хотите использовать с одним и тем же указателем функции, должны иметь одинаковые параметры и тип возвращаемого значения!
  4. ^ «Опыт: средний уровень языка: C++: использование функтора для обратных вызовов в C++». DevX.com. 31 января 2005 г. Проверено 13 апреля 2011 г. Если вы хотите использовать функцию-член в качестве функции обратного вызова, то функция-член должна быть связана с объектом класса, прежде чем ее можно будет вызвать. В этом случае вы можете использовать функтор [с примером на этой странице].

Внешние ссылки