Osoitin ja viittaus C++:ssa Osoitin yksinkertaiseen tietotyyppiin Osoitin on muuttuja, joka sisältää jonkin toisen samantyyppisen muuttujan osoitteen. Ohessa on esimerkkiohjelma, jossa määritellään kokonaislukumuuttuja i ja osoitin kokonaislukuun *p. Ohjelmassa pyritään kasvattamaan muuttujan arvoa yhdellä eri tavoilla. int i=1; //Kokonaislukumuuttuja int *p; //Osoitin kokonaislukuun p=&i; //Sijoitetaan i:n osoite p:n arvoksi //Kasvatetaan muuttujan i arvoa yhdellä. //Oikeita tapoja: //Tapa 1: i++; //Tapa 2: Kasvatetaan p:n osoittaman muistipaikan arvoa (*p)++; //Väärä tapa. Miksi? *p++; //Myös tämä on väärin. Miksi? *(p++);
return 0; Ohjelman tulostus näyttää seuraavalta: Mieti, mitä kahdessa viimeisessä tapauksessa oikeastaan tapahtuu. Tässä nähdään osoittimiin liittyvä suurin vaara. Varsinkin aloittelevan ohjelmoijan on hyvin helppo siirtää osoitin osoittamaan väärään muistipaikkaan. Tässä tapauksessa kaksi viimemainittua lausetta siirtävät ensin osoitinta seuraavaan muistipaikkaan ja sitten tulostavat uuden muistipaikan sisällön. Osoitin taulukkoon Seuraava erimerkki liittyy taulukon alkioiden osoittamiseen: #include <iostream> int luvut[]=100,200,300,400,500; //Kokonaislukutaulukko int *p; //Osoitin kokonaislukuun int i=0; p=luvut; //Mihin p osoittaa? cout << *p << endl; //Tulostetaan sen sisältö return 0;
Esimerkissä on määritelty kokonaislukutaulukko luvut[] ja alustettu sen arvot. Taulukon koko on 5. Lisäksi on määritelty osoitinmuuttuja *p. Ensimmäinen tulostus kertoo, mihin p osoittaa sijoituksen p=luvut jälkeen. Sijoituslause p=luvut; Asettaa osoittimen osoittamaan taulukon 1. alkioon. Saman asian voi tehdä myös lauseella p=&luvut[0]; Miksi näin? C/C++:ssa taulukon nimi on OSOITIN TAULUKON ENSIMMÄISEEN ALKIOON. Taulukon alkiot voidaan tulostaa osoittimen avulla siirtämällä osoitinta taulukon alkioista toiseen lauseella p++; Esimerkiksi toteutus while-silmukalla: while (i<5) cout << *p << endl; p++; //Siirtää osoittimen seuraavaan taulukon alkioon i++; //Kasvattaa silmukkalaskurin arvoa yhdellä Osoittimet funktion parametreina Funktiolle voidaan välittää tietoa kahdella mekanismilla:
Arvoparametrina: Parametreina annettavien muuttujien arvot kopioidaan toisiin muistipaikkoihin. Funktio käsittelee alkuperäisten muuttujien kopioita, ei itse alkuperäisiä muuttujia. Tästä seuraa, että funktio ei pysty muuttamaan alkuperäisiä (parametreina välitettäviä) arvoja. Muuttujaparametrina: Parametreina annetaan muuttujien keskusmuistiosoitteet. Funktio käsittelee siten alkuperäisiä muuttujia suoraan muistissa. Funktio pystyy muuttamaan muuttujien arvoja. #include <iostream> void f1(int); //Arvoparametri void f1(int *); //Muuttujaparametri int a=1; f1(a); f1(&a); return 0; void f1(int a) void f1(int *a) (*a)++; Esimerkkiohjelmasta kannattaa huomata muutama juttu. Samasta funktiosta on tässä kaksi erilaista versiota. Toinen ottaa parametrinaan kokonaisluvun ja toinen osoittimen. Tätä kutsutaan funktion ylikuormaamiseksi (function overloading). Funktioiden tulee erota toisistaan parametrien tyypin tai lukumåärän perusteella. Paluuarvon tyyppien ero ei riitä.
Ensimmäinen funktiokutsu arvoparametrilla tekee muuttujasta a kopion, joka lähetetään funktiolle. Funktiossa käsitellään samannimistä muuttujaa a, mutta se on kopio, ei alkuperäinen. Muuttujan arvon kasvattaminen muuttaa kopion, ei alkuperäisen arvoa. Jälkimmäisessä funktiokutsussa välitetään alkuperäisen muuttujan a osoite. Nyt funktio kasvattaa alkuperäisen muuttujan arvoa. Viittaukset C++:ssa on osoittimien lisäksi toinenkin tapa suoraan muistiosoitukseen. Viittaus (reference) on tunnuksen vaihtoehtoinen nimi. Viittausmuuttuja on alustettava määrittelyn yhteydessä ja se viittaa aina samaan alustuksen yhteydessä määriteltyyn muuttujaan. Sitä ei voi vaihtaa osoittamaan johonkin toiseen muuttujaan. Viittausta käytetään hyvin paljon C++:n metodien yhteydessä. #include <iostream> void f1(int); void f1(int *); void f2(int &); int a=1; f1(a); f1(&a); f2(a); return 0;
void f1(int a) void f1(int *a) (*a)++; void f2(int &a) Tässä edelliseen esimerkkiin on lisätty viittausmuuttujaa käyttävä funktio f2. Olisiko ollut mahdollista tehdä kolmas ylikuormattu versio funktiosta f1? Ei, koska funktiota, jonka parametrina on tavallinen muuttuja ja viittausmuuttuja kutsutaan samalla tavalla eli niitä ei voi erottaa toisistaa parametrien tyypin perusteella. Viittauksen ongelma on, että funktiokutsun perusteella ei voi tietää, käytetäänkö viittausta vai tavallista muuttujaa. Funktio voikin yllättäen muuttaa muuttujan arvoa käyttäjän tietämättä. Jos halutaan varmistaa, että näin ei vahingossa käy, voidaan viittausparametri määritellä vakioksi: void f3(const int &a) Edellinen määritys johtaa kääntäjän virheilmoituksen. Koska funktiossa yritetään muuttaa vakioksi määriteltyä arvoa. Jatkossa luokkien yhteydessä sekä tavallisia että vakioviittauksia käytetään jatkuvasti. Miksi niitä käytetään eikä tavallisia muuttujia? Syy on se, että viittausta käytettäessä ei tietoja kopioida kuten tavallista muuttujaa käytettäessä. Lienee selvää, että tietojen jatkuva kopiointi muistissa on paljon hitaampaa kuin yhden viittauksen tekeminen tietoon. Varsinkin jos ohjelmassa käsitellään suuria tietomääriä tai luodaan ja tuhotaan olioita dynaamisesti ohjelman ajon aikana. Siksi on hyvä opetella oikeaoppinen viittauksen käyttö heti olio-ohjelmoinnin aluksi.