Funktiot

Funktioiden perusteet


Tarkastellaan seuraavia koodeja, jotka palauttavat kolmesta luvusta keskimmäisen:

Python

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()

C++

#include <iostream>

using namespace std;

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

int main() {
    cout << keskimmainen(4, 5, 1) << endl;
}
  • C++:ssa funktion parametrit ja paluuarvo pitää tyypittää. Näin kääntäjä voi tarkistaa, että funktiota kutsutaan oikeantyyppisillä parametreilla ja että paluuarvoa käytetään oikein.
  • Funktion kutsun muoto on molemmissa kielissä sama: funktion_nimi(parametrit).
  • Funktion paluuarvo palautetaan molemmissa kielissä return-käskyllä.
  • Pythonissa vertailuoperaattoreita voi ketjuttaa, kuten a <= b <= c. C++:ssa ehdot kirjoitetaan erillisiksi vertailuiksi ja yhdistetään loogisella operaattorilla &&.

Funktion määrittely ja esittely

Ennen funktion kutsumista C++-kääntäjän täytyy tietää funktion nimi, parametrien tyypit ja lukumäärä sekä paluuarvon tyyppi. Funktion voi määritellä kokonaan ennen sen kutsukohtaa:

#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äärittely sisältää koko funktion koodin eli rungon.

Vaihtoehtoisesti funktion voi esitellä ennen kutsukohtaa ja määritellä vasta sen jälkeen:

#include <iostream>

using namespace std;

// Funktion esittely
double keskiarvo(double luku1, double luku2);

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

// Funktion määrittely
double keskiarvo(double luku1, double luku2) {
    return (luku1 + luku2) / 2;
}

Esittely sisältää vähimmäistiedot, joita kääntäjä tarvitsee tarkistaakseen funktion kutsut. Sen yleinen muoto on:

paluuarvon_tyyppi funktion_nimi(parametrien_määrittelyt);

Esittelyriviä ilman puolipistettä kutsutaan myös funktion otsikoksi. Funktio täytyy silti määritellä jossain, sillä määrittely sisältää suoritettavat käskyt.

Arvoa palauttamattomat funktiot

Jos funktio ei palauta arvoa, sen tyypiksi merkitään void. Tällaista funktiota kutsutaan usein aliohjelmaksi.

#include <iostream>

using namespace std;

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

int main() {
    tulosta_kertotaulu(7);
}

void-funktiossa ei tarvita return-käskyä, jos funktio päättyy normaalisti. Suoritus palaa kutsukohtaan automaattisesti funktion rungon lopussa. return; voi kuitenkin päättää funktion aiemmin.

Funktioiden kuormittaminen

Pythonin dynaamisen tyypityksen ansiosta sama funktio voi käsitellä eri tyyppisiä parametreja ja toimia parametrin tyypin mukaan:

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:
        # Muut tapaukset

C++:n staattisessa tyypityksessä parametrien tyypit täytyy tietää käännösaikana. C++ tarjoaa useita tapoja toteuttaa yleiskäyttöisiä eli polymorfisia funktioita. Yksi tapa on funktioiden kuormittaminen: samalla nimellä voidaan määritellä useita funktioita, kunhan niiden parametrien tyypit tai lukumäärät eroavat.

#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 double-parametrien versiota
    cout << neliosumma(1.2, 3.4) << endl;

    // Kutsutaan int-parametrien versiota
    cout << neliosumma(3, 4) << endl;
}

Kääntäjä valitsee kutsuun sopivan version argumenttien perusteella. Pelkkä paluuarvon tyypin ero ei riitä kuormittamiseen.

Oletusarvoiset parametrit

Kuormitusta tarvitaan myös silloin, kun samannimisillä funktioilla on eri määrä parametreja. Jos osa parametreista voidaan jättää pois, voidaan käyttää oletusarvoja. Oletusarvoiset parametrit sijoitetaan parametrilistan loppuun.

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

Jos sisennyksen määrää ei anneta, käytetään oletusarvoa nolla:

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

C++:ssa on myös muita vastaavien tilanteiden käsittelyyn tarkoitettuja mekanismeja. Niitä käsitellään jatkokursseilla, kuten Ohjelmointi 3: Tekniikat ja Ohjelmistojen suunnittelu.

main-funktion paluuarvo

C++:ssa main-funktion paluuarvon tyyppi on aina int. Onnistuneen suorituksen merkiksi palautetaan tavallisesti EXIT_SUCCESS; virhetilanteessa voidaan palauttaa EXIT_FAILURE. Nämä vakiot määritellään <cstdlib>-kirjastossa. Niiden arvot ovat tavallisesti vastaavasti 0 ja 1.

Joskus main-funktio päättyy käskyyn return 0;. Kuvaavat vakiot tekevät tarkoituksen selvemmäksi kuin niin sanotut taikanumerot.

Vaikka main-funktion tyyppi on int, sen lopusta voi jättää return-käskyn pois: tällöin kääntäjä käsittelee sen ikään kuin funktio palauttaisi EXIT_SUCCESS.

Varoitus

Käytä EXIT_SUCCESS- ja EXIT_FAILURE-vakioita vain main-funktion paluuarvoina. Ne eivät ole bool-tyyppisiä, eivätkä yleensä toimi odotetusti muiden funktioiden paluuarvoina.