Funktiot

Tarkastellaan seuraavia koodeja:

def keskimmäinen(luku1, luku2, luku3):
    if luku2 <= luku1 <= luku3 \
           or luku3 <= luku1 <= luku2:
        return luku1
    elif luku1 <= luku2 <= luku3 \
             or luku3 <= luku2 <= luku1:
        return luku2
    else:
        return luku3

def main():
    print(keskimmäinen(4, 5, 1))

main()
#include <iostream>

using namespace std;

int keskimmainen(int luku1, int luku2, int luku3) {
    if ( (luku2 <= luku1 and luku1 <= luku3)
           or (luku3 <= luku1 and luku1 <= luku2) ) {
        return luku1;
    } else if ( (luku1 <= luku2 and luku2 <= luku3)
           or (luku3 <= luku2 and luku2 <= luku1) ) {
        return luku2;
    } else {
        return luku3;
    }
}

int main() {
    cout << keskimmainen(4, 5, 1) << endl;
}
  • Esimerkkikoodien keskeisimmät erot liittyvät dynaamisen staattisen tyypityksen asettamiin vaatimuksiin: C++:ssa ohjelmoijan täytyy eksplisiittisesti (erikseen) määrätä funktion paluuarvon ja parametrien tyypit, jotta kääntäjä voi tarkastaa, että funktion kutsukohdassa käytetyt parametrit ovat sopivan tyyppisiä ja että paluuarvoa käytetään hyväksyttävällä tavalla.

  • Varsinainen funktion kutsunotaatio on kummassakin kielessä identtinen:

    funktion_nimi(pilkuilla_erotellut_parametrit)
    
  • Myös funktion paluuarvo määrätään kummassakin kielessä aivan samoin return-käskyn avulla.

  • Lisäksi esimerkeistä käy ilmi vertailuoperaattoreihin liittyvä yksityiskohta: Pythonissa vertailuoperaattoreita voi ketjuttaa:

    if a <= b <= c:
        ...
    

    C++:ssa vastaavat loogiset lausekkeet pitää kirjoittaa auki and-operaattorin avulla:

    if ( a <= b and b <= c ) {
        ...
    }
    

C++-funktioiden yksityiskohdat

Ennen kuin C++:ssa voi kutsua funktiota, kääntäjän pitää olla tietoinen funktion olemassaolosta, parametrien tyypeistä ja lukumäärästä sekä funktion paluuarvon tyypistä.

Funktio voidaan määritellä (define) kokonaisuudessaan ennen kuin sitä yritetään kutsua:

#include <iostream>

using namespace std;

double keskiarvo(double luku1, double luku2) {
    return (luku1 + luku2) / 2;
}

int main() {
    cout << keskiarvo(2.0, 5.0) << endl;
}

Funktion määrittelyllä tarkoitetaan funktion koodin kirjoittamista kokonaisuudessaan.

Funktio voidaan myös esitellä (declare) ennen kutsukohtaa ja määritellä vasta kutsukohdan jälkeen:

#include <iostream>

using namespace std;

double keskiarvo(double luku1, double luku2);

int main() {
    cout << keskiarvo(2.0, 5.0) << endl;
}

double keskiarvo(double luku1, double luku2) {
    return (luku1 + luku2) / 2;
}

Funktion esittelyssä funktiosta kerrotaan minimaalinen määrä informaatiota, jotta kääntäjä voi tarkastaa, että funktiota kutsutaan oikein. C++:ssa funktion esittely on muotoa:

paluuarvon_tyyppi funktion_nimi(pilkuilla_erotellut_parametrimuuttujien_määrittelyt);

Yllä olevaa riviä (ilman puolipistettä) voidaan kutsua myös funktion otsikoksi.

Funktio on tietysti aina määriteltävä jossain vaiheessa, koska vasta määrittely sisältää funktion rungon eli ne käskyt, jotka funktiota kutsuttaessa halutaan suorittaa.

Jos C++:ssa on tarve määritellä funktio, joka ei palauta mitään arvoa (tällaista funktiota kutsutaan usein aliohjelmaksi), se tapahtuu erikoisen tietotyypin void avulla:

#include <iostream>

using namespace std;

void tulosta_kertotaulu(int minka_luvun) {
   if ( minka_luvun <= 0 ) {
      cout << "Virhe: oltava positiiviluku!" << endl;
      return;
   }
   int kertoja = 1;
   while ( kertoja <= 9 ) {
      cout << kertoja * minka_luvun << endl;
      ++kertoja;
   }
}

int main() {
   tulosta_kertotaulu(7);
}

Aliohjelmafunktiossa ei ole pakko olla return-käskyä ollenkaan, vaan siitä palataan automaattisesti, kun funktion rungon loppusulku tulee käskyjä suoritettaessa vastaan.

Parametrien määrä ja tyypit

Koska Pythonissa on dynaaminen tyypitys, yksi ja sama funktio voi periaatteessa käsitellä useita eri tyyppisiä parametreja ja toimia eri tavoin parametrin tyypistä riippuen. Käytännössä tämä tapahtuisi jotenkin seuraavasti:

def funktio(param):
    if type(param) is int:
        # Mitä jos parametri on int-tyyppinen?
    elif type(param) is float:
        # Mitä jos parametri on float-tyyppinen?
    else:
        # Mitä tehdään muussa tapauksessa?

Koska C++:ssa on staattinen tyypitys, edellisen kaltainen järjestely ei toimi, sillä kääntäjän on tiedettävä parametrien tyypit jo käännösaikana.

C++:ssa on kuitenkin useampikin mekanismi, jolla vastaavanlaisia geneerisiä/polymorfisia funktioita voidaan toteuttaa. Helppotajuisin näistä mekanismeista on funktioiden kuormittaminen, millä tarkoitetaan sitä, että ohjelmoija voi määritellä useita saman nimisiä funktioita, kunhan niiden parametrien tyypeissä ja/tai lukumäärissä on eroja. Kun kuormitettua funktiota aikanaan kutsutaan, kääntäjä osaa päätellä kutsukohdassa käytetyistä todellisista parametreista, mitä versiota funktiosta on tarkoitus kutsua:

#include <iostream>

using namespace std;

double neliosumma(double a, double b) {
    return (a + b) * (a + b);
}

int neliosumma(int a, int b) {
    return (a + b) * (a + b);
}

int main() {
    // Kutsutaan ensimmäistä versiota
    cout << neliosumma(1.2, 3.4) << endl;

    // Kutsutaan toista versiota
    cout << neliosumma(3, 4) << endl;
}

Kuormittamista tapahtuu siis myös silloin, kun samannimisillä funktioilla on eri määrä parametreja. Erityisesti jos funktioiden parametrit ovat toistensa osajoukkoja, voidaan vaihtoehtoisesti käyttää oletusarvoisia parametreja. Oletusarvoisten parametrien on oltava parametrilistan viimeisiä parametreja. Esimerkiksi funktion

void print_value(int value, unsigned int indent = 0);

jälkimmäinen parametri on oletusarvoinen. Funktio tulostaa arvon annetulla sisennyksellä. Jos sisennyksen määrää ei anneta, ei sisennetä lainkaan, koska oletusarvona on nolla. Seuraavat kutsut ovat siis mahdollisia:

print_value(1);
print_value(2, 4);
print_value(3, 0);

Kuten edellä vihjattiin, C++:ssa on muitakin mekanismeja vastaavien tilanteiden käsittelyyn. Niihin tutustutaan jatkokursseilla (Ohjelmointi 3: Tekniikat ja Ohjelmistojen suunnittelu).

main-funktion erityispiirteet

C++-ssa main-funktion paluuarvon tyyppi on aina int. Tapana on, että jos ohjelman suoritus sujuu ongelmitta, ohjelma palauttaa arvon EXIT_SUCCESS. Jos suorituksessa tapahtuu virheitä, paluuarvona käytetään EXIT_FAILURE. Nämä ovat vakioita, jotka on määritelty kirjastossa cstdlib. EXIT_SUCCESS on arvoltaan 0 ja EXIT_FAILURE 1. Toisinaan näkee ohjelmakoodia, jossa main päättyy yksinkertaisesti käskyyn return 0. Ohjelmakoodin luettavuuden vuoksi selkeällä tavalla nimettyjen vakioiden käyttäminen on kuitenkin aina parempi kuin taikanumeroiden.

Periaatteessa main-funktion pitäisi päättyä return-käskyyn, koska sen tyyppi on int. Tämä funktio muodostaa kuitenkin poikkeuksen. Jos return-käskyä ei ole, kääntäjä laittaa main-funktion paluuarvoksi automaattisesti EXIT_SUCCESS.

Varoitus

Käytä yllä mainittuja vakioita (EXIT_SUCCESS ja EXIT_FAILURE) ainoastaan main-funktion paluuarvona. Muiden funktioiden paluuarvoina ne eivät todennäköisesti toimi odottamallasi tavalla. (Nämä vakiot eivät ole bool-tyyppisiä.)