Parametrien välittäminen

Koska C++:ssa ohjelmoija voi muuttujaa määritellessään päättää itse, luodaanko aito muuttuja vai pelkkä viite olemassa olevaan muuttujaan, avaa tämä uusia mahdollisuuksia myös funktion muodollisten parametrien määrittelyssä.

Ennen muodollisten parametrien määritelmää pitää tietää, mitä tarkoitetaan todellisilla parametreilla. Nämä ovat funktion kutsussa esiintyviä parametreja. Muodolliset parametrit taas esiintyvät funktion otsikossa. Niiden avulla funktion rungossa päästään käsiksi todellisiin parametreihin tai niiden arvoihin.

Jollei ohjelmoija erikseen määrää toisin, C++:ssa funktion parametrit ovat oletusarvoisesti arvoparametreja:

#include <iostream>

using namespace std;

void arvoparametrifunktio(int luku) {
    luku = 2 * luku;
}

int main() {
    int muuttuja = 7;
    cout << muuttuja << endl;  // Tulostuu 7
    arvoparametrifunktio(muuttuja);
    cout << muuttuja << endl;  // Tulostuu edelleen 7
}

Edellä olevassa koodissa funktion muodollinen parametri luku on uusi muuttuja, joka alustetaan todelliselle parametrille muuttuja evaluoituvalla arvolla 7. Vaikka funktio muuttaa muodollisen parametrinsa arvon kaksinkertaiseksi, ei sillä ole vaikutusta todelliseen parametriin, koska kyseessä on eri muuttuja.

Parametrin välittäminen arvona

Ohjelmoija voi kuitenkin halutessaan määritellä osan tai kaikki funktion muodolliset parametrit viiteparametreiksi:

#include <iostream>

using namespace std;

// Ainoat erot edelliseen koodiin verrattuna ovat
// selkeyden vuoksi muutettu funktion nimi ja
// seuraavalle riville ilmestynyt &-merkki.
void viiteparametrifunktio(int& luku) {
    luku = 2 * luku;
}

int main() {
    int muuttuja = 7;
    cout << muuttuja << endl;  // Tulostuu 7
    viiteparametrifunktio(muuttuja);
    cout << muuttuja << endl;  // Tulostuu 14
}

Uudessa versiossa funktion parametri luku onkin viiteparametri, johon tehdyt muutokset heijastuvat suoraan todelliseen parametriin muuttuja.

Parametrin välittäminen viitteenä

Koska C++:ssa viite antaa lisänimen olemassa olevalle muuttujalla (tai tarkemmin ottaen: jo entuudestaan käytössä olevalle muistialueelle), seuraava koodi on virheellinen:

void viiteparametrifunktio(int& luku) {
    luku = 2 * luku;
}

int main() {
    viiteparametrifunktio(7); // VIRHE
}

Syy: literaaliin ei C++:ssa voi olla viitteitä, se ei ole muuttuja.

Pythonissa asioiden tila saattaa pikaisesti ajateltuna tuntua siltä, että funktion parametrit ovat arvoparametreja. Todellisuudessa Pythonissa kaikki “muuttujat” noudattavat viitesemantiikkaa (eli ovat viitteitä), funktioiden parametrit mukaan lukien.

Virheellinen päätelmä johtunee ainakin osittain siitä, että Pythonin yhteydessä pitää muistaa myös käsitteet muuttuva ja muuttumaton tietotyyppi (mutable ja immutable), jotka ovat Pythonin mekanismi viitesemantiikan toteuttamiseksi osalle sen tietotyypeistä. Muuttumatonta tietotyyppiä oleva arvo funktion parametrina käyttäytyy näennäisesti kuin se olisi arvoparametri.

Vakioparametrit

Edellä tarkasteltiin tilannetta, jossa parametrina välitettävää muuttujaa haluttiin muuttaa funktion sisällä. Joskus voi tulla vastaan aivan päinvastaisia tilanteita: Halutaan, että parametrina välitettävää muuttujaa ei muuteta (tai sitä ei saa muuttaa) funktion sisällä. Tällöin parametri voidaan määritellä vakioksi avainsanalla const. (Samaa avainsanaa käytimme jo kohdassa 2.3 nimettyjen vakioiden määrittelyyn.)

Avainsana const kirjoitetaan muodollisen parametrin eteen seuraavasti:

void vakioparametrifunktio(string const& sana) {
    cout << sana << endl;
    string paikallinen_sana = sana;
}

Vakioiksi määriteltyjen parametrien käyttö on rajoitettua, sillä C++-kääntäjä tarkistaa, että parametria ei muuteta. Esimerkiksi edellä olevassa koodissa parametri tulostetaan ja sen sisältämä arvo sijoitetaan paikalliseen muuttujaan, ja nämä molemmat ovat sallittuja toimenpiteitä. Seuraavassa on esimerkki vakioparametrin virheellisestä käytöstä:

void vakioparametrifunktio(string const& sana) {
    sana = "uusi sana";  // VIRHE
}

int main() {
    vakioparametrifunktio("moi"); // SALLITTU
}

Edellisissä esimerkeissä parametri sana on lisäksi viiteparametri. Onko tässä nyt mitään järkeä, sillä viiteparametrithan juuri mahdollistivat sen, että muodollista parametria voi muuttaa, ja muutos vaikuttaa myös todelliseen parametriin? Eikö parametrin määrittely vakioksi vain estä tämän mahdollisuuden?

Mietitään ensin, kannattaisiko vakioparametrin olla arvoparametri. Tämä olisi täysin mahdollista, mutta lisäetuna saisimme ainoastaan sen, että kääntäjä ilmoittaisi virheestä, jos yritämme muuttaa parametrin arvoa. Tilanne ei kuitenkaan juurikaan muuttuisi, sillä muodollisen parametrin arvon muuttamisella ei ole vaikutusta todelliseen parametriin, kuten edellä huomasimme.

Mitä järkeä on sitten käyttää vakioparametrina viiteparametria? Edellä puhuimme arvo- ja viitesemantiikasta. Arvosemantiikan mukaisessa sijoittamisessa tapahtuu arvojen kopiointi, mutta viitesemantiikassa viite siirretään viittaamaan eri tietoalkioon. Vastaava ero on arvo- ja viiteparametreilla. Kuvitellaan, että parametrina on hyvin iso tietorakenne tai säiliö (joista puhumme kahdella seuraavalla kierroksella). Tällöin koko tietorakenteen kopiointi (alkio alkiolta) on hyvin raskas toimenpide verrattuna siihen, että vain siirtäisimme viitteen, joka viittaa kyseisen tietorakenteen alkuun, viittaamaan johonkin muualle.

Jos siis haluamme välittää ison tietorakenteen parametrina, se on järkevintä välittää viitteenä. Jos lisäksi haluamme, että kyseistä parametria ei (vahingossakaan) muuteta funktion sisällä, niin parametri kannattaa lisäksi määritellä vakioksi.

Edellä mainitusta syystä vakioparametrit ovat usein viitteitä (tai ne voivat olla osoittimia, joista puhutaan myöhemmin tällä kierroksella).

Huomaa, että yksinkertaisia tyyppejä (kuten int) ei kannata välittää vakioviitteinä, vaan on parempi käyttää arvoparametreja. Vakioviitteillä ei saavutettaisi mitään tehokkuusetuja, sillä yksinkertaisten tyyppien koko on (ainakin suunnilleen) sama kuin viitteen koko.