Olio-ohjelmointi Olioperusteinen ohjelmointi C++-kielellä. 1. Johdanto

Samankaltaiset tiedostot
812347A Olio-ohjelmointi, 2015 syksy 2. vsk. III Olioperusteinen ohjelmointi C++:lla

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

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

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

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

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

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

Olio-ohjelmointi Javalla

Luokan muodostimet (Constructors)

815338A Ohjelmointikielten periaatteet Harjoitus 5 Vastaukset

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

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

Javan perusteita. Janne Käki

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

Oliot viestivät metodeja kutsuen

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

A) on käytännöllinen ohjelmointitekniikka. = laajennetaan aikaisemmin tehtyjä luokkia (uudelleenkäytettävyys)

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

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

Olio-ohjelmointi Syntaksikokoelma

UML ja luokkien väliset suhteet

7. Oliot ja viitteet 7.1

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

1. Omat operaatiot 1.1

9. Periytyminen Javassa 9.1

812341A Olio-ohjelmointi Peruskäsitteet jatkoa

Osoitin ja viittaus C++:ssa

815338A Ohjelmointikielten periaatteet

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

4. Olio-ohjelmoinista lyhyesti 4.1

Taulukot. Jukka Harju, Jukka Juslin

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

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

UNIVERSITY OF OULU DEPARTMENT OF INFORMATION PROCESSING SCIENCE

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

YHTEYSSUHDE (assosiation)

Java-kielen perusteet

19. Olio-ohjelmointia Javalla 19.1

812336A C++ -kielen perusteet,

15. Ohjelmoinnin tekniikkaa 15.1

12 Mallit (Templates)

Osa III. Olioiden luominen vapaalle muistialueelle

Kooste. Esim. Ympyrän keskipiste voidaan ajatella ympyrän osaksi.

Sisällys. JAVA-OHJELMOINTI Osa 7: Abstrakti luokka ja rajapinta. Abstraktin luokan idea. Abstrakti luokka ja metodi. Esimerkki

Sisällys. JAVA-OHJELMOINTI Osa 6: Periytyminen ja näkyvyys. Luokkahierarkia. Periytyminen (inheritance)

2. Olio-ohjelmoinista lyhyesti 2.1

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

KOHDELUOKAN MÄÄRITTELY

9. Periytyminen Javassa 9.1

Mikä yhteyssuhde on?

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

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

Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op Pakkaukset ja määreet

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

812347A Olio-ohjelmointi, 2015 syksy 2. vsk. IX Suunnittelumallit Proxy, Factory Method, Prototype ja Singleton

Tietueet. Tietueiden määrittely

Sisällys. 19. Olio-ohjelmointia Javalla. Yleistä. Olioiden esittely ja alustus

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

Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op

812347A Olio-ohjelmointi, 2015 syksy 2. vsk. IV Periytyminen ja monimuotoisuus

1 Tehtävän kuvaus ja analysointi

Olio-ohjelmointi Suunnittelumallit Proxy, Factory Method, Prototype ja Singleton. 1. Proxy (Edustaja)

Periytyminen (inheritance)

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

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

Geneeriset luokat. C++ - perusteet Java-osaajille luento 6/7: Template, tyyppi-informaatio, nimiavaruudet. Geneerisen luokan käyttö.

ITKP102 Ohjelmointi 1 (6 op)

15. Ohjelmoinnin tekniikkaa 15.1

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

812347A Olio-ohjelmointi, 2015 syksy 2. vsk. VII Suunnittelumallit Adapter ja Composite

Aalto Yliopisto T Informaatioverkostot: Studio 1. Oliot ja luokat Javaohjelmoinnissa

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

ITKP102 Ohjelmointi 1 (6 op)

Lyhyt kertaus osoittimista

13 Operaattoreiden ylimäärittelyjä

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

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

Loppukurssin järjestelyt C:n edistyneet piirteet

Loppukurssin järjestelyt

Merkkijono määritellään kuten muutkin taulukot, mutta tilaa on varattava yksi ylimääräinen paikka lopetusmerkille:

Metodien tekeminen Javalla

Java-kielen perusteet

Ohjelmoinnin perusteet Y Python

Java kahdessa tunnissa. Jyry Suvilehto

Rajapinnasta ei voida muodostaa olioita. Voidaan käyttää tunnuksen tyyppinä. Rajapinta on kuitenkin abstraktia luokkaa selvästi abstraktimpi tyyppi.

ITKP102 Ohjelmointi 1 (6 op)

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

ITKP102 Ohjelmointi 1 (6 op)

Concurrency - Rinnakkaisuus. Group: 9 Joni Laine Juho Vähätalo

Sisällys. 15. Lohkot. Lohkot. Lohkot

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

11/20: Konepelti auki

Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op Rajapinnat ja sisäluokat

Lohkot. if (ehto1) { if (ehto2) { lause 1;... lause n; } } else { lause 1;... lause m; } 15.3

Sisällys. 11. Rajapinnat. Johdanto. Johdanto

P e d a c o d e ohjelmointikoulutus verkossa

public static void main (String [] args)

Periytyminen. Luokat ja olio-ohjelmointi

Ohjelmoinnin perusteet Y Python

Transkriptio:

Olio-ohjelmointi Olioperusteinen ohjelmointi C++-kielellä Tässä osassa käsitellään olioperusteista (object-based) ohjelmointia, ts. olio-ohjelmointia ilman periytymisen hyödyntämistä. Olio-ohjelmoinnin peruskäsitteitä käsitellään myös rinnakkaisella kurssilla Oliosuuntautunut analyysi ja suunnittelu. Tässä keskitytään pääasiassa olioperusteisen ohjelman toteuttamiseen C++ - kielellä. Materiaali pohjautuu enimmäkseen opetusmonisteen [CppTut] lukuun 2 ja kurssin C++-kielen perusteet luentomateriaaliin. Päivi Hietasen kirjassa [Hie] asiaa käsitellään osassa IV. Teoksen [Bud] luvuissa 4-7 perehdytään olioperusteiseen ohjelmointiin yleisesti käyttäen muitakin kieliä esimerkkeinä. 1. Johdanto Olio-ohjelmoinnissa kapseloidaan tiedot ja sitä käsittelevät funktiot yhdeksi kokonaisuudeksi, jota kutsutaan luokaksi. Luokka määrittelee siis tietotyypin. Valmista luokkaa voidaan hyödyntää käyttämällä sen palveluita luokan ilmentymien eli olioiden avulla. Luokan määrittely kertoo, mitä tietojäseniä sen olioilla on. Luokan metodit määrittelevät puolestaan toimenpiteet, joita oliolle voi tehdä. Samasta luokasta voidaan luoda useita olioita samoin kuin perustietotyyppien muuttujia voi ohjelmassa olla monia kerrallaan. Tietojen kätkemisellä eli piilottamisella tarkoitetaan sitä, että luokissa määriteltyjä tietoja voidaan käsitellä ainoastaan kyseisessä luokissa määritellyillä metodeilla. Toteutustavan piilotuksella tarkoitetaan taas sitä, että luokka piilottaa sisälleen palveluidensa toteutustavan metodeina. Luokkaa käyttävä ulkopuolinen koodi näkee palvelun ainoastaan nimitasolla. Luokka erottaa täten palvelun liittymän ja toteutustavan. C++:n oliot ovat muuttujina samanlaisia kuin sisäisten tyyppien muuttuja. Siten olio voi olla eliniältään 1. Automaattinen, jolloin kääntäjä huolehtii sen alustamisesta ja tuhoamisesta ilman ohjelmoijan erillisiä toimenpiteitä. 2. Staattinen, jolloin muuttuja on määrittelyn jälkeen olemassa koko ohjelman suorituksen ajan. 3. (Keko)dynaaminen, jolloin ohjelmoija varaa muuttujan kekomuistista. Tällöin muuttujan tuhoaminen on myös ohjelmoijan vastuulla. Tässä suhteessa C++ poikkeaa Java-kielestä, jossa kaikki oliomuuttujat ovat kekodynaamisia. Javan automaattisen roskienkeruun ansiosta ohjelmoijan ei kuitenkaan tarvitse itse vapauttaa olioiden muistia kuten C++-kielessä. Olemassa olevista luokista voidaan määritellä uusia luokkia koostamalla tai periyttämällä. Tässä osassa ei vielä käsitellä periytymistä. 1

2. Luokan määrittely ja jäsenten näkyvyys C++-kielessä luokka määritellään yleisimmin avainsanalla class; myös avainsanat struct ja union määrittelevät luokat, joiden oletusominaisuudet poikkeavat hieman class-avainsanalla määritellystä luokasta. Tässä käytetään aina luokan määrittelyssä avainsanaa class. Luokkamääritys koostuu mainitusta avainsanasta, luokan nimestä ja puolipisteeseen päättyvästä lohkosta, jonka sisällä esitellään luokan attribuutit ja metodit. Luokan määrittely on siis syntaktisesti seuraavan muotoinen: class LuokkaNimi attribuuttien esittely metodien esittely ; // HUOMAA PUOLIPISTE! Yleensä C++-ohjelmassa luokan määrittely on omassa otsikkotiedostossaan; näin on aina oltava, mikäli luokkaa halutaan käyttää useammasta moduulista. Luokan metodit toteutetaan yleensä luokan määrittelystä erillään kooditiedostossa. Luokka muodostaa oman nimiavaruutensa, joten samannimisiä jäseniä voidaan käyttää eri luokissa. Luokan jäsenten näkyvyys C++-kielessä voi olla kolmea tyyppiä: private, protected ja public. Private-tyyppiset jäsenet näkyvät ainoastaan luokan sisällä, protected-tyyppiset luokassa ja sen aliluokissa sekä public-tyyppiset kaikkialla, myös luokan ulkopuolella. Public-tyyppiset jäsenet muodostavat luokan käyttöliittymän, joka hyvän suunnittelutavan mukaisesti tulisi pitää mahdollisimman suppeana. C++:ssa oletusnäkyvyytenä on private, toisin kuin Java-kielessä, jossa oletusnäkyvyys on oma tyyppinsä, ns. pakkausnäkyvyys. Oletetaan, että ohjelmassa tarvittaisiin luokka, jonka oliot kuvaisivat tason pisteitä. Luokkaan on sisällytettävä jäsenmuuttujat pisteen x- ja y-koordinaateille. Nämä määritellään privatetyyppisiksi, jotta niitä ei voi muutella tahattomasti luokan ulkopuolelta. Mikäli luokkaan tarvittaisiin toiminto, joka tulostaa pisteen, se toteutettaisiin luokan julkisena metodina. Tällaisen luokan luokkakaavio olisi seuraavan kaltainen: Kuva. Tason pistettä kuvaavavan luokan luokkakaavio. Seuraavaksi esitetään edellä mainitun luokan C++-kielinen määrittely, joka on sijoitettu otsikkotiedostoon nimeltä piste.h. Luokka sisältää siis kaksi private-tyyppistä attribuuttia sekä yhden julkisen metodin esittelyn. Attribuuttien tietotyyppi on double, jolla esitetään C++kielessä desimaalilukuja. Huomaa myös tiedoston alussa ja lopussa sijaitsevat #-merkillä alkavat esikääntäjän käskyt. Ne muodostavat ns. include-vahdin, joka estää otsikkotiedoston useammankertaisen sisällyttämisen käännösyksikköön. Include-vahtia on syytä käyttää aina otsikkotiedostoissa. 2

// Tiedosto piste.h #ifndef PISTE_H_INCLUDED #define PISTE_H_INCLUDED class Piste private: double x_coord; double y_coord; ; public: void tulosta(); #endif Kaikki luokan määrittelyssä esitellyt metodit on toteutettava ja kaikille luokkaan toteutetuille metodeille on löydyttävä esittely luokasta. Luokan määrittely kertoo yleensä ainoastaan sen, mitä palveluita luokan oliot tarjoavat. Metodin määritys kertoo sen sijaan palvelun toteutustavan. Kuten yllä mainittiin, metodit toteutetaan tavallisesti erillään luokan määrittelystä. Luokan metodin syntaksi on seuraava: paluutyyppi LuokkaNimi::metodinNimi(parametrit) metodin runko Huomaa, että metodin määrittelyssä täytyy näkyä metodin sisältävän luokan nimi, koska eri luokissa voi olla samannimisiä metodeja. Alla on toteutettu Piste-luokan (toistaiseksi) ainoa metodi kooditiedostoon piste.cpp. // Tiedosto piste.cpp #include "piste.h" void Piste::tulosta() std::cout << "(" << x_coord << "," << y_coord << ")" << std::endl; Metodia voitaisiin käyttää seuraavasti pääohjelmassa // Tiedosto piste_main.cpp #include "piste.h" int main(int argc, char** argv) Piste p3; // Luodaan Piste-luokan olio p3.tulosta(); // Kutsutaan olion tulosta-metodia return 0; 3

Aluksi ohjelmassa luodaan Piste-luokan olio nimeltään p3. Tämän jälkeen kutsutaan olion metodia tulosta, joka tulostaa attribuuttien arvot konsolille. Koska luokan attribuuteille ei kuitenkaan aseteta arvoja missään vaiheessa, tulostettavat arvot ovat satunnaisia ja tulostus voi olla esimerkiksi (1.79193e-307,1.7921e-307). 3. Olion luominen ja tuhoaminen Olioihin voi kohdistua seuraavia automaattisia toimintoja: tilanvaraus, alustus, kopiointi, sijoitus, tyhjennys ja tuhoaminen. Tilanvarauksessa varataan tietokoneen muistista riittävä tila, jotta olion tietojäsenet voidaan tallentaa muistiin. Olion tyhjennys tarkoittaa sen tietojäsenten tyhjentämistä ja tuhoaminen olion tilanvarauksen purkamista. Termiä tyhjennys käytetään kuitenkin varsin harvoin. Alustuksessa annetaan olion tietojäsenille alkuarvot. Automaattinen toiminto voi aktivoitua järjestelmän käynnistämänä tai ohjelmoijan tarkoituksellisesti käynnistämänä. Muut olioihin kohdistuvat toiminnot liittyvät usein olion tilan eli olion attribuuttien tutkimiseen tai muuttamiseen. Kun olio luodaan, sille varataan muistista tila; olion tilanvaraus- ja tilanvapautushetki riippuu olion eliniän tyypistä. Tämän jälkeen olio alustetaan, mihin liittyvät toimenpiteet tehdään erityisessä tätä tarkoitusta varten kirjoitetussa metodissa, jota kutsutaan muodostimeksi eli konstruktoriksi (constructor). Yleensä muodostimessa annetaan ainakin olion attribuuteille tarkoituksenmukaiset arvot. Luokalla voi olla useita ylikuormitettuja muodostimia eri käyttötilanteita varten. Jokaista oliota luotaessa kutsutaan kerran jotakin luokan muodostinta. Muodostimen tunnistaa ohjelmakoodista seuraavista seikoista: Muodostin on luokan jäsenfunktio, jonka nimi on sama kuin luokan nimi ja jolla ei ole lainkaan paluuarvotyyppiä. Muodostimet voidaan jakaa seuraaviin ryhmiin: oletusmuodostin (default constructor), kopiointimuodostin (copy constructor) sekä ohjelmoijan määrittelemät parametrilliset muodostimet. Oletusmuodostinta voidaan kutsua ilman parametreja: se alustaa olion attribuutit oletusarvoilla. Oletusmuodostin voi olla kääntäjän generoima tai ohjelmoijan kirjoittama metodi. Mikäli ohjelmoija ei toteuta luokkaan lainkaan muodostimia, kääntäjä generoi luokalle oletusmuodostimen. Esimerkiksi edellisen kappaleen Piste-luokkaan ei ollut toteutettu muodostinta, joten Piste-oliota luotaessa kutsuttiin kääntäjän tekemää oletusmuodostinta. C++kääntäjän generoima oletusmuodostin ei tee varsinaisesti mitään. Oletusmuodostinta tarvitaan kuitenkin esimerkiksi luotaessa oliotaulukoita, koska varattaessa taulukolle tila, on varattava sen yksittäisille oliojäsenille tila. Tällöin järjestelmä kutsuu oletusmuodostinta automaattisesti olion tilanvarauksen yhteydessä. Ohjelmoija voi halutessaan määritellä oletusmuodostimen, jossa attribuutit alustetaan sopivilla arvoilla. Luokkaan voidaan määritellä myös parametrillisia muodostimia, jotka ovat aina ohjelmoijan itse luomia. Ylikuormittamalla voidaan samaan luokkaan tehdä useita muodostimia, kunhan niiden parametrilistat vain poikkeavat toisistaan. Kääntäjä ei generoi oletusmuodostinta, jos luokalle on määritelty yksikin parametrillinen muodostin. Tämän voi havaita käytettäessä oliotaulukoita. Oletetaan, että Piste-luokkaan olisi kirjoitettu parametrillinen muodostin, jolloin luokan koodi voisi olla 4

// Tiedosto piste.h #ifndef PISTE_H_INCLUDED #define PISTE_H_INCLUDED class Piste private: double x_coord; double y_coord; ; public: Piste(double x,double y); void tulosta(); #endif Muodostimen toteutus lisätään kooditiedostoon piste.cpp : Piste::Piste(double x,double y) x_coord = x; y_coord = y; Nyt pääohjelmaa #include "piste.h" int main(int argc, char** argv) Piste ptaulu[5]; // Oliotaulukko // Taulukon käsittelyä return 0; käännettäessä saadaan seuraavan tyyppinen virheilmoitus: piste_main.cpp: In function 'int main(int, char**)': piste_main.cpp:7:16: error: no matching function for call to 'Piste::Piste()' piste_main.cpp:7:16: note: candidates are: piste.h:11:3: note: Piste::Piste(double, double) piste.h:11:3: note: candidate expects 2 arguments, 0 provided Virheilmoituksen toiselta riviltä käy ilmi, että taulukkoa varattaessa yritetään muodostaa oliot oletusmuodostimen avulla. Tällaista ei kuitenkaan luokassa ole, joten joudutaan virhetilanteeseen. Koodi voidaan saada toimivaksi ilman oletusmuodostinta, jos taulukkoon sijoitetaankin osoittimia kekodynaamisesti luotaviin olioihin. Tällöin kuitenkin ohjelmoijan on itse varattava oliot ja myös tuhottava ne esimerkiksi seuraavasti: 5

#include "piste.h" int main(int argc, char** argv) Piste *ptaulu[5]; // Taulukossa osoittimia Piste-olioihin // Luodaan oliot: for(int i=0; i < 5; i++) ptaulu[i] = new Piste(1.0,2.0); // Taulukon käsittelyä // Tuhotaan oliot for(int i=0; i < 5; i++) delete ptaulu[i]; return 0; Java-kielen oliot ovat aina viitetyyppisiä, joten Javan oliotaulukkojen käsittely muistuttaa edellistä esimerkkiä, mutta Java-ohjelmoijan ei tarvitse kantaa huolta olioiden tuhoamisesta kielen automaattisen roskankeruun vuoksi. Muodostimessa suoritettavat attribuuttien alustukset voidaan tehdä myös ns. alustuslistan avulla. Tällöin attribuuttien alustus tapahtuu niiden määrittelyn yhteydessä. Tavallisesti on samantekevää, käyttääkö alustuslistaa vai alustaako attribuutin muodostimen rungossa. Joissakin tapauksissa muodostimen rungossa ei enää voi tehdä alustusta: tällöin on käytettävä alustuslistaa. Yleisin tällainen tapaus on yliluokan alustaminen aliluokan muodostimessa. Tähän tutustutaan tarkemmin periytymisen yhteydessä. Muulloin alustuslistaa on käytettävä, kun alustettava attribuutti on vakio tai viittaustyyppinen. Seuraavassa on Piste-luokkaan lisätty oletusmuodostin. Pisteiden koordinaatit parametreinaan saava parametrillinen muodostin on muutettu käyttämään alustuslistaa, jossa attribuutit alustetaan. // Tiedosto piste.h #ifndef PISTE_H_INCLUDED #define PISTE_H_INCLUDED class Piste private: double x_coord; double y_coord; public: Piste(); Piste(double x,double y); void tulosta(); ; #endif Muodostimien toteutukset kooditiedostossa piste.cpp : 6

Piste::Piste() x_coord = 0; y_coord = 0; Piste::Piste(double x,double y):x_coord(x),y_coord(y) Oletusmuodostimessa koordinaattien arvoiksi sijoitetaan nollat muodostimen rungossa. Nyt pääohjelman #include "piste.h" int main(int argc, char** argv) Piste p3; p3.tulosta(); // Käyttää oletusmuodostinta Piste p1(-3.23,4.11); // Käyttää parametrillista muodostinta p1.tulosta(); return 0; tulostus olisi (0,0) (-3.23,4.11) Ensimmäinen tulostuslause tulostaa nyt nollapisteen, koska kääntäjän generoiman oletusmuodostimen tilalle on tullut ohjelmoijan laatima oletusmuodostin, joka alustaa attribuutit nolliksi. Kopiointimuodostin alustaa (yleensä) olion attribuutit saman luokan toisen olion vastaavilla arvoilla. Ohjelmoijan ei useimmiten tarvitse kirjoittaa itse kopiointimuodostinta, koska tällöin kääntäjä generoi kopiointimuodostimen, joka kopioi uudelle oliolle parametrina välitetyn saman luokan olion attribuuttien sisällön. Jos kääntäjän generoima muodostin ei kuitenkaan sovi haluttuun tilanteeseen, ohjelmoija voi itse määritellä kopiointimuodostimen. Kopiointimuodostin on muodollisesti seuraavan kaltainen: Sen ensimmäinen parametri on viittaus saman luokan olioon (yleensä määritelty vakioksi const-määreellä) ja sen mahdollisilla muilla parametreilla tulee olla oletusarvo. Kopiointimuodostimen pari on sijoitusoperaattori. Jos ohjelmoija ei määrittele luokalle sijoitusoperaattoria, kääntäjä luo sellaisen. Kääntäjän luoma sijoitusoperaattori toimii kääntäjän luoman kopiointimuodostimen tapaan: se kopioi lähdeoliosta attribuutit kohdeolioon. Mikäli ohjelmoija kirjoittaa kopiointimuodostimen ja muuttaa sen oletussemantiikkaa, on yleensä myös sijoitusoperaattori kirjoitettava toimimaan vastaavalla tavalla. Sijoitusoperaattori on aina luokan jäsenfunktio ja sen muoto on Luokka& operator=(const Luokka&); 7

Alla olevassa esimerkissä on kirjoitettu Piste-luokalle kopiointimuodostin ja sijoitusoperaattori. Huomaa, että tässä tapauksessa noudatetaan oletussemantiikkaa, joten niiden kirjoittaminen ei ole tarpeellista ja ne voitaisiin myös jättää toteuttamatta. // Tiedosto piste.h #ifndef PISTE_H_INCLUDED #define PISTE_H_INCLUDED class Piste private: double x_coord; double y_coord; public: Piste(); Piste(double x,double y); // Kopiointimuodostin Piste(const Piste& p); void tulosta(); // Hajotin ~Piste(); ; // Sijoitusoperaattori: Piste& operator=(const Piste &p); #endif Toteutukset kooditiedostoon piste.cpp : // Kopiointimuodostin Piste::Piste(const Piste& p) x_coord = p.x_coord; y_coord = p.y_coord; // Sijoitusoperaattori Piste& Piste::operator=(const Piste& p) // Tarkistetaan sijoitetaanko itseensä: if(this == &p) return *this; x_coord = p.x_coord; y_coord = p.y_coord; return *this; // Hajotin Piste::~Piste() 8

Näitä operaatioita voitaisiin testata pääohjelmassa seuraavasti: #include "piste.h" int main(int argc, char** argv) Piste p3; p3.tulosta(); Piste p1(-3.23,4.11); p1.tulosta(); Piste p2(p3); p2.tulosta(); p2 = p1; p2.tulosta(); return 0; Edellä piste p2 luodaan ensin kopiointimuodostinta käyttäen, jolloin se saa attribuuteikseen pisteen p3 attribuuttien arvot. Sijoituksen jälkeen pisteen p2 attribuuttien arvot muuttuvat samoiksi kuin pisteen p1. Nämä seikat voi havaita ohjelman tulostuksesta. Muiden operaattoreiden toteuttamiseen perehdytään ylikuormittamisen käsittelyn yhteydessä. Joskus on tehtävä ohjelmallisia resurssien vapauttamisia, kun oliota tuhotaan. Nämä toimenpiteet tehdään luokan hajottimessa eli destruktorissa (destructor). Luokan hajotinta kutsutaan automaattisesti, kun olion varaama muistitila vapautetaan. Hajotin on luokan jäsenfunktio, jonka nimi on ~-merkkiä seuraava luokan nimi ja jolla ei ole paluuarvotyyppiä. Luokalla on aina täsmälleen yksi hajotin: ellei ohjelmoija määrittele hajotinta, kääntäjä luo sellaisen. Edellä olevassa Piste-luokan koodissa on hajotin toteutettuna. Koska luokassa ei vaadita erityisiä resurssien vapauttamisia, hajottimen runko on tyhjä. Kuten alussa mainittiin, C++-kielen olio voi olla muiden muuttujien tapaan eliniältään automaattinen eli pinodynaaminen, staattinen tai kekodynaaminen. Automaattiset eli pinodynaamiset oliot ovat tyypillisesti lyhytikäisiä. Ne ovat lohkossa paikallisia ja ovat olemassa tietyn palveluketjun tai metodin suorituksen ajan. Niille varataan automaattisesti muistitila pinomuistista olion määrittelyn yhteydessä ja varattu muisti vapautetaan automaattisesti lohkon loppuessa. Kekodynaamiset oliot ovat yleensä pitkäikäisempiä kuin automaattiset oliot. Niiden vaatima muisti varataan kekomuistista ohjelmoijan käskystä new-operaattoria käyttämällä. Tällaisen olion muisti ei vapaudu automaattisesti C++-kielessä, vaan ohjelmoijan on huolehdittava siitä käyttämällä delete-operaattoria. Operaattori new palauttaa osoittimen, jonka tarkoitetyyppi on luotavan olion luokkatyyppi. Operaattori delete voidaan kohdistaa saatuun osoittimeen seuraavaan tapaan: Piste *p1 = 0; // Määrittelee osoittimen, alustaa nollaksi p1 = new Piste(1.5,2.3); // Osoitin osoittaa dynaamisesti varattuun olioon delete p1; // Tuhoaa varatun olion 9

Staattiset oliot ovat olemassa koko sovelluksen suorituksen ajan. Niille varataan muisti ohjelman käynnistyessä ja niiden muisti vapautuu ohjelman loppuessa. Nämä oliot ovat joko globaaleja tai static-määreellä esiteltyjä paikallisia olioita. Jälkimmäisiä tarvitaan, kun funktiossa on oltava muuttuja, joka säilyttää arvonsa kutsujen välillä, mutta ei kuitenkaan haluta tehdä muuttujasta globaalia. Staattiset oliot vapautuvat, kun pääohjelman suoritus loppuu. Niitä ei tarvitse siis dynaamisten olioiden tapaan erikseen vapauttaa. Muutetaan Piste-luokan muodostinta ja hajotinta niin, että ne tulostavat ilmoituksen, kun niitä kutsutaan: Piste::Piste(double x,double y):x_coord(x),y_coord(y) std::cout << "Luodaan piste (" << x_coord << "," << y_coord << ")" << std::endl; Piste::~Piste() std::cout << "Tuhotaan piste (" << x_coord << "," << y_coord << ")" << std::endl; Tutkitaan ohjelman #include "piste.h" void luopisteet() Piste *p1 = new Piste(1,1); Piste p2(2,2); static Piste p3(3,3); int main(int argc, char** argv) std::cout << "Kutsutaan funktiota luopisteet" << std::endl; luopisteet(); std::cout << "Palattiin funktiosta luopisteet" << std::endl; std::cout << "Kutsutaan toisen kerran funktiota luopisteet" << std::endl; luopisteet(); std::cout << "Palattiin toisen kerran funktiosta luopisteet" << std::endl; return 0; käyttäytymistä. Kun ohjelma suoritetaan, tulostuu Kutsutaan funktiota luopisteet Luodaan piste (1,1) Luodaan piste (2,2) Luodaan piste (3,3) Tuhotaan piste (2,2) Palattiin funktiosta luopisteet Kutsutaan toisen kerran funktiota luopisteet Luodaan piste (1,1) Luodaan piste (2,2) Tuhotaan piste (2,2) 10

Palattiin toisen kerran funktiosta luopisteet Tuhotaan piste (3,3) Funktiossa luopisteet luodaan kolme Piste-oliota, ensimmäinen kekodynaamisena, toinen automaattisena ja kolmas staattisena muuttujana. Kun funktiota kutsutaan ensimmäisen kerran, luodaan kaikki kolme pistettä, mikä näkyykin tulostuksesta. Kun funktiosta poistutaan, automaattinen muuttuja p2 tuhotaan, joten sen hajotinta kutsutaan funktiosta palattaessa. Tämä näkyy myös tulostuksesta. Sen sijaan kekodynaamisesti ja staattisesti varattavia olioita ei tuhota. Kun funktiota kutsutaan toisen kerran, varataan jälleen kekomuistista olio, johon osoitin p1 osoittaa. Samoin varataan automaattinen olio p2, staattinen olio sen sijaan varattiin jo ensimmäisellä kutsulla; sitä ei enää luoda vaan funktiossa käytettäisiin jo aiemmin varattua oliota. Jälleen funktiosta poistuttaessa tuhotaan automaattinen olio. Lopulta ohjelman päättyessä vapautetaan staattinen olio ja sen hajotinta kutsutaan. Kekodynaamisesti varattuja olioita ei vapauteta missään vaiheessa, joten niiden hajotinta ei kutsuta. Näin ollen ohjelmassa on muistivuoto, sillä varattua muistia ei missään vaiheessa vapauteta. Tämä on luonnollisesti ohjelmointivirhe. Lisäksi aika pahakin sellainen, koska funktiosta palattaessa varattuun muistiin osoittava osoitin menetetään. Näin ollen muistia ei voisikaan pääohjelmassa vapauttaa. Järjestelmä kyllä vapauttaa prosessin muistin prosessin päättyessä, joten varatut oliot eivät tässä tapauksessa jää kummittelemaan ikuisiksi ajoiksi. Ohjelmoijan on kuitenkin aina syytä huolehtia varaamansa kekodynaamisen muistin asianmukaisesta vapauttamisesta. 4. Saanti- ja asetusmetodit Edellisen kappaleen esimerkeissä Piste-olion koordinaatteja ei voi suoraan lukea, koska niiden jäsenmuuttujat ovat private-tyyppisiä. Niiden arvot saadaan näkyville ainoastaan kutsumalla luokan tulostusmetodia. Private-näkyvyyden vuoksi kerran luodun Piste-olion koordinaatteja ei voi myöskään enää muuttaa. Tämän vuoksi koordinaatin arvon lukeminen tai muuttaminen johtaa kääntäjän virheilmoitukseen. Jos esimerkiksi kirjoitettaisiin pääohjelmaksi #include "piste.h" int main(int argc, char** argv) Piste p3; p3.tulosta(); // Käyttää oletusmuodostinta Piste p1(-3.23,4.11); // Käyttää parametrillista muodostinta p1.tulosta(); std::cout << X-koordinaatti on << p1.x_coord << std::endl; // TAI p1.x_coord = 2.1; return 0; niin käännettäessä ohjelmaa saataisiin seuraavan tyyppinen virheilmoitus: In file included from piste_main.cpp:2:0: piste.h: In function 'int main(int, char**)': piste.h:9:10: error: 'double Piste::x_coord' is private double x_coord; ^ piste_main.cpp:13:4: error: within this context p1.x_coord = 2.1; 11

Ohjelman saisi kääntymään, jos muuttaisi koordinaattien jäsenmuuttujat Piste-luokkaan julkisiksi (public). Tämä tuntuukin houkuttelevalta ajatukselta, mikäli koordinaattien arvoja joutuu lukemaan tai muuttelemaan ohjelmassa usein. Menettelytapa ei ole kuitenkaan hyvä, koska se sallii helposti myös jäsenmuuttujien tahattoman muuttamisen, mikä johtaa herkästi ohjelmointivirheisiin. Tämän takia public-tyyppisiä jäsenmuuttujia käytetään ainoastaan harvinaisissa erikoistapauksissa ja hyvään ohjelmointitapaan kuuluu jäsenmuuttujien määritteleminen private-tyyppisiksi. Tällöin kirjoitetaan tarvittaville jäsenmuuttujille erityiset saanti- ja asetusmetodit. Saantimetodi palauttaa jäsenmuuttujan arvon ja asetusmetodi asettaa muuttujalle uuden arvon. Näin voidaan kontrolloida 1. että jäsenmuuttujan arvoa ei muuteta tahattomasti ja 2. että jäsenmuuttuja saa vain sallittuja arvoja. Esimerkin Piste-luokalle voitaisiin saanti- ja asetusmetodit toteuttaa vaikkapa seuraavasti. Tiedostoon piste.h lisättäisiin metodien prototyypit: public: void setx(double xval); void sety(double yval); double getx(); double gety(); Metodien toteutukset kirjoitettaisiin kooditiedostoon piste.cpp : void Piste::setX(double xval) x_coord = xval; void Piste::setY(double yval) y_coord = yval; double Piste::getX() return x_coord; double Piste::getY()T return y_coord; Tällöin, olettaen, että luokassa on oletusmuodostin joka alustaa koordinaatit nolliksi, pääohjelma #include "piste.h" int main(int argc, char** argv) Piste p; std::cout << "X = " << p.getx() << " Y = " << p.gety() << std::endl; p.setx(8.32); p.sety(-6.45); p.tulosta(); return 0; 12

tulostaisi X = 0 Y = 0 (8.32,-6.45) Nyt luokka on käyttökelpoisempi, koska koordinaattien arvoja voidaan ohjelmallisesti muuttaa. Samoin koordinaattien arvot saadaan lukuina, jolloin niillä voidaan operoida. Saanti- ja asetusmetodeja ei kannata kuitenkaan kirjoittaa rutiininomaisesti kaikille jäsenmuuttujille, vaan tarkoituksenmukaisesti ohjelman toiminnan kannalta. Mikäli luokan ulkopuolelta ei esimerkiksi koskaan muuteta jäsenmuuttujan arvoa, ei ole syytä kirjoittaa asetusmetodia. Myöskään saantimetodia ei tarvita, jos luokan ulkopuolella ei tarvitse tietää jäsenmuuttujan arvoa. Huomaa edellisen esimerkin metodien nimeäminen: usein saantimetodin nimeksi annetaan getmuuttujanimi, kun attribuutin nimi on muuttujanimi. Vastaavasti asetusmetodin nimeksi kirjoitetaan setmuuttujanimi. Tällä kurssilla käytetään tavallisesti tällaista nimeämismenetelmää. Semantiikaltaan epätavalliset luokan jäsenet Luokkaan voi jäseniksi määritellä myös tavallisesta toiminnasta poikkeavasti käyttäytyviä jäseniä. Ehkä tavallisimpia ovat vakiomuotoiset metodit. Nämä ovat metodeja, joilla ei ole oikeutta muuttaa olion tilaa. Yleensä ainakin attribuuttien arvot palauttavat metodit on syytä määritellä vakiometodeiksi; vakiometodien käyttö lisää turvallisuutta, koska niissä ei voi tahattomasti muuttaa olion attribuutteja. Jos olio on määritelty vakioksi, ainoastaan sen vakiomuotoisia metodeja voi kutsua. Metodi määritellään vakiometodiksi lisäämällä sen esittelyn loppuun avainsana const. Tämä määrittely kuuluu myös metodin tyyppiin, joten luokkaan on mahdollista (joskaan ei yleensä suositeltavaa) tehdä metodit, joiden tyypit eroavat ainoastaan tässä suhteessa. Edellä käsitellyssä Piste-luokassa metodi tulosta ei saisi muuttaa olion tilaa, joten se kannattaisi määritellä vakiomuotoiseksi: // Tiedosto piste.h #ifndef PISTE_H_INCLUDED #define PISTE_H_INCLUDED class Piste private: double x_coord; double y_coord; public: Piste(); Piste(double x,double y); // Kopiointimuodostin Piste(const Piste& p); void tulosta() const; // Hajotin ~Piste(); ; // Sijoitusoperaattori: Piste& operator=(const Piste &p); #endif 13

Myös kooditiedostossa olevaan toteutukseen on lisättävä const-avainsana: void Piste::tulosta() const std::cout << "(" << x_coord << "," << y_coord << ")" << std::endl; Luokka voi myös sisältää vakiomuotoisia attribuutteja. Niitä ei voi enää määrittelyn jälkeen muuttaa ja ne on alustettava muodostimen alustuslistassa. Nämä attribuutit merkitään avainsanalla const. Luokkakohtaiset jäsenet liittyvät koko luokkaan eivätkä yksittäiseen olioon. Tällaiset jäsenet voivat olla joko attribuutteja tai metodeja. Luokkakohtaiset attribuutit sisältävät luokkaa kuvailevia tietoja, jotka ovat kaikkien luokan olioiden käytettävissä luokan metodeissa. Luokkakohtainen tieto on luokan sisällä luonteeltaan globaalia ja luokkakohtaiset attribuutit käyttäytyvät semanttisesti kuten funktion staattiset muuttujat. Myös niiden muistinvaraus tapahtuu staattisesti, ts. järjestelmä varaa luokkakohtaisille attribuuteille tilan samaan aikaan globaalien muuttujien tilanvarauksen kanssa. Staattiset tiedot ovat käytettävissä riippumatta olioiden eliniästä ja vaikka luokasta ei olisi luotu lainkaan olioita. Luokkakohtaisten tietojen käsittely tapahtuu pääasiassa luokkametodeissa, jotka määritellään sellaisiksi static-määreellä. Näitä metodeja voidaan käyttää, vaikka luokan olioita ei ole saatavilla. Toisaalta static-metodeissa ei luonnollisesti voikaan käsitellä muita kuin luokkaattribuutteja. Luokkakohtaisten attribuuttien ja metodien esittely on seuraavaa muotoa. // Tiedosto lampotila.h #ifndef LAMPOTILA_H_INCLUDED #define LAMPOTILA_H_INCLUDED class Lampotila private: double asteet; static double ABS_NOLLA; public: Lampotila(double x); double getasteet() const; static double getabsnolla(); ; #endif Edellä on Lampotila-luokkaan määritelty yhden tavallisen oliokohtaisen attribuutin lisäksi luokkakohtainen attribuutti ABS_NOLLA, joka antaa absoluuttisen nollapisteen lämpötilan Celsius-asteina. Koska attribuutti on yksityinen, sitä ei voi lukea luokan ulkopuolelta, minkä vuoksi on esitelty myös luokkakohtainen metodi getabsnolla(). Luokkakohtaista attribuuttia ei saa alustaa luokan määrittelyssä, joten se on tehtävä kooditiedostossa, jossa ovat myös luokan metodien toteutukset: 14

#include "lampotila.h" double Lampotila::ABS_NOLLA = -273.15; Lampotila::Lampotila(double x):asteet(x) double Lampotila::getAsteet() const return asteet; double Lampotila::getAbsNolla() return ABS_NOLLA; Huomaa, miten luokkakohtainen muuttuja alustetaan. Luokkakohtaista metodia voidaan kutsua suoraan luokan nimen avulla, riippumatta olioista. Tosin myös olion kautta tehtävä kutsu on mahdollinen. Seuraavassa on esimerkki kummastakin kutsutavasta: #include "lampotila.h" int main(int argc, char** argv) std::cout << "Absoluuttinen nollapiste = " << Lampotila::getAbsNolla() << std::endl; Lampotila lt(23.25); std::cout << "Olion lampotila on " << lt.getasteet() << std::endl; std::cout << "Absoluuttinen nollapiste = " << lt.getabsnolla() << std::endl; return 0; Koska absoluuttinen nollapiste on muuttumaton (ainakin nykyfysiikan käsitysten mukaan), edellä käytettyä muuttujaa ei olisi syytä muuttaa edes luokan sisällä, joten se voitaisiin määritellä vakioksi. Tällöin koodi muuttuisi seuraavan kaltaiseksi // lampotila.h class Lampotila private: double asteet; const static double ABS_NOLLA; // kooditiedosto const double Lampotila::ABS_NOLLA = -273.15; 15

5. Koosteoliot Luokan attribuuttina voi olla myös toisen tai saman luokan olio. Tällöin olioiden välillä vallitsee koostumussuhde. Toisen olion sisältävää oliota sanotaan koosteolioksi, kun toiseen olioon sisältyvä olio on osaolio. Mikäli osaolio ei voi olla olemassa koosteoliosta riippumatta, on kysymyksessä aito kooste (kompositio); tällöin koosteolio omistaa osaolion ja on vastuussa sen käsittelystä. Vaikka koosteolio omistaakin osaolion, se voi käyttää ainoastaan osaolionsa julkista rajapintaa, ellei turvauduta erikoisjärjestelyihin. C++-kielessä on nimittäin ns. ystävämekanismi, jonka avulla luokalle voi antaa pääsyn toisen luokan yksityisiin jäseniin. Tämän mekanismin käyttäminen johtaa kuitenkin helposti huonoihin ohjelmointikäytäntöihin, koska se rikkoo tietojen kätkemisen periaatteen. Siksi mekanismia ei käsitellä tässä. Oletetaan, että on toteutettu henkilöä kuvaava luokka Henkilo: // Tiedosto Henkilo.h #ifndef HENKILO_H_INCLUDED #define HENKILO_H_INCLUDED #include <string> class Henkilo private: std::string etunimi; std::string sukunimi; std::string sotu; public: Henkilo(std::string en,std::string sn,std::string stu); ~Henkilo(); ; std::string getetunimi() const; std::string getsukunimi() const; std::string getsotu() const; #endif // Tiedosto Henkilo.cpp #include <string> #include "henkilo.h" Henkilo::Henkilo(std::string en,std::string sn,std::string stu): etunimi(en),sukunimi(sn),sotu(stu) std::string Henkilo::getEtunimi() const return etunimi; std::string Henkilo::getSukunimi() const return sukunimi; std::string Henkilo::getSotu() const return sotu; 16

Tätä luokkaa voitaisiin käyttää esimerkiksi kirjaa kuvaavan luokan tekijää mallintavana osaoliona seuraavan kaavion esittämällä tavalla: Kuva. Kaavio koostamisesta. Ohjelmassa kooste voitaisiin toteuttaa seuraavasti: // Tiedosto Kirja.h #ifndef KIRJA_H_INCLUDED #define KIRJA_H_INCLUDED #include "henkilo.h" class Kirja private: std::string nimi; Henkilo tekija; std::string isbn; ; #endif public: void print(std::ostream &os) const; Kirja(std::string n,const Henkilo &h,std::string isb); ~Kirja(); // Tiedosto Kirja.cpp #include <string> #include "henkilo.h" #include "kirja.h" Kirja::Kirja(std::string n,const Henkilo &h,std::string isb): nimi(n),tekija(h),isbn(isb) void Kirja::print(std::ostream &os) const os << tekija.getetunimi() << " " << tekija.getsukunimi() <<":"<< std::endl; os << nimi << std::endl; os << "ISBN: " << isbn; 17

Edellisessä esimerkissä Henkilo-luokan olio on Kirja-luokan muuttujatyyppinen attribuutti. Tällaisessa tapauksessa koosteolion ja osaolion eliniät ovat väistämättä yhtä pitkät. Osaolio luodaan samalla kuin koosteoliokin ja tuhotaan myös samanaikaisesti. Huomaa, että Kirja-luokan muodostimen alustuslistassa alustetaan osaolio tekija Henkilo-luokan kopiointimuodostinta käyttäen. Koosteolion hajottimessa ei muuttujatyyppisen attribuutin tapauksessa yleensä jouduta tekemään erityisiä operaatioita, koska osaolion hajotinta kutsutaan automaattisesti koosteolion tuhoutuessa. Luokkaan on toteutettu muodostimen ja hajottimen lisäksi ainoastaan yksi metodi, joka tulostaa olion tiedot. Huomaa, että kirjan tekijän tietoja tulostettaessa on käytettävä Henkilo-luokan attribuuttien saantimetodeja, koska Kirja-luokassa ei ole suoraa pääsyä ko. attribuutteihin. Osaolio voi olla myös dynaaminen, jolloin luokan attribuutti on osoitintyyppinen ja se laitetaan osoittamaan kekodynaamisesti luotuun olioon. Tällöin olioiden eliniät eivät automaattisesti olekaan yhtä pitkät, vaan osaolio voi jäädä olemaan koosteolion jo tuhouduttua. Tällä mekanismilla voidaan toteuttaa myös olioiden välinen löyhä sidos ja sama olio voi olla monen koosteolion osana. Tällaisessa tapauksessa on huolehdittava tarkasti resurssien vapauttamisesta: osaoliota ei saa tuhota ennenkuin viimeinenkin sen sisältävistä koosteolioista on lakannut tarvitsemasta sen palveluita. Seuraavassa esimerkissä Kurssi-luokalla on kurssin opettajaa kuvaava dynaaminen Henkiloluokan osaolio, jolloin luokan attribuutti on osoitin Henkilo-luokkaan. // Tiedosto Kurssi.h #ifndef KURSSI_H_INCLUDED #define KURSSI_H_INCLUDED #include "henkilo.h" class Kurssi private: std::string nimi; Henkilo* popettaja; int pisteet; ; #endif public: void print(std::ostream &os) const; Kurssi(std::string n,const Henkilo &ope,int credits); Kurssi(const Kurssi &k); Kurssi& operator=(const Kurssi& k); ~Kurssi(); 18

// Tiedosto Kurssi.cpp #include <string> #include "henkilo.h" #include "kurssi.h" Kurssi::Kurssi(std::string n,const Henkilo &ope,int credits): nimi(n),pisteet(credits) popettaja = new Henkilo(ope); Kurssi::Kurssi(const Kurssi &k) nimi = k.nimi; pisteet = k.pisteet; if(k.popettaja!= 0) popettaja = new Henkilo(*(k.pOpettaja)); else popettaja = 0; Kurssi::~Kurssi() if (popettaja!= 0) delete popettaja; Kurssi& Kurssi::operator=(const Kurssi& k) if (this == &k) return *this; nimi = k.nimi; pisteet = k.pisteet; if(popettaja!= 0) delete popettaja; if(k.popettaja!= 0) popettaja = new Henkilo(*(k.pOpettaja)); else popettaja = 0; return *this; void Kurssi::print(std::ostream &os) const os << popettaja->getetunimi() << " " << popettaja->getsukunimi() <<":"<< std::endl; os << nimi << " ECTS: " << pisteet; Tulostusmetodin toteuttaminen ei juuri poikkea aiemmasta. Muussa toteutuksessa on selviä eroja. Muodostimessa varataan olio kekodynaamisesti ja alustetaan se parametrioliolla. Hajottimessa on erikseen vapautettava osaolion muisti, koska kekodynaamista muistia ei 19

automaattisesti vapauteta. Myös kopiointimuodostin ja sijoitusoperaattori on toteutettava. Mikäli näin ei tehdä, kopioitavan Kurssiolion opettajaolioon kohdistuva osoitin kopioituu uuden Kurssiolion opettajaolion osoittimen arvoksi. Kun ensimmäinen olio tuhotaan, sen opettajaolion muisti vapautetaan. Mutta nyt kopion osoitin jää osoittamaan vapautettuun muistiin ja kopioolion käyttäminen voi johtaa hankaliin virhetilanteisiin ohjelmassa. Sama päättely koskee sijoitusoperaattoria. Näin ollen sekä kopiointimuodostimessa että sijoitusoperaattorissa on varattava kekodynaamisesti muisti osaoliolle. Sijoitusoperaattorissa on lisäksi vapautettava aiemman osaolion varaama muisti. Lähteet [Bud] Budd, Timothy A: An Introduction to Object-Oriented Programming, Addison-Wesley 2002 [CppTut] Juustila, A. Kettunen, H. Kilpi, T. Räisänen, T. Vesanen A.: C++ -tutoriaali (Opetusmoniste) 2004, Saatavana http://herkules.oulu.fi/isbn9514279425/ [Hie] Hietanen, P.: C++ ja olio-ohjelmointi, 3. laitos, Docendo 2004 20