Luokat. Luokat ja olio-ohjelmointi



Samankaltaiset tiedostot
Virtuaalifunktiot ja polymorfismi

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

815338A Ohjelmointikielten periaatteet Harjoitus 5 Vastaukset

Osoitin ja viittaus C++:ssa

Periytyminen. Luokat ja olio-ohjelmointi

12 Mallit (Templates)

Mallit standardi mallikirjasto parametroitu tyyppi

Olio-ohjelmointi Javalla

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

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

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

Ohjelmoinnin perusteet Y Python

1. Olio-ohjelmointi 1.1

Esimerkkiprojekti. Mallivastauksen löydät Wroxin www-sivuilta. Kenttä Tyyppi Max.pituus Rajoitukset/Kommentit

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

Oliosuunnitteluesimerkki: Yrityksen palkanlaskentajärjestelmä

Harjoitus Olkoon olemassa luokat Lintu ja Pelikaani seuraavasti:

Operaattoreiden uudelleenmäärittely

Ohjelmoinnin perusteet Y Python

Omat tietotyypit. Mikä on olio?

Olio-ohjelmoinnissa luokat voidaan järjestää siten, että ne pystyvät jakamaan yhteisiä tietoja ja aliohjelmia.

Osoittimet. Mikä on osoitin?

Ohjelman virheet ja poikkeusten käsittely

Operaattorin ylikuormitus ja käyttäjän muunnokset

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

Ohjelmoinnin peruskurssi Y1

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

ITKP102 Ohjelmointi 1 (6 op)

2. Olio-ohjelmoinista lyhyesti 2.1

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

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

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

Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op Taulukot & Periytyminen

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

Javan perusteita. Janne Käki

Sisällys. Metodien kuormittaminen. Luokkametodit ja -attribuutit. Rakentajat. Metodien ja muun luokan sisällön järjestäminen. 6.2

9. Periytyminen Javassa 9.1

Ohjelmoinnin perusteet Y Python

1. Omat operaatiot 1.1

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

ITKP102 Ohjelmointi 1 (6 op)

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

Olio-ohjelmointi 2. välikoe HYV5SN

13 Operaattoreiden ylimäärittelyjä

Ohjelmoinnin perusteet Y Python

ITKP102 Ohjelmointi 1 (6 op)

Taulukot. Jukka Harju, Jukka Juslin

Tietueet. Tietueiden määrittely

Ohjelmointikielet ja -paradigmat 5op. Markus Norrena

SQL-perusteet, SELECT-, INSERT-, CREATE-lauseet

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

Luku 6. Dynaaminen ohjelmointi. 6.1 Funktion muisti

Muuttujien roolit Kiintoarvo cin >> r;

JAVA-PERUSTEET. JAVA-OHJELMOINTI 3op A JAVAN PERUSTEET LYHYT KERTAUS JAVAN OMINAISUUKSISTA JAVAN OMINAISUUKSIA. Java vs. C++?

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

Olio-ohjelmointi Syntaksikokoelma

Johdatus Ohjelmointiin

Kääntäjän virheilmoituksia

Java-kielen perusteet

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

UML Luokkakaavio 14:41

Ohjelmoinnin jatkokurssi, kurssikoe

7. Oliot ja viitteet 7.1

1 Tehtävän kuvaus ja analysointi

Tietotyypit ja operaattorit

Ohjelmoinnin perusteet Y Python

15. Ohjelmoinnin tekniikkaa 15.1

Ohjelmoinnin perusteet Y Python

ITKP102 Ohjelmointi 1 (6 op)

Luokan operaatiot. Osoittimet ja viittaukset luokan olioihin

Olio-ohjelmointi: Luokkien toteuttaminen. Jukka Juslin

15. Ohjelmoinnin tekniikkaa 15.1

14. Poikkeukset 14.1

Sisällys. 9. Periytyminen Javassa. Periytymismekanismi Java-kielessä. Periytymismekanismi Java-kielessä

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

Ohjelmointi 2 / 2010 Välikoe / 26.3

Java-kielen perusteet

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

4. Olio-ohjelmoinista lyhyesti 4.1

Ohjelmoinnin peruskurssi Y1

Sisällys. 6. Metodit. Oliot viestivät metodeja kutsuen. Oliot viestivät metodeja kutsuen

Sisällys. 9. Periytyminen Javassa. Periytymismekanismi Java-kielessä. Periytymismekanismi Java-kielessä

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

Sisällys. 14. Poikkeukset. Johdanto. Johdanto

Funktiomallit Funktiomallin määrittely

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

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

Ohjelmistojen mallintaminen Olioiden yhteistyö Harri Laine 1

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

9. Periytyminen Javassa 9.1

Ohjelmoinnin perusteet Y Python

Tenttikysymykset. + UML-kaavioiden mallintamistehtävät

C# olio-ohjelmointi perusopas

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

812341A Olio-ohjelmointi Peruskäsitteet jatkoa

IDL - proseduurit. ATK tähtitieteessä. IDL - proseduurit

Koottu lause; { ja } -merkkien väliin kirjoitetut lauseet muodostavat lohkon, jonka sisällä lauseet suoritetaan peräkkäin.

815338A Ohjelmointikielten periaatteet Harjoitus 4 vastaukset

ATK tähtitieteessä. Osa 3 - IDL proseduurit ja rakenteet. 18. syyskuuta 2014

Apuja ohjelmointiin» Yleisiä virheitä

Transkriptio:

Luokat 12 Luokat Tässä luvussa laajennamme edellisessä luvussa käsittelemäämme struktuurityyppiä ja siirrymme yhteen C++-ohjelmoijan kaikkein tärkeimmistä välineistä: luokkiin. Käsittelemme myöskin joitakin olio-ohjelmoinnin ideoita ja miten näitä ideoita sovelletaan käytännössä. Käsittelemme seuraavia aiheita: Mitkä ovat olio-ohjelmoinnin perusperiaatteet Miten uusi tietotyyppi luodaan luokaksi ja miten luokan olioita käsitellään Mitä ovat luokan muodostinfunktiot ja miten niitä kirjoitetaan Mikä on oletusmuodostinfunktio ja miten voit tehdä oman oletusmuodostinfunktion Mikä on oletuskopiomuodostin Mikä on ystäväfunktio Mitä oikeuksia ystäväluokalla on Mikä on this-osoitin, miten ja milloin sitä käytetään Luokat ja olio-ohjelmointi Ennen kuin siirrymme kieleen, syntaksiin ja luokkien ohjelmointitekniikoihin, käsittelemme miten jo oppimasi liittyy olio-ohjelmointiin. Perustana olio-ohjelmoinnissa (kutsutaan usein termillä OOP, object oriented programming ) on se, että ohjelmat kirjoitetaan ratkaistavan ongelman muodostavien olioiden avulla, joten ohjelman kehitysprosessiin kuuluu tilanteesta riippuvien tyyppien muodostaminen. Jos kirjoitat ohjelmaa, jonka tarkoituksena on pankkitilisi seuraaminen, sinulla mitä todennäköisemmin tulee olla tietotyyppejä, kuten Tili ja Tilisiirto. Jos ohjelman tarkoitus on analysoida jääkiekkoottelun tuloksia, tyyppeinä olisivat esimerkiksi Pelaaja ja Joukkue. Perustietotyypeillä ei voi mallintaa elävän elämän olioita (tai edes kuvitteellisia) kunnolla. Jääkiekkoilijaa ei voi mallintaa pelkillä tyypeillä double tai int, tai millään muullakaan perustietotyypillä. Tarvitsemme useita erilaisia ja eri tyyppisiä arvoja, jotta voimme kuvata jääkiekkoilijan. 443

C++ Ohjelmoijan käsikirja Struktuureilla saimme yhden ratkaisutavan. Olemme nähneet, kuinka voimme struktuurien avulla yhdistää erityyppisiä muuttujia. Struktuurin määrittelyssä voi olla myös funktioita. Edellisessä luvussa määrittelimme struktuurin Laatikko, joka kuvasi laatikkoa. Tämän uuden tyypin avulla pystyimme määrittelemään Laatikko-tyyppisiä muuttujia aivan samaan tapaan kuin perustietotyyppiäkin olevia muuttujia. Pystyimme luomaan ja käsittelemään niin montaa Laatikko-oliota kuin ohjelmassa tarvitsimme. Tämä on jo varsin lähellä elävän elämän oliota. Voimme tietysti käyttää struktuuria myös jääkiekkoilijan tai pankkitilin kohdalla tai minkä tahansa muunkin kohdalla. Struktuurien avulla voimme mallintaa millaisia olioita tahansa ja rakentaa ohjelma niiden ympärille. Eli mihin olio-ohjelmointia sitten oikeastaan tarvitaan? No, kyllä sitä tarvitaan. Struktuuri, sellaisena kuin me olemme sen määritelleet, on suuri askel eteenpäin, mutta olio-ohjelmointiin kuuluu muutakin. Käyttäjän määrittelemien tietotyyppien lisäksi olio-ohjelmointiin kuuluu muitakin tärkeitä lisäideoita kuten kapselointi, tiedon kätkentä, periytyminen ja polymorfismi. Struktuuri ei pysty näitä toteuttamaan. Tarkastellaan seuraavaksi, mitä kukin näistä olio-ohjelmoinnin ideoista pitää sisällään. Näin saamme hyvän perustan yksityiskohtaiselle C++-ohjelmoinnin opiskelulle tässä ja seuraavissa neljässä luvussa. Kapselointi Yleisesti ottaen, tietyn tyyppisen olion määrittelyyn kuuluu joukko erityisiä asioita, joiden avulla oliosta tehdään se, mitä se on. Olio sisältää määritellyn joukon tietoalkioita, jotka kuvaavat olion käyttötarkoituksen riittävän tarkasti. Laatikon kohdalla ne voisivat olla yksinkertaisesti kolme mittaa: pituus, syvyys ja korkeus. Lentokoneen kohdalla niitä olisi huomattavasti enemmän. Olio sisältää myös joukon funktioita, joilla olion tietoalkioita käsitellään. Ne määrittelevät olion operaatiot - eli mitä olion kanssa voi tehdä ja mitä ei. Jokainen tietyntyyppinen olio sisältää nämä samat asiat: joukon tietoalkioita eli jäsenmuuttujia ja joukon operaatioita eli jäsenfunktioita. Tätä tietoalkioiden ja funktioiden yhdistämistä kutsutaan kapseloinniksi. Alla oleva kaavio selventää tätä pankin lainatilin avulla. LainaTili -olio Olio sisältää kaiken, millä sen ominaisuudet ja operaatiot voidaan määritellä. Saldo: 50000 mk KorkoPros: 22% Laskekorko() Jäsenmuuttujat määrittelevät olion ominaisuudet. Jäsenfunktiot määrittelevät, mitä olion kanssa voidaan tehdä. Kapselointi 444

Luokat Jokaisen LainaTili-olion ominaisuudet on kuvattu samoilla jäsenmuuttujilla - tässä tapauksessa toinen sisältää lainan saldon ja toinen korkoprosentin. Jokainen olio sisältää myöskin jäsenfunktiot, jotka määrittelevät olion operaatiot: tässä ainut jäsenfunktio laskee koron ja lisää sen lainasaldoon. Ominaisuudet ja operaatiot kapseloidaan jokaiseen LainaTili-olioon. LainaTiliolioon kuuluvat ominaisuudet ja operaatiot ovat tietysti itse päätettävissä. Voit määritellä sen varsin erilaiseksi omassa ohjelmassasi, mutta riippumatta siitä, millaiseksi sen määrittelet, kaikki ominaisuudet ja operaatiot kapseloidaan jokaiseen sentyyppiseen olioon. Tiedon kätkentä Pankki ei tietystikään halua, että lainan saldoa (eikä myöskään korkoprosenttia) muutetaan omavaltaisesti olion ulkopuolelta, kuten pystyimme edellisen kappaleen struktuureiden kohdalla tekemään. Jos tämä sallittaisiin, syntyisi kaaos. Ideaalitilanne on, jos LainaTili-olion jäsenmuuttujat voidaan suojata ulkoiselta käsittelyltä ja että niitä voitaisiin käsitellä ainoastaan kontrolloidusti. Sitä ominaisuutta, että olion jäsenmuuttujat voidaan tehdä sellaisiksi, että niitä ei voi käsitellä suoraan, kutsutaan tiedon kätkennäksi. Yleensä jäsenmuuttujiin ei pääse käsiksi olion ulkopuolelta. LainaTili-olio saldo: 50000 mk korkopros: 22% Olion jäsenmuuttujat tulisi yleensä olla kätkettynä. Jäsenfunktioiden avulla voidaan tarpeen mukaan muuttaa jäsenmuuttujien tietoja. laskekorko() käsittelee Jäsenfunktioiden avulla voidaan jäsenmuuttujien sisältöä muuttaa kontrolloidusti. Tiedon kätkentä LainaTili-olion kohdalla voidaan jäsenfunktioiden avulla varmistaa, että jäsenmuuttujia muutetaan vain tietyllä tavalla ja että arvot ovat sallittuja. Korko ei esimerkiksi saa olla negatiivinen ja saldo tarkoittaa pankilta lainattua rahasummaa eikä päinvastoin. Tiedon kätkentä on tärkeä ominaisuus, koska sitä tarvitaan, jos haluat säilyttää olion eheyden. Jos olion on tarkoitus esittää ankkaa, sillä ei tulisi olla neljää jalkaa. Tämän kontrollointi voidaan tehdä siten, että jalkojen lukumäärää ei voida käsitellä suoraan - tieto piilotetaan. Oliolla voi tietysti olla tietoja, joiden arvoaluetta ei voi tarkasti määritellä, mutta myös tällöinkin arvoja voidaan kontrolloida. Esimerkiksi ankka ei yleensä paina yli 100 kiloa. Olioon kuuluvan tiedon kätkentä tarkoittaa sitä, että sitä ei voida käsitellä suoraan, mutta arvoja voidaan käsitellä olion jäsenfunktioiden avulla - joko muuttaa arvoa kontrolloidusti tai vain lukea arvo. Jäsenfunktiot voivat varmistaa, että niiltä pyydetty muutos on sallittu. Olion tietojen kätkentä ei ole pakollista, mutta se on yleensä järkevää ainakin muutamasta syystä. Ensinnäkin, kuten jo mainittiin, olion eheyden säilyttäminen vaatii, että muutoksia voidaan kontrolloida. Toiseksi, olion tietojen käsittely suoraan romuttaa koko olio-ohjelmoinnin idean. Olio-ohjelmoinnin tulee olla olioiden ehdoilla ohjelmoimista, ei olion muodostavien bittien ehdoilla ohjelmoimista. 445

C++ Ohjelmoijan käsikirja Jäsenmuuttujien voidaan ajatella kuvaavan olion tilaa ja niitä käsitteleviä jäsenfunktioita olion liittymänä ulkomaailmaan. Luokan avulla ohjelmointi tapahtuu sitten liittymänä toimivien funktioiden kanssa. Luokan liittymää käyttävä ohjelma on täten riippuvainen vain funktion nimistä, niiden parametreistä ja paluuarvoista. Funktioiden sisäinen toiminta ei vaikuta niitä käyttävän ohjelman toimintaan. Tämä tarkoittaa, että luokan liittymä tulee saada oikeaksi jo suunnitteluvaiheessa, mutta luokan toteutusta voit muuttaa haluamallasi tavalla ilman mitään muutoksia sitä käyttävään ohjelmaan. Perityminen Periytymisellä tarkoitetaan mahdollisuutta määritellä tyyppi perustuen jo olemassa olevaan tyyppiin. Oletetaan esimerkiksi, että olet määritellyt tyypin PankkiTili, joka sisältää jäseniä, joita voidaan käyttää kaikentyyppisten pankkitilien yhteydessä. Periytymisen avulla voit sitten luoda tyypin LainaTili, joka on PankkiTili-tyypin erikoismuoto. Voit määritellä LainaTili-tyypin muuten samanlaiseksi kuin PankkiTili, mutta siihen on lisätty muutama ominaisuus ja operaatio. LainaTili-tyyppi perii kaikki PankkiTili-tyypin jäsenet. PankkiTili-tyyppiä kutsutaan uuden luokan kantaluokaksi. Sanomme, että LainaTili on peritty PankkiTili-luokasta. Jokainen LainaTili-olio sisältää kaikki jäsenet, jotka ovat PankkiTili-oliossakin, mutta siihen voidaan määritellä uusia jäseniä tai perityt funktiot voidaan määritellä uudelleen, jotta ne olisivat sopivammat uudelle oliolle. Tämä jälkimmäinen ominaisuus on erittäin hyödyllinen, kuten tulet huomaamaan, kun käsittelemme aihetta lisää. Laajennetaan tätä esimerkkiä ja lisätään siihen uusi tyyppi, ShekkiTili, jossa PankkiTili-tyyppiin on lisätty uusia ominaisuuksia. Koko tilanne havainnollistetaan alla olevassa kaaviossa: saldo korkopros PankkiTili laskekorko() saldo korkopros LainaTili laskekorko() Tyypit LainaTili ja ShekkiTili ovat molemmat periytetty tyypistä PankkiTili, joten ne perivät tämän tyypin jäsenet. saldo korkopros käyttövara ShekkiTili laskekorko() Periytyminen 446

Luokat Tyypit LainaTili ja ShekkiTili esitellään siten, että ne periytetään tyypistä PankkiTili. Ne perivät PankkiTili-tyypin jäsenmuuttujat ja jäsenfunktiot, mutta niihin voidaan määritellä uusia, vain niihin liittyviä ominaisuuksia. Tässä esimerkissä luokkaan ShekkiTili on lisätty jäsenmuuttuja kayttovara ja molemmat perityt luokat määrittelevät uudelleen jäsenfunktion laskekorko(), joka muuten perittäisiin kantaluokalta. Tämä on järkevää, koska koron käsittely lainatilin ja shekkitilin kohdalla on varsin erilaista. Polymorfismi Sana polymorfismi tarkoittaa kykyä olettaa erilaisia asioita eri aikoina. C++:n polymorfismiin liittyy aina olion jäsenfunktion kutsuun osoittimen tai viittauksen avulla. Tällaisella funktion kutsulla voi olla erilaisia vaikutuksia eri aikoina - eräänlainen Jekyll ja Hyde -funktiokutsu. Mekanismi toimii vain olioille, joiden tyyppi on periytetty yleisestä tyypistä, kuten meidän PankkiTili-tyypistä. Polymorfismi tarkoittaa, että olioita, jotka kuuluvat periytymissuhteessa oleviin perhe -luokkiin, voidaan käsitellä kantaluokan osoittimien ja viittausten avulla. Äskeisessä esimerkissämme olevia olioita LainaTili ja ShekkiTili voidaan kumpaakin käsitellä osoittimilla tai viittauksilla PankkiTili-olioon. Osoitinta tai viittausta voidaan sitten käyttää minkä tahansa olion, johon se viittaa, perityn jäsenfunktion kutsumiseen. Tämä idea ja sen vaikutukset on helpompi ymmärtää, jos katsomme esimerkkiä. Oletetaan, että meillä on tyypit LainaTili ja ShekkiTili määritelty kuten edellä, eli perustuen PankkiTili-tyyppiin. Oletetaan lisäksi, että olemme määritelleet näiden tyyppiset oliot LTili ja STili, kuten kaaviosta näkyy. Koska molemmat tyypit on periytetty PankkiTili-tyypistä, muuttujaa, jonka tyyppi on osoitin tyyppiin PankkiTili, kuten kaavion pptili, voidaan käyttää kumman tahansa olion osoitteen tallettamiseen. PankkiTili* pptili; LainaTili LTili; ShekkiTili STili; Polymorfismi PankkiTili saldo korkopros laskekorko() pptili = &STili; pptili->laskekorko(); // Hyvittää tiliä pptili = &LTili; pptili->laskekorko(); // Velottaa tiliä LainaTili saldo korkopros laskekorko() saldo korkopros kayttovara laskekorko() ShekkiTili Kaaviossa olevassa koodissa käytetään edellisessä luvussa näkemäämme merkintätäpaa olion jäsenfunktion kutsumisessa osoittimen kautta. 447

C++ Ohjelmoijan käsikirja Polymorfismin hienous piilee siinä, että kutsuttava funktio funktion kutsussa pptili- >laskekorko() riippuu siitä, mihin pptili osoittaa. Jos se osoittaa LainaTili-olioon, kutsutaan tämän olion laskekorko()-funktiota ja korko vähennetään tililtä. Jos se osoittaa ShekkiTiliolioon, tulos on eri, koska tämän olion laskekorko()-funktiossa korko lisätään tilille. Kutsuttavaa funktiota ei päätetä ohjelman käännösaikana, vaan ohjelman suoritusaikana. Näin ollen sama funktion kutsu voi kutsua eri asioita riippuen siitä, mihin osoitin osoittaa. Tässä esimerkissä on vain kaksi eri tyyppiä, mutta periaatteessa tyyppejä voi olla niin monta kuin sovelluksesi tarvitsee. Tarvitset hieman lisää tietoa C++-kielestä, jotta voit toteuttaa edellä kuvattua toiminnallisuutta ja juuri tätä on tarkoituskin käsitellä tässä luvussa ja seuraavissa neljässä luvussa. Polymorfismia käytetään omissa ohjelmissasi luvussa 16. Matka sinne alkaa kuitenkin tästä ja käsittelemme ensin avainsanaa class, jolla määritellään uusia luokkia. Terminologiaa Seuraavassa on yhteenveto terminologiasta, jota käytämme C++:n luokkia käsitellessämme. Se sisältää joitakin termejä, joihin olemme törmänneet jo aiemmin. Luokka on käyttäjän määrittelemä tietotyyppi. Luokassa esiteltyjä muuttujia ja funktioita kutsutaan luokan jäseniksi. Muuttujia kutsutaan jäsenmuuttujiksi ja funktioita jäsenfunktioiksi. Luokan jäsenfunktioita kutsutaan joskus myös metodeiksi, mutta tässä kirjassa ei tätä termiä käytetä. Kun olemme määritelleet luokan, voimme esitellä tämän luokkatyypin muuttujan (kutsutaan myös luokan ilmentymäksi). Jokainen ilmentymä on luokan olio. Olio-ohjelmointi on ohjelmointitapa, joka perustuu ideaan, että omat tietotyypit määritellään luokiksi. Siihen kuuluu tiedon kapselointi, luokkien periytyminen ja polymorfismi, joita juuri käsittelimme. Kun pääsemme olio-ohjelmoinnin yksityiskohtiin, koodi saattaa näyttää paikkapaikoin monimutkaiselta. Palaamalla takaisin perusteisiin voidaan asioita selventää; joten käytä yllä olevaa hyödyksesi ja muista mitä oliot itse asiassa ovat. Olio-ohjelmointi on ohjelmien kirjoittamista ongelman olioiden ehdoilla. Kaikki C++:n luokkien toiminnot ovat olemassa siitä syystä, että tämä voidaan suorittaa mahdollisimman joustavasti. Palataan takaisin luokkiin ja aloitetaan luokkien määrittelyllä. 448

Luokan määrittely Luokat Kuten olemme jo aikaisemmin maininneet, luokka, samoin kuin struktuuri, on käyttäjän määrittelemä tyyppi. Tyypin määrittely avainsanalla class on perusteiltaan sama kuin määrittely avainsanalla struct, mutta tulos on erilainen. Katsotaan seuraavaksi, kuinka määrittelemme luokan, joka kuvaa laatikkoa. Käytämme tätä tutkiessamme, miten tuloksena oleva luokka eroaa aikaisemmin määrittelemästämme struktuurista. Luodaksemme luokan, kirjoitamme vain avainsanan class avainsanan struct tilalle: class Laatikko double pituus; double syvyys; double korkeus; //Funktio, joka laskee laatikon tilavuuden double tilavuus() return pituus * syvyys * korkeus; ; Olemme tässä ottaneet edellisen luvun Laatikko-struktuurin ja korvanneet avainsanan struct avainsanalla class. Tämän tyyppistä oliota käyttävästä ohjelmasta huomaa heti struktuurin ja luokan merkittävän eron. Et voisi tässä viitata mihinkään jäsenmuuttujaan tai kutsua funktiota tilavuus() luokan ulkopuolelta. Jos yrität, koodi ei käänny. Kun luot luokan, kaikki sen jäsenet ovat oletusarvoisesti kätkettyjä, eli niihin ei pääse käsiksi luokan ulkopuolelta. Niiden sanotaan oleva luokan yksityisiä (private) jäseniä ja niitä voidaan käsitellä vain saman luokan jäsenfunktioista. Jotta luokan jäsen olisi käytettävissä funktiossa, joka ei ole luokan jäsen, sinun tulee määritellä ne luokan julkisiksi (public) jäseniksi avainsanalla public. Struktuurin jäsenet ovat oletusarvoisesti julkisia, joten ne ovat aina käytettävissä. Avainsanoja class ja struct käytetään sellaisten tietotyyppien luonnissa, jotka noudattavat olio-ohjelmoinnin periaatteita. class-tyyppisen olion jäsenet ovat oletusarvoisesti yksityisiä ja struct-tyyppisen olion jäsenet ovat oletusarvoisesti julkisia. Katsotaan seuraavaksi kuinka esittelemme luokan jäsenet julkisiksi: class Laatikko public: double pituus; double syvyys; double korkeus; 449

C++ Ohjelmoijan käsikirja ; //Funktio, joka laskee laatikon tilavuuden double tilavuus() return pituus * syvyys * korkeus; Avainsana public on saantitavan hallintakomento. Se määrittelee, onko jäsen käytettävissä ohjelman eri osissa. Luokan jäseniin, jotka ovat julkisia, pääsee käsiksi luokan ulkopuolelta, eli tällaisia jäseniä ei ole kätketty. Luokan jäsen määritellään julkiseksi avainsanalla public, jonka perään kirjoitetaan kaksoispiste. Kaikki tämän hallintakomennon perässä olevat jäsenet seuraavaan hallintakomentoon saakka ovat julkisia. Luokan Laatikko oliot kapseloivat kolme jäsenmuuttujaa ja yhden jäsenfunktion. Jokaisessa Laatikko-oliossa ovat kaikki neljä ja koska olemme määritelleet ne julkisiksi, ne ovat suoraan käytettävissä luokan ulkopuolelta. Käytössä on kaksi muutakin saantitavan hallintakomentoa: private ja protected. Luokan jäsenet, jotka ovat yksityisiä tai suojattuja (protected), ovat kätkettyjä - eli niitä ei voi suoraan käyttää luokan ulkopuolelta. Jos kirjoittaisimme seuraavan rivin edellä olleeseen esimerkkiin, private: niin kaikki tämän rivin perässä olevat jäsenet ovat yksityisiä eikä julkisia. Käsittelemme avainsanan private vaikutuksia tarkemmin myöhemmin tässä luvussa ja protected-avainsanaa käsittelemme luvussa 15. Yleensä luokan määrittelyssä on useampia kuin yksi edellä olleista saantitavan hallintakomennoista. Näin voit sijoittaa jäsenmuuttujat ja jäsenfunktiot omiin ryhmiin saantitavan mukaan. Luokan määrittelyn rakenne on helpompi nähdä, jos järjestät jäsenmuuttujat ja jäsenfunktiot ryhmiin erikseen. Näemme kuinka lähellä struktuuri on luokkaa, kun muutamme edellisen luvun esimerkkiä. Tämä ei ole vielä hyvää luokan suunnittelua, mutta pääsemme lähemmäksi parantamalla luokkaa pala kerrallaan. Samalla näemme kunkin uuden ominaisuuden positiivisen vaikutuksen lopputulokseen. 450 Kokeile itse - Luokan käyttö Seuraavassa on muutettu versio edellisen luvun esimerkistä 11.1. Tässä käytetään luokkaa struktuurin sijaan: // Esimerkki 12.1 - Laatikkoluokan käyttö #include <iostream> using namespace std; // Laatikon luokka class Laatikko public: double pituus;

Luokat double syvyys; double korkeus; ; // Funktio, joka laskee laatikon tilavuuden double tilavuus() return pituus * syvyys * korkeus; int main() Laatikko ensimmainenltk = 80.0, 50.0, 40.0 ; // Lasketaan laatikon tilavuus double ensltktilavuus = ensimmainenltk.tilavuus(); cout cout << "Ensimmäisen Laatikon koko on " << ensimmainenltk.pituus << " x " << ensimmainenltk.syvyys << " x " << ensimmainenltk.korkeus cout << "Ensimmäisen Laatikon tilavuus on " << ensltktilavuus Laatikko toinenltk = ensimmainenltk; // Samaksi kuin 1. laatikko // Kasvatetaan toisen laatikon kokoa 10% toinenltk.pituus *= 1.1; toinenltk.syvyys *= 1.1; toinenltk.korkeus *= 1.1; cout << "Toisen laatikon koko on " << toinenltk.pituus << " by " << toinenltk.syvyys << " by " << toinenltk.korkeus cout << "Toisen laatikon tilavuus on " << toinenltk.tilavuus() cout << "Koon kasvattaminen 10% kasvatti tilavuutta " << static_cast<long>((toinenltk.tilavuus()-ensltktilavuus)*100.0/ ensltktilavuus) << "%" return 0; Ohjelman tulostus on sama kuin edellisessäkin versiossa. Kuinka se toimii main()-funktion koodi on aivan sama kuin ennenkin. Tämä siksi, että olemme esitelleet luokan jäsenet julkisiksi, joten luokka toimii kuten struktuuri. Kaikki struktuurien yhteydessä käsittelemämme pätee tässä myös luokille. Jäseneen osoitus -operaattoria käytetään aivan samaan tapaan luokkien ja struktuurien kohdalla. 451

C++ Ohjelmoijan käsikirja Yksi tämän esimerkin osa ei vastaa luokkien yleistä käyttöä. Esittelemme ja alustamme esimerkissä luokan olion seuraavalla alkuarvoluettelolla: Laatikko ensimmainenltk = 80.0, 50.0, 40.0 ; Tämä lause on aivan laillinen, ainakin tässä tapauksessa. Luokan olion jäsenmuuttujia ei yleensä kuitenkaan alusteta tällä tavalla, koska jäsenmuuttujat ovat yleensä kätkettyjä. Kätkettyjä jäsenmuuttujia ei voi alustaa alkuarvoluettelon avulla. Luokan olion jäsenmuuttujien alustus tapahtuu erillisen jäsenfunktion avulla. Tätä jäsenfunktiota kutsutaan muodostinfunktioksi ja sillä voidaan alustaa kätkettyjä jäsenmuuttujia. Muodostinfunktiot Luokan muodostinfunktio on erikoislaatuinen funktio, joka eroaa tavallisesta jäsenfunktiosta. Muodostinfunktiota kutsutaan, kun luokan uusi ilmentymä luodaan. Sen avulla uusi olio voidaan alustaa luonnin yhteydessä ja myöskin varmistaa, että jäsenmuuttujiin sijoitetaan ainoastaan sallittuja arvoja. Sellaisia luokan olioita, joiden luokan määrittelyssä on määritelty muodostinfunktio, ei voi alustaa alkuarvoluettelon avulla. Luokan muodostinfunktiolla on aina sama nimi kuin sen luokalla. Funktio Laatikko() on esimerkiksi luokkamme Laatikko muodostinfunktio. Lisäksi muodostinfunktio ei palauta paluuarvoa, joten sillä ei myöskään ole paluuarvon tyyppiä. Muodostinfunktiolle ei saa määritellä paluuarvon tyyppiä, edes void ei kelpaa. Muodostinfunktion päätehtävä on kaikkien luotavan olion jäsenmuuttujien alustus ja alkuarvojen oikeellisuuden tarkistaminen. Kokeile itse - Muodostinfunktion lisääminen Laatikko-luokkaan Laajennetaan edellisen esimerkkimme Laatikko-luokkaa lisäämällä siihen muodostinfunktio: // Esimerkki 12.2 - Muodostinfunktion käyttö #include <iostream> using namespace std; // Laatikon luokka class Laatikko public: double pituus; double syvyys; double korkeus; 452 //Muodostinfunktio Laatikko(double pituusarvo, double syvyysarvo, double korkeusarvo) cout << Laatikon muodostinfunktiota kutsuttu pituus = pituusarvo; syvyys = syvyysarvo; korkeus = korkeusarvo;

Luokat ; // Funktio, joka laskee laatikon tilavuuden double tilavuus() return pituus * syvyys * korkeus; int main() Laatikko ensimmainenltk(80.0, 50.0, 40.0); // Lasketaan laatikon tilavuus double ensltktilavuus = ensimmainenltk.tilavuus(); cout cout << "Ensimmäisen Laatikon koko on " << ensimmainenltk.pituus << " x " << ensimmainenltk.syvyys << " x " << ensimmainenltk.korkeus cout << "Ensimmäisen Laatikon tilavuus on " << ensltktilavuus return 0; Tämän esimerkin tulostus on seuraava: Laatikon muodostinfunktiota kutsuttu Ensimmäisen laatikon koko on 80 x 50 x 40 Ensimmäisen laatikon tilavuus on 160000 Kuinka se toimii Laatikko-luokan muodostinfunktion määrittelyssä on kolme double-tyyppistä parametriä, jotka vastaavat jäsenten pituus, syvyys ja korkeus alkuarvoja: Laatikko(double pituusarvo, double syvyysarvo, double korkeusarvo) cout << Laatikon muodostinfunktiota kutsuttu pituus = pituusarvo; syvyys = syvyysarvo; korkeus = korkeusarvo; Kuten huomaat, paluuarvoa ei ole määritelty ja muodostinfunktion nimi on sama kuin luokan nimi. Muodostinfunktion ensimmäisessä lauseessa tulostetaan viesti, josta näemme, milloin sitä on kutsuttu. Tätä et tietysti tee lopullisessa ohjelmassa, mutta koska siitä nähdään helposti, milloin muodostinfunktiota kutsutaan, sitä käytetään usein ohjelman testauksessa. Me käytämme sitä usein havainnollistamaan, mitä esimerkkiohjelmissa tapahtuu. Moudostinfunktion loppukoodi on varsin selkeää. Siinä vain sijoitetaan parametreinä saadut alkuarvot jäsenmuuttujiin. Tarpeen mukaan voisimme lisätä alkuarvojen oikeellisuustarkistuksia. Todellisessa ohjelmassa varmasti tekisitkin näin, mutta tässä päätarkoituksemme on nähdä, miten muodostinfunktio toimii, joten pidämme sen yksinkertaisena. 453

C++ Ohjelmoijan käsikirja main()-funktiossa esittelemme olion ensimmainenltk lauseella: Laatikko ensimmainenltk(80.0, 50.0, 40.0); Jäsenmuuttujien pituus, syvyys ja korkeus alkuarvot ovat olion nimen perässä olevissa sulkeissa. Ne välitetään parametreinä muodostinfunktiolle. Kun muodostinfunktiota kutsutaan, se tulostaa viestin, joten näemme, että olion määrittely itse asiassa kutsuu luokkaan lisäämäämme muodostinfunktiota. Koska luokan määrittelyssä on esitelty muodostinfunktio, emme voi alustaa oliota alkuarvoluettelon avulla. Lause, jota käytimme edellisessä esimerkissä, ei enää käänny: Laatikko ensimmainenltk = 80.0, 50.0, 40.0 ; //Väärin!! Täytyy kutsua // muodostinfunktiota Olio, jonka luokkaan on määritelty yksikin muodostinfunktio, täytyy luoda muodostinfunktion avulla. Seuraavat kaksi lausetta main()-funktiossa tulostavat laatikon mitat ja tilavuuden samaan tapaan kuin aikaisemminkin, joten meillä on todisteet siitä, että jäsenmuuttujat sisältävät arvot, jotka välitimme muodostinfunktiolle. Muodostinfunktion määrittelyn sijoittaminen luokan ulkopuolelle Kun käsittelimme edellisessä luvussa struktuureja, näimme, että jäsenfunktio voidaan sijoittaa struktuurin määrittelyn ulkopuolelle. Näin voidaan tehdä myös luokkien ja luokkien muodostinfunktioiden kohdalla. Voimme määritellä luokan Laatikko otsikkotiedostossa: // Laatikko.h #ifndef LAATIKKO_H #define LAATIKKO_H class Laatikko public: double pituus; double syvyys; double korkeus; // Muodostinfunktio Laatikko(double pituusarvo, double syvyysarvo, double korkeusarvo); ; // Funktio, joka laskee laatikon tilavuuden double tilavuus(); #endif 454

Nyt voimme sijoittaa jäsenfunktioiden määrittelyt.cpp-tiedostoon. Jokainen funktion nimi, mukaan lukien muodostinfunktioiden nimet, täytyy kirjoittaa luokan nimen ja näkyvyysalueoperaattorin kanssa: // Laatikko.cpp #include <iostream> #include "Laatikko.h" using namespace std; // Muodostinfunktion määrittely Laatikko::Laatikko(double pituusarvo, double syvyysarvo, double korkeusarvo) cout << "Laatikon muodostinfunktiota kutsuttu" pituus = pituusarvo; syvyys = syvyysarvo; korkeus = korkeusarvo; // Funktio, joka laskee laatikon tilavuuden double Laatikko::tilavuus() return pituus * syvyys * korkeus; Luokat Meidän tulee muistaa sisällyttää luokan Laatikko sisältävä otsikkotiedosto, muutoin kääntäjä ei tiedä, että Laatikko on luokka. Luokkien määrittelyn erottaminen jäsenfunktioiden määrittelystä on yhdenmukainen tarkentimien.h ja.cpp kanssa. Näin saamme koodistamme helpommin hallittavan. Kaikkien lähdetekstitiedostojen, jotka haluavat käyttää Laatikko-tyyppiä, tulee sisällyttää otsikkotiedosto Laatikko.h. Ohjelmoijan, joka käyttää tätä luokkaa, ei tarvitse päästä käsiksi jäsenfunktioiden määrittelyyn, luokan määrittelevä otsikkotiedosto riittää. Niin kauan kuin luokan määrittely pysyy kiinteänä, voit vapaasti muuttaa jäsenfunktioiden toteutusta ilman, että sillä on merkitystä luokkaa käyttäviin ohjelmiin. Jäsenfunktion määrittely luokan ulkopuolella ei ole tarkalleen sama kuin määrittelyn sijoittaminen luokan sisälle. Funktion määrittelyt, jotka ovat luokan määrittelyn sisällä, ovat automaattisesti avoimia funktioita (tämä ei kuitenkaan tarkoita, että ne toteutettaisiin avoimina funktioina - kääntäjä päättä tämän yhä funktion ominaisuuksien perusteella. Tätä käsittelimme luvussa 8.). Jäsenfunktiot, jotka määritellään luokan ulkopuolella, voivat olla avoimia ainoastaan, jos ne eksplisiittisesti määritellään sellaisiksi. Tämän luvun loput esimerkit olettavat, että olet jakanut Laatikko-luokan.h ja.cpptiedostoiksi, sekä funktioon main(), joka tällä hetkellä näyttää tältä: // Esimerkki 12.2 Luokan muodostinfunktion käyttö #include <iostream> #include "Laatikko.h" using namespace std; 455

C++ Ohjelmoijan käsikirja int main() Laatikko ensimmainenltk(80.0, 50.0, 40.0); // Lasketaan laatikon tilavuus double ensltktilavuus = ensimmainenltk.tilavuus(); cout cout << "Ensimmäisen laatikon koko on " << ensimmainenltk.pituus << " * " << ensimmainenltk.syvyys << " * " << ensimmainenltk.korkeus cout << "Ensimmäisen laatikon tilavuus on " << ensltktilavuus return 0; Oletusmuodostinfunktio Esimerkkimme luokasta, jolla on muodostinfunktio, näyttää varsin suoraviivaiselta, mutta kuten aina, pinnan alla on tärkeitä hienouksia. Kun määrittelemme luokalle muodostinfunktion, muokkaamme luokkaa tavalla, joka ei ole aivan välittömästi selvää. Tarkastellaan nyt tätä. Jokaisella määrittelemälläsi luokalla on vähintään yksi muodostinfunktio, koska luokan olio luodaan aina muodostinfunktiolla. Jos et määrittele luokallesi muodostinfunktiota (kuten esimerkissä 12.1), kääntäjä luo oletusmuodostinfunktion, jota sitten käytetään luokan olioiden luonnissa. Oletusmuodostinfunktiolla ei ole parametrejä. Kun esittelet luokan, kääntäjä luo oletusmuodostinfunktion automaattisesti, mutta vain jos et itse määrittele luokalle muodostinfunktiota. Heti kun lisäät luokallesi muodostinfunktion, kääntäjä olettaa, että oletusmuodostinfunktio on nyt sinun vastuullasi, eikä tee sitä automaattisesti. Seuraava esimerkki selventää, miten oletusmuodostinfunktio voi vaikuttaa koodiisi. Lisätään ohjelmaamme toinen Laatikko-olio, mutta alustetaan se eri tavalla kuin ensimmainenltk. Muuta esimerkin 12.2 main()-funktiota seuraavasti: int main() Laatikko ensimmainenltk(80.0, 50.0, 40.0); // Lasketaan laatikon tilavuus double ensltktilavuus = ensimmainenltk.tilavuus(); cout cout << "Ensimmäisen laatikon koko on " << ensimmainenltk.pituus << " * " << ensimmainenltk.syvyys << " * " << ensimmainenltk.korkeus cout << "Ensimmäisen laatikon tilavuus on " << ensltktilavuus 456

Luokat Laatikko pieniltk; pieniltk.pituus = 10.0; pieniltk.syvyys = 5.0; pieniltk.korkeus = 4.0; // Ei käänny, muodostinfunktio on jo määritelty // Lasketaan pienen laatikon tilavuus cout << "Pienen laatikon koko on " << pieniltk.pituus << " * " << pieniltk.syvyys << " * " << pieniltk.korkeus cout << "Pienen laatikon tilavuus on " << pieniltk.tilavuus() return 0; Uusi koodi yrittää luoda uuden olion pieniltk esittelyllä, jossa muodostinfunktiolle ei välitetä parametrejä. Sen sijaan sijoitamme alkuarvot jäsenmuuttujiin eksplisiittisesti kolmella sijoituslauseella. Tämä ei kuitenkaan käänny. Kääntäjän virheilmoitus viittaa seuraavaan riviin: Laatikko pieniltk; // Ei käänny, muodostinfunktio on jo määritelty Tässä kääntäjä etsii oletusmuodostinfunktiota - eli muodostinfunktiota, jolla ei ole parametrejä. Tämä ohjelma käyttää kuitenkin luokkaa Laatikko, johon on määritelty muodostinfunktio: Laatikko::Laatikko(double pituusarvo, double syvyysarvo, double korkeusarvo) cout << "Laatikon muodostinfunktiota kutsuttu" pituus = pituusarvo; syvyys = syvyysarvo; korkeus = korkeusarvo; Koska olemme esitelleet luokalle muodostinfunktion, kääntäjä ei muodosta oletusmuodostinfunktiota, mikä aiheuttaa virheen. Kääntäjän muodostama oletusmuodostinfunktio ei tee mitään - erityisesti, se ei alusta luotavan olion jäsenmuuttujia. Tämähän on kaikkea muuta kuin toivottavaa: tavoitteenammehan on, että voimme kontrolloida muuttujiemme arvoja, joten jos ajattelet käyttäväsi oletusmuodostinfunktiota, joudut tekemään kuitenkin oman muodostinfunktion. Jotta uusi main()-funktiomme toimii, voimme lisätä oman oletusmuodostinfunktiomme luokan määrittelyyn. Kokeile itse - Oletusmuodostinfunktio Lisätään esimerkkiimme oma oletusmuodostinfunktio. Nyt lisäämme vain koodin, joka rekisteröi, että oletusmuodostinfunktiota on kutsuttu. Palaamme jäsenmuuttujien alustukseen myöhemmin. Seuraavassa on Laatikko.h-otsikkotiedoston uusi versio: 457

C++ Ohjelmoijan käsikirja class Laatikko public: double pituus; double syvyys; double korkeus; // Muodostinfunktiot Laatikko(); //Oletusmuodostinfunktio Laatikko(double pituusarvo, double syvyysarvo, double korkeusarvo); ; // Funktio, joka laskee laatikon tilavuuden double tilavuus(); Meidän tulee lisäksi lisätä oletusmuodostinfunktion määrittely tiedostoon Laatikko.cpp. // Oletusmuodostinfunktion määrittely Laatikko::Laatikko() cout << "Oletusmuodostinfunktiota on kutsuttu" pituus = syvyys = korkeus = 1; // Oletusmitat Voimme nyt käyttää näitä seuraavassa main()-funktion versiossa: // Esimerkki 12.3 Luokan oletusmuodostinfunktion käyttö #include <iostream> #include "Laatikko.h" using namespace std; int main() Laatikko ensimmainenltk(80.0, 50.0, 40.0); // Lasketaan laatikon tilavuus double ensltktilavuus = ensimmainenltk.tilavuus(); cout cout << "Ensimmäisen laatikon koko on " << ensimmainenltk.pituus << " * " << ensimmainenltk.syvyys << " * " << ensimmainenltk.korkeus cout << "Ensimmäisen laatikon tilavuus on " << ensltktilavuus Laatikko pieniltk; pieniltk.pituus = 10.0; pieniltk.syvyys = 5.0; pieniltk.korkeus = 4.0; 458 // Lasketaan pienen laatikon tilavuus cout << "Pienen laatikon koko on " << pieniltk.pituus << " * " << pieniltk.syvyys << " * "

Luokat << pieniltk.korkeus cout << "Pienen laatikon tilavuus on " << pieniltk.tilavuus() return 0; Tulostus näyttää nyt seuraavalta: Laatikon muodostinfunktiota kutsuttu Ensimmäisen laatikon koko on 80 * 50 * 40 Ensimmäisen laatikon tilavuus on 160000 Oletusmuodostinfunktiota on kutsuttu Pienen laatikon koko on 10 * 5 * 4 Pienen laatikon tilavuus on 200 Kuinka se toimii Ohjelman tässä versiossa olemme määritelleet omat muodostinfunktiomme, joten kääntäjä ei tee omaa oletusmuodostinfunktiota. Tästä syystä olemme tehneet oman oletusmuodostinfunkiomme. Kääntäjältä ei tule mitään virheilmoituksia ja kaikki toimii. Ohjelman tulostuksesta näemme, että oletusmuodostinfunktiota kutsutaan pieniltk-olion esittelyssä. Oletusmuodostinfunktiomme alustaa kaikki luomansa olion jäsenmuuttujat: pituus = syvyys = korkeus = 1; // Oletusmitat Voimme nyt käyttää oletusmuodostinfunktiota Laatikko-olion määrittelyssä ja alustuksessa: Laatikko pieniltk; Muodostinfunktion, jolla ei ole parametrejä, tärkeä ominaisuus on se, että sitä voidaan kutsua ilman parametrejä - kutsussa ei tarvita edes sulkeita. Yllä oleva lause määrittelee vain luokan tyypin, laatikko, ja olion nimen, pieniltk. Määrittelemällä oletusmuodostinfunktion, joka asettaa arvot jäsenmuuttujiin, voimme varmistaa, että meillä ei ole laatikoita, joiden jäsenmuuttujat sisältävät roskaa. Kun olemme turvallisesti määritelleet ja alustaneet pieniltk-olion, voimme muuttaa mitat haluamiksemme: pieniltk.pituus = 10.0; pieniltk.syvyys = 5.0; pieniltk.korkeus = 4.0; Eräs tämän esimerkin asia on saattanut jäädä sinulta kaiken innostuksen alta huomaamatta: olemme määritelleet muodostinfunktion uudelleen samaan tapaan kuin määrittelimme funktioita uudelleen luvussa 9. Luokalla Laatikko on kaksi muodostinfunktiota, jotka eroavat toisistaan vain parametriluettelon perusteella. Toisessa on kolme double-tyyppistä parametriä ja toisessa ei ole lainkaan parametrejä. 459

C++ Ohjelmoijan käsikirja Oletusarvoiset alkuarvot Kun käsittelimme C++:n tavallisia funktioita, näimme miten funktion parametreille voidaan määritellä oletusarvoja funktion prototyypissä. Voimme tehdä tämän myös luokan jäsenfunktioille, muodostinfunktiot mukaan lukien. Jos sijoitamme jäsenfunktion määrittelyn luokan määrittelyn sisälle, voimme sijoittaa parametrien oletusarvot funktion otsikkoon. Jos kirjoitamme ainoastaan funktioiden esittelyt luokan määrittelyyn, parametrien oletusarvojen tulee olla funktion esittelyssä, ei funktion määrittelyssä. Olemme päättäneet oletusmuodostinfunktiossamme, että Laatikko-olion oletuskoko on kuutio, jonka jokaisen sivun pituus on 1. Nyt voimme muuttaa edellisen esimerkin luokan määrittelyn seuraavanlaiseksi: class Laatikko public: double pituus; double syvyys; double korkeus; // Muodostinfunktiot Laatikko(); //Oletusmuodostinfunktio Laatikko(double pituusarvo = 1.0, double syvyysarvo = 1.0, double korkeusarvo = 1.0); ; // Funktio, joka laskee laatikon tilavuuden double tilavuus(); Jos teemme tämän muutoksen edelliseen esimerkkiin, mitä tapahtuu? Saamme tietysti kääntäjän virheilmoituksen! Kääntäjä antaa virheen, että olemme määritelleet oletusmuodostinfunktion useampaan kertaan. main()-funktion koodi saa aikaan virheen, koska meillä on moniselitteinen uudelleenmääritellyn funktion kutsu: Laatikko pieniltk; Syy sekaannukseen on siinä, että tämä lause on kummankin muodostinfunktion sallittu kutsu. Tällaista muodostinfunktion, jolla on parametrien oletusarvot, kutsua ei voida erottaa oletusmuodostinfunktion kutsusta. Koska emme määrittele yhtäkään parametriä, kääntäjä ei pysty päättämään kumpaa funktiota tarkoitetaan. Toisin sanoen, muodostinfunktio, jolla on oletusarvot, toimii myöskin oletusmuodostinfunktiona. Luonnollinen ratkaisu tähän on poistaa muodostinfunktio, jolla ei ole lainkaan parametrejä. Jos teet näin, kaikki kääntyy ja toimii OK. Älä kuitenkaan oleta, että tämä on aina paras tapa toteuttaa oletusmuodostinfunktio. On olemassa monia tilanteita, joissa et halua asettaa oletusarvoja tällä tavalla. Tällöin sinun tulee kirjoittaa erillinen oletusmuodostinfunktio. On myös tilanteita, joissa et halua käyttää oletusmuodostinfunktiota lainkaan, vaikka olisit määritellyt muitakin muodostinfunktioita. Näin voit varmistaa, että kaikki luokan oliot on alustettu niiden esittelyssä eksplisiittisesti. 460

Alkuarvolistan käyttö muodostinfunktiossa Luokat Tähän saakka olemme alustaneet olion jäsenet luokan muodostinfunktiossa eksplisiittisellä sijoituksella. Käytössä on kuitenkin toinenkin tekniikka, jossa käytetään alkuarvolistaa. Voimme havainnollistaa tätä luokan Laatikko vaihtoehtoisella muodostinfunktiolla: // Muodostinfunktion määrittely alkuarvolistan avulla Laatikko::Laatikko(double parvo, double sarvo, double karvo) : pituus(parvo), syvyys(sarvo), korkeus(karvo) cout << "Laatikon muodostinfunktiota kutsuttu" Nyt arvoja ei sijoiteta jäsenmuuttujiin muodostinfunktion rungossa olevilla sijoituslauseilla. Ne määritellään funktiomuodossa ja ovat alkuarvolistassa osana funktion otsikkoa. Jäsen pituus alustetaan esimerkiksi arvolla parvo. Huomaa, että muodostinfunktion alkuarvolista erotetaan parametriluettelosta kaksoispisteellä ja alkuarvot erotellaan toisistaan pilkuilla. Tämä on itse asiassa paljon muutakin kuin pelkkä uusi merkintätapa. Alustuksen suorituksessa on tässä tärkeä ero. Kun alustat jäsenmuuttujan sijoituslauseella muodostinfunktion rungossa, jäsenmuuttuja luodaan ensin (kutsumalla muodostinfunktiota, jos se on luokan ilmentymä) ja sen jälkeen sijoitus suoritetaan omana operaationaan. Kun käytät alkuarvolistaa, alkuarvot sijoitetaan jäsenmuuttujiin luonnin yhteydessä. Tämä voi olla huomattavasti tehokkaampaa kuin sijoitus muodostinfunktion rungossa, varsinkin jos jäsenmuuttuja on luokan ilmentymä. Jos muutat edellisen esimerkin muodostinfunktion tällaiseksi, huomaat, että se toimii aivan samalla tavalla. Tämä tekniikka on tärkeä toisestakin syystä. Kuten tulemme huomaamaan, se on ainut tapa alustaa tietyntyyppiset jäsenmuuttujat. explicit-avainsanan käyttö Yksiparametristen muodostinfunktioiden yhteydessä piilee vaara, koska kääntäjä voi käyttää tällaista muodostinfunktiota automaattisena tyypinmuunnoksena parametrin tyypistä luokan tyyppiin. Tämä saattaa joissain tilanteissa saada aikaan epätoivottuja tuloksia. Katsotaan yhtä tällaista tilannetta esimerkin avulla. Oletetaan, että määrittelemme luokan, joka kuvaa kuutiota, jonka kaikki sivut ovat yhtä pitkiä: class Kuutio public: double sivu; ; Kuutio(double sivu); double tilavuus(); bool vertaatil(kuutio akuutio); // Muodostinfunktio // Laskee kuution tilavuuden // Vertaa kuution tilavuutta toiseen 461

C++ Ohjelmoijan käsikirja Voimme määritellä muodostinfunktion seuraavasti: Kuutio::Kuutio(double sivu) : pituus(sivu) Kuution tilavuuden laskeva funktio määritellään seuraavasti: double Kuutio::tilavuus() return sivu * sivu * sivu; Lopuksi määrittelemme jäsenfunktion vertaatil() seuraavasti: bool Kuutio::vertaaTil(Kuutio akuutio) return tilavuus() > akuutio.tilavuus(); Muodostinfunktio tarvitsee vain yhden double-tyyppisen parametrin. Kääntäjä voi selvästikin muuntaa muodostinfunktion avulla double-tyyppisen arvon Kuutio-tyypiksi, mutta millaisissa tilanteissa tämä voisi tapahtua? Tarkastellaan Kuutio-luokkaa lisää. Luokassa määritellään myös funktio tilavuus() ja funktio vertaatil(), joka vertaa nykyistä oliota parametrinään saamaan Kuutio-tyyppiseen olioon ja palauttaa arvon true, jos nykyisen olion tilavuus on suurempi. Voisit käyttää sitä esimerkiksi seuraavalla tavalla: Kuutio ltk1(5.0); Kuutio ltk2(3.0); if(ltk1.vertaatil(ltk2)) cout << endl << "ltk1 on suurempi"; else cout << endl << "ltk1 ei ole suurempi"; Tämä on vielä varsin suoraviivaista, mutta mitä tapahtuu, jos joku tätä luokkaa käyttävä kirjoittaa seuraavaa: if(ltk1.vertaatil(50.0)) cout << endl << "ltk1:n tilavuus on suurempi kuin 50"; else cout << endl << "ltk1:n tilavuus ei ole suurempi kuin 50"; Henkilö, joka on kirjoittanut tällaisen koodin, on ymmärtänyt väärin vertaatil()-funktion ja luulee, että se vertaa nykyistä oliota lukuun 50.0. Kääntäjä tietää, että vertaatil()-funktion parametrin tulee olla Kuutio-olio, mutta se kääntää tämän tyytyväisenä, koska käytössä on muodostinfunktio, joka muuntaa parametrin 50.0 Kuutio-olioksi. Kääntäjän tuottama koodi vastaa seuraavaa koodia: if(ltk1.vertaatil(kuutio(50.0))) cout << endl << "ltk1:n tilavuus on suurempi kuin 50"; else cout << endl << "ltk1:n tilavuus ei ole suurempi kuin 50"; 462 Funktio ei vertaa ltk1:n tilavuutta arvoon 50.0 vaan arvoon 125000.0, joka on olion Kuutio(50.0) tilavuus! Tulos on kaukana halutusta. Voit kuitenkin estää tämän painajaisen määrittelemällä muodostinfunktion avainsanalla explicit:

Luokat class Kuutio public: double sivu; ; explicit Kuutio(double sivu); double tilavuus(); bool vertaatil(kuutio akuutio); // Muodostinfunktio // Laskee kuution tilavuuden // Vertaa kuution tilavuutta toiseen Kääntäjä ei koskaan käytä eksplisiittiseksi määriteltyä muodostinfunktiota automaattisessa tyypinmuunnoksessa. Sitä voidaan käyttää ainoastaan ohjelman koodissa muodostettaessa olio eksplisiittisesti. Koska automaattista tyypinmuunnosta käytetään määritelmän mukaan muunnettaessa annettu tyyppi toiseksi tyypiksi, explicit-avainsanaa tarvitsee käyttää ainoastaan yksiparametristen muodostinfunktioiden yhteydessä. Luokan yksityiset jäsenet Muodostinfunktion yksi tärkeä tehtävä on varmistaa, että olion kaikkiin jäsenmuuttujiin sijoitetaan tarkoituksenmukaiset arvot. Voit esimerkiksi varmistaa, että kaikki Laatikko-olion mitat ovat positiivisia lisäämällä muodostinfunktioon muutamia tarkistuksia: Laatikko::Laatikko(double parvo, double sarvo, double karvo) : pituus(parvo), syvyys(sarvo), korkeus(karvo) cout << "Laatikon muodostinfunktiota kutsuttu." // Varmistetaan positiiviset mitat if(pituus <= 0.0) pituus = 1.0; if(syvyys <= 0.0) syvyys = 1.0; if(korkeus <= 0.0) korkeus = 1.0; Nyt voimme olla varmoja, että riippumatta muodostinfunktiolle välitetyistä parametreistä, Laatikko-olion mitat ovat aina sallitut. Tietysti voisi olla järkevää tulostaa ilmoitus, että tällainen muutos on tehty. Ongelmamme on nyt, että vaikka olemme varmistaneet jäsenmuuttujien alustuksen oikeellisuuden, mikään ei estä niiden muuttamista luokan ulkopuolelta esimerkiksi seuraavilla lauseilla: Laatikko ltk(10.0, 10.0, 5.0); ltk.pituus = -20.0 //Sijoitetaan kielletty laatikon mitta Voimme tehdä tämän, koska jäsenmuuttujat on määritelty avainsanalla public. Voit estää tällaisen kätkemällä jäsenmuuttujat, mikä tapahtuu yksinkertaisesti määrittelemällä luokan jäsenmuuttujat avainsanalla private. Luokan yksityisiin jäseniin päästään (yleensä) käsiksi vain luokan jäsenmuuttujista. Tavallinen funktio, joka ei ole annetun luokan jäsen, ei voi suoraan käyttää tämän luokan yksityisiä jäseniä. Tämä havainnollistetaan seuraavassa kaaviossa: 463

C++ Ohjelmoijan käsikirja Luokan olio Tavallinen funktio, joka ei ole luokan jäsen Ei pääsyä OK OK Julkiset jäsenmuuttujat Julkiset jäsenfunktiot Yksityiset jäsenmuuttujat Yksityiset jäsenfunktiot Käsittely luokan jäsenistä on aina OK. Ei pääsyä Yleisesti ottaen on hyvän olio-ohjelmointitavan mukaista pitää jäsenmuuttujat yksityisinä niin kauan kuin mahdollista. Olio-ohjelmoinnin koko ideahan on loppujen lopuksi siinä, että ohjelmat tehdään olioiden ehdoilla ilman että käsitellään suoraan niiden sisältöä. Luokan julkisia jäseniä, joita tavallisesti ovat funktiot, kutsutaan joskus yleisellä termillä luokan rajapinta. Luokan rajapinnan avulla voit muokata ja käsitellä luokan olioita, joten se määrittelee, mitä voit oliolla tehdä ja mitä olio voi tehdä puolestasi. Pitämällä luokan sisällön yksityisenä, voit myöhemmin muuttaa sitä haluamallasi tavalla välittämättä luokkaa käyttävästä koodista, koska koodi käyttää luokkaa julkisen rajapinnan kautta. Eli luokan julkinen rajapinta on erotettu luokan toteutuksesta. Kokeile itse - Yksityiset jäsenmuuttujat Voimme kirjoittaa Laatikko-luokan jälleen uudelleen. Määritellään jäsenmuuttujat nyt yksityisiksi ja katsotaan, miten luokkaa voidaan nyt käyttää. Tässä ovat muutokset otsikkotiedostoon Laatikko.h: // Laatikko.h #ifndef LAATIKKO_H #define LAATIKKO_H class Laatikko public: // Muodostinfunktio Laatikko(double pituusarvo = 1.0, double syvyysarvo = 1.0, double korkeusarvo = 1.0); // Funktio, joka laskee laatikon tilavuuden double tilavuus(); 464

Luokat private: double pituus; double syvyys; double korkeus; ; #endif Voimme myöskin lisätä mittojen tarkistuksen koodiin tiedostossa Laatikko.cpp: // Laatikko.cpp #include <iostream> #include "Laatikko.h" using namespace std; Laatikko::Laatikko(double parvo, double sarvo, double karvo) : pituus(parvo), syvyys(sarvo), korkeus(karvo) cout << "Laatikon muodostinfunktiota kutsuttu." // Varmistetaan positiiviset mitat if(pituus <= 0.0) pituus = 1.0; if(syvyys <= 0.0) syvyys = 1.0; if(korkeus <= 0.0) korkeus = 1.0; // Funktio, joka laskee laatikon tilavuuden double Laatikko::tilavuus() return pituus * syvyys * korkeus; Se, että jäsenfunktioiden määrittelyt ovat luokan ulkopuolella, ei vaikuta luokan jäseniin käsiksi pääsemiseen. Kaikki luokan jäsenet ovat käytettävissä jäsenfunktion rungosta, riippumatta siitä, missä funktion määrittely on. Uuden Laatikko-luokkamme testausta varten voimme kirjoittaa uuden main()-funktion: // Esimerkki 12.4 - Luokan yksityisten jäsenten käyttö #include <iostream> #include "Laatikko.h" using namespace std; int main() cout Laatikko ensimmainenltk(2.2, 1.1, 0.5); Laatikko toinenltk; Laatikko* pkolmasltk = new Laatikko(15.0, 20.0, 8.0); 465

C++ Ohjelmoijan käsikirja cout << "Ensimmäisen laatikon tilavuus = " << ensimmainenltk.tilavuus() // toinenltk.pituus = 4.0; // Poista kommentointi ja saat virheen cout << "Toisen laatikon tilavuus = " << toinenltk.tilavuus() cout << "Kolmannen laatikon tilavuus = " << pkolmasltk->tilavuus() delete pkolmasltk; return 0; Ohjelman tulostus näyttää seuraavalta: Laatikon muodostinfunktiota kutsuttu. Laatikon muodostinfunktiota kutsuttu. Laatikon muodostinfunktiota kutsuttu. Ensimmäisen latikon tilavuus on 1.21 Toisen laatikon tilavuus on 1 Kolmannen laatikon tilavuus on 2400 Kuinka se toimii Käytämme public-avainsanaa aloittamaan julkisen osan, jossa meillä ovat jäsenfunktioiden esittelyt: muodostinfunktio ja funktio tilavuus(). Luokan julkisten jäsenten sijoittaminen ennen yksityisiä jäseniä on tarkkaan harkittu - luokan julkiset jäsenet kiinnostavat koodia selaavia enemmän, koska niihin pääsee käsiksi luokan ulkopuolelta. Luokan Laatikko määrittelyssä esitellään kaikki jäsenmuuttujat yksityisiksi käyttämällä privateavainsanaa. Kaikki esittelyt tästä eteenpäin aina seuraavaan saantitavan hallintakomentoon saakka ovat yksityisiä ja niihin ei pääse käsiksi luokan ulkopuolelta. Tätä on tiedon kätkentä käytännössä. Jos poistat kommentoinnin seuraavasta main()-funktion lauseesta, // toinenltk.pituus = 4.0; // Poista kommentointi ja saat virheen koodi ei enää käänny, koska luokan yksityistä jäsenmuuttujaa ei voi käsitellä luokan ulkopuolelta. Ainoa tapa saada arvo Laatikko-olion jäsenmuuttujaan on muodostinfunktion tai jäsenfunktion käyttö. On omalla vastuullasi, että kaikki tavat, joilla haluat yksityisiä jäsenmuuttujia käsitellä, on toteutettu jäsenfunktiolla. 466 Voimme sijoittaa myös funktioita luokan private-osaan, jolloin niitä voidaan kutsua vain toisista jäsenfunktioista. Jos sijoitat funktion tilavuus() private-osaan, lauseet, jotka main()-funktiossa kutsuvat sitä, saavat aikaan kääntäjän virheilmoituksen. Jos sijoitat muodostinfunktion privateosaan, et voi esitellä kyseisen luokan olioita lainkaan.

On monia tilanteita, joissa haluat tehdä funktioista yksityisiä. Saatat esimerkiksi tarvita apufunktiota, jota tarvitaan tietyn toiminnan suorittamiseen luokan muissa jäsenfunktioissa, mutta jota ei tarvita luokan ulkopuolella. Palataan takaisin main()-funktioon, jossa ensin esittelemme kaksi Laatikko-oliota seuraavilla lauseilla: Laatikko ensimmainenltk(2.2, 1.1, 0.5); Laatikko toinenltk; Luokat Kumpikin näistä kutsuu samaa muodostinfunktiota (no, eihän niitä olekaan kuin yksi), mutta toinenltk-olion esittely käyttää hyväksi parametrin oletusarvoa, jolloin toinenltk on kuutio, jonka sivujen pituus on 1.0. Seuraava lause esittelee osoittimen Laatikko-olioon, pkolmasltk, ja luo olion vapaasta muistista new-operaattorilla: Laatikko* pkolmasltk = new Laatikko(15.0, 20.0, 8.0); Operaattori new kutsuu samaa Laatikko-muodostinfunktiota, kuten voit tulostuksesta nähdä. Operaattorin palauttama osoite talletetaan muuttujaan pkolmasltk. Nyt tulostamme kaikkien Laatikko-olioiden tilavuudet. Dynaamisesti luomamme olion kohdalla käytämme jäseneen viittaus -operaattoria kutsuessamme funktiota tilavuus(): cout << "Kolmannen laatikon tilavuus = " << pkolmasltk->tilavuus() Tämä esimerkki havainnollisti, että luokka toimii yhä tyydyttävästi, vaikka sen jäsenmuuttujat on määritelty avainsanalla private. Suurin ero on, että ne ovat nyt kokonaan suojassa ulkopuolisilta. Koska kaikki jäsenmuuttujat on kätketty, niitä voi käyttää vain luokan julkisten jäsenfunktioiden kautta. Meidän Laatikko-luokkamme kohdalla tämä tarkoittaa vain yhtä funktiota: muodostinfunktiota. Yksityisten jäsenten käyttö Toisaalta, luokan jäsenmuuttujien määrittely yksityiseksi on varsin voimakasta. On hyvä suojata ne tarpeettomalta käsittelyltä, mutta olemme jo käsitelleet tästä seuraavaa rajoitetta: jos emme tiedä tietyn Laatikko-olion mittoja, emme saa niitä mitenkään selville. Ei sen itse asiassa näin salaista tarvitsisi olla? Älä huolestu: meidän ei tarvitse palata taaksepäin ja esitellä jäsenmuuttujia public-avainsanalla. Voimme poistaa ongelman lisäämällä luokkaan jäsenfunktion, joka palauttaa jäsenmuuttujan arvon. Pääsemme käsiksi Laatikko-olion mittoihin lisäämällä kolme funktiota luokan määrittelyyn: class Laatikko public: 467

C++ Ohjelmoijan käsikirja // Muodostinfunktio Laatikko(double pituusarvo = 1.0, double syvyysarvo = 1.0, double korkeusarvo = 1.0); // Funktio, joka laskee laatikon tilavuuden double tilavuus(); //Funktiot, jotka palauttavat jäsenmuuttujien arvot double luepituus() return pituus; double luesyvyys() return syvyys; double luekorkeus() return korkeus; private: double pituus; double syvyys; double korkeus; ; Olemme lisänneet luokkaan funktiot, jotka palauttavat jäsenmuuttujien arvot. Tämä tarkoittaa, että jäsenmuuttujien arvot voidaan vapaasti lukea, mutta niitä ei voida muuttaa, joten niiden eheys säilyy ilman, että ne pidettäisiin salaisina. Tämän tyyppisten funktioiden määrittely on yleensä luokan määrittelyssä, koska ne ovat lyhyitä, ja tämä tekee niistä oletusarvoisesti avoimia funktioita. Näin ollen myös ylimääräinen työ arvojen lukemiseksi on mahdollisimman pieni. Funktioita, joita käytetään jäsenmuuttujien arvon lukemiseen, kutsutaan yleensä saantifunktioiksi. Olisimme voineet käyttää näitä saantifunktioita edellisessä esimerkissäkin, kun tulostimme dynaamisesti luomamme Laatikko-olion mitat: cout << Laatikon koko on << pkolmasltk->luepituus() << * << pkolmasltk->luesyvyys() << * << pkolmasltk->luekorkeus() << * <<endl; Voit käyttää tätä tapaa minkä tahansa luokan kanssa. Riittää kun teet funktion jokaiselle jäsenmuuttujalle, jonka haluat näkyä luokan ulkopuolelle, ja niiden arvoja voidaan käyttää ilman, että luokan turvallisuus kärsii. Jos sijoitat näiden funktioiden määrittelyt luokan määrittelyn ulkopuolelle, sinun tulisi määritellä ne inline-avainsanalla. Jos esimerkiksi olisimme pelkästään esitelleet luepituus()-funktion luokan määrittelyssä, meidän tulisi määritellä se tiedostossa Laatikko.cpp seuraavasti: inline double Laatikko::luePituus() return pituus; Saattaa kuitenkin olla tilanteita, joissa haluat, että jäsenmuuttujia voidaan muuttaa luokan ulkopuolelta. Jos teet jäsenfunktion tällaiseen tarkoitukseen sen sijaan, että jäsenmuuttujia käsiteltäisiin suoraan, voidaan samalla suorittaa arvojen oikeellisuustarkistuksia. Voit esimerkiksi lisätä funktion, jolla muutetaan Laatikko-olion korkeutta: 468 class Laatikko public: // Muodostinfunktio Laatikko(double pituusarvo = 1.0, double syvyysarvo = 1.0, double korkeusarvo = 1.0);