Toiminnon osoitin - Function pointer
Toiminto osoitin , kutsutaan myös aliohjelman osoitin tai menettely osoitin , on osoitin , joka viittaa toiminnon. Toisin kuin data-arvoon viitataan, toimintoosoitin osoittaa suoritettavaa koodia muistissa. Funktion osoittimen poikkeaminen tuottaa viitatun funktion , joka voidaan kutsua ja välittää argumentteja aivan kuten tavallisessa funktiokutsussa. Tällainen herätys tunnetaan myös "epäsuoraa" puhelu, koska toiminto on turvauduttu epäsuorasti läpi vaihtelevan sijasta suoraan läpi kiinteän tunnisteen tai osoitteen.
Toiminnon osoittimia voidaan käyttää koodin yksinkertaistamiseen tarjoamalla yksinkertainen tapa valita suoritettava toiminto ajoaikojen arvojen perusteella.
Toiminnon osoittimia tukevat kolmannen sukupolven ohjelmointikielet (kuten PL / I , COBOL , Fortran , dBASE dBL ja C ) ja olio-ohjelmointikielet (kuten C ++ , C # ja D ).
Yksinkertaiset toimintoviitteet
Funktion (tai aliohjelman) osoittimen yksinkertaisin toteutus on muuttuja, joka sisältää toiminnon osoitteen suoritettavassa muistissa. Vanhemmat kolmannen sukupolven kielet , kuten PL / I ja COBOL , sekä nykyaikaisemmat kielet, kuten Pascal ja C, toteuttavat yleensä toiminto-osoittimia tällä tavalla.
Esimerkki julkaisussa C
Seuraava C-ohjelma kuvaa kahden toiminnon osoittimen käyttöä:
- func1 ottaa yhden kaksoistarkkuuden (kaksois) parametrin ja palauttaa toisen kaksoisparametrin, ja se määritetään funktiolle, joka muuntaa senttimetrit tuumiksi.
- func2 vie osoittimen vakiomerkkiryhmään sekä kokonaislukuun ja palauttaa osoittimen merkille , ja se osoitetaan C-merkkijononkäsittelyfunktiolle, joka palauttaa osoittimen tietyn merkin ensimmäiseen esiintymiseen merkkimatriisissa.
#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;
}
Seuraava ohjelma käyttää funktiosoitinta kutsumaan yhden kahdesta toiminnosta ( sintai cos) epäsuorasti toisesta toiminnosta ( compute_sumlaskemalla funktion Riemann-integraation likiarvon ). Ohjelma toimii siten, että sillä on toiminnon mainkutsutoiminto compute_sumkahdesti, välittämällä se osoitin kirjastotoiminnolle sinensimmäisen kerran ja osoittimen toimimaan costoisen kerran. Funktio compute_sumpuolestaan kutsuu yhtä kahdesta toiminnosta epäsuorasti vähentämällä sen funktiosoitin argumenttia funcpuseita kertoja, laskemalla yhteen kutsutun funktion palauttamat arvot ja palauttamalla tuloksena olevan summan. Nämä kaksi summaa kirjoitetaan vakiotulokseen 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;
}
Toiminnot
Toiminnot tai toiminto-objektit ovat samanlaisia kuin toiminto-osoittimet, ja niitä voidaan käyttää samalla tavalla. Functor on luokan tyyppinen objekti, joka toteuttaa funktiokutsuoperaattorin ja sallii objektin käytön lausekkeissa käyttäen samaa syntaksia kuin toimintokutsu. Toiminnot ovat tehokkaampia kuin yksinkertaiset toiminto-osoittimet, ja ne pystyvät sisällyttämään omat tietoarvonsa ja antavat ohjelmoijan jäljitellä sulkemia . Niitä käytetään myös takaisinsoittotoimintoina, jos jäsenfunktiota on tarpeen käyttää takaisinsoittotoimintona.
Monet "puhtaat" olio-kielet eivät tue funktion osoittimia. Jotain vastaavaa voidaan toteuttaa tällaisissa kielillä, kuitenkin käyttäen viittauksia ja rajapintoja , jotka määrittävät yhden menetelmän (jäsenfunktiogeneraattori). CLI-kielet , kuten C # ja Visual Basic .NET, toteuttavat tyyppiturvallisia toiminto-osoittimia edustajien kanssa .
Muilla kielillä, jotka tukevat ensiluokkaisia toimintoja , toimintoja pidetään datana, ja muut toiminnot voivat välittää, palauttaa ja luoda niitä dynaamisesti suoraan, jolloin toiminto-osoittimia ei tarvita.
Toimintojen osoittimien laaja käyttö toimintojen kutsumiseksi voi tuottaa hidastuksen koodille nykyaikaisissa prosessoreissa, koska haaranennuste ei välttämättä pysty selvittämään, mihin haarautua (se riippuu toiminto-osoittimen arvosta ajon aikana), vaikka tämä vaikutus voidaan liioitella, koska se kompensoidaan usein runsaasti vähentämällä indeksoimattomia taulukoita.
Menetelmän osoittimet
C ++ sisältää olio-ohjelmoinnin tuen , joten luokilla voi olla menetelmiä (joita yleensä kutsutaan jäsenfunktioksi). Ei-staattisilla jäsenfunktioilla (instanssimenetelmillä) on implisiittinen parametri ( tämä osoitin), joka on osoitin objektille, jota se käyttää, joten objektin tyyppi on sisällytettävä funktion osoittimen tyyppiin. Menetelmää käytetään sitten kyseisen luokan objektissa käyttämällä yhtä "pointer-to-member" -operaattoreista: .*tai ->*(objektin tai osoittimen vastaavaa).
Vaikka funktiosoittimet C: ssä ja C ++: ssa voidaan toteuttaa yksinkertaisina osoitteina, niin tyypillisesti sizeof(Fx)==sizeof(void *)C ++: n jäsenosoittimet toteutetaan joskus "rasvaosoittimina", tyypillisesti kaksi tai kolme kertaa yksinkertaisemman funktion osoittimen kokoinen, virtuaalisten asioiden käsittelemiseksi menetelmät ja virtuaaliperintö .
C ++: ssa
C ++: ssa C: ssä käytetyn menetelmän lisäksi on mahdollista käyttää myös C ++ - standardin kirjastoluokkamalli std :: -funktiota , jonka esiintymät ovat funktio-objekteja:
#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;
}
Osoittimet jäsentoimintoihin C ++: ssa
Näin C ++ käyttää funktion osoittimia käsitellessään luokkien tai rakenteiden jäsentoimintoja. Nämä kutsutaan objektiosoittimella tai tällä puhelulla. Ne ovat tyyppiturvallisia, koska voit soittaa kyseisen luokan jäsenille (tai johdannaisille) vain tämän tyyppisellä osoittimella. Tämä esimerkki osoittaa myös typedefin käytön osoittimen jäseneksi -toiminnolle, joka on lisätty yksinkertaisuuden vuoksi. Staattisten jäsenfunktioiden toiminto-osoittimet tehdään perinteisellä C-tyylillä, koska tälle kutsulle ei tarvita objektiosoitinta.
#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;
}
Vaihtoehtoinen C- ja C ++ -syntaksi
Edellä annettu C- ja C ++ -syntaksi on kanoninen, jota käytetään kaikissa oppikirjoissa - mutta sitä on vaikea lukea ja selittää. Jopa yllä olevat typedefesimerkit käyttävät tätä syntaksia. Jokainen C- ja C ++-kääntäjä tukee kuitenkin selkeämpää ja ytimekkäämpää mekanismia funktiosoittimien ilmoittamiseksi: käytä typedef, mutta älä tallenna osoitinta osana määritelmää. Huomaa, että ainoa tapa, jolla tällaista typedefvoidaan todella käyttää, on osoittimen kanssa - mutta se korostaa sen osoittavuutta.
C ja 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 ++
Näissä esimerkeissä käytetään yllä olevia määritelmiä. Huomaa erityisesti, että yllä olevaa määritelmää Fnvoidaan käyttää osoitin-jäsen-funktio-määritelmissä:
// 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);
Katso myös
Viitteet
Ulkoiset linkit
- Usein kysytyt kysymykset toiminnon osoittimista, toiminnon osoittimilla vältettävät asiat, joitakin tietoja toiminto-objektien käytöstä
- Function Pointer -oppaat , opas C / C ++ -toiminnon osoittimiin, soittopyyntöihin ja toiminto-objekteihin (funktoreihin)
- Jäsentoimintoviitteet ja nopeimmat mahdolliset C ++ -edustajat , CodeProject -artikkeli Don Clugston
- Osoittimen oppaat, C ++ -dokumentaatio ja oppaat
- C-osoittimet selittivät C-osoittimien visuaalisen oppaan
- Suojattu toimintojen osoitin ja takaisinkutsut Windows-ohjelmoinnissa , R. Selvamin CodeProject-artikkeli
- C-kirja , funktion osoittimet kirjaimessa "The C Book"
- Toiminnon osoittimet dBASE dBL: ssä , toimintoosoitin dBASE dBL: ssä