14. oppitunti. Operaattorin ylikuormitus. Osa. Operaattorin ylikuormittaminen



Samankaltaiset tiedostot
Osa III. Olioiden luominen vapaalle muistialueelle

Osa III. Edelliset kolme lukua ovat käsitelleet viittausten ja osoittimien käyttöä. Tämän luvun aiheita ovat:

13 Operaattoreiden ylimäärittelyjä

11. oppitunti III. Viittaukset. Osa. Mikä on viittaus?

Osoitin ja viittaus C++:ssa

Osa. Mitä ovat vakiot jäsenfunktiot Kuinka erotetaan luokan käyttöliittymä sen toteutuksesta

Luokassa määriteltävät jäsenet ovat pääasiassa tietojäseniä tai aliohjelmajäseniä. Luokan määrittelyyn liittyvät varatut sanat:

Operaattoreiden ylikuormitus. Operaattoreiden kuormitus. Operaattoreiden kuormitus. Operaattoreista. Kuormituksesta

Osa. Johdanto C++ - kieleen. Oppitunnit 6 Perusluokat 7 Lisää luokista 8 Kehittynyt ohjelman kulku

Osa. Erikoisaiheet. Oppitunnit 20 Erikoisluokat ja -funktiot 21 Esikäsittelijä

T Olio-ohjelmointi Osa 3: Luokka, muodostin ja hajotin, this-osoitin Jukka Jauhiainen OAMK Tekniikan yksikkö 2010

12 Mallit (Templates)

815338A Ohjelmointikielten periaatteet Harjoitus 5 Vastaukset

Osa. Listaus 2.1. HELLO.CPP esittelee C++ -ohjelman osat. 14: #include <iostream.h> 15: 16: int main() 17: {

15. oppitunti. Taulukot. Osa. Mikä on taulukko?

Operaattoreiden uudelleenmäärittely

Sisällys. 7. Oliot ja viitteet. Olion luominen. Olio Java-kielessä

Olion elinikä. Olion luominen. Olion tuhoutuminen. Olion tuhoutuminen. Kissa rontti = null; rontti = new Kissa();

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

Taulukot. Jukka Harju, Jukka Juslin

Java-kielen perusteet

Operaattorin ylikuormitus ja käyttäjän muunnokset

21. oppitunti. Esikäsittelijä. Osa. Esikäsittelijä ja kääntäjä

VIII. Osa. Liitteet. Liitteet Suoritusjärjestys Varatut sanat Binääri- ja heksamuoto

Ohjelman virheet ja poikkeusten käsittely

Java-kielen perusteet

Osa VII. Mitä mallit ovat ja kuinka niitä käytetään Miksi mallit tarjoavat paremman vaihtoehdon makroille Kuinka luokkamalleja luodaan

Mallit standardi mallikirjasto parametroitu tyyppi

Ohjelmointitaito (ict1td002, 12 op) Kevät Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen

C++11 lambdat: [](){} Matti Rintala

7. Oliot ja viitteet 7.1

Tietotyypit ja operaattorit

4. Luokan testaus ja käyttö olion kautta 4.1

Ohjelmassa muuttujalla on nimi ja arvo. Kääntäjä ja linkkeri varaavat muistilohkon, jonne muuttujan arvo talletetaan.

Tehtävä 1. TL5302 Olio-ohjelmointi Koe Malliratkaisuja. Tässä sekä a)- että b)-kohdan toimiva ratkaisu:

Olio-ohjelmointi Javalla

C++11 Syntaksi. Jari-Pekka Voutilainen Jari-Pekka Voutilainen: C++11 Syntaksi

812347A Olio-ohjelmointi, 2015 syksy 2. vsk. X Poikkeusten käsittelystä

Osa. Mikä on linkitetty lista Kuinka linkitetty lista luodaan Kuinka toiminnallisuus kapseloidaan periytymisen kautta

Sisällys. 1. Omat operaatiot. Yleistä operaatioista. Yleistä operaatioista

Periytyminen. Luokat ja olio-ohjelmointi

1. Omat operaatiot 1.1

Ohjelmassa henkilön etunimi ja sukunimi luetaan kahteen muuttujaan seuraavasti:

Tietueet. Tietueiden määrittely

Sisältö. 2. Taulukot. Yleistä. Yleistä

4. oppitunti. Ilmaukset ja ohjelmalauseet. Osa

5. oppitunti. Funktiot. Osa. Mikä on funktio?

ITKP102 Ohjelmointi 1 (6 op)

Sisällys. Yleistä attribuuteista. Näkyvyys luokan sisällä ja ulkopuolelta. Attribuuttien arvojen käsittely aksessoreilla. 4.2

Metodit. Metodien määrittely. Metodin parametrit ja paluuarvo. Metodien suorittaminen eli kutsuminen. Metodien kuormittaminen

Virtuaalifunktiot ja polymorfismi

C-kielessä taulukko on joukko peräkkäisiä muistipaikkoja, jotka kaikki pystyvät tallettamaan samaa tyyppiä olevaa tietoa.

C++ rautaisannos. Kolme tapaa sanoa, että tulostukseen käytetään standardikirjaston iostreamosassa määriteltyä, nimiavaruuden std oliota cout:

Javan perusteita. Janne Käki

\+jokin merkki tarkoittaa erikoismerkkiä; \n = uusi rivi.

Ohjelmointi 2. Jussi Pohjolainen. TAMK» Tieto- ja viestintäteknologia , Jussi Pohjolainen TAMPEREEN AMMATTIKORKEAKOULU

2. Lisää Java-ohjelmoinnin alkeita. Muuttuja ja viittausmuuttuja (1/4) Muuttuja ja viittausmuuttuja (2/4)

Metodien tekeminen Javalla

on ohjelmoijan itse tekemä tietotyyppi, joka kuvaa käsitettä

Yleistä. Nyt käsitellään vain taulukko (array), joka on saman tyyppisten muuttujien eli alkioiden (element) kokoelma.

Muuttujat ja kontrolli. Ville Sundberg

T Olio-ohjelmointi Osa 5: Periytyminen ja polymorfismi Jukka Jauhiainen OAMK Tekniikan yksikkö 2010

Sisällys. Yleistä attribuuteista. Näkyvyys luokan sisällä. Tiedonkätkentä. Aksessorit. 4.2

Kääntäjän virheilmoituksia

Ohjelmointitaito (ict1td002, 12 op) Kevät Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen

Luokan operaatiot. Osoittimet ja viittaukset luokan olioihin

Olio-ohjelmointi Syntaksikokoelma

Sisällys. 3. Muuttujat ja operaatiot. Muuttujat ja operaatiot. Muuttujat. Operaatiot. Imperatiivinen laskenta. Muuttujat. Esimerkkejä: Operaattorit.

UML ja luokkien väliset suhteet

Luokan muodostimet (Constructors)

Kompositio. Mikä komposition on? Kompositio vs. yhteyssuhde Kompositio Javalla Konstruktorit set-ja get-metodit tostring-metodi Pääohjelma

Jypelin käyttöohjeet» Miten voin liittää törmäyksiin tapahtumia?

815338A Ohjelmointikielten periaatteet Harjoitus 2 vastaukset

Sisältö. 22. Taulukot. Yleistä. Yleistä

3. Muuttujat ja operaatiot 3.1

Osa. Toimintojen toteuttaminen ohjelmissa vaatii usein haarautumisia ja silmukoita. Tässä luvussa tutustummekin seuraaviin asioihin:

Sisällys. 16. Ohjelmoinnin tekniikkaa. Aritmetiikkaa toisin merkiten. Aritmetiikkaa toisin merkiten

JUnit ja EasyMock (TilaustenKäsittely)

16. Ohjelmoinnin tekniikkaa 16.1

ITKP102 Ohjelmointi 1 (6 op)

Ohjelmoinnin perusteet Y Python

Harjoitustyö: virtuaalikone

Olio-ohjelmointi 2. välikoe HYV5SN

16. Ohjelmoinnin tekniikkaa 16.1

Sisällys. 17. Ohjelmoinnin tekniikkaa. Aritmetiikkaa toisin merkiten. for-lause lyhemmin

7. Näytölle tulostaminen 7.1

Osoittimet. Mikä on osoitin?

Pong-peli, vaihe Aliohjelman tekeminen. Muilla kielillä: English Suomi. Tämä on Pong-pelin tutoriaalin osa 3/7. Tämän vaiheen aikana

Ohjelmointi 1 / 2009 syksy Tentti / 18.12

Rakenteiset tietotyypit Moniulotteiset taulukot

ITKP102 Ohjelmointi 1 (6 op)

ITKP102 Ohjelmointi 1 (6 op)

Ohjelmoinnin jatkokurssi, kurssikoe

1 Tehtävän kuvaus ja analysointi

Apuja ohjelmointiin» Yleisiä virheitä

Muodostinfunktiot, tuhoajafunktiot ja sijoitusoperaattorit

Listarakenne (ArrayList-luokka)

24. oppitunti VII. Poikkeukset ja virheiden käsittely. Osa

815338A Ohjelmointikielten periaatteet Harjoitus 4 vastaukset

812336A C++ -kielen perusteet,

Transkriptio:

Osa IV 14. oppitunti Operaattorin ylikuormitus Edellisessä luvussa opit ylikuormittamaan metodeita ja luomaan kopiomuodostimen, joka tekee syvän kopion. Tässä luvussa käsitellään seuraavia aiheita: Kuinka jäsenfunktioita ylikuormitetaan Kuinka sijoitusoperaattori ylikuormitetaan hallitsemaan muistia Kuinka luodaan funktioita, jotka tukevat luokkia, joissa on dynaamisesti varattuja muuttujia Operaattorin ylikuormittaminen C++ -kielessä on lukuisia sisäisiä tietotyyppejä, kuten int, real, char, jne. Jokaisella noista tyypeistä on joukko sisäisiä operaattoreita, kuten

200 14. oppitunti yhteenlasku (+), kertolasku (*), jne. C++ mahdollistaa noiden operaattoreiden lisäämisen myös omiin luokkiin. Tutkiaksemme operaattorin ylikuormittamista luodaan listauksessa 14.1 uusi luokka, Counter. Counter-oliota käytetään laskemiseen (yllätys!) silmukoissa sekä muissa sovelluksissa, joissa lukua kasvatetaan, vähennetään tai muokataan muulla tavalla. Listaus 14.1. Counter-luokka. 1: // Listaus 14.1 2: // Counter-luokka 3: 4: #include <iostream.h> 5: 6: class Counter 7: { 8: public: 9: 10: Counter(); 11: ~Counter(){} 12: int GetItsVal()const { return itsval; } 13: void SetItsVal(int x) {itsval = x; } 14: 15: private: 16: int itsval; 17: 18: }; 19: 20: Counter::Counter(): 21: itsval(0) 22: {}; 23: 24: int main() 25: { 26: Counter i; 27: cout << "The value of i is " << i.getitsval() << endl; 28: return 0; 29: } The value of i is 0. Kyseessä on melko hyödytön luokka. Se määritellään riveillä 7-18. Sen ainoa jäsenmuuttuja on int. Rivillä 10 on esitelty oletusmuodostin ja sen toteutus on rivillä 20. Se alustaa jäsenmuuttujan, itsval, arvolla 0. Päinvastoin kuin aitoa int-muuttujaa, ei laskurioliota voida kasvattaa, vähentää, käyttää yhteenlaskuun tai sijoitukseen eikä muutenkaan muokata. Tämä tekee myös sen arvon tulostamisen vaikeaksi.

Operaattorin ylikuormitus 201 Kasvatusfunktion muodostaminen Operaattorin ylikuormittaminen tuo paljon toiminnallisuutta, johon käyttäjän määrittelemä luokka ei itsessään kykene. Listaus 14.2 havainnollistaa kasvatusoperaattorin ylikuormittamista. Listaus 14.2. Kasvatusoperaattorin ylikuormittaminen. 1: // Inkrementti-operaattorin ylimäärittely 2: 3: typedef unsigned short USHORT; 4: #include <iostream.h> 5: 6: 7: class Counter 8: { 9: public: 10: Counter(); 11: ~Counter(){} 12: USHORT GetItsVal()const { return itsval; } 13: void SetItsVal(USHORT x) {itsval = x; } 14: void Increment() { ++itsval; } 15: const Counter& operator++ (); 16: 17: private: 18: USHORT itsval; 19: 20: }; 21: 22: Counter::Counter(): 23: itsval(0) 24: {}; 25: 26: const Counter& Counter::operator++() 27: { 28: ++itsval; 29: return *this; 30: } 31: 32: int main() 33: { 34: Counter i; 35: cout << "The value of i is " << i.getitsval() << endl; 36: i.increment(); 37: cout << "The value of i is " << i.getitsval() << endl; 38: ++i; 39: cout << "The value of i is " << i.getitsval() << endl; 40: Counter a = ++i; 41: cout << "The value of a: " << a.getitsval(); 42: cout << " and i: " << i.getitsval() << endl; 43: return 0; 44: } The value of i is 0 The value of i is 1 The value of i is 2 The value of a: 3 and i: 3

202 14. oppitunti Operaattorin ++ toteutus riveillä 26-30 on muutettu viittaamaan thisosoittimeen ja palauttamaan nykyisen olion. This-osoitin mahdollistaa Counter-olion sijoittamisen muuttujaan a. Jos Counter-olio varaisi muistia, olisi tärkeää korvata kopiomuodostin. Tässä tapauksessa kopiomuodostin toimii hyvin. Huomaa, että palautettu arvo on Counter-viittaus, jolloin ei luoda ylimääräistä tilapäiskopiota. Kyseessä on const-viittaus, koska tätä Counteroliota käyttävän funktion ei tule muuttaa arvoa. Jälkiliiteoperaattorin ylikuormittaminen Entä, jos on tarvetta ylikuormittaa jälkiliitteenä oleva kasvatusoperaattori? Tällöin kääntäjällä on ongelmia. Kuinka se osaa erottaa etuliite- ja jälkiliiteoperaattorin? Yleissäännön mukaan kokonaislukumuuttuja viedään parametrina operaattorin esittelyyn. Parametrin arvo ohitetaan; se on vain signaali siitä, että kyseessä on jälkiliiteoperaattori. Etu- ja jälkiliitteen ero Ennen kuin luot jälkiliiteoperaattorin sinun on ymmärrettävä, kuinka se eroaa etuliiteoperaattorista. Etuliite kertoo: kasvata ja nouda sitten, jälkiliite: nouda ja kasvata sitten. Kun etuliiteoperaattori kasvattaa arvoa ja palauttaa sitten itse kohteen, täytyy jälkiliiteoperaattorin palauttaa arvo ennen sen kasvattamista. Tällöin on siis luotava tilapäinen kopio. Tuo tilapäiskopio tallentaa alkuperäisen arvon silloin, kun alkuperäistä kohdetta kasvatetaan. Tilapäiskopio kuitenkin palautetaan, koska jälkiliiteoperaattori käsittelee alkuperäistä arvoa. Jos kirjoitat esimerkiksi a = x++; ja x oli 5, on a lauseen suorittamisen jälkeen 5, mutta x taas 6. Tällöin siis muuttujaan a sijoitetaan x:n arvo ennen sen kasvattamista. Jos x on olio, sen jälkiliiteoperaattorin täytyy sijoittaa alkuperäinen arvo tilapäiseen olioon, kasvattaa sitten x:n arvoksi 6 ja palauttaa tuo tilapäiskopio sijoitettavaksi a:han. Huomaa, että koska tilapäiskopio palautetaan, se on palautettava arvona, ei viittauksena, koska tilapäiskopion näkyvyysalue päättyy funktion päättyessä. Listaus 14.3. havainnollistaa etuliite- ja jälkiliiteoperaattoreiden käyttöä.

Operaattorin ylikuormitus 203 Listaus 14.3. Etuliite- ja jälkiliiteoperaattorit. 1: // Listaus 14.3 2: // Palautetaan uudelleen viittaava viittaus 3: 4: typedef unsigned short USHORT; 5: 6: #include <iostream.h> 7: 8: class Counter 9: { 10: public: 11: Counter(); 12: ~Counter(){} 13: USHORT GetItsVal()const { return itsval; } 14: void SetItsVal(USHORT x) {itsval = x; } 15: const Counter& operator++ (); // edessä 16: const Counter operator++ (int); // jäljessä 17: 18: private: 19: USHORT itsval; 20: }; 21: 22: Counter::Counter(): 23: itsval(0) 24: {} 25: 26: const Counter& Counter::operator++() 27: { 28: ++itsval; 29: return *this; 30: } 31: 32: const Counter Counter::operator++(int) 33: { 34: Counter temp(*this); 35: ++itsval; 36: return temp; 37: } 38: 39: int main() 40: { 41: Counter i; 42: cout << "The value of i is " << i.getitsval() << endl; 43: i++; 44: cout << "The value of i is " << i.getitsval() << endl; 45: ++i; 46: cout << "The value of i is " << i.getitsval() << endl; 47: Counter a = ++i; 48: cout << "The value of a: " << a.getitsval(); 49: cout << " and i: " << i.getitsval() << endl; 50: a = i++; 51: cout << "The value of a: " << a.getitsval(); 52: cout << " and i: " << i.getitsval() << endl; 53: return 0; 54: } The value of i is 0 The value of i is 1

204 14. oppitunti The value of i is 2 The value of a: 3 and i: 3 The value of a: 4 and i: 4 Jälkiliiteoperaattori esitellään rivillä 15 ja toteutetaan riveillä 32-36. Huomaa, että rivin 14 kutsussa ei käytetä lippukokonaislukua (x), vaan se on normaalissa muodossa. Jälkiliiteoperaattori käyttää lippua (x) kertomaan, että kyseessä on jälkiliite eikä etuliite. Lippuarvoa ei kuitenkaan koskaan käytetä. Operaattori + Kasvatusoperaattori on unaarinen operaattori, eli yksioperandinen operaattori. Operaattori kohdistuu siis vain yhteen kohteeseen. Yhteenlaskuoperaattori (+) on binäärioperaattori, jossa on kaksi operandia. Kuinka yhteenlaskuoperaattori sitten ylikuormitetaan? Tavoitteena on mahdollisuus esitellä kaksi Counter-muuttujaa ja laskea ne yhteen, kuten seuraavassa esimerkissä: Counter varone, vartwo, varthree; varthree = varone + vartwo; Voisimme aloittaa nytkin kirjoittamalla funktion Add(), joka ottaa parametrikseen Counter-olion, laskee yhteen arvot ja palauttaa muutetun Counter-olion. Listaus 14.4. havainnollistaa tätä lähestymistapaa. Listaus 14.4. Add()-funktio. 1: // Listaus 14.4 2: // Add-metodi 3: 4: typedef unsigned short USHORT; 5: 6: #include <iostream.h> 7: 8: class Counter 9: { 10: public: 11: Counter(); 12: Counter(USHORT initialvalue); 13: ~Counter(){} 14: USHORT GetItsVal()const { return itsval; } 15: void SetItsVal(USHORT x) {itsval = x; } 16: Counter Add(const Counter &); 17: 18: private: 19: USHORT itsval; 20: 21: }; 22: 23: Counter::Counter(USHORT initialvalue): 24: itsval(initialvalue)

Operaattorin ylikuormitus 205 25: {} 26: 27: Counter::Counter(): 28: itsval(0) 29: {} 30: 31: Counter Counter::Add(const Counter & rhs) 32: { 33: return Counter(itsVal+ rhs.getitsval()); 34: } 35: 36: int main() 37: { 38: Counter varone(2), vartwo(4), varthree; 39: varthree = varone.add(vartwo); 40: cout << "varone: " << varone.getitsval()<< endl; 41: cout << "vartwo: " << vartwo.getitsval() << endl; 42: cout << "varthree: " << varthree.getitsval() << endl; 43: 44: return 0; 45: } varone: 2 vartwo: 4 varthree: 6 Add()-funktio esitellään rivillä 16. Se ottaa parametrikseen vakion Counterviittauksen, joka on nykyiseen olioon lisättävä luku. Se palauttaa Counterolion, joka sijoitetaan rivillä 38. varone on olio, vartwo on Add()-funktion parametri ja tulos sijoitetaan varthree-olioon. Jotta voitaisiin luoda varthree alustamatta sen arvoa, tarvitaan oletusmuodostin. Oletusmuodostin alustaa itsval-muuttujan arvoksi 0 (rivit 27-29). Koska varone ja vartwo on alustettava nollasta poikkeavin arvoin, luodaan toinen muodostin (rivit 23-25). Toinen ratkaisu olisi antaa oletusarvo 0 rivillä 11 esitellylle muodostimelle. operator+ ylikuormittaminen Itse Add()-funktio on riveillä 30-33. Se toimii, mutta sen käyttö on hieman luonnotonta. Yhteenlaskuoperaattorin ylikuormittaminen takaisi Counterluokan luonnollisemman käytön. Listaus 14.5 havainnollistaa tätä. Listaus 14.5. Yhteenlaskuoperaattori. 1: //operator+ ylimäärittely 2: 3: typedef unsigned long ULONG; 4: #include <iostream.h> 5: 6: 7: class Counter

206 14. oppitunti 8: { 9: public: 10: Counter(); 11: Counter(ULONG initialvalue); 12: ~Counter(){} 13: ULONG GetItsVal()const { return itsval; } 14: void SetItsVal(ULONG x) {itsval = x; } 15: Counter operator+ (const Counter &); 16: private: 17: ULONG itsval; 18: }; 19: 20: Counter::Counter(ULONG initialvalue): 21: itsval(initialvalue) 22: {} 23: 24: Counter::Counter(): 25: itsval(0) 26: {} 27: 28: Counter Counter::operator+ (const Counter & rhs) 29: { 30: return Counter(itsVal + rhs.getitsval()); 31: } 32: 33: int main() 34: { 35: Counter varone(2), vartwo(4), varthree; 36: varthree = varone + vartwo; 37: cout << "varone: " << varone.getitsval()<< endl; 38: cout << "vartwo: " << vartwo.getitsval() << endl; 39: cout << "varthree: " << varthree.getitsval() << endl; 40: 41: return 0; 42: } varone: 2 vartwo: 4 varthree: 6 operator+ esitellään rivillä 15 ja määritellään riveillä 28-31. Vertaa näitä rivejä Add()-funktion riveihin; ne ovat lähes identtiset. Niiden käyttötavat ovat kuitenkin erilaiset. On luonnollisempaa koodata näin: varthree = varone + vartwo; kuin näin: varthree = varone.add(vartwo); Ei kovinkaan suuri muutos, mutta tarpeeksi suuri tekemään ohjelmasta helpomman käyttää ja ymmärtää.

Operaattorin ylikuormitus 207 Operaattorin ylikuormittamisen rajoitukset Sisäisten tietotyyppien (kuten int) operaattoreita ei voida ylikuormittaa. Niiden suoritusjärjestystä ei voida muuttaa eikä myöskään sitä, kuinka moneen operandiin (yhteen tai useampaan) ne kohdistuvat. Uusia operaattoreita ei voida luoda eli et voi luoda esimerkiksi operaattoria ** olemaan 'potenssiin korotus' -operaattori. Mitä voidaan ylikuormittaa Operaattorin ylikuormittaminen on hämärimpiä C++ -alueita uusille ohjelmoijille. On houkuttelevaa luoda uusia käyttötarkoituksia joillekin operaattoreille, mutta liika monimutkaisuus tekee koodista vaikealukuista. Tietenkin yhteenlaskuoperaattorin muuttaminen vähennyslaskuksi tai kertolaskuoperaattorin yhteenlaskuksi voi olla hauskaa, mutta sellainen ei sovi ammattilaisohjelmoijille. Vaarat piilevät hyvää tarkoittavassa, mutta turhassa operaattorin käytössä; esimerkiksi + voisi tarkoittaa kirjainten yhdistämistä ja / taas merkkijonon pilkkomista. Tuollaista käyttöä voisi hyvinkin harkita, mutta tulee muistaa, että ylikuormittamisen tarkoituksena on käytettävyyden ja ymmärrettävyyden lisääminen. operator= Kääntäjä antaa siis käyttöön oletusmuodostimen, tuhoajafunktion ja kopiomuodostimen. Neljäs ja viimeinen kääntäjän tarjoama toiminto (ellet määritä uusia) on sijoitusoperaattori (operator=()). Tätä operaattoria kutsutaan aina, kun sijoitat olioon. Esimerkiksi: CAT catone(5,7); CAT cattwo(3,4); // muuta koodia cattwo = catone; Aluksi luodaan oliot catone ja cattwo. Ne alustetaan alkuarvoin. Huomaa, että tässä tapauksessa ei kutsuta kopiomuodostinta. cattwo on jo olemassa; sitä ei tarvitse muodostaa. Luvussa 13, "Kehittyneet funktiot", keskusteltiin pinnallisten ja jäsenkohtaisten kopioiden sekä tarkkojen kopioiden eroista. Pinnallinen kopio kopioi vain jäsenet, jolloin molemmat oliot päätyvät osoittamaan samaan muistialueeseen. Tarkka kopio varaa tarvittavan muistin. Kuva 13.1 havainnollisti tätä ajattelua; tutki kuvaa uudelleen virkistääksesi muistiasi. Sama ajattelutapa koskee sijoitusta kuin kopiomuodostinta. Sijoitusoperaattoria koskee kuitenkin vielä yksi lisäseikka. Olio cattwo on jo

208 14. oppitunti olemassa ja muisti on jo varattu. Tuo muisti täytyy vapauttaa haluttaessa välttää muistikato. Niinpä ensimmäinen seikka sijoitusoperaattoria toteutettaessa on tuhota muisti, johon osoittimet osoittavat. Mutta mitä tapahtuu, jos sijoitat cattwoolion itseensä seuraavalla tavalla: cattwo = cattwo; Kukaan ei tietenkään tee noin tarkoituksella, mutta ohjelman on kyettävä selviytymään siitä. Ja vielä tärkeämpää, tällaista voi sattua vahingossa, kun viitatut ja uudelleenviitatut osoittimet kätkevät sen seikan, että olion sijoitus tapahtuu siihen itseensä. Jos et selvittäisi tuota ongelmaa huolellisesti, cattwo tuhoaisi oman muistivarauksensa. Sen jälkeen, kun se olisi valmis kopioimaan muistiin kohteen oikealta puolelta sijoitusoperaattoria, syntyisi hyvin suuri ongelma; muistia ei enää olisikaan. Suojautuakseen tältä sijoitusoperaattorin täytyy tarkistaa, onko sijoitusoperaattorin oikealla puolella olio itse. Se tekee sen tutkimalla thisosoitinta. Listaus 14.6 esittelee luokan, jossa on sijoitusoperaattori. Listaus 14.6. Sijoitusoperaattori. 1: // Kopiomuodostimet 2: 3: #include <iostream.h> 4: 5: class CAT 6: { 7: public: 8: CAT(); // oletusmuodostin 9: // Kopiomuodostin ja tuhoaja ei mukana 10: int GetAge() const { return *itsage; } 11: int GetWeight() const { return *itsweight; } 12: void SetAge(int age) { *itsage = age; } 13: CAT operator=(const CAT &); 14: 15: private: 16: int *itsage; 17: int *itsweight; 18: }; 19: 20: CAT::CAT() 21: { 22: itsage = new int; 23: itsweight = new int; 24: *itsage = 5; 25: *itsweight = 9; 26: } 27: 28: 29: CAT CAT::operator=(const CAT & rhs) 30: { 31: if (this == &rhs) // tarkistetaan, ovatko oliot samoja

Operaattorin ylikuormitus 209 32: return *this; 33: delete itsage; 34: delete itsweight; 35: itsage = new int; 36: itsweight = new int; 37: *itsage = rhs.getage(); 38: *itsweight = rhs.getweight(); 39: return *this; 40: } 41: 42: 43: int main() 44: { 45: CAT frisky; 46: cout << "frisky's age: " << frisky.getage() << endl; 47: cout << "Setting frisky to 6...\n"; 48: frisky.setage(6); 49: CAT whiskers; 50: cout << "whiskers' age: " << whiskers.getage() << endl; 51: cout << "copying frisky to whiskers...\n"; 52: whiskers = frisky; 53: cout << "whiskers' age: " << whiskers.getage() << endl; 54: return 0; 55: } frisky's age: 5 Setting frisky to 6 whiskers' age: 5 copying frisky to whiskers whiskers' age: 6 Listaus 14.6 tuo takaisin CAT-luokan ja siinä ei enää tilan säästämiseksi ole kopiomuodostinta eikä tuhoajafunktiota. Rivillä 14 esitellään sijoitusoperaattori, joka määritellään sitten riveillä 29-37. Rivillä 32 testataan nykyinen olio (CAT, johon sijoitetaan), jotta nähtäisiin, onko se sama kuin CAT, joka sijoitetaan. Tämä toteutetaan tarkistamalla, onko rhs:n osoite sama kuin this-osoittimessa oleva osoite. Vaihtoehtoisesti voidaan testaus suorittaa viittaamalla this-osoittimella uudelleen ja katsomalla ovatko oliot samat: if (*this == rhs) Tietenkin myös yhtäsuuruusoperaattori (==) voidaan ylikuormittaa, jolloin voidaan määrätä, mitä olioiden yhtäsuuruus tarkoittaa.

210 14. oppitunti Muunnosoperaattorit Mitä tapahtuu yritettäessä sijoittaa sisäistä tyyppiä (int, unsigned short, tms.) oleva muuttuja käyttäjän määrittelemän luokan olioon? Listaus 14.7 palauttaa mieliin Counter-luokan ja yrittää sijoittaa Counter-olion intmuuttujaan. Listausta 14.7 ei voida kääntää! Listaus 14.7. Counter-olion sijoittaminen int-muuttujaan. 1: // Koodia ei voida kääntää 2: 3: typedef unsigned short USHORT; 4: 5: #include <iostream.h> 6: 7: class Counter 8: { 9: public: 10: Counter(); 11: ~Counter(){} 12: USHORT GetItsVal()const { return itsval; } 13: void SetItsVal(USHORT x) {itsval = x; } 14: private: 15: USHORT itsval; 16: 17: }; 18: 19: Counter::Counter(): 20: itsval(0) 21: {} 22: 23: int main() 24: { 25: USHORT theshort = 5; 26: Counter thectr = theshort; 27: cout << "thectr: " << thectr.getitsval() << endl; 28: return ;0 29: } Compiler error! Unable to convert int to Counter Riveillä 7-17 esitellyllä Counter-luokalla on vain oletusmuodostin. Siinä ei ole mitään erityistä metodia int-muuttujan muuntamiseksi Counter-olioksi, joten rivi 26 aiheuttaa kääntäjän virheilmoituksen. Kääntäjä ei osaa päätellä, ellet kerro sitä, että annettu int tulisi sijoittaa olion jäsenmuuttujaan itsval. Listaus 14.8 korjaa tilanteen luomalla muunnosoperaattorin: muodostimen, joka ottaa parametrikseen int-muuttujan ja tuottaa Counter-olion.

Operaattorin ylikuormitus 211 Listaus 14.8. int-muunnos Counter-olioksi. 1: // Listaus 14.12 2: // Muodostin muunnosoperaattorina 3: 4: typedef unsigned short USHORT; 5: 6: #include <iostream.h> 7: 8: class Counter 9: { 10: public: 11: Counter(); 12: Counter(USHORT val); 13: ~Counter(){} 14: USHORT GetItsVal()const { return itsval; } 15: void SetItsVal(USHORT x) {itsval = x; } 16: private: 17: USHORT itsval; 18: 19: }; 20: 21: Counter::Counter(): 22: itsval(0) 23: {} 24: 25: Counter::Counter(USHORT val): 26: itsval(val) 27: {} 28: 29: 30: int main() 31: { 32: USHORT theshort = 5; 33: Counter thectr = theshort; 34: cout << "thectr: " << thectr.getitsval() << endl; 35: return 0; 36: } thectr: 5 Rivillä 12 on tuo tärkeä muutos. Siinä muodostin ylikuormitetaan ottamaan parametrikseen int-tyypin. Muodostin toteutetaan riveillä 25-27. Muodostin luo Counter-olion, jossa int on mukana. Nyt kääntäjä pystyy kutsumaan muodostinta, joka ottaa int-argumentin. Katso kuitenkin, mitä tapahtuu, jos yrität kääntää sijoituksen seuraavasti: 1: Counter thectr(5); 2: USHORT theshort = thectr; 3: cout << "theshort : " << theshort << endl;

212 14. oppitunti Koodi aiheuttaisi taasen kääntäjän virheen. Vaikka kääntäjä tietääkin nyt, kuinka Counter-olio muodostetaan int-tyypistä, se ei osaa tehdä käänteistä operaatiota. Operaattori unsigned short() Ratkaistakseen edellä kuvatun ja muut samanlaiset ongelmat C++ sisältää muunnosoperaattoreita, joita voidaan lisätä luokkaan. Luokka voi määrittää, kuinka tehdä muunnokset sisäisiin tyyppeihin. Listaus 14.9 havainnollistaa tätä. Yksi huomautus kuitenkin: muunnosoperaattorit eivät määritä palautusarvoa, vaikka ne palauttavatkin muunnetun arvon. Listaus 14.9. Counter-olion muuntaminen unsigned short() -tyypiksi. 1: // Listaus 14.9 2: // Muodostin muuntajana 3: typedef unsigned short USHORT; 4: 5: #include <iostream.h> 6: 7: class Counter 8: { 9: public: 10: Counter(); 11: Counter(USHORT val); 12: ~Counter(){} 13: USHORT GetItsVal()const { return itsval; } 14: void SetItsVal(USHORT x) {itsval = x; } 15: operator unsigned short(); 16: private: 17: USHORT itsval; 18: 19: }; 20: 21: Counter::Counter(): 22: itsval(0) 23: {} 24: 25: Counter::Counter(USHORT val): 26: itsval(val) 27: {} 28: 29: Counter::operator unsigned short() 30: { 31: return (int (itsval) ); 32: } 33: 34: int main() 35: { 36: Counter ctr(5); 37: int theshort = ctr; 38: cout << "theshort: " << theshort << endl; 39: return 0; 40: } theshort: 5

Operaattorin ylikuormitus 213 Rivillä 15 esitellään muunnosperaattori. Huomaa, että sillä ei ole palautusarvoa. Funktion toteutus on riveillä 29-32. Rivi 31 palauttaa itsvalarvon muunnettuna int-tyypiksi. Nyt kääntäjä tietää, kuinka int-tyyppejä muunnetaan Counter-olioiksi ja päinvastoin ja ne voidaan sijoittaa vapaasti toisiinsa. Yhteenveto Tässä luvussa käsiteltiin operaattorin ylikuormittamista. Kääntäjä tarjoaa kopiomuodostimen ja operator= -operaattorin, ellet luo omia. Ne tekevät kuitenkin vain jäsenkohtaisen kopion luokasta. Sellaisten luokkien kohdalla, joissa on jäsentietoina osoittimia vapaaseen muistiin, täytyy nuo metodit korvata niin, että muistia varataan kohdeoliolle. Melkein kaikki C++ -operaattorit voidaan ylikuormittaa, vaikkakin turhanpäiväistä ylikuormittamista tulee välttää. Operaattoreiden operandimäärää ei voida muuttaa eikä myöskään kehittää uusia operaattoreita. this-osoitin viittaa nykyiseen olioon ja se on kaikkien jäsenfunktioiden näkymätön parametri. Ylikuormitetut operaattorit palauttavat usein uudelleenviitatun this-osoittimen. Muunnosoperaattoreilla voidaan luoda luokkia, joita voidaan käyttää ilmauksissa, joissa yleensä käytetään eri tyyppisiä kohteita. Ne ovat poikkeuksia sääntöön, jonka mukaan kaikki funktiot palauttavat ulkoisen arvon. Muodostimien ja tuhoajafunktioiden tapaan niillä ei ole palautusarvoa. Kysymyksiä ja Vastauksia K Miksi ylikuormittaisin operaattorin, kun voin helposti luoda metodin? V On helpompi käyttää ylikuormitettuja operaattoreita silloin, kun niiden käyttäytyminen on selkeää. Ylikuormittamisella voidaan matkia sisäisten tyyppien toiminnallisuutta.

214 14. oppitunti K Mitä eroa on kopiomuodostimella ja sijoitusoperaattorilla? V Kopiomuodostin luo uuden olion, jolla on samat arvot kuin kohdeoliolla. Sijoitusoperaattori muuttaa olemassa olevaa oliota niin, että sillä on samat arvot kuin toisella oliolla. K Mitä tapahtuu jälkiliiteoperaattorissa käytetylle int-tyypille? V Ei mitään. Tuota int-tyyppiä ei koskaan käytetä muutoin kuin lippuna etuliite- ja jälkiliiteoperaattoreiden ylikuormituksessa.