Tie koodariksi

C++-ohjelmointi

Luku 6: Funktiot ja näkyvyys

Funktion määrittely

Funktion määrittelyn runko on seuraava:
tyyppi nimi(parametrit) {
    koodi
}
Alussa oleva tyyppi on funktion palautusarvon tyyppi. Jos funktiolla ei ole palautusarvoa, tässä kohtaa lukee void. Tämän jälkeen tulee funktion nimi ja lopuksi sulkujen sisällä funktion parametrit.

Komento return poistuu funktiosta. Jos funktiolla on palautusarvo, se annetaan tämän komennon perässä.

Funktio täytyy määritellä ennen, kuin sitä kutsutaan toisessa funktiossa.

Esimerkkejä

Seuraava funktio rivi tulostaa rivin, jossa on n tähteä:
void rivi(int n) {
    for (int i = 1; i <= n; i++) {
        cout << "*";
    }
    cout << "\n";
}
Seuraava funktio pienin puolestaan palauttaa pienemmän sen parametreista:
int pienin(int a, int b) {
    if (a < b) return a;
    else return b;
}
Funktioita voisi käyttää näin pääohjelmassa:
int main() {
    rivi(5); // *****
    rivi(8); // ********
    cout << pienin(5,3) << "\n"; // 3
}

Parametrien välitys

Kun funktiolle annetaan parametri, se kopioidaan funktioon kuin uudeksi muuttujaksi. Niinpä jos funktio muuttaa parametria, tämä muutos ei välity funktion kutsukohtaan:
void testi(int x) {
    x++;
    cout << x << "\n"; // 2
}

int main() {
    int a = 1;
    testi(a);
    cout << a << "\n"; // 1
}
Kuitenkin jos funktion määrittelyssä parametrin edessä on &-merkki, se välitetään viittauksena (reference), jolloin parametria ei kopioida ja funktiossa tehty muutos heijastuu kutsukohtaan:
void testi(int &x) {
    x++;
    cout << x << "\n"; // 2
}

int main() {
    int a = 1;
    testi(a);
    cout << a << "\n"; // 2
}

Funktion ylikuormitus

Funktion ylikuormitus (overloading) tarkoittaa, että saman nimistä funktiota voi kutsua monella eri tavalla. Tällöin funktiosta on useita määritelmiä, joissa on eri parametrit.

Seuraavassa esimerkissä funktio rivi(n,c) tulostaa rivin, jossa on n kertaa merkki c, ja funktio rivi(n) tulostaa rivin, jossa on n tähteä. Jälkimmäinen funktio on toteutettu ensimmäisen erikoistapauksena.

void rivi(int n, char c) {
    for (int i = 1; i <= n; i++) {
        cout << c;
    }
    cout << "\n";
}

void rivi(int n) {
    rivi(n,'*');
}

int main() {
    rivi(5); // *****
    rivi(5,'@'); // @@@@@
}

Näkyvyysalue

Muuttujan näkyvyysalue (scope) tarkoittaa, missä ohjelman osissa muuttuja on käytettävissä. Globaali (global) muuttuja näkyy kaikissa funktioissa, kun taas paikallinen (local) muuttuja näkyy vain funktion sisällä.

Muuttujasta tulee globaali, kun sen määrittelee ohjelman päätasolla (samalla tasolla kuin funktiot). Globaalia muuttujaa voi käyttää kaikissa funktioissa määrittelykohdasta alkaen. Lisäksi globaali muuttuja saa automaattisesti alkuarvon 0, jos sille ei anneta arvoa määrittelyssä.

Esimerkiksi seuraavassa koodissa on globaali muuttuja x, jota käytetään kahdessa funktiossa:

int x; // alkuarvo 0

void testi() {
    x++;
}

int main() {
    testi();
    cout << x << "n"; // 1
}
Funktiossa määritelty muuttuja on paikallinen, ja sitä voi käyttää määrittelykohdasta alkaen sen lohkon sisällä, jossa se on määritelty (eli merkkien { ja } välissä olevalla alueella).

Esimerkiksi seuraavassa koodissa muuttuja x on määritelty if-lohkon sisällä, minkä vuoksi se ei ole enää käytettävissä lohkon päättymisen jälkeen.

if (...) {
    int x = 5;
    cout << x << "\n"; // 5
}
cout << x << "\n"; // virhe!
Useammalla muuttujalla voi olla sama nimi, kunhan niillä on eri näkyvyysalue. Tällöin kussakin tilanteessa käytetään syvimmällä määriteltyä muuttujaa.

Esimerkiksi seuraavassa koodissa muuttuja x on määritelty kolmella eri tasolla. Funktion sisällä oleva lohko ei liity ehtoon eikä silmukkaan, vaan sen ainoana tarkoituksena on luoda näkyvyysalue.

int x = 1;

void testi() {
    int x = 2;
    {
        int x = 3;
        cout << x << "\n"; // 3
    }
    cout << x << "\n"; // 2
}

int main() {
    testi();
    cout << x << "\n"; // 1
}