12 Mallit (Templates) Malli on määrittely, jota käyttämällä voidaan luoda samankaltaisten aliohjelmien ja luokkien perheitä. Malli on ohje kääntäjälle luoda geneerisestä tyyppiriippumattomasta ohjelmakoodista tyyppiin sidottua ohjelmakoodia. Malleja käytetään, kun samanlainen logiikka toistuu eri tietotyyppejä käytettäessä. Mallin avulla voidaan samasta lähdekoodista luoda uusia instansseja (Olioita) eri tyyppien käsittelyä varten kirjoittamatta lähdekoodia uudelleen. Malleja käytetään tyypillisesti tietorakenneluokkien toteuttamisessa. Mallien avulla voidaan rakentaa myös kokonaisia sovellusrunkoja. Malleilla ohjelmointi on ns. geneeristä ohjelmointia (Generic programming). Usein on ohjelmoitaessa tarve soveltaa samaa ohjelmalogiikkaa eri tilanteissa. Tämä on mahdollista malleja käyttämällä. Tällöin mallin lähdekoodissa ei viitata mihinkään tiettyyn tyyppiin tai luokkaan, vaan viittaukset korvataan viittauksilla generiseen tyyppiin, joka edustaa mitä tahansa tyyppiä. Geneerinen lähdekoodi ei ole kuitenkaan suoraan käännettävissä ja ajettavissa. Ajettava ohjelmakoodi saadaan aikaan instantioimalla mallista määrättyyn tyyppiin sidottu ohjelmakoodi. Kun malli otetaan käyttöön, korvautuu geneerinen tyyppi mallille parametrina välitetyllä tyypillä. Mallista voidaan käyttää myös nimitystä parametrisoitu tyyppi (Parametrized type). Mallit ovat aliohjelmamalleja (Function templates) tai luokkamalleja (Class templates). Aliohjelmamalli mahdollistaa yhden aliohjelman logiikan käyttämisen eri tyyppien yhteydessä. Luokkamallit mahdollistavat kokonaisen luokan ohjelmakoodin uudelleenkäytön uusille tyypeille. 12.1 Aliohjelmamalli Aina samanlaisena toistuva aliohjelmalogiikka voidaan kirjoittaa aliohjelmamalliksi (Function template), jota voidaan käyttää sopiville tietotyypeille. Ohjelmakoodia ei tarvitse kirjoittaa kuin kerran. 1
Kääntäjä generoi aliohjelman kutsun yhteydessä käytettyyn tyyppiin sopivan aliohjelman. Ilman aliohjelmamalleja on usein kirjoitettava useita eri aliohjelmia, joissa logiikka on sama, mutta käsiteltävän tiedon tyyppi vaihtelee. template <Geneerinen_tyyppi> Aliohjelman_toteutus. Aliohjelmamallin esittely ja määrittely. Mallin esittely ja määrittely alkaa varatulla sanalla template. Sen jälkeen < > -merkkien väliin kirjoitetaan ohjelmakoodissa käytettävien geneeristen tyyppien tunnukset. Tyypin nimenä käytetään tavallisesti yhtä suurta kirjainta, esim. T. < > -merkkien välissä voi olla useita eri geneerisiä tyyppitunnuksia pilkuilla erotettuina. Kunkin tunnuksen eteen kirjoitetaan varattu sana class tai typename. typename on uusi lisäys C++-standardiin. Se ilmaisee class-sanaa paremmin, että tyyppiparametri voi olla muukin kuin luokka. typename ei välttämättä ole käytettävissä vanhemmissa ympäristöissä. Kun aliohjelmamallia on käytetty jossakin kohtaa ohjelmakoodia, kääntäjä instantioi aliohjelmasta tyyppikohtaisen aliohjelmainstanssin. Kääntäjä korvaa geneerisen tyypin T kutsussa käytetyn tiedon tyypillä. #include <iostream> using namespace std ; template <typename T> T Summa (T Luku1, T Luku2) ; //Prototyyppi int main () cout << " Luku1 + Luku2 = " << Summa(2,5) << endl ; cout << " Luku1 + Luku2 = " << Summa(2.2,10.1) << endl ; // Huom! A +! = b sillä (65 + 33 = 98). cout << " Merkki + Merkki = " << Summa ('A', '!' ) << endl ; 2
return 0 ; template <typename T> T Summa (T Luku1, T Luku2) return (Luku1 + Luku2) ; 12.2 Luokkamallit Luokkamalli sisältää tarvittavat tietojäsenet ja geneeristä tietotyyppiä käsittelevät metodimallit. Luokkamallin avulla voidaan uudelleenkäyttää kaikkia luokan metodeita kirjoittamatta niitä uudelleen. Luokkamallin tyypillisiä käyttökohteita ovat mm. Tietorakenneluokat. Luokkamallin määrittely ja luokkamallin metodien toteutus kirjoitetaan samaan tiedostoon. Luokkamalli metodeineen sijoitetaan otsikkotiedostoon (hpp). Metodeita ei sijoiteta erilliseen cpp-tiedostoon, koska ne eivät ole sellaisenaan käännettäviä. Metodit voidaan kääntää vasta, kun kääntäjä on korvannut geneerisen tyypin käytetyllä tyypillä tai luokalla. Malli on siis ohje kääntäjälle generoida uusi luokka. Vasta generoitu luokka on käännettävää ohjelmakoodia. template <Geneerinen_tyyppi> class Malliluokka ; Luokkamallin määrittely aloitetaan varatulla sanalla template. Sen jälkeen < > -merkkien väliin kirjoitetaan ohjelmakoodissa käytettävien geneeristen tyyppien tunnukset samoin kuin aliohjelmamallien yhteydessä neuvottiin. Metodien esittelyn yhteydessä ei tarvitse käyttää merkintää 3
template <typename T> tai template <class T>. Metodien toteutuksen yhteydessä ko. merkintä tarvitaan myös. Luokkamallin metodit toteutetaan metodimalleina. Kukin metodi määritellään kuten aliohjelmamallin yhteydessä oli esillä. Luokkamallin metodit toteutetaan luokan määrittelyn kanssa samaan tiedostoon. Kaikissa aliohjelmissa, joissa viitataan luokkamallista instantioitavaan luokkaan, merkitään luokan nimen yhteyteen geneerinen tyyppi. Malliluokka <Geneerinen_tyyppi> Luokkamallien ja metodimallien määrittely alkaa aina tekstillä template <class T>. Aina kun ohjelmakoodissa halutaan viitata talletettavan olion tyyppiin, käytetään tyyppinä geneeristä tyyppiä T. Aina kun ohjelmakoodissa halutaan viitata parametrisoidun tyypin perusteella instantioitavaan luokkaan, käytetään tyyppinä tyyppiä Luokan_nimi<T> Luokkamalli sellaisenaan ei ole käännettävää ja suoritettavaa ohjelmakoodia. Luokkamalli saadaan käyttöön instantioimalla siitä uusi luokka parametreina välitetyn tyypin perusteella. Malliluokka <Parametri> Olio ; Parametri on sen tyypin tai luokan nimi, jonka perusteella luokkamalli halutaan instantioida uudeksi luokaksi. Uudeksi instantioidun luokan tunnus on siis Malliluokka<Parametri>. Uuden luokan instantioinnin yhteydessä voidaan luoda olio uuteen luokkaan. Samaa luokkamallia voidaan käyttää nyt eri tilanteissa eri tyyppisten parametrien yhteydessä. #include <iostream> using namespace std ; template <class T> class Malli_Kissa 4
private: string Nimi ; T Paino ; public: ; Malli_Kissa(string KissanNimi) ; ~Malli_Kissa() ; void AsetaPaino(T KissanPaino) ; void NaytaPaino() ; template <class T> Malli_Kissa<T>::Malli_Kissa(string KissanNimi) Nimi = KissanNimi ; template <class T> Malli_Kissa<T>::~Malli_Kissa() template <class T> void Malli_Kissa<T>::AsetaPaino(T KissanPaino) Paino = KissanPaino ; template <class T> void Malli_Kissa<T>::NaytaPaino() cout << endl << Nimi << " painaa " << Paino << " kiloa." << endl ; 5
int main () Malli_Kissa<int> SuurpiirteinenKatti("SuurpiirteinenKatti") ; Malli_Kissa<double> TarkkaKatti("TarkkaKatti") ; SuurpiirteinenKatti.AsetaPaino(3) ; TarkkaKatti.AsetaPaino(3.245) ; SuurpiirteinenKatti.NaytaPaino() ; TarkkaKatti.NaytaPaino() ; cout << endl ; return 0 ; 6
13 Operaattorien ylikuormaus Operaattoreiden ylikuormaus tarkoittaa aliohjelman toteuttamista siten, että aliohjelman nimi on jokin operaattorimerkki. Aliohjelma voi kantaa nimenään mm. aritmeettista operaattoria tai vertailuoperaattoria. Kuormitetut operaattorit mahdollistavat jo aikaisemmin tällä kurssilla esillä olleen string-luokan, jossa merkkijonoja voitiin vertailla yhtäsuuruusoperaattorilla == eikä tarvittu C:n standardikirjaston strcmp-funktiota. Kukin operaattori kuormitetaan omassa aliohjelmassaan. Operaattorin kuormitusaliohjelman nimi on operator op missä op on jokin operaattorimerkki. C++ mahdollistaa seuraavien operaattorien ylikuormauksen luokkien yhteydessä: + - * / = < > += -= *= /= << >> <<= >>= ==!= <= >= ++ -- % & ^! ~ &= ^= = && %= [] () new delete 13.1 Sijoitusoperaattorin = ylikuormaus Kopiomuodostimella voitiin luoda uusi olio ja kopioida vanhan olion tiedot siihen. Jos ohjelmassa halutaan sijoittaa jo olemassa olevan olion tiedot johonkin toiseen olemassa olevaan olioon, voidaan määritellä sijoitusoperaattori = siten, että yhtäsuuruusmerkin oikealla puolella olevan olion tietojäsenten sisällöt kopioituvat vasemmalla puolella oleviin vastaaviin tietojäseniin. Esimerkiksi suorakulmioluokkaan voidaan määritellä olla sijoitusoperaattori = seuraavasti: 7
class suorakulmio private: double leveys,korkeus; public: // suorakulmio(double,double); suorakulmio() leveys=0; korkeus=0; // ; Luokan määrittelyosassa voidaan kirjoittaa: void operator = (const suorakulmio &); ja itse metodi on vastaavasti: void suorakulmio::operator = (const suorakulmio &s) this->korkeus=s.korkeus; this->leveys=s.leveys; Ylikuormattua metodia voidaan kutsua seuraavasti: int main() suorakulmio olio(1,2); suorakulmio tokaolio; //tokaolion leveys on 0 ja korkeus 0 tokaolio=olio; //tokaolion leveys on nyt 1 ja korkeus 2 return 0; 13.2 Vertailuoperaattorin == ylikuormaus Suorakulmioluokkaan voitaisiin lisätä vertailu, joka vertaa kahden suorakulmio-olion yhtäsuuruutta: Lisätään luokan määrittelyyn: bool operator==(const suorakulmio &); Vertailuoperaattori == palauttaa bool-tyyppisen totuusarvon, joka on tosi eli 1, jos kahden olion kaikki 8
vastaavat tietojäsenet ovat yhtäsuuria, ja arvon epätosi eli 0, jos yksikin on erisuuri. Itse ylikuormauksen tekevä metodi näyttää seuraavalta: bool suorakulmio::operator ==(const suorakulmio &s) if (this->leveys == s.leveys && this->korkeus == s.korkeus) else return true; return false; Saattaa vaikuttaa vähän monimutkaiselta, mutta suomennetaan: this-osoitin osoittaa yhtäsuuruusmerkin vasemmalla puolella olevaan olioon. Sen tietojäseniin viitataan nuolioperaattorilla, koska kyseessä on osoitin. Vertailuoperaatiolle viedään viittausparametrin välityksellä toinen vertailtava olio s, johon viitataan tavalliseen tapaan pisteoperaattorilla. 13.3 Aritmeettisten operaattoreiden ylikuormaus Tehdään seuraavaksi operaattori +, joka laskee yhteen kaksi oliota siten, että olioiden korkeudet ja leveydet lasketaan erikseen yhteen. Luokan määrittelyssä: Metodi: suorakulmio operator + (const suorakulmio &); suorakulmio suorakulmio::operator + (const suorakulmio &s) suorakulmio temp; temp.leveys=this->leveys+s.leveys; temp.korkeus=this->korkeus+s.korkeus; return temp; 9
13.4 Syöttö- ja tulostusoperaattorien ylikuormaus. Luokan ystävät Perustietotyyppien syötön ja tulostuksen yhteydessä olemme tutustuneet << ja >> operaattoreihin. Ne on kuormitettu iostream-kirjaston luokkiin. Ne ovat valmiita luokkia, joihin ohjelmoija ei voi lisätä uusia aliohjelmia. Jos << tai >> operaattoreita halutaa käyttää omien luokkien olioiden syöttöön tai tulostukseen, ne on määriteltävä kullekin ohjelmoijan määrittelemälle tyypille. Suorakulmioluokan tapauksessa olisi aika viileetä, jos luku saataisiin tulostettua suoraan vanhalla tutulla tavalla suorakulmio sk(1,1); cout << sk << endl; Tämähän ei onnistu suoraan, koska cout osaa tulostaa vain tavallisen tietotyypin, eli yksittäisen luvun tai merkkijonon. Tulostusoperaattori ei siis kuulu suorakulmioluokkaan. Se ei siis periaatteessa pysty tulostamaan suoraan luokan yksityisiä tietojäseniä. Tähän asti on koko ajan korostettu sitä, että ainoastaan luokan omat metodit voivat käsitellä yksityisiä tietojäseniä. Tähän löytyy kuitenkin ratkaisu. On nimittäin mahdollista määritellä tulostusoperaattori luokan ystäväksi (friend). Avainsanaa friend käyttämällä voidaan jollekin toiselle luokalle antaa oikeus käsitellä luokan yksityisiä tietojäseniä. Ystäväksi voidaan määritellä yksittäinen luokan ulkopuolinen funktio tai kokonainen toinen luokka. Avainsanoilla private ja public ei ole mitään vaikutusta ystäväfunktioon tai luokkaan. Tarkastellaan seuraavaksi, miten suorakulmio tulostus voidaan tehdä <<-operaattoria ylikuormittamalla. Syöttö- ja tulostusoperaattorien ylikuormauksen yleinen muoto on: ostream& operator << (ostream&, const Class &); Ostream on C++:n yleinen tulostusvirta (output stream), joka on määritelty iostream-kirjastossa. Meidän riittää tietää, että juuri se ohjaa merkit näytölle. Ylikuormattu <<-operaattori siis palauttaa viittauksen tulostusvirtaan ostream. Se ottaa kaksi viittausparametria, viittauksen ostreamiin ja vakioviittauksen käyttäjän määrittelemään luokkaan. Suorakulmioluokalle toteutus näyttää seuraavalta: Luokan määrittelyssä: friend ostream &operator<<(ostream &,const suorakulmio &); Varsinainen metodi näyttää seuraavalta: 10
ostream &operator << (ostream &os, const suorakulmio &s) os << s.leveys << " " << s.korkeus << endl; return os; Viittausmuuttuja os on tulostusvirta, joka tässä korvaa coutin. Kutsuvalle ohjelmalle palautetaan viittaus tulostusvirtaan mahdollista tulostusten ketjutusta varten. 11