Ukazatel funkce - Function pointer

Ukazatel funkce , také nazýván podprogram ukazatel nebo ukazatel postup , je ukazatel , který ukazuje na funkci. Na rozdíl od odkazování na datovou hodnotu ukazuje ukazatel funkce na spustitelný kód v paměti. Dereferencí ukazatele funkce se získá odkazovaná funkce , kterou lze vyvolat a předat argumenty stejně jako při normálním volání funkce. Takové vyvolání je také známé jako „nepřímé“ volání, protože funkce je vyvolána nepřímo prostřednictvím proměnné namísto přímo prostřednictvím pevného identifikátoru nebo adresy.

Ukazatele funkcí lze použít ke zjednodušení kódu poskytnutím jednoduchého způsobu výběru funkce k provedení na základě hodnot za běhu.

Ukazatele funkcí jsou podporovány programovacími jazyky třetí generace (například PL / I , COBOL , Fortran , dBASE dBL a C ) a objektově orientovanými programovacími jazyky (například C ++ , C # a D ).

Jednoduché funkční ukazatele

Nejjednodušší implementace ukazatele funkce (nebo podprogramu) je jako proměnná obsahující adresu funkce ve spustitelné paměti. Starší jazyky třetí generace, jako jsou PL / I a COBOL , stejně jako modernější jazyky, jako je Pascal a C, obecně implementují ukazatele funkcí tímto způsobem.

Příklad v C

Následující program C ilustruje použití dvou funkčních ukazatelů:

  • func1 vezme jeden parametr s dvojitou přesností (double) a vrátí další double a je přiřazen funkci, která převádí centimetry na palce.
  • func2 vezme ukazatel na konstantní pole znaků i celé číslo a vrátí ukazatel na znak a je přiřazen funkci zpracování řetězce C, která vrací ukazatel na první výskyt daného znaku v poli znaků.
#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;
}

Následující program používá ukazatel funkce k vyvolání jedné ze dvou funkcí ( sinnebo cos) nepřímo z jiné funkce ( compute_sumpočítá aproximaci Riemannovy integrace funkce ). Program pracuje tak, že má funkci mainvolání funkce compute_sumdvakrát, předá jí ukazatel na funkci knihovny sinpoprvé a ukazatel na funkci cospodruhé. Funkce compute_sumzase vyvolá jednu ze dvou funkcí nepřímo tím funcp, že několikrát dereferuje svůj argument ukazatele funkce , sečte hodnoty, které vyvolaná funkce vrátí, a vrátí výsledný součet. Tyto dva součty jsou zapsány na standardní výstup pomocí 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 nebo funkční objekty jsou podobné ukazatelům funkcí a lze je použít podobným způsobem. Funktor je objekt typu třídy, který implementuje operátor volání funkce , což umožňuje použití objektu v rámci výrazů pomocí stejné syntaxe jako volání funkce. Funktory jsou výkonnější než jednoduché ukazatele funkcí, jsou schopné obsahovat své vlastní datové hodnoty a umožňují programátorovi emulovat uzávěry . Používají se také jako funkce zpětného volání, pokud je nutné použít funkci člena jako funkci zpětného volání.

Mnoho „čistých“ objektově orientovaných jazyků nepodporuje ukazatele funkcí. Něco podobného lze implementovat i v těchto druzích jazyků, a to pomocí odkazů na rozhraní, která definují jedinou metodu (členskou funkci). Jazyky CLI, jako je C # a Visual Basic .NET, implementují ukazatele funkcí bezpečných pro typ s delegáty .

V jiných jazycích, které podporují prvotřídní funkce , jsou funkce považovány za data a lze je předávat, vracet a vytvářet dynamicky přímo jinými funkcemi, což eliminuje potřebu ukazatelů funkcí.

Rozsáhlé používání ukazatelů funkcí k volání funkcí může u moderních procesorů způsobit zpomalení kódu, protože prediktor větve nemusí být schopen zjistit, kam se má větvit (záleží na hodnotě ukazatele funkce za běhu), i když to efekt může být nadhodnocen, protože je často dostatečně kompenzován výrazně sníženým neindexovaným vyhledáváním tabulky.

Ukazatele metody

C ++ zahrnuje podporu objektově orientovaného programování , takže třídy mohou mít metody (obvykle označované jako členské funkce). Nestatické členské funkce (metody instance) mají implicitní parametr ( tento ukazatel), který je ukazatelem na objekt, na kterém pracuje, takže typ objektu musí být zahrnut jako součást typu ukazatele funkce. Metoda se poté použije na objekt dané třídy pomocí jednoho z operátorů "ukazatel-na-člena": .*nebo ->*(pro objekt nebo ukazatel na objekt).

Přestože lze ukazatele funkcí v C a C ++ implementovat jako jednoduché adresy, takže obvykle sizeof(Fx)==sizeof(void *)jsou členské ukazatele v C ++ někdy implementovány jako „tlusté ukazatele“, obvykle dvakrát nebo třikrát větší než ukazatel jednoduchých funkcí, aby bylo možné vypořádat se s virtuálními metody a virtuální dědičnost .

V C ++

V C ++, kromě metody použité v C, je také možné použít standardní šablonu třídy knihovny C ++ std :: function , jejíž instance jsou funkční objekty:

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

Ukazatele na členské funkce v C ++

Takto C ++ používá ukazatele funkcí při jednání s členskými funkcemi tříd nebo struktur. Ty jsou vyvolány pomocí ukazatele objektu nebo tohoto volání. Jsou typově bezpečné v tom, že můžete volat členy této třídy (nebo deriváty) pouze pomocí ukazatele daného typu. Tento příklad také ukazuje použití typedef pro ukazatel na členskou funkci přidanou pro jednoduchost. Ukazatele funkcí na statické členské funkce se provádějí v tradičním stylu „C“, protože pro toto volání není vyžadován žádný ukazatel objektu.

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

Alternativní syntaxe C a C ++

Výše uvedená syntaxe C a C ++ je kanonická, která se používá ve všech učebnicích - ale je těžké ji číst a vysvětlovat. I výše uvedené typedefpříklady používají tuto syntaxi. Nicméně, každý C a C ++ kompilátor podporuje více jasné a stručné mechanismus na ukazateli funkce deklarovat: použití typedef, ale ne ukládat ukazatel jako součást definice. Všimněte si, že jediný způsob, jak lze tento druh typedefskutečně použít, je ukazatel - ale to zdůrazňuje jeho ukazatelnost.

C a 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 ++

Tyto příklady používají výše uvedené definice. Zejména si všimněte, že výše uvedená definice pro Fnmůže být použita v definicích ukazatelů na členské funkce:

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

Viz také

Reference

externí odkazy