Funktio-osoittimet¶
Usein ohjelmointikielistä puhuttaessa sanotaan, että ohjelmiin liittyy tietoa ja toimenpiteitä. Jako ei kuitenkaan ole näin suoraviivainen. Myös funktiot ovat tietoa.
Joskus sanotaan, että funktiot ovat “ensimmäisen luokan kansalaisia”. Tällä tarkoitetaan, että funktioita voi käsitellä samoin kuin muitakin tietoalkioita. Funktion voi esimerkiksi välittää parametrina toiselle funktiolle tai funktion paluuarvona tai tallentaa muuttujaan. Tämä tapahtuu C++:ssa niin kutsutun funktio-osoittimen avulla.
Funktiota, joka ottaa parametrinaan toisen funktion tai palauttaa toisen funktion, kutsutaan korkeamman asteen funktioksi (higher-order function). Funktioita, jotka eivät ole korkeampaa astetta, voidaan kutsua ensimmäisen asteen funktioiksi (first-order function).
Numeerinen integrointi¶
Tutkitaan esimerkkiä, jonka löydät hakemistosta examples/10/integral
.
Numeerisessa integroinnissa ideana on approksimoida funktion kuvaajan ja X-akselin väliin jäävän alueen pinta-alaa siten, että jaetaan alue kapeisiin suorakaiteen muotoisiin alueisiin ja lasketaan näiden suorakaiteen muotoisten alueiden pinta-alat yhteen.
Yllä olevassa kuvassa esitetään mustalla piirretyn kuvaajan ja X-akselin väliin jäävän alueen pinta-alan approksimointia kahdella eri tavalla. Vihreäksi väritettyjen suorakaiteiden pinta-alat yhteen laskemalla saataisiin alasumma pinta-alan likiarvolle. Keltaiset suorakaiteet ovat osittain piilossa vihreiden suorakaiteiden takana, mutta laskemalla niiden pinta-alat yhteen, saataisiin puolestaan yläsumma samalle pinta-alan likiarvolle. Likiarvoa saataisiin tarkennettua suorakaiteiden lukumäärää kasvattamalla (eli siis niiden leveyttä pienentämällä).
Esimerkkiohjelmassamme ei lasketa alasummaa eikä yläsummaa, vaan suorakaiteen korkeus määräytyy aina sen mukaan, mikä funktion arvo suorakaiteen keskikohdassa on. Kuvassa nämä pisteet on merkitty sinisellä pallukalla.
Ohjelmaesimerkissä on toteutettu funktio integrate
, joka saa
ensimmäisenä parametrinaan integroitavan funktion f
:
double integrate(Func f, double left, double right, int number_of_partitions = 500);
Funktion toteutuksessa on käytetty apuna määrittelyä
using Func = decltype(&polynomial);
Käskyllä decltype
saadaan selville minkä tahansa muuttujan tai
funktion tietotyyppi.
Yhtä hyvin oltaisiin voitu määritellä C++:n tyyppimäärittelysyntaksilla:
using Func = double(*)(double);
joka tarkoittaa, että Func
on osoitin funktioon, joka palauttaa
doublen ja saa parametrinaan doublen. Määrittelyssä ensimmäinen double
on paluuarvo, (*)
tarkoittaa funktio-osoitinta ja sulkujen sisällä
on parametriluettelo.
Tietenkin olisi mahdollista myös jättää using
-lause kokonaan pois,
ja määritellä funktio integrate
seuraavasti:
double integrate(double(*f)(double), double left, double right, int number_of_partitions = 500);
Huomaa, että edellä ei siis vain korvata tunnistetta Func
sillä,
mitä using
-lauseessa luki sijoitusoperaattorin oikealla puolella.
Kiinnitä huomiosi siihen, missä kohdassa funktio-osoittimen nimi f
lukee.
Kun funktiota integrate
kutsutaan, pitää sille antaa parametrina osoitin
funktioon, esimerkiksi:
cout << integrate(sin, 0, 2) << endl;
cout << integrate(cos, 0, 2) << endl;
cout << integrate(sqrt, 0, 2) << endl;
cout << integrate(polynomial, 0, 2) << endl;
Huomaa, että näissä ei ole sulkeita ensimmäisen parametrin perässä, koska kyse ei ole funktiokutsusta vaan osoittimesta funktioon.
Funktio-osoittimien käyttötarkoituksia¶
Palaamme funktio-osoittimiin seuraavien kierrosten aikana. Funktio-osoittimista on hyötyä esimerkiksi seuraavissa tilanteissa:
- Haluamme valita, mitä toiminnallisuutta ohjelmassa suoritetaan,
mutta emme halua toteuttaa ohjelmaan valtavaa
if
-rakennetta, jonka jokaisessa lohkossa on yksi valittavana olevista toiminnallisuuksista (vrt. esimerkkiexamples/04/datadrivenprogramming
ja materiaalin kohta Tieto-ohjattu ohjelmointi 4-kierrokselta). - Toteutamme graafisia käyttöliittymiä ja haluamme sitoa jonkin toiminnon johonkin käyttöliittymäkomponenttiin.