Wskaźnik funkcji - Function pointer

Wskaźnik funkcji , zwany również wskaźnik podprogram lub procedura wskaźnik , to wskaźnik , który wskazuje na funkcję. W przeciwieństwie do odwoływania się do wartości danych, wskaźnik funkcji wskazuje na kod wykonywalny w pamięci. Wyłuskanie wskaźnika funkcji daje w wyniku funkcję , do której istnieje odwołanie , którą można wywoływać i przekazywać argumenty tak samo, jak w normalnym wywołaniu funkcji. Takie wywołanie jest również znane jako wywołanie „pośrednie”, ponieważ funkcja jest wywoływana pośrednio przez zmienną zamiast bezpośrednio przez stały identyfikator lub adres.

Wskaźników funkcji można użyć do uproszczenia kodu, zapewniając prosty sposób wybierania funkcji do wykonania na podstawie wartości czasu wykonywania.

Wskaźniki funkcji są obsługiwane przez języki programowania trzeciej generacji (takie jak PL/I , COBOL , Fortran , dBASE dBL i C ) oraz języki programowania obiektowego (takie jak C++ , C# i D ).

Proste wskaźniki funkcji

Najprostszą implementacją wskaźnika do funkcji (lub podprogramu) jest zmienna zawierająca adres funkcji w pamięci wykonywalnej. Starsze języki trzeciej generacji, takie jak PL / I i COBOL , a także nowsze języki, takie jak Pascal i C, generalnie implementują wskaźniki funkcji w ten sposób.

Przykład w C

Poniższy program w C ilustruje użycie dwóch wskaźników funkcji:

  • func1 pobiera jeden parametr podwójnej precyzji (podwójny) i zwraca inny parametr podwójnej precyzji i jest przypisany do funkcji, która konwertuje centymetry na cale.
  • func2 pobiera wskaźnik do stałej tablicy znaków oraz liczby całkowitej i zwraca wskaźnik do znaku i jest przypisana do funkcji obsługującej łańcuch C, która zwraca wskaźnik do pierwszego wystąpienia danego znaku w tablicy znaków.
#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;
}

Następny program używa wskaźnika funkcji do wywołania jednej z dwóch funkcji ( sinlub cos) pośrednio z innej funkcji ( compute_sum, obliczanie przybliżenia całkowania Riemanna funkcji ). Program działa poprzez dwukrotne mainwywołanie funkcji compute_sum, przekazując jej wskaźnik do funkcji bibliotecznej sinza pierwszym razem i wskaźnik do funkcji cosza drugim razem. Funkcja compute_sumz kolei wywołuje jedną z dwóch funkcji pośrednio przez funcpwielokrotne wyłuskanie argumentu wskaźnika funkcji , zsumowanie wartości zwracanych przez wywołaną funkcję i zwrócenie sumy wynikowej. Dwie sumy są wypisywane na standardowe wyjście przez 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;
}

Funktory

Funktory lub obiekty funkcyjne są podobne do wskaźników funkcji i mogą być używane w podobny sposób. Funktor to obiekt typu klasy, który implementuje operator wywołania funkcji , co pozwala na użycie obiektu w wyrażeniach używających tej samej składni, co wywołanie funkcji. Funktory są potężniejsze niż proste wskaźniki funkcji, ponieważ mogą zawierać własne wartości danych i pozwalają programiście emulować domknięcia . Są one również używane jako funkcje zwrotne, jeśli konieczne jest użycie funkcji składowej jako funkcji zwrotnej.

Wiele „czystych” języków obiektowych nie obsługuje wskaźników do funkcji. Coś podobnego można jednak zaimplementować w tego rodzaju językach, korzystając z odwołań do interfejsów, które definiują pojedynczą metodę (funkcja elementu członkowskiego). Języki interfejsu wiersza polecenia, takie jak C # i Visual Basic .NET, implementują wskaźniki funkcji bezpiecznych dla typu z delegatami .

W innych językach obsługujących funkcje pierwszej klasy funkcje są traktowane jako dane i mogą być przekazywane, zwracane i tworzone dynamicznie bezpośrednio przez inne funkcje, eliminując potrzebę stosowania wskaźników do funkcji.

Obszerne używanie wskaźników funkcji do wywoływania funkcji może powodować spowolnienie kodu na nowoczesnych procesorach, ponieważ predyktor gałęzi może nie być w stanie określić, dokąd rozgałęziać się (zależy to od wartości wskaźnika funkcji w czasie wykonywania), chociaż to efekt może być zawyżony, ponieważ jest często kompensowany przez znacznie zredukowane nieindeksowane wyszukiwania tabel.

Wskaźniki metod

C++ zawiera obsługę programowania obiektowego , więc klasy mogą mieć metody (zwykle nazywane funkcjami składowymi). Niestatyczne funkcje składowe (metody instancji) mają niejawny parametr ( wskaźnik this ), który jest wskaźnikiem do obiektu, na którym działają, więc typ obiektu musi być zawarty jako część typu wskaźnika funkcji. Metoda jest następnie używana na obiekcie tej klasy przy użyciu jednego z operatorów „wskaźnik do elementu członkowskiego”: .*lub ->*(odpowiednio dla obiektu lub wskaźnika do obiektu).

Chociaż wskaźniki do funkcji w C i C++ mogą być zaimplementowane jako proste adresy, więc zazwyczaj sizeof(Fx)==sizeof(void *)wskaźniki składowe w C++ są czasami implementowane jako „grube wskaźniki”, zwykle dwa lub trzy razy większe od prostego wskaźnika do funkcji, w celu radzenia sobie z wirtualnymi metody i dziedziczenie wirtualne .

W C++

W C ++ oprócz metody używanej w C możliwe jest również użycie standardowego szablonu klasy biblioteki C ++ std :: function , którego instancjami są obiekty funkcyjne:

#include <iostream>
#include <functional>

static double derivative(const std::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;
    std::cout << "d/dx(x ^ 2) [@ x = " << x << "] = " << derivative(f, x, 1e-5) << std::endl;
    return 0;
}

Wskaźniki do funkcji składowych w C++

W ten sposób C++ używa wskaźników do funkcji, gdy zajmuje się funkcjami składowymi klas lub struktur. Są one wywoływane za pomocą wskaźnika do obiektu lub wywołania this. Są bezpieczne pod względem typu, ponieważ możesz wywoływać tylko członków tej klasy (lub instrumentów pochodnych) za pomocą wskaźnika tego typu. Ten przykład ilustruje również użycie typedef dla wskaźnika do funkcji składowej dodanej dla uproszczenia. Wskaźniki funkcji do statycznych funkcji składowych są wykonywane w tradycyjnym stylu „C”, ponieważ dla tego wywołania nie jest wymagany wskaźnik do obiektu.

#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 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;
}

Alternatywna składnia C i C++

Podana powyżej składnia C i C++ jest kanoniczna używana we wszystkich podręcznikach - ale jest trudna do odczytania i wyjaśnienia. Nawet powyższe typedefprzykłady używają tej składni. Jednak każdy kompilator C i C++ obsługuje bardziej przejrzysty i zwięzły mechanizm deklarowania wskaźników funkcji: use typedef, ale nie przechowuj wskaźnika jako części definicji. Zauważ, że jedynym sposobem, w jaki można tego rodzaju typedefużyć, jest użycie wskaźnika - ale to podkreśla jego znaczenie.

C i 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);

C++

W tych przykładach zastosowano powyższe definicje. W szczególności zauważ, że powyższą definicję for Fnmożna stosować w definicjach funkcji wskaźnika do elementu członkowskiego:

// 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);

Zobacz też

Bibliografia

Linki zewnętrzne