Virtuaalifunktiot ja polymorfismi
|
|
|
- Leena Heikkinen
- 10 vuotta sitten
- Katselukertoja:
Transkriptio
1 Virtuaalifunktiot ja polymorfismi 16 Virtuaalifunktiot ja polymorfismi Polymorfismi on niin tehokas olio-ohjelmoinnin ominaisuus, että tulet varmastikin käyttämään sitä lähes kaikissa C++-ohjelmissasi. Polymorfismi vaatii periytettyjä luokkia, ja tämän luvun sisältö nojaa vahvasti edellisessä luvussa käsiteltyihin periytymisen perusteisiin. Tämän luvun aiheita ovat: Mikä polymorfismi on ja miten luokat saadaan hyödyntämään polymorfismia Mikä on virtuaalifunktio Milloin ja miksi luokkahierarkiassa tarvitaan virtuaalisia tuhoajafunktioita Miten virtuaalifunktioiden oletusparametrejä käytetään Mikä on aito virtuaalifunktio ja miten funktio määritellään sellaiseksi Mikä on abstrakti luokka Miten luokan tyyppejä voidaan muuntaa luokkahierarkiassa Miten olioon osoittavan osoittimen tyyppi saadaan selville suoritusaikana Mitä ovat osoittimet jäseniin ja miten niitä käytetään Polymorfismin perusteet Kuten tulemme huomaamaan, polymorfismiin kuuluu aina olioon osoittavan osoittimen tai viittauksen käyttö kutsuttaessa funktiota. Lisäksi polymorfismi toimii ainoastaan luokkahierarkiassa, joten mahdollisuus periyttää luokka toisesta luokasta on polymorfismin perustana. Mitä polymorfismilla sitten saadaan aikaan? Saamme karkean kuvan sen toiminnasta tutkimalla esimerkkiä, jossa on lisää laatikoita, mutta ensin meidän tulee ymmärtää, mikä merkitys on osoittimella kantaluokkaan. 621
2 C++ Ohjelmoijan käsikirja Osoitin kantaluokkaan Eräs asia, jota käsittelimme edellisessä luvussa, oli se, että periytetyn luokan olio on kantaluokan olioiden alijoukko - toisin sanoen, jokainen periytetty luokka on myös kantaluokka. Eli voit aina käyttää osoitinta kantaluokkaan tallettamaan periytetyn luokan olion osoitteen - voit itse asiassa käyttää jopa osoitinta epäsuoraan kantaluokkaan tähän tarkoitukseen. Alla olevassa kaaviossa PahviLaatikko-luokka on periytetty Laatikko-kantaluokasta ja KauraLtk-luokka on moniperitty Laatikko- ja Sisalto-luokista. Kaavio havainnollistaa, miten osoittimia kantaluokkiin voidaan käyttää periytettyjen olioiden osoitteiden tallettamiseen tällaisessa luokkahierarkiassa. Class Laatikko KauraLtk aamiainen; // Voit tallettaa aammiainen- // olion osoitteen mihin tahansa // kantaosoittimeen: Class PahviLaatikko: public Class Sisalto PahviLaatikko* ppahviltk = &aamiainen; Laatikko* pltk = &aamiainen; Sisalto* psisalto = &aamiainen; Class KauraLtk: public Carton, public Contents Vastakkaiseen suuntaan tämä ei onnistu. Esimerkiksi et voi käyttää KauraLtk*-tyyppistä osoitinta tallettamaan (suoran tai epäsuoran) kantaluokan osoitetta. Tämä on loogista, koska kantaluokka ei kuvaa täydellistä periytetyn luokan oliota. Periytetyn luokan olio sisältää aina jokaisen kantaluokkansa alijoukon, mutta kukin kantaluokka kuvaa vain osaa periytetyn luokan oliosta. Katsotaan esimerkkiä. Oletetaan, että periytämme kaksi luokkaa edellisen luvun mukaisesta Laatikko-luokasta. Tarkoituksenamme on kuvata niillä erilaisia säiliöitä. PahviLaatikkoluokan määrittely on muotoa: class PahviLaatikko: public Laatikko // Luokan yksityiskohdat... Uuden VahvaLtk-luokan määrittely on samantyyppinen: class VahvaLtk: public Laatikko // Luokan yksityiskohdat
3 Virtuaalifunktiot ja polymorfismi Oletetaan nyt, että jokaisen periytetyn tyypin tilavuus lasketaan eri tavalla. Pahvista tehdyn PahviLaatikko-luokan kohdalla tilavuutta täytyy vähentää vain vähän, koska materiaalin paksuus on varsin pieni. VahvaLtk:n kohdalla tilavuutta saattaa tarvita vähentää enemmän, koska kyseessä on vahva ja suojaava rakenne. Edellä olleiden luokkien määrittelyjen pohjalta voimme esitellä ja alustaa osoittimen seuraavasti: PahviLaatikko apahviltk(10.0, 10.0, 5.0); Laatikko* pltk = &apahviltk; Osoitin pltk, joka on tyyppiä osoitin Laatikko-olioon, alustetaan apahviltk:n (joka on tyyppiä PahviLaatikko) osoitteella. Tämä on mahdollista, koska PahviLaatikko on periytetty Laatikkoluokasta ja sisältää täten Laatikko-tyyppisen aliolion. Voimme käyttää samaa osoitinta tallettamaan myös VahvaLtk-olion osoitteen, koska VahvaLtk-luokka on myöskin periytetty Laatikko-luokasta: VahvaLtk kassakaappi(12.0, 8.0, 4.0); pltk = &kassakaappi; Osoitin pltk voi tiettynä hetkenä sisältää minkä tahansa olion osoitteen, jonka kantaluokkana on Laatikko. Osoittimen tyyppi on määrittelyhetkellä staattinen tyyppi - pltk:n staattinen tyyppi on osoitin Laatikko-olioon. Koska pltk on osoitin kantaluokkaan, sillä on myös dynaaminen tyyppi, joka vaihtelee sen mukaan, mihin osoitin osoittaa. Kun pltk osoittaa PahviLaatikkoolioon, sen dynaaminen tyyppi on osoitin PahviLaatikko-luokkaan. Kun pltk osoittaa VahvaLtkolioon, sen dynaaminen tyyppi on osoitin VahvaLtk-luokkaan. Kun pltk osoittaa Laatikkotyyppiseen olioon, sen dynaaminen tyyppi on sama kuin sen staattinen tyyppi. Juuri tässä on polymorfismin taika. Käsittelemme hetken kuluttua ehtoja, joiden voimassa ollessa voit käyttää osoitinta pltk kutsuessasi kantaluokassa tai missä tahansa periytetyissä luokissa määriteltyä funktiota. Funktio valitaan tällöin pltk:n dynaamisen tyypin mukaan. Tarkastellaan seuraavaa lausetta: pltk->tilavuus(); Jos pltk sisältää PahviLaatikko-olion osoitteen, voimme käyttää tätä lausetta kutsuessamme PahviLaatikko-olion tilavuus()-funktiota. Jos pltk osoittaa VahvaLtk-olioon, lause kutsuu VahvaLtk-olion tilavuus()-funktiota. Tämä toimii samalla tavalla muidenkin Laatikko-luokasta periytettyjen luokkien kohdalla. Lause pltk->tilavuus(); voi saada aikaan eri toiminnan, riippuen siitä, mihin pltk osoittaa. Tai mikä tärkeämpää, suoritusaikana valitaan oliolle, johon pltk osoittaa, sopiva toiminta. (Ikään kuin osoittimella olisi sisäänrakennettu switch-lause, joka testaa tyypin ja valitsee kutsuttavan funktion sen mukaan.) Tämä on erittäin tehokas mekanismi. Tulet törmäämään useasti tilanteisiin, joissa käsiteltävänä olevan olion tyyppiä ei voida ratkaista etukäteen - eli tyyppiä ei voida ratkaista suunnitteluhetkellä eikä käännöshetkellä, ainoastaan suoritusaikana. Tällaiset tilanteet voidaan ratkaista helposti polymorfismin avulla. Sitä käytetään runsaasti käyttäjän kanssa vuorovaikutuksessa olevissa ohjelmissa, joissa käyttäjä voi syöttää minkä tahansa tyyppisiä syötteitä. 623
4 C++ Ohjelmoijan käsikirja Esimerkiksi grafiikkasovelluksessa, jossa voidaan piirtää erimuotoisia kuvioita - ympyröitä, viivoja, käyriä jne - voidaan määritellä jokaiselle kuviotyypille oma luokka, joka on periytetty yhteisestä kantaluokasta Kuvio. Ohjelma tallettaa käyttäjän piirtämän kuvion osoitteen kantaluokan osoittimeen pkuvio, joka on tyyppiä osoitin Kuvio-olioon, ja piirtää sopivan kuvion esimerkiksi lauseella pkuvio->piirra();. Tämä kutsuu osoittimen osoittaman kuvion piirra()- funktiota. Eli tämä yksi lause pystyy piirtämään minkä tahansa kuvion. Jotta tämä toimii, on tärkeää, että kutsuttava funktio on kantaluokan jäsen. Katsotaan seuraavaksi tarkemmin perittyjen funktioiden toimintaa. Perittyjen funktioiden kutsuminen Ennen kuin siirrymme tarkemmin polymorfismin saloihin, tulee meidän tarkastella tarkemmin perittyjen funktioiden toimintaa ja miten ne ovat suhteessa sen luokan funktioihin, josta ne on periytetty. Muutamme Laatikko-luokkaa siten, että lisäämme siihen funktion, joka laskee Laatikko-olion tilavuuden sekä toisen funktion, joka tulostaa tilavuuden. Luokkien uudet tiedostot Laatikko.h ja Laatikko.cpp ovat seuraavassa: // Laatikko.h #ifndef LAATIKKO_H #define LAATIKKO_H class Laatikko public: Laatikko(double pitarvo = 1.0, double syvarvo = 1.0, double korkarvo = 1.0); // Funktio, joka näyttää olion tilavuuden void naytatilavuus() const; // Funktio, joka laskee Laatikko-olion tilavuuden double tilavuus() const; protected: double pituus; double syvyys; double korkeus; ; #endif // Laatikko.cpp #include <iostream> #include "Laatikko.h" using namespace std; 624 Laatikko::Laatikko(double parvo, double sarvo, double karvo) : pituus(parvo), syvyys(sarvo), korkeus(karvo) void Laatikko::naytaTilavuus() const
5 Virtuaalifunktiot ja polymorfismi cout << "Laatikon käytettävissä oleva tilavuus on " << tilavuus() << endl; double Laatikko::tilavuus() const return pituus * syvyys * korkeus; Emme enää tarvitse käyttäjän määrittelemää tuhoajafunktiota emmekä tulostuslausetta muodostinfunktiossa, joten ne on poistettu. Tällaisen Laatikko-luokan avulla voimme tulostaa Laatikko-olion käytettävissä olevan tilavuuden kutsumalla kyseisen olion naytatilavuus()- funktiota. Käytämme samoja jäsenmuuttujia (pituus, syvyys ja korkeus), jotka on määritelty suojatuiksi, joten niihin ei päästä käsiksi luokasta periytettyjen luokkien jäsenmuuttujista. Määrittelemme myöskin VahvaLtk-luokan, jonka kantaluokkana on Laatikko. VahvaLtk-olion materiaali suojaa laatikon sisältöä, joten sen käytettävissä oleva tilavuus on vain 85% tavallisen Laatikko-olion tilavuudesta. Tästä syystä tarvitsemme toisen version tilavuus()-funktiosta, joka ottaa tämän huomioon: // VahvaLtk.h #ifndef VAHVALTK_H #define VAHVALTK_H #include "Laatikko.h" class VahvaLtk : public Laatikko // Periytetty luokka public: // Muodostinfunktio VahvaLtk(double pituusarvo, double syvyysarvo, double korkeusarvo); ; // Funktio, joka laskee VahvaLtk:n tilavuuden (15% menee laatikolle) double tilavuus() const; #endif // VahvaLtk.cpp #include "VahvaLtk.h" VahvaLtk::VahvaLtk(double parv, double sarv, double karv) : Laatikko(pArv,sArv,kArv) double VahvaLtk::tilavuus() const return 0.85 * pituus * syvyys * korkeus; Periytetyssä luokassa olisi tietysti voinut olla muitakin jäseniä, mutta haluamme pitää sen nyt yksinkertaisena, keskittyen vain perittyjen funktioiden toimintaan. Periytetyn luokan muodostinfunktio kutsuu alkuarvolistassaan yksinkertaisesti kantaluokan muodostinfunktiota, joka asettaa jäsenmuuttujien arvot. Periytetyn luokan muodostinfunktiossa ei tarvita yhtään lausetta. Määrittelemme myöskin uuden version tilavuus()-funktiosta, joka korvaa kantaluokasta perityn version. Ideana tässä on, että saamme perityn naytatilavuus()-funktion kutsumaan periytetyn luokan versiota tilavuus()-funktiosta, kun kutsumme sitä VahvaLtk-luokan oliosta. Katsotaan, kuinka se toimii. 625
6 C++ Ohjelmoijan käsikirja Kokeile itse - Periytetyn luokan käyttö Voimme testata uutta periytettyä luokkaamme yksinkertaisesti luomalla Laatikko-olion ja VahvaLtk-olion, joiden mitat ovat samat, ja vertaamalla, onko niiden lasketut tilavuudet samat. Tämän tekevä main()-funktio näyttää seuraavalta: // Esimerkki 16.1 Perittyjen funktioiden toiminta periytetyssä luokassa #include <iostream> #include "Laatikko.h" // Laatikko-luokka #include "VahvaLtk.h" // VahvaLtk-luokka using namespace std; int main() Laatikko omaltk(20.0, 30.0, 40.0); // Esitellään kantaluokka VahvaLtk vahval(20.0, 30.0, 40.0); // Esitellään periytetty laatikko - sama koko cout << endl; omaltk.naytatilavuus(); // Näytetään kantaluokan laatikon tilavuus vahval.naytatilavuus(); // Näytetään periytetyn luokan laatikon tilavuus return 0; Kun suoritan ohjelman, se tulostaa: Laatikon käytettävissä oleva tilavuus on Laatikon käytettävissä oleva tilavuus on Kuinka se toimii Periytetyn luokan olion tilavuuden pitäisi olla pienempi kuin kantaluokan olion, joten ohjelmamme ei toimi niin kuin pitäisi. Yritetään saada selville, missä menee väärin. Funktion naytatilavuus() toinen kutsu on periytetyn luokan oliolle VahvaLtk, mutta selvästikään tätä ei oteta huomioon. VahvaLtk-olion tilavuuden tulisi olla 85% tavallisen samankokoisen Laatikkoolion tilavuudesta. Ongelmana on se, että kun tilavuus()-funktiota kutsutaan naytatilavuus()-funktiosta, kääntäjä kutsuu aina kantaluokan tilavuus()-funktiota. Sillä ei ole vaikutusta, miten naytatilavuus()- funktiota kutsutaan, se ei koskaan kutsu VahvaLtk-luokan versiota tilavuus()-funktiosta. Kun funktioiden kutsut ovat tällä tavalla kiinteät jo ennen ohjelman suoritusta, funktion kutsua kutsutaan staattiseksi sidonnaksi. Termiä aikainen sidonta käytetään myös usein. Tässä esimerkissä tietty tilavuus()-funktio sidotaan funktioon naytatilavuus() ohjelman suorituksen aikana. Joka kerta, kun naytatilavuus()-funktiota kutsutaan, kutsutaan kantaluokan tilavuus()-funktiota, joka siihen on sidottu. 626
7 Virtuaalifunktiot ja polymorfismi! Samanlainen sidonta tapahtuu myös periytetyssä luokassa VahvaLtk, jos asetamme ehdot oikein. Eli jos lisäämme naytatilavuus()-funktion (joka kutsuu tilavuus()- funktiota) VahvaLtk-luokkaan, tilavuus()-funktion kutsu sidottaisiin staattisesti periytetyn luokan funktioon. Entäpä jos kutsummekin tilavuus()-funktiota suoraan VahvaLtk-oliosta? Lisätään lauseet, jotka kutsuvat tilavuus()-funktiota suoraan VahvaLtk-oliosta kantaluokan osoittimen avulla: cout << vahval:n tilavuus on << vahval.tilavuus() << endl; Laatikko *pltk = &vahval; cout << vahval:n tilavuus pltk:n kautta on << pltk->tilavuus() << endl; Lisää nämä lauseet main()-funktioon juuri ennen return-lausetta. Kun suoritat ohjelman, saat tulostuksena: Laatikon käytettävissä oleva tilavuus on Laatikon käytettävissä oleva tilavuus on vahval:n tilavuus on vahval:n tilavuus pltk:n kautta on Tämä on varsin informatiivinen. Kuten huomaamme, periytetyn luokan tilavuus()-funktion kutsu kutsuu periytetyn luokan tilavuus()-funktiota, mitä juuri halusimmekin. Kutsu kantaluokan osoittimen pltk avulla kutsuu kuitenkin kantaluokan versiota tilavuus()-funktiosta, vaikka pltk sisältää vahval:n osoitteen. Toisin sanoen molemmat kutsut sidotaan staattisesti. Kääntäjä toteuttaa nämä kutsut seuraavasti: cout << vahval:n tilavuus on << vahval.vahvaltk::tilavuus() << endl; Laatikko *pltk = &vahval; cout << vahval:n tilavuus pltk:n kautta on << pltk->laatikko::tilavuus() << endl; Funktion staattinen kutsu osoittimen avulla ratkaistaan pelkästään osoittimen tyypin perusteella eikä osoitetun olion perusteella. Osoitin pltk on tyyppiä osoitin Laatikko-olioon, joten kaikki staattiset kutsut pltk:n avulla kutsuvat Laatikko-luokan jäsenfunktiota. Kaikki kantaluokan staattisesti sidotun osoittimen avulla kutsutut funktiot kutsuvat kantaluokan funktiota. Haluamme esimerkkimme avulla selvittää, mitä tilavuus()-funktiota missäkin tilanteessa käytetään. Eli, jos kutsumme naytatilavuus()-funktiota periytetyn luokan oliosta, haluamme, että kutsutaan periytetyn luokan tilavuus()-funktiota, ei kantaluokan versiota. Vastaavasti, jos kutsumme tilavuus()-funktiota kantaluokan osoittimen avulla, haluamme, että kutsutaan osoitetulle oliolle sopivaa funktiota. Tällaista kutsutaan dynaamiseksi sidonnaksi tai myöhäiseksi sidonnaksi. Ohjelma ei kuitenkaan vielä toimi haluamallamme tavalla, koska meidän täytyy kertoa kääntäjälle, että tilavuus()-funktio Laatikko-luokassa ja siitä periytetyissä luokissa on erikoinen. Haluamme, että kutsut ratkaistaan dynaamisesti. Nyt meidän tulee määritellä tilavuus()- funktio virtuaalifunktioksi. 627
8 C++ Ohjelmoijan käsikirja Virtuaalifunktiot Kun esittelet funktion kantaluokassa virtuaaliseksi, kerrot kääntäjälle, että haluat funktion sidottavan dynaamisesti kaikista kyseisestä kantaluokasta periytetyissä luokissa. Virtuaalifunktio esitellään kantaluokassa virtual-avainsanalla. class Laatikko public: virtual double tilavuus() const... ; class PahviLaatikko: public Laatikko public: virtual double tilavuus() const... ; class VahvaLtk: public Laatikko public: virtual double tilavuus() const... ; class MuuLtk: public Laatikko public: virtual double tilavuus() const... ; PLtk on tyyppiä osoitin Laatikko-olioon. Kutsuttava tilavuus() -funktion versio riippuu sen olion tyypistä, johon pltk osoittaa double tulos = pltk->tilavuus(); Funktio, jonka esittelet virtuaaliseksi kantaluokassa, on virtuaalinen kaikissa kantaluokasta periytetyissä (joko suorissa tai epäsuorissa) luokissa. Jotta polymorfisminen toiminta saadaan aikaan, jokainen periytetty luokka voi toteuttaa oman virtuaalifunktion (vaikka se ei pakollista olekaan; palaamme tähän kohta). Virtuaalifunktion kutsut voidaan suorittaa muuttujan, joka on osoitin tai viittaus kantaluokan olioon, avulla. Yllä oleva kaavio havainnollistaa, miten virtuaalifunktion kutsu osoittimen avulla ratkaistaan dynaamisesti. Osoitinta kantaluokkaan käytetään tallettamaan jonkin periytetyn luokan olion osoite. Se voi osoittaa mihin tahansa kolmesta periytetystä luokasta tai luonnollisestikin kantaluokan olioon. Mitä tilavuus()- funktiota milloinkin kutsutaan, riippuu osoittimen kutsuhetkellä osoittaman olion tyypistä. Luokan määrittely polymorfistiseksi tarkoittaa, että se on periytetty luokka, jossa on vähintään yksi virtuaalifunktio. Ennen kuin siirrymme eteenpäin, huomaa, että virtuaalifunktion kutsu olion avulla ratkaistaan aina staattisesti. Virtuaalifunktion kutsu ratkaistaan dynaamisesti vain silloin, kun kutsu tapahtuu osoittimen tai viittauksen avulla. Nyt voimme testata virtuaalifunktioita. 628
9 Virtuaalifunktiot ja polymorfismi Kokeile itse - Virtuaalifunktioiden käyttö Meidän tulee muuttaa edellisen esimerkkimme Laatikko-luokkaa hieman, jotta saamme esimerkin toimimaan haluamallamme tavalla. Meidän tulee lisätä virtual-avainsana tilavuus()-funktion esittelyyn: class Laatikko public: Laatikko(double pitarvo = 1.0, double syvarvo = 1.0, double korkarvo = 1.0); // Funktio, joka näyttää olion tilavuuden void naytatilavuus() const; // Funktio, joka laskee Laatikko-olion tilavuuden virtual double tilavuus() const; protected: double pituus; double syvyys; double korkeus; ; Huomaa, että sinun ei tarvitse lisätä virtual-avainsanaa funktion määrittelyyn. Itse asiassa olisi virhe tehdä niin. Jotta saamme esimerkin vielä kiinnostavammaksi, toteutamme tilavuus()-funktion uuteen PahviLaatikko-luokkaan hieman eri tavalla: // Pahvil.h #ifndef PAHVILAATIKKO_H #define PAHVILAATIKKO_H #include <string> #include "Laatikko.h" using std::string; class PahviLaatikko : public Laatikko public: // Muodostinfunktio, joka kutsuu kantaluokan muodostinfunktiota PahviLaatikko(double pa, double sa, double ka, string materiaali = "Pahvi"); // Kopiomuodostin PahviLaatikko(const PahviLaatikko& apahviltk); // Tuhoajafunktio ~PahviLaatikko(); // Funktio, joka laskee PahviLaatikko-olion tilavuuden double tilavuus() const; 629
10 C++ Ohjelmoijan käsikirja private: string* pmateriaali; ; #endif // Pahvil.cpp #include "Pahvil.h" PahviLaatikko::PahviLaatikko(double pa, double sa, double ka, string materiaali) : Laatikko(pa, sa, ka) pmateriaali = new string(materiaali); PahviLaatikko::PahviLaatikko(const PahviLaatikko& apahviltk) pituus = apahviltk.pituus; syvyys = apahviltk.syvyys; korkeus = apahviltk.korkeus; pmateriaali = new string(*apahviltk.pmateriaali); PahviLaatikko::~PahviLaatikko() delete pmateriaali; double PahviLaatikko::tilavuus() const double til = (pituus - 0.5) * (syvyys - 0.5) * (korkeus - 0.5); return til > 0.0? til : 0.0; PahviLaatikko-luokan tilavuus()-funktio olettaa, että materiaalin paksuus on 0.25, joten 0.5 vähennetään jokaisesta mitasta. Jos PahviLaatikko-olio on jostain syystä luotu siten, että jokin mitta on pienempi kuin 0.5, tilavuudeksi lasketaan 0. Käytämme myöskin esimerkissä 16.1 määriteltyä VahvaLtk-luokkaa. Voimme nyt muuttaa edellisen esimerkin main()-funktiota ottamalla PahviLaatikko-olion käsittelyyn mukaan. Kutsumme myöskin naytatilavuus()-funktiota pltk-osoittimen avulla: // Laatikko-luokka // VahvaLtk-luokka // PahviLaatikko- // Esimerkki 16.2 Virtuaalifunktioiden käyttö #include <iostream> #include "Laatikko.h" #include "VahvaLtk.h" #include "Pahvil.h" luokka using namespace std; 630 int main() Laatikko omaltk(20.0, 30.0, 40.0); // Esitellään kantalaatikko VahvaLtk vahval(20.0, 30.0, 40.0); // Esitellään periytetty laatikko - sama koko PahviLaatikko apahviltk(20.0, 30.0, 40.0); // Toinen periytetty laatikko
11 Virtuaalifunktiot ja polymorfismi cout << endl; omaltk.naytatilavuus(); vahval.naytatilavuus(); apahviltk.naytatilavuus(); cout << endl; // Näytetään kantalaatikon tilavuus // Näytetään periytetyn laatikon tilavuus // Näytetään periytetyn laatikon tilavuus // Nyt kokeillaan kantaluokan osoitinta Laatikko-oliolle Laatikko* pltk = &omaltk; // Osoittaa tyyppiin Laatikko cout << "omaltk:n tilavuus pltk:n kautta on " << pltk->tilavuus() << endl; pltk->naytatilavuus(); cout << endl; // Nyt kokeillaan kantaluokan osoitinta VahvaLtk-oliolle pltk = &vahval; // Osoittaa tyyppiin Laatikko cout << "vahval:n tilavuus pltk:n kautta on " << pltk->tilavuus() << endl; pltk->naytatilavuus(); cout << endl; // Nyt kokeillaan kantaluokan osoitinta PahviLaatikko-oliolle pltk = &apahviltk; // Osoittaa tyyppiin Laatikko cout << "apahviltk:n tilavuus pltk:n kautta on " << pltk->tilavuus() << endl; pltk->naytatilavuus(); return 0; Voit nyt kääntää esimerkin uudelleen. Kun suoritat sen, tulostuksen tulisi näyttää seuraavalta: Laatikon käytettävissä oleva tilavuus on Laatikon käytettävissä oleva tilavuus on Laatikon käytettävissä oleva tilavuus on omaltk:n tilavuus pltk:n kautta on Laatikon käytettävissä oleva tilavuus on vahval:n tilavuus pltk:n kautta on Laatikon käytettävissä oleva tilavuus on apahviltk:n tilavuus pltk:n kautta on Laatikon käytettävissä oleva tilavuus on ! Kuinka se toimii Avainsana virtual kantaluokan tilavuus()-funktion yhteydessä on riittävä määrittely, jotta kaikki tilavuus()-funktiot periytetyissä luokissa ymmärretään myös virtuaalisiksi. Voit halutessasi käyttää virtual-avainsanaa periytettyjen luokkien funktioiden yhteydessä - kuten edellisessä kaaviossa havainnollistettiin. Tässä esimerkissä jätimme virtual-avainsanan pois tilavuus()-funktioiden määrittelyistä havainnollistaaksemme, että sen käyttö ei ole pakollista. Suosittelen kuitenkin, että käytät virtual-avainsanaa kaikissa periytettyjen luokkien virtuaalifunktioiden esittelyissä, koska se tekee periytetyn luokan esittelyä lukevalle selväksi, että funktio on itse asiassa virtuaalinen ja että se sidotaan dynaamisesti. 631
12 C++ Ohjelmoijan käsikirja Ohjelma tekee selvästikin nyt sen, mitä halusimmekin. Funktion naytatilavuus() ensimmäinen kutsu Laatikko-tyyppiselle oliolle omaltk on seuraava: omaltk.naytatilavuus(); // Näytetään kantalaatikon tilavuus Tämä yksinkertaisesti kutsuu kantaluokan versiota tilavuus()-funktiosta, koska omaltk on tyyppiä Laatikko. Seuraava naytatilavuus()-funktion kutsu on VahvaLtk-tyyppiselle oliolle vahval: vahval.naytatilavuus(); // Näytetään periytetyn laatikon tilavuus Tämä lause kutsuu Laatikko-luokassa määriteltyä naytatilavuus()-funktiota - itse asiassa naytatilavuus()-funktiosta ei ole muuta versiota. Funktio on peritty VahvaLtk-luokan julkisena jäsenenä, joten sitä voidaan kutsua tällä tavalla. Funktion tilavuus() kutsu naytatilavuus()- funktiossa sidotaan periytetyssä luokassa määriteltyyn versioon, koska tilavuus() on virtuaalifunktio. Saamme näin oikein lasketun tilavuuden VahvaLtk-oliolle. Kolmas naytatilavuus()-funktion kutsu on PahviLaatikko-oliolle: apahviltk.naytatilavuus(); // Näytetään periytetyn laatikon tilavuus 632 Funktio naytatilavuus() on peritty PahviLaatikko-luokkaan ja tilavuus()-funktion kutsu sidotaan PahviLaatikko-luokan versioon, joten saamme jälleen oikein lasketun tilavuuden. Seuraavaksi kutsumme pltk-osoittimen avulla tilavuus()-funktiota suoraan sekä epäsuorasti naytatilavuus()-funktion kautta. Osoitin sisältää aluksi Laatikko-olion omaltk osoitteen ja sen jälkeen kahden periytetyn olion osoitteen. Kunkin olion tulostus näyttää, että oikea versio tilavuus()-funktiosta valitaan automaattisesti jokaisessa tilanteessa. Eli kyseessä on täydellisen polymorfismin esimerkki. Virtuaalifunktion vaatimukset Jotta funktio toimii virtuaalisesti, sinun tulee esitellä ja määritellä se periytetyssä luokassa saman nimiseksi kuin kantaluokassa ja sen parametriluettelon tulee olla sama kuin kantaluokassa. Lisäksi, jos olet esitellyt kantaluokan funktion const-tyyppiseksi, tulee sinun esitellä periytetyn luokan funktio myös const-tyyppiseksi. Yleensä periytetyn luokan funktion paluuarvon tyypin tulee olla sama kuin kantaluokassa, mutta tähän on poikkeus, kun kantaluokan funktion paluuarvo on osoitin tai viittaus luokkatyyppiin. Tällöin periytetyn luokan versio virtuaalifunktiosta voi palauttaa osoittimen tai viittauksen muuhunkin tyyppiin kuin kantaluokan tyyppiin. Emme käsittele tätä tämän enempää. Voit päätellä virtuaalifunktioiden määrittelyn säännöistä, että jos yrität käyttää eri parametrejä periytetyn luokan virtuaalifunktiossa, kuin mitä käytit kantaluokassa, virtuaalifunktion mekanismi ei toimi. Periytetyn luokan funktio sidotaan tällöin staattisesti käännösaikana. Näin on myös silloin, jos unohdat esitellä periytetyn luokan funktion const-tyyppiseksi, jos kantaluokan funktio on const-tyyppinen. Voit testata tätä poistamalla const-avainsanan PahviLaatikko-luokassa olevan tilavuus()- funktion esittelystä ja suorittamalla edellisen esimerkin uudelleen. Tämä tarkoittaa sitä, että PahviLaatikko-luokan tilavuus()-funktio ei enää täsmää Laatikko-luokassa esiteltyä
13 Virtuaalifunktiot ja polymorfismi virtuaalifunktiota, joten periytetyn luokan tilavuus()-funktio ei ole virtuaalinen. Näin ollen sidonta on staattinen, eli PahviLaatikko-luokan kohdalla kutsuttava funktio on kantaluokan versio, kun kutsutaan kantaluokan osoittimen avulla tai epäsuorasti naytatilavuus()-funktion kautta. Jos periytetyn funktion nimi ja parametriluettelo ovat samat kuin kantaluokassa esitetyllä virtuaalifunktiolla, paluuarvon tyypin pitää olla virtuaalifunktion sääntöjen mukainen. Jos se ei ole, periytetyn luokan funktio ei käänny. Tässä yhteydessä haluan lisätä pienen varoituksen, että älä koskaan esittele virtuaalifunktiota avoimeksi (inline). Avointen funktioiden tarkoitus on se, että ne korvataan käännösaikana, joten virtuaalifunktio ei voi olla avoin. Sinun tulee aina esitellä virtuaalifunktiot virtuaalisiksi. Virtuaalifunktiot ja luokkahierarkia Jos haluat, että funktiotasi kohdellaan virtuaalisena kantaluokan osoittimen kanssa, sinun tulee esitellä se virtuaaliseksi kantaluokassa. Voit esitellä kantaluokassa niin monta virtuaalifunktiota kuin haluat, mutta kaikkia virtuaalifunktioita ei tarvitse esitellä virtuaalisiksi kaikkein ylimmässä kantaluokassa. Tätä havainnollistetaan seuraavassa kaaviossa. class Laatikko virtual double tilavuus(); Class PahviLaatikko: public Laatikko virtual double tilavuus(); virtual void teetuo(int i); class VahvaLtk: public Laatikko virtual double tilavuus(); virtual string teetama(int i); class SaleLtk: public PahviLaatikko virtual void teetuo(int i); Perii tilavuus() -funktion PahviLaatikko-luokasta class SuuriLtk: public VahvaLtk virtual string teetama(int i); Perii tilavuus() -funktion VahvaLtk- luokasta class PieniLtk: public vahvaltk virtual double tilavuus(); virtual string teetama(int i); class Paketti: public PahviLaatikko virtual double tilavuus(); virtual void teetuo(int i); Tilavuus() On virtuaalinen kaikissa luokissa. TeeTuo() On virtuaalinen PahviLaatikko- SaleLtk- ja Pakettiluokissa TeeTama() On virtuaalinen VahvaLtk-, SuuriLtk- ja PieniLtkluokissa Kun esittelet funktion virtuaaliseksi yhdessä luokassa, funktio on virtuaalinen kaikissa luokissa, jotka on periytetty suoraan tai epäsuoraan tästä luokasta. Esimerkiksi kaikki luokat, jotka on periytetty Laatikko-luokasta, perivät tilavuus()-funktion virtuaalisena. Voit kutsua näiden luokkien tilavuus()-funktiota Laatikko*-tyyppisen osoittimen pltk avulla, koska osoitin voi sisältää minkä tahansa hierarkiassa olevan olion osoitteen: double tulos = pltk->tilavuus(); //Kutsuu minkä tahansa luokan funktiota SaleLtk-luokassa ei esitellä virtuaalifunktiota tilavuus(), joten PahviLaatikko-luokalta perittyä versiota käytetään SaleLtk-olioiden kohdalla. Se on peritty virtuaalifunktiona, joten sitä kutsutaan polymorfistisesti. 633
14 C++ Ohjelmoijan käsikirja Tyyppiä PahviLaatikko* olevaa osoitinta ppahviltk voidaan käyttää kutsuttaessa tilavuus()-funktiota, mutta vain PahviLaatikko-luokan kohdalla tai SaleLtk- ja Pakettiluokkien kohdalla, joiden kantaluokkana on PahviLaatikko: tulos = ppahviltk->tilavuus() //PahviLaatikko-, SaleLtk- ja Paketti-luokista PahviLaatikko-luokka ja siitä periytetyt luokat sisältävät myös virtuaalifunktion teetuo(). Tätä funktiota voidaan kutsua polymofistisesti PahviLaatikko*-tyyppisen osoittimen avulla: ppahviltk->teetuo(12); //PahviLaatikko-, SaleLtk- ja Paketti-luokista Huomaa, että et voi kutsua näiden luokkien teetuo()-funktiota käyttämällä osoitinta pltk, koska Laatikko-luokka ei sisällä funktiota teetuo(). Vastaavasti virtuaalifunktiota teetama() voidaan kutsua luokista VahvaLtk, SuuriLtk ja PieniLtk käyttämällä kantaluokan osoitinta pvahvaltk: string vastaus = pvahvaltk-teetama(3); //VahvaLtk-, SuuriLtk- ja PieniLtk-luokista Luonnollisestikin samaa osoitinta voidaan käyttää kutsuttaessa näiden luokkien tilavuus()- funktioita. Saantitavat ja virtuaalifunktiot Periytetyn luokan virtuaalifunktion saantitapa voi olla eri kuin saantitapa kantaluokassa. Kun kutsut virtuaalifunktiota osoittimen kautta, kantaluokan saantitapa ratkaisee, onko funktio kutsuttavissa periytetystä luokasta. Jos virtuaalifunktio on kantaluokassa julkinen, sitä voidaan kutsua kantaluokan osoittimen (tai viittauksen) avulla kaikkien periytettyjen luokkien kohdalla, riippumatta periytettyjen luokkien saantitavasta. Voimme havainnollistaa tätä muuttamalla hieman edellistä esimerkkiä. Kokeile itse - Virtuaalifunktioiden saantitavan vaikutus Muuta edellisen esimerkin VahvaLtk-luokan määrittelyä muuttamalla tilavuus()-funktio suojatuksi ja lisäämällä virtual-avainsana sen esittelyyn: class VahvaLtk : public Laatikko // Periytetty luokka public: // Muodostinfunktio VahvaLtk(double pituusarvo, double syvyysarvo, double korkeusarvo); protected: // Funktio, joka laskee VahvaLtk:n tilavuuden (15% menee laatikolle) virtual double tilavuus() const; ; main()-funktiota tulee myös muuttaa hieman: 634
15 Virtuaalifunktiot ja polymorfismi // Laatikko-luokka // VahvaLtk-luokka // PahviLaatikko- // Esimerkki 16.3 Saantitavat ja virtuaalifunktiot #include <iostream> #include "Laatikko.h" #include "VahvaLtk.h" #include "Pahvil.h" luokka using namespace std; int main() Laatikko omaltk(20.0, 30.0, 40.0); // Esitellään kantalaatikko VahvaLtk vahval(20.0, 30.0, 40.0); // Esitellään periytetty laatikko - sama koko PahviLaatikko apahviltk(20.0, 30.0, 40.0);// Toinen periytetty laatikko cout << endl; omaltk.naytatilavuus(); // Näytetään kantalaatikon tilavuus vahval.naytatilavuus(); // Näytetään periytetyn laatikon tilavuus apahviltk.naytatilavuus(); // Näytetään periytetyn laatikon tilavuus cout << endl; //cout << "vahval:n tilavuus on " << vahval.tilavuus() << endl; //Poista kommentointi niin saat virheen! // Nyt kokeillaan kantaluokan osoitinta Laatikko-oliolle Laatikko* pltk = &omaltk; // Osoittaa tyyppiin Laatikko cout << "omaltk:n tilavuus pltk:n kautta on " << pltk->tilavuus() << endl; pltk->naytatilavuus(); cout << endl; // Nyt kokeillaan kantaluokan osoitinta VahvaLtk-oliolle pltk = &vahval; // Osoittaa tyyppiin Laatikko cout << "vahval:n tilavuus pltk:n kautta on " << pltk->tilavuus() << endl; pltk->naytatilavuus(); cout << endl; // Nyt kokeillaan kantaluokan osoitinta PahviLaatikko-oliolle pltk = &apahviltk; // Osoittaa tyyppiin Laatikko cout << "apahviltk:n tilavuus pltk:n kautta on " << pltk->tilavuus() << endl; pltk->naytatilavuus(); return 0; Ei varmastikaan ole yllätys, että tämä koodi tuottaa saman tulostuksen kuin edellinenkin esimerkki. Kuinka se toimii Vaikka tilavuus()-funktio on esitelty suojatuksi VahvaLtk-luokassa, voimme silti kutsua sitä vahval-olion kohdalla naytatilavuus()-funktion kautta, joka on peritty Laatikko-luokasta. Voimme kutsua sitä myös kantaluokan osoittimen pltk avulla. Jos kuitenkin poistat kommentoinnin riviltä, jossa kutsutaan tilavuus()-funktiota suoraan vahval-olion kautta, koodi ei käänny. Tässä vaikuttaa se, sidotaanko kutsu dynaamisesti vai staattisesti. Kun käytät luokan oliota, kutsu sidotaan staattisesti (eli kääntäjän toimesta) ja koska tilavuus()-funktio on suojattu 635
16 C++ Ohjelmoijan käsikirja VahvaLtk-oliossa, kutsu vahval-olion kautta ei käänny. Kaikki muut kutsut sidotaan ohjelman suorituksen aikana - ne ovat polymorfistisia funktiokutsuja. Tällöin kantaluokan virtuaalifunktion saantitapa periytyy kaikkiin periytettyihin luokkiin. Tämä siitä huolimatta, että saantitapa on määritelty eksplisiittisesti periytetyssä luokassa: eksplisiittinen määrittely on voimassa vain staattisesti sidotuissa funktiokutsuissa. Parametrien oletusarvot virtuaalifunktioissa Koska oletusarvot käsitellään käännösaikana, voit saada odottamattomia vaikutuksia, jos käytät virtuaalifunktiossa parametrien oletusarvoja. Jos kantaluokan virtuaalifunktion esittelyssä on parametreillä oletusarvoja, ja kutsut funktiota kantaluokan osoittimen avulla, saat aina oletusparametrin arvon kantaluokan versiosta. Periytetyn luokan version parametrien oletusarvoilla ei ole vaikutusta. Voimme havainnollistaa tätä muuttamalla edellistä esimerkkiä lisäämällä tilavuus()-funktion parametrille oletusarvon. Kokeile itse - Parametrien oletusarvot Muuta Laatikko.cpp-tiedostossa olevaa tilavuus()-funktion esittelyä seuraavasti: double Laatikko::tilavuus(const int i) const cout << "Parametri = " << i << endl; return pituus * syvyys * korkeus; Muuta hieman myös funktion esittelyä Laatikko-luokassa: // Funktio, joka laskee Laatikko-olion tilavuuden virtual double tilavuus(const int i = 5) const; Parametrillä ei ole muuta tarkoitusta kuin havainnollistaa, miten oletusarvoja käsitellään. Muuta PahviLaatikko-luokkaa samalla tavalla, mutta anna parametrin oletusarvoksi 50 ja VahvaLtkluokassa oletusarvoksi 500. Voit lisäksi palauttaa VahvaLtk-luokan tilavuus()-funktion saantitavaksi julkisen. Äläkä unohda, että sinun tulee sisällyttää iostream-otsikkotiedosto kahteen cpptiedostoon. Tekemällä nämä muutokset luokkien määrittelyihin, voimme kokeilla oletusarvoja edellä näkemällämme main()-funktiolla, jossa kommentoimme rivin, jossa kutsutaan vahval-olion tilavuus()-funktiota suoraan. Saat seuraavanlaisen tulostuksen: Parametri = 5 Laatikon käytettävissä oleva tilavuus on Parametri = 5 Laatikon käytettävissä oleva tilavuus on Parametri = 5 Laatikon käytettävissä oleva tilavuus on Parametri = 500 vahval:n tilavuus on Parametri = 5 omaltk:n tilavuus pltk:n kautta on 24000
17 Virtuaalifunktiot ja polymorfismi Parametri = 5 Laatikon käytettävissä oleva tilavuus on Parametri = 5 vahval:n tilavuus pltk:n kautta on Parametri = 5 Laatikon käytettävissä oleva tilavuus on Parametri = 5 apahviltk:n tilavuus pltk:n kautta on Parametri = 5 Laatikon käytettävissä oleva tilavuus on Kuinka se toimii Yhtä kutsua lukuun ottamatta tulostettu oletusarvo on peräisin kantaluokan funktiosta. Poikkeuksena on, kun kutsumme tilavuus()-funktiota vahval-olion avulla. Tämä sidotaan staattisesti, joten VahvaLtk-luokan parametrin oletusarvoa käytetään. Kaikki muut kutsut sidotaan dynaamisesti, joten kantaluokan oletusarvoa käytetään. Viittausten käyttö kutsuttaessa virtuaalifunktioita Voit kutsua virtuaalifunktioita myös viittauksen avulla ja viittausparametrit ovat tehokas tapa polymorfismin soveltamisessa. Virtuaalifunktion kutsumisella viittaustyyppisen muuttujan avulla ei ole samaa taikaa kuin osoittimen avulla kutsuttaessa, koska viittausmuuttuja alustetaan vain kerran ja ne voivat näin ollen kutsua vain sen olion funktioita. Funktion viittausparametrit ovat kuitenkin eri asia. Oletetaan, että määrittelet funktion, jonka parametri on viittaus kantaluokkaan. Voit tällöin välittää periytetyn luokan olion funktiolle parametrinä. Funktiossa voit käyttää viittausparametriä virtuaalifunktion kutsumisessa. Kun funktiotasi suoritetaan, välitetylle oliolle sopiva virtuaalifunktio valitaan automaattisesti. Näemme, miten tämä toimii, kun muutamme esimerkin 16.2 main()-funktiota siten, että se kutsuu funktiota, jonka parametri on tyyppiä viittaus Laatikko-olioon. Kokeile itse - Viittausten käyttö virtuaalifunktioissa Tässä esimerkissä lisäämme uuden, erillisen globaalin funktion naytatilavuus(), joka tulostaa olion tilavuuden. Välitämme sille viittausparametrin, jota käytämme olion naytatilavuus()- funktiota kutsuessamme. Funktion määrittely on seuraava: void naytatilavuus(const Laatikko& rltk) rltk.naytatilavuus(); Voimme kutsua tätä funktiota main()-funktiostamme ja välittää sille parametrinä viittauksia periytettyjen luokkien olioihin. Ainoat muutokset, jotka sinun tulee tehdä luokkien määrittelyihin, ovat tilavuus()-jäsenfunktioiden määrittelyt, joista sinun tulee poistaa parametri i ja i:n tulostavat rivit. Meidän tulee muuttaa main()-funktiota seuraavasti: 637
18 C++ Ohjelmoijan käsikirja // Esimerkki 16.5 Virtuaalifunktioiden käyttö viittausten kautta #include <iostream> #include "Laatikko.h" // Laatikko-luokka #include "VahvaLtk.h" // VahvaLtk-luokka #include "Pahvil.h" // PahviLaatikkoluokka using namespace std; void naytatilavuus(const Laatikko& rltk); // Globaalin funktion prototyyppi int main() Laatikko omaltk(20.0, 30.0, 40.0); // Esitellään kantalaatikko VahvaLtk vahval(20.0, 30.0, 40.0); // Esitellään periytetty laatikko - sama koko PahviLaatikko apahviltk(20.0, 30.0, 40.0);// Toinen periytetty laatikko cout << endl; naytatilavuus(omaltk); naytatilavuus(vahval); naytatilavuus(apahviltk); cout << endl; // Näytetään kantalaatikon tilavuus // Näytetään periytetyn laatikon tilavuus // Näytetään periytetyn laatikon tilavuus // Rivit poistettu return 0; // Globaali funktio, joka näyttää laatikon tilavuuden void naytatilavuus(const Laatikko& rltk) rltk.naytatilavuus(); Esimerkin suorittaminen saa aikaan tulostuksen: Laatikon käytettävissä oleva tilavuus on Laatikon käytettävissä oleva tilavuus on Laatikon käytettävissä oleva tilavuus on Kuinka se toimii Funktiossa main() luomme kantaluokan olion omaltk ja kaksi eri periytetyn luokan oliota vahval ja apahviltk. Tämän jälkeen kutsumme globaalia naytatilavuus()-funktiota, kukin olio vuorollaan parametrinä. Kuten tulostuksesta huomaat, jokaisessa tapauksessa kutsutaan oikeaa tilavuus()-funktiota. Eli polymorfismi toimii viittausparametrin välityksellä. Joka kerta, kun funktiota kutsutaan, viittausparametri alustetaan parametrinä välitetyn olion mukaan. Koska parametri on viittaus kantaluokan olioon, kääntäjä tekee suoritusaikana sidonnan virtuaaliseen tilavuus()-funktioon. Jos olisit määritellyt parametrin viittaukseksi periytettyyn luokkaan, funktiokutsu olisi sidottu staattisesti, koska se olisi ollut täydellisesti määritelty. Dynaaminen sidonta suoritetaan vain, jos kutsu suoritetaan kantaluokan viittauksen avulla. 638
19 Virtuaalifunktiot ja polymorfismi Virtuaalifunktion kantaluokan version kutsuminen Olemme jo nähneet, kuinka helppoa on periytetyn luokan olion osoittimen tai viittauksen avulla kutsua periytetyn luokan versiota virtuaalifunktiosta - kutsut sidotaan tällöin dynaamisesti. Mutta mitä meidän tarvitsee tehdä samassa tilanteessa, jos haluammekin kutsua kantaluokan funktiota periytetyn luokan olion avulla? Laatikko-luokassa on tilanne, jossa saattaisimme haluta kutsua kantaluokan funktiota. Saattaisi olla hyödyllistä laskea tilavuuden ero PahviLaatikko- tai VahvaLtk-olioiden kohdalla verrattuna Laatikko-olioon. Yksi tapa tämän suorittamiseen on laskea erotus kantaluokan ja periytetyn luokan tilavuus()-funktion palauttamien tulosten välillä. Voit pakottaa, että kantaluokan virtuaalifunktiota kutsutaan staattisesti, kun käytät luokan nimeä yhdessä näkyvyysalueoperaattorin kanssa. Oletetaan, että meillä on osoitin pltk, joka on määritelty seuraavasti: PahviLaatikko apahviltk(40.0, 30.0, 20.0); Laatikko* pltk = &apahviltk; Voimme laskea tilavuuksien erotuksen PahviLaatikko-olion kohdalla seuraavasti: double erotus = pltk->laatikko::tilavuus() - pltk->tilavuus(); Lauseke pltk->laatikko::tilavuus() kutsuu kantaluokan versiota tilavuus()-funktiosta. Luokan nimi yhdessä näkyvyysalueoperaattorin kanssa määrittelee tietyn tilavuus()-funktion, joten kyseessä on staattinen sidonta, joka ratkaistaan käännösaikana.! Näkyvyysalueoperaattorin avulla voit kutsua kantaluokan versiota mistä tahansa jäsenfunktiosta, jos saantitapa sallii funktion kutsumisen. Huomaa, että et voi käyttää tätä tekniikkaa tietyn periytetyn luokan funktion kutsumiseen osoittimen avulla. Lauseke pltk->pahvilaatikko::tilavuus() ei käänny, koska PahviLaatikko::tilavuus() ei ole Laatikko-luokan jäsen. Kun kutsut funktiota osoittimen avulla, kutsu on joko staattinen, osoitettavan luokan jäsenfunktion kutsu tai dynaaminen virtuaalifunktion kutsu. Kantaluokan virtuaalifunktion kutsuminen periytetyn luokan olion avulla on myöskin helppoa. Voit käyttää static_cast-muunnosta periytetystä luokasta kantaluokkaan ja käyttää muunnoksen tulosta kantaluokan funktion kutsumiseen. Voimme laskea tilavuuden erotuksen apahviltk-olion kohdalla lauseella: double erotus = static_cast<laatikko>(apahviltk).tilavuus() - apahviltk.tilavuus(); Tämän lauseen molemmat funktiokutsut sidotaan staattisesti. Muuntamalla apahviltk Laatikko-tyyppiseksi, kutsuttava funktio on Laatikko-luokan tilavuus()-funktio. Virtuaalifunktioiden kutsut olion avulla sidotaan aina staattisesti. 639
20 C++ Ohjelmoijan käsikirja Olioiden osoittimien muuntaminen Jos ohjelmassasi on osoitin periytettyyn luokkaan, voit helposti muuntaa sen osoittimeksi kantaluokkaan. Tämä voidaan tehdä sekä suorien että epäsuorien kantaluokkien kohdalla. Esitellään esimerkiksi osoitin PahviLaatikko-luokkaan seuraavasti: PahviLaatikko* ppahviltk = new PahviLaatikko(30, 40, 10); Voimme muuntaa tämän osoittimen automaattisesti PahviLaatikko-luokan suoran kantaluokan osoittimeksi (Laatikko-luokkahan on PahviLaatikko-luokan suora kantaluokka): Laatikko* pltk = ppahviltk; Tuloksena on osoitin Laatikko-olioon, joka alustetaan osoittamaan uuteen PahviLaatikko-olioon. Voimme automaattisesti muuntaa periytetyn luokan olion osoittimen epäsuoran kantaluokan osoittimeksi. Oletetaan, että meillä on viereisen kaavion mukainen hierarkia: class Laatikko class PahviLaatikko: public Laatikko class KauraLtk: public PahviLaatikko Tässä Laatikko on PahviLaatikko-luokan suora kantaluokka, joten se on KauraLtk-luokan epäsuora kantaluokka. Voimme nyt kirjoittaa seuraavaa: Laatikko* pltk = pkauraltk; Tämä lause muuntaa pkauraltk:n sisältämän osoitteen tyypin tyypistä osoitin KauraLtk-olioon tyyppiin osoitin Laatikko-olioon. Jos sinun tulee määritellä muunnos eksplisiittisesti, voit käyttää static_cast()-operaattoria: Laatikko* pltk = static_cast<laatikko*>(pkauraltk); Kääntäjä pystyy yleensä suorittamaan tämän muunnoksen, koska se pystyy päättelemään, että Laatikko on KauraLtk:n kantaluokka. Koska KauraLtk-olio sisältää Laatikko-olion, muunnos on mahdollinen. Muunnos ei ole mahdollinen, jos Laatikko-luokkaan ei päästä käsiksi, tai jos Laatikko-luokka on virtuaalinen kantaluokka. Kääntäjä huomaa tällaiset tilanteet. Seuraavassa kaaviossa on kaikki mahdolliset muunnokset alkaen KauraLtk-luokasta. 640
21 Virtuaalifunktiot ja polymorfismi Muunnos suoraan kantaluokkaan: ppahviltk = ppak; KauraLtk* ppak = new KauraLtk; ppak KauraLtk-olio ppak KauraLtk-olio ppahviltk PahviLaatikko -olio PahviLaatikko -olio Laatikko- olio Laatikko- olio Muunnos epäsuoraan kantaluokkaan: pltk = ppak; ppak KauraLtk-olio Osoittimien muunnokset hierarkiassa ylöspäin pltk PahviLaatikko -olio Laatikko- olio Kuten huomaat, jokaisen tapauksen tuloksena on osoitin kohteena olevan tyypin aliolioon. On erittäin helppoa mennä sekaisin, kun pohdit osoittimen muunnoksia luokkatyyppien välillä. Muista, että osoitin tiettyyn luokkatyyppiin voi osoittaa vain kyseisen tyyppisiin olioihin tai periytetyn luokan olioihin, ei toisinpäin. Tarkasti ottaen, osoitin ppahvilaatikko voi sisältää PahviLaatikko-tyyppisen olion osoitteen (joka on KauraLtk-olion aliolio) tai KauraLtk-tyyppisen olion osoitteen. Se ei voi sisältää Laatikko-tyyppisen olion osoitetta, koska KauraLtk on eräänlainen PahviLaatikko, mutta Laatikko ei ole. Edellä kerrotusta huolimatta on joskus mahdollista tehdä muunnoksia toiseenkin suuntaan. Osoittimen muuntaminen luokkahierarkiassa alaspäin, kantaluokasta periytettyyn luokkaan, on erilainen tilanne, koska muunnos riippuu siitä, mihin kantaluokan osoitin osoittaa. Muunnos esimerkiksi kantaluokan olion osoittimesta pltk periytetyn luokan olion osoittimeen voidaan suorittaa vain, jos kantaluokan osoitin osoittaa PahviLaatikko-luokan Laatikko-aliolioon. Jos näin ei ole, muunnoksen tulos on määrittelemätön. PahviLaatikko* ppahviltk = static_cast<pahvilaatikko*>(pltk); Laatikko* pltk = new PahviLaatikko; ppahviltk PaviLaatikko-olio PaviLaatikko-olio pltk Laatikko -olio pltk Laatikko -olio KauraLtk* ppak = static_cast<kauraltk*>(pltk); PaviLaatikko-olio Osoittimien muunnokset hierarkiassa alaspäin ppak pltk Laatikko -olio??? - määrittelemätön 641
22 C++ Ohjelmoijan käsikirja Kaavio näyttää muunnoksen osoittimesta pltk, joka sisältää PahviLaatikko-olion osoitteen. Muunnos tyyppiin PahviLaatikko* toimii, koska olio on tyyppiä PahviLaatikko. Tyyppiin KauraLtk* muunnoksen tulos on kuitenkin määrittelemätön, koska sen tyyppistä oliota ei ole olemassa. Jos et ole aivan varma, että muunnos on sallittu, älä käytä sitä. Osoittimen muuntaminen hierarkiassa alaspäin onnistuu, jos osoitin sisältää kohteena olevan tyypin osoitteen. Muunnos ei tarkista, onko tämä totta, joten jos yrität käyttää muunnosta tilanteessa, jossa et ole aivan varma mihin osoitin osoittaa, voit saada määrittelemättömän tuloksen. Tästä syystä sinun tulee muuntaa luokkahierarkiassa alaspäin eri tavalla: tavalla, jolla muunnos voidaan tarkistaa suoritusaikana. Dynaaminen muunnos Dynaaminen muunnos on muunnos, joka tehdään suoritusaikana. Se suoritetaan dynamic_cast<>()-operaattorilla. Voit käyttää tätä operaattoria ainoastaan polymorfististen luokkien osoittimien ja viittausten yhteydessä - eli luokkien yhteydessä, joilla on vähintään yksi virtuaalifunktio. Tämä siitä syystä, että ainoastaan polymorfististen luokkien osoittimet sisältävät tietoa, jota dynamic_cast<>()-operaattori tarvitsee muunnoksen tarkistamiseen. Tämä operaattori on tarkoitettu muuntamaan luokkahierarkian luokkien osoittimien välillä tai luokkahierarkian luokkien viittausten välillä. Huomaa, että tyypit, joiden välillä teet muunnoksen, täytyy olla osoittimia tai viittauksia saman luokkahierarkian luokkiin. Et voi käyttää dynamic_cast<>()-operaattoria mihinkään muuhun. Aloitamme operaattorin tutkimisen osoittimien dynaamisesta tyypinmuunnoksesta. Osoittimien dynaamiset muunnokset Voimme erottaa kaksi dynaamista muunnosta. Ensimmäinen on muunnos hierarkiassa alaspäin, suoran tai epäsuoran kantaluokan osoittimesta periytetyn luokan osoittimeen. Tätä kutsutaan alasmuunnokseksi. Toinen mahdollisuus on muuntaa hierarkiassa ristiin; tätä kutsutaan ristiinmuunnokseksi. Näitä kumpaakin havainnollistetaan alla olevassa kaaviossa. class Laatikko Alasmuunnoksen esimerkki: Tyypistä osoitin Laatikko-olioon tyyppiin osoitin PahviLtk-olioon Ristiinmuunnoksen esimerkki: Tyypistä osoitin Laatikko-olioon tyyppiin osoitin Sisalto-olioon class PahviLaatikko: public Laatikko class Sisalto class KauraLtk : public PahviLaatikko, publicsisalto 642
23 Virtuaalifunktiot ja polymorfismi Osoittimelle pltk, joka on tyyppiä osoitin Laatikko-luokkaan, voimme kirjoittaa kaaviossa kuvatun alasmuunnoksen seuraavasti: PahviLaatikko* ppahviltk = dynamic_cast<pahvilaatikko*>(pltk); Kuten huomaat, dynamic_cast<>()-operaattori kirjoitetaan samaan tapaan kuin static_cast<>()- operaattorikin. Kohdetyyppi kirjoitetaan kulmasulkeiden sisään ja lauseke, jonka haluat muuntaa uuteen tyyppiin, kirjoitetaan tavallisten sulkeiden sisään. Jotta tämä muunnos on sallittu, luokilla Laatikko ja PahviLaatikko tulee olla virtuaalisia funktioita, joko omia tai perittyjä. Jotta muunnos toimii, pltk:n tulee osoittaa joko PahviLaatikko-olioon tai KauraLtk-olioon, koska vain näiden tyyppiset oliot sisältävät PahviLaatikko-aliolion. Jos muunnos ei onnistu, osoittimen ppahviltk arvoksi asetetaan 0. Kaaviossa esitetty ristiinmuunnos voidaan kirjoittaa seuraavasti: Sisalto* psisalto = dynamic_cast<sisalto*>(pltk); Kuten edellisessäkin tapauksessa, Sisalto- ja Laatikko-luokkien tulee kummankin olla polymorfistisia, jotta muunnos on sallittu. Muunnos onnistuu ainoastaan, jos pltk sisältää KauraLtk-tyyppisen olion osoitteen, koska se on ainut tyyppi, joka sisältää Sisalto-olion ja johon voidaan viitata Laatikko*-tyyppisellä osoittimella. Jälleen, jos muunnos ei onnistu, psisaltoosoittimen arvoksi asetetaan 0. Alasmuuntaminen luokkahierarkiassa dynamic_cast<>()-operaattorin avulla voi epäonnistua, mutta tällöin palautetaan null-osoitin, eikä vain määrittelemätöntä arvoa. Tämä antaakin sinulle jo vihjeen, miten tätä muunnosta tulee käyttää. Oletetaan, että sinulla on jokin olio, johon osoitetaan Laatikko*-tyyppisellä osoittimella ja haluat kutsua ei-virtuaalista PahviLaatikkoluokan jäsenmuuttujaa. Kantaluokan osoittimen avulla voit kutsua ainoastaan periytetyn luokan virtuaalifunktiota, mutta dynamic_cast<>()-operaattorin avulla voit kutsua ei-virtuaalista funktiota. Oletetaan, että pinta_ala() on ei-virtuaalinen PahviLaatikko-luokan jäsenfunktio. Voit kutsua sitä lauseella: dynamic_cast<pahvilaatikko*>(pltk)->pinta_ala(); Tämä on kuitenkin varsin vaarallista. Sinun täytyy yhä olla varma, että pltk osoittaa PahviLaatikko-olioon tai sellaisen luokan olioon, jolla on PahviLaatikko-luokka kantaluokkana. Jos näin ei ole, dynamic_cast<>()-operaattori palauttaa null-osoittimen ja kutsu epäonnistuu. Voit korjata tämän käyttämällä ennen funktion kutsua dynamic_cast<>()-operaattoria esimerkiksi seuraavasti: if(pahvilaatikko* ppahviltk = dynamic_cast<pahvilaatikko*>(pltk)) ppahviltk->pinta_ala(); Nyt kutsumme funktiota vain, jos muunnoksen tulos ei ole null. Huomaa, että et voi poistaa const-määreen vaikutusta dynamic_cast<>()-operaattorilla. Jos osoitin, josta olet muuntamassa, on const-tyyppinen, osoittimen tyyppi, joksi olet muuntamassa, tulee myöskin olla const-tyyppinen. Jos haluat muunta const-tyyppisestä osoittimesta muuhun kuin const-tyyppiseen osoittimeen, sinun tulee ensin muuntaa muunnettavaa tyyppiä olevaan muuhun kuin const-tyyppiseen osoittimeen const_cast<>()-operaattorilla. 643
24 C++ Ohjelmoijan käsikirja Viittausten muuntaminen Voit käyttää dynamic_cast<>()-operaattoria myös funktion viittausparametrien muuntamiseen, jos haluat muuntaa viittauksen luokkahierarkiassa alaspäin. Seuraavassa esimerkissä funktion teetuo() parametri on viittaus kantaluokan (Laatikko) olioon. Funktion rungossa voimme muuntaa parametrin periytetyn luokan viittaukseksi: double teetuo(laatikko& rltk)... PahviLaatikko& rpahviltk = dynamic_cast<pahvilaatikko&>(rltk);... Tämä lause muuntaa tyypistä viittaus Laatikko-olioon tyyppiin viittaus PahviLaatikko-olioon. Yleisesti ottaen parametrin arvo ei tietenkään aina ole PahviLaatikko-olion viittaus, jolloin muunnos ei onnistu. Null-viittausta ei ole olemassa, joten tämä epäonnistuu eri tavalla kuin osoittimien yhteydessä: funktion suoritus päättyy ja bad_cast-tyyppinen poikkeus muodostetaan. Emme ole vielä käsitelleet poikkeuksia, mutta seuraavassa luvussa ymmärrät, mitä tämä tarkoittaa. Polymorfismin kustannukset Ilmaista ateriaa ei olekaan ja sama pätee myös polymorfismiin. Polymorfismista on kustannuksia kahdella tavalla: se vie enemmän muistia ja virtuaalisten funktioiden kutsu vaatii lisätyötä. Kumpikin johtuu siitä, miten virtuaalifunktiot yleensä toteutetaan. Oletetaan, että meillä on kaksi luokkaa A ja B, joilla on samat jäsenmuuttujat, mutta A sisältää virtuaalifunktioita ja B:n funktiot eivät ole virtuaalisia. Tällöin A-tyyppinen olio vie enemmän muistia kuin B-tyyppinen olio. Voit tehdä yksinkertaisen ohjelman, jossa on kaksi tällaisten luokkien oliota. sizeof()- operaattorilla näet itse eron muistimäärässä. Myöskin virtuaalifunktioita sisältävän ohjelman.exe-tiedosto on suurempi kuin ohjelman, jossa ei ole virtuaalifunktioita. Syy kasvaneeseen muistimäärään on siinä, että kun luomme polymorfistisen luokan olion (kuten A edellä), olioon luodaan erikoinen osoitin. Tätä osoitinta käytetään olion virtuaalifunktioiden kutsumisessa. Osoitin osoittaa tauluun, joka sisältää luokalle luotujen funktioiden osoittimia. Tätä taulua kutsutaan yleensä termillä vtable ja siinä on yksi osoitin luokan jokaista virtuaalista funktiota kohti. 644
25 Virtuaalifunktiot ja polymorfismi polio->teelisaa(); Virtuaalifunktion kutsu osoittimen kautta on epäsuora funktiokutsu class olio jäsenmuuttujat osoitin vtable Jokainen luokan olio, jolla on virtuaalifunktioita, sisältää jäsenmuuttujien lisäksi osoittimen luokan vtable-tauluun. vtable virtuaalinen fctn-soitin virtuaalinen fctn-soitin virtuaalinen fctn-soitin Yksi jokaista virtuaalifunktiota kohti virtuaalinen fctn-soitin Virtuaalifunktioiden osoittimien taulukko luodaan jokaiselle luokalle, jolla on virtuaalifunktioita teetama() teetuo() teelisaa() teevahe() Kun funktiota kutsutaan kantaluokan olioon osoittavan osoittimen avulla, seuraavat tapahtumat suoritetaan: Ensiksi olion vtable-osoitinta käytetään kyseisen luokan vtable-taulukon löytämiseen. Tämän jälkeen etsitään kutsuttava funktio kyseisen luokan vtable-taulukosta, yleensä siirtymän avulla. Lopuksi funktiota kutsutaan epäsuorasti vtable-taulukon osoittimen avulla. Tämä epäsuora kutsu on hieman hitaampi kuin vastaava ei-virtuaalisen funktion kutsu, joten jokainen virtuaalifunktion kutsu hidastaa ohjelman suoritusta hieman. Tämä hidastus on kuitenkin varsin pieni, eikä se aiheuta mitään toimenpiteitä. Muutama tavu enemmän jokaisessa oliossa ja hieman hidastunut funktioiden kutsu ovat mitättömiä verrattuna polymorfismin tehoon ja joustavuuteen. Puhtaat virtuaalifunktiot Saatat joskus törmätä tilanteeseen, jossa on kantaluokka ja useita periytettyjä luokkia sekä virtuaalifunktio, joka on määritelty uudelleen jokaisessa periytetyssä luokassa, mutta funktiota ei voida määritellä kantaluokassa järkevästi. Voit esimerkiksi määritellä kantaluokan Kuvio, josta periytät erilaisia kuvioita esittävät luokat, kuten Ympyra, Suorakaide, Kayra jne. Kuvio-luokkaan voisi kuulua virtuaalifunktio piirra(), jota kutsutaan tietyn kuvion piirtämiseksi, mutta Kuvio-luokka on abstrakti: piirra()-funktiolle ei ole mitään järkevää määrittelyä Kuvio-luokassa. Nyt kuvaan astuu puhdas virtuaalifunktio. Puhtaan virtuaalifunktion päätarkoitus on mahdollistaa periytetyn luokan version kutsumisen polymorfistisesti. Puhdas virtuaalifunktio esitellään muuten samalla syntaksilla kuin tavallinenkin virtuaalifunktio, mutta sen esittelyyn lisätään = 0. Puhtaalla virtuaalifunktiolla ei yleensä ole toteutusta lainkaan. 645
26 C++ Ohjelmoijan käsikirja Jos tämä kaikki kuulostaa sekavalta, näemme seuraavasta esimerkistä, miten puhdas virtuaalifunktio luodaan. Seuraavassa määritellään juuri käsittelemämme Kuvio-luokka: // Yleinen kuvioiden kantaluokka class Kuvio public: // Puhdas virtuaalifunktio, joka piirtää kuvion virtual void piirra() const = 0; // Puhdas virtuaalifunktio, joka siirtää kuvion virtual void siirra(const Piste& uusisijainti) = 0; protected: Piste sijainti; // Kuvion sijainti Kuvio(const Piste& kuvionsijainti) : sijainti(kuvionsijainti) ; Kuvio-luokka sisältää Piste-tyyppisen jäsenmuuttujan (joka on toinen luokkatyyppi), joka sisältää kuvion sijainnin. Se on kantaluokassa, koska jokaisella kuviolla täytyy olla sijainti ja muodostinfunktio alustaa sen. Funktio piirra() on virtuaalinen, koska olemme käyttäneet virtual-avainsanaa ja puhdas, koska = 0 (parametriluettelon perässä) määrittelee, että funktiota ei ole määritelty tässä luokassa. Toisin sanoen piirra()-funktio on puhdas virtuaalifunktio. Myöskin siirra()- funktio on puhdas virtuaalifunktio. Luokkaa, joka sisältää puhtaan virtuaalifunktion, kutsutaan abstraktiksi luokaksi. Tässä tapauksessa Kuvio-luokka sisältää kaksi puhdasta virtuaalifunktiota - piirra() ja siirra() - joten se on varmastikin abstrakti luokka. Tarkastellaan hieman tarkemmin, mitä tämä tarkoittaa. 646 Abstraktit luokat Vaikka Kuvio-luokalla on jäsenmuuttuja ja muodostinfunktio, se ei ole täydellinen olion määrittely, koska funktioita piirra() ja siirra() ei ole määritelty. Tästä syystä emme voi luoda Kuvio-luokan ilmentymiä; se on olemassa vain siitä periytettävien luokkien määrittelyä varten. Koska et voi luoda olioita abstraktista luokasta, et voi käyttää sitä funktion parametrin tyyppinä etkä paluuarvon tyyppinä. Huomaa kuitenkin, että abstraktin luokan osoittimia tai viittauksia voidaan käyttää funktion parametrinä tai paluuarvona. Nyt mieleesi herää kysymys: Jos abstraktin luokan ilmentymää ei kerran voi luoda, miksi se sisältää muodostinfunktion? Vastauksena on se, että muodostinfunktio on olemassa jäsenmuuttujien alustusta varten. Tätä varten abstraktin luokan muodostinfunktiota voidaan kutsua periytetyn luokan muodostinfunktion alkuarvolistasta. Jos yrität kutsua muodostinfunktiota jostain muualta, saat kääntäjän virheilmoituksen. Koska abstraktin luokan muodostinfunktiota ei voi kutsua yleisesti, se on järkevää esitellä luokan suojatuksi jäseneksi, kuten olemme tehneet Kuvio-luokassa. Huomaa, että abstraktin luokan muodostinfunktio ei saa kutsua puhdasta virtuaalifunktiota; puhtaan virtuaalifunktion kutsumisen tulos on määrittelemätön. Jokaisessa luokassa, joka periytetään Kuvio-luokasta, täytyy määritellä sekä piirra()- että siirra()-funktio, ellei se itse ole abstrakti luokka. Tarkemmin sanottuna, jos kaikkia abstraktin
27 Virtuaalifunktiot ja polymorfismi kantaluokan puhtaita virtuaalifunktioita ei määritellä periytetyssä luokassa, puhdas virtuaalifunktio peritään sellaisenaan ja periytetty luokka on myöskin abstrakti luokka. Havainnollistaaksemme tätä, voimme määritellä uuden luokan, Ympyra, jonka kantaluokkana on Kuvio: // Ympyra-luokka class Ympyra: public Kuvio public: Ympyra(Piste keskipiste, double ympyransade) : Kuvio(keskipiste), sade(ympyransade) virtual void piirra() const // Ympyrän keskipiste on kantaluokalta perityssä pisteessä sijainti cout << " Ympyrän keskipiste " << sijainti << " säde " << sade << endl; virtual void siirra(const Piste& uusikeskipiste) sijainti = uusikeskipiste; ; private: double sade; // Ympyrän säde Funktiot piirra() ja siirra() on määritelty, joten kyseessä ei ole abstrakti luokka. Jos toinen olisi jätetty määrittelemättä, Ympyra-luokka olisi ollut abstrakti. Luokka sisältää muodostinfunktion, joka alustaa kantaluokan aliolion kutsumalla kantaluokan muodostinfunktiota. Voit kutsua abstraktin kantaluokan muodostinfunktiota periytetyn luokan muodostinfunktion alkuarvolistasta. Abstrakti luokka voi tietysti sisältää myös ei-puhtaita ja ei-virtuaalisia funktioita. Se voi sisältää myös kuinka monta puhdasta virtuaalifunktiota tahansa, kuten esimerkkimmekin osoittaa. Jos luokassa on yksikin puhdas virtuaalifunktio, luokka on abstrakti. Periytetyn luokan tulee määritellä kantaluokan kaikki puhtaat virtuaaliset funktiot; muutoin se on itsekin abstrakti luokka. Katsotaan toimivaa esimerkkiä abstrakteista luokista. Kokeile itse - Abstrakti luokka Määrittelemme uuden version Laatikko-luokastamme, jossa tilavuus()-funktio on määritelty puhtaaksi virtuaalifunktioksi: class Laatikko public: Laatikko(double pitarvo = 1.0, double syvarvo = 1.0, double korkarvo = 1.0); // Funktio, joka laskee Laatikko-olion tilavuuden virtual double tilavuus() const = 0; 647
28 C++ Ohjelmoijan käsikirja protected: double pituus; double syvyys; double korkeus; ; Emme enää tarvitse tilavuus()-funktion määrittelyä tiedostossa Laatikko.cpp ja jätämme tästä esimerkistä pois myös naytatilavuus()-funktion. Koska Laatikko on nyt abstrakti luokka, emme enää voi luoda sen tyyppisiä olioita. PahviLaatikko- ja VahvaLtk-luokat määrittelevät kummatkin tilavuus()-funktion, joten ne eivät ole abstrakteja luokkia ja voimme käyttää niiden tyyppisiä olioita havainnollistaessamme, että virtuaalinen tilavuus()-funktio toimii kuten ennenkin: // Esimerkki 16.6 Abstraktin kantaluokan käyttö #include <iostream> #include "Laatikko.h" #include "VahvaLtk.h" #include "Pahvil.h" // Laatikko-luokka // VahvaLtk-luokka // PahviLaatikko-luokka using namespace std; int main() cout << endl; VahvaLtk vahval(20.0, 30.0, 40.0); Laatikko* pltk = &vahval; // Talletetaan ValhvaLtk-olion osoite cout << "vahval:n tilavuus on " << pltk->tilavuus() << endl; PahviLaatikko apahviltk(20.0, 30.0, 40.0); pltk = &apahviltk; // Talletetaan PahviLaatikko-olion osoite cout << "apahviltk:n tilavuus on " << pltk->tilavuus() << endl; return 0; Tämä ohjelma saa aikaan tulostuksen: vahval:n tilavuus on apahviltk:n tilavuus on Kuinka se toimii Puhtaan virtuaalifunktion tilavuus() määrittely Laatikko-luokassa varmistaa, että luokkien PahviLaatikko ja VahvaLtk tilavuus()-funktiot ovat myöskin virtuaalisia. Täten voimme kutsua niitä kantaluokan osoittimen avulla ja kutsu ratkaistaan dynaamisesti. Määrittelemme ja alustamme ensin VahvaLtk-olion ja osoittimen pltk: VahvaLtk vahval(20.0, 30.0, 40.0); Laatikko* pltk = &vahval; // Talletetaan ValhvaLtk-olion osoite pltk sisältää nyt VahvaLtk-olion vahval osoitteen. Käytämme kantaluokan Laatikko osoitinta kutsuessamme VahvaLtk-luokan versiota tilavuus()-funktiosta: cout << "vahval:n tilavuus on " << pltk->tilavuus() << endl; 648
29 Virtuaalifunktiot ja polymorfismi Tulostuksesta huomaat, että funktion kutsu ratkaistaan dynaamisesti VahvaLtk-luokan versioon tilavuus()-funktiosta. Seuraavaksi talletamme PahviLaatikko-olion osoitteen osoittimeen pltk: PahviLaatikko apahviltk(20.0, 30.0, 40.0); pltk = &apahviltk; // Talletetaan PahviLaatikko-olion osoite VahvaLtk-olion osoite osoittimessa pltk korvataan PahviLaatikko-olion osoitteella. Tämän jälkeen käytämme osoitinta pltk tulostaessamme PahviLaatikko-olion tilavuuden: cout << "apahviltk:n tilavuus on " << pltk->tilavuus() << endl; Koska pltk sisältää PahviLaatikko-olion osoitteen, kutsu ratkaistaan dynaamisesti PahviLaatikkoluokan tilavuus()-funktioon. Abstrakti luokka rajapintana Joskus abstraktia luokkaa käytetään, koska funktiolla ei ole järkevää määrittelyä luokassa - järkevä määrittely on vain periytetyssä luokassa. Abstraktin luokan käytölle on kuitenkin muitakin käyttötarkoituksia. Abstraktia luokkaa, joka sisältää ainoastaan puhtaita virtuaalifunktioita, voidaan käyttää määrittelemään luokan standardi rajapinta. Tavallisesti se sisältää joukon toisiinsa liittyvien funktioiden esittelyjä, jotka liittyvät jollain tavalla toisiinsa ja tukevat tiettyä toiminnallisuutta - esimerkiksi modeemin avulla kommunikointia. Kuten olemme jo todenneet, tällaisesta abstraktista kantaluokasta periytettyjen luokkien täytyy määritellä kunkin virtuaalifunktion toteutus, mutta virtuaalifunktion toteutustapa riippuu täysin periytetyn luokan toteuttavasta henkilöstä. Abstrakti luokka määrittelee rajapinnan, mutta toteutus (periytetyn luokan kautta) on vapaa ja joustava. Epäsuorat abstraktit kantaluokat Tässä luvussa mainitsimme pikaisesti epäsuoran periytymisen. Jos meillä on kantaluokka ja siitä periytetty luokka, voi olla, että kantaluokka on periytetty vielä ylemmästä kantaluokasta luokkahierarkiassa. Tällöin kaikkein alimman periytetyn luokan epäsuora kantaluokka on tuo kaikkein ylin kantaluokka. Voit luoda niin monitasoisen luokkahierarkian kuin haluat. Pieni lisäys edelliseen esimerkkiin havainnollistaa abstraktien luokkien periytymistä. Lisäksi esimerkki havainnollistaa virtuaalifunktioiden käyttöä periytymisen toisella tasolla (eli luokan kohdalla, joka on periytetty luokasta, joka on periytetty toisesta luokasta). Kokeile itse - Useamman tasoinen periytyminen Voimme määritellä luokan Astia, joka kuvaa yleistä säiliötä, jota voidaan käyttää Laatikkoluokan abstraktina kantaluokkana. Tämä mahdollistaa eri tyyppisten säiliöiden (esimerkiksi pullo ja kannu) periyttämisen Astia-luokasta. Tällöin voimme laskea niiden tilavuuden polymorfistisesti. Kirjoitamme Astia-luokan määrittelyn uuteen otsikkotiedostoon Astia.h: // Astia.h - Abstrakti Astia-luokka #ifndef ASTIA_H #define ASTIA_H 649
30 C++ Ohjelmoijan käsikirja class Astia public: virtual double tilavuus() const = 0; ; #endif Tämä on abstrakti luokka, koska se sisältää puhtaan virtuaalifunktion tilavuus(). Muutamme nyt Laatikko-luokan kantaluokaksi Astia-luokan: // Laatikko.h #ifndef LAATIKKO_H #define LAATIKKO_H #include "Astia.h" class Laatikko : public Astia public: Laatikko(double pitarvo = 1.0, double syvarvo = 1.0, double korkarvo = 1.0); // Funktio, joka laskee Laatikko-olion tilavuuden virtual double tilavuus() const; protected: double pituus; double syvyys; double korkeus; ; #endif Sen lisäksi, että olemme lisänneet Astia-luokan otsikkotiedoston ja periyttäneet Laatikko-luokan Astia-luokasta, olemme myöskin palauttaneet tilavuus()-funktion tavalliseksi virtuaalifunktioksi (ei enää puhdas virtuaalifunktio), joten lisäämme funktion määrittelyn Laatikko.cpp-tiedostoon: double Laatikko::tilavuus() const return pituus * syvyys * korkeus; Nyt lisäämme vielä uuden Astia-luokasta periytetyn luokan, joka määrittelee kannun. Määrittelyn sijoitamme tiedostoon Kannu.h ja toteutuksen tiedostoon Kannu.cpp: // Kannu.h Luokka, joka kuvaa sylinterin muotoista kannua #ifndef KANNU_H #define KANNU_H #include "Astia.h" class Kannu : public Astia public: Kannu(double kannunhalk, double kannunkork); virtual double tilavuus() const; 650
31 Virtuaalifunktiot ja polymorfismi protected: double halkaisija; double korkeus; static const double pi; ; #endif Tämä luokka määrittelee Kannu-olion, joka mallintaa sylinterin muotoista kannua, kuten oluttölkki. Luokka määrittelee pi:n const-tyyppiseksi staattiseksi muuttujaksi, koska sitä tarvitaan luokan jäsenfunktioissa. Luokan staattiset jäsenmuuttujat ovat olemassa, vaikka muodostinfunktiota ei olisi kutsuttu kertaakaan, joten meidän täytyy alustaa pi globaalissa nimiavaruudessa. Voimme tehdä tämän sijoittamalla määrittelyn tiedostoon Kannu.cpp: // Kannu.cpp #include "Kannu.h" Kannu::Kannu(double kannunhalk, double kannunkork) : halkaisija(kannunhalk), korkeus(kannunkork) // Funktio, joka laskee Kannu-olion tilavuuden double Kannu::tilavuus() const return pi * halkaisija * halkaisija * korkeus / 4; // Kannu-luokan määrittelyt const double Kannu::pi = ; // Alustaa staattisen muuttujan Olisimme voineet sijoittaa pi:n määrittelyn myös otsikkotiedostoon luokan määrittelyn perään, mutta meidän olisi tullut varmistaa, että määrittely olisi tullut #ifndef/#endif-komentojen väliin, koska jokainen staattinen jäsenmuuttuja voidaan määritellä ohjelmassa vain kertaalleen. Usko tai älä, voimme testata kaikkia luokkia, uusia ja vanhoja, varsin lyhyellä main()-funktiolla: // Esimerkki 16.7 Epäsuoran kantaluokan käyttö #include <iostream> #include "Laatikko.h" #include "VahvaLtk.h" #include "Pahvil.h" #include "Kannu.h" using namespace std; int main() Laatikko altk(40, 30, 20); Kannu akan(10, 3); PahviLaatikko apahviltk(40, 30, 20); VahvaLtk vahval(40, 30, 20); Astia* pastiat[] = &altk, &akan, &apahviltk, &vahval ; // Laatikko-luokka // VahvaLtk-luokka // PahviLaatikko-luokka // Kannu-luokka 651
32 C++ Ohjelmoijan käsikirja cout << endl; for(int i = 0 ; i < sizeof pastiat / sizeof(pastiat[0]) ; i++) cout << "Tilavuus on " << pastiat[i]->tilavuus() << endl; return 0; Ohjelman tulostus näyttää seuraavalta: Tilavuus on Tilavuus on Tilavuus on Tilavuus on Kuinka se toimii Meillä on esimerkissä kolmitasoinen luokkahierarkia, jota havainnollistetaan seuraavassa kaaviossa: Astia on abstrakti luokka eikä sen ilmentymiä voi luoda Puhdas virtuaalifunktio Astia tilavuus() Kannu- ja Laatikko- luokkien suora kantaluokka PahviLaatikko - ja VahvaLtk-luokkien epäsuora kantaluokka Kannu tilavuus() Laatikko tilavuus() PahviLaatikko - ja VahvaLtk-luokkien suora kantaluokka Funktio tilavuus() on virtuaalinen kaikissa luokissa, joiden suora tai epäsuora kantaluokka on Astia PahviLaatikko tilavuus() VahvaLtk tilavuus() 652 Funktio tilavuus() on virtuaalinen kaikissa luokissa, joiden suorana tai epäsuorana kantaluokkana on Astia-luokka. Jos periytetyssä luokassa ei määritellä kantaluokassa puhtaaksi virtuaalifunktioksi esiteltyä funktiota, funktio peritään puhtaana virtuaalifunktiona, jolloin periytetystä luokasta tulee myöskin abstrakti luokka. Voit testata tätä poistamalla const-esittelyn joko Kannu- tai Laatikko-luokista. Se tekee funktion eri funktioksi kuin kantaluokan puhdas virtuaalifunktio, joten periytetty luokka perii kantaluokan version ja ohjelma ei käänny. Tällä kertaa käytämme osoitin Astia-olioon -tyyppisten osoittimien taulukkoa virtuaalifunktioiden testauksessa: Laatikko altk(40, 30, 20); Kannu akan(10, 3); PahviLaatikko apahviltk(40, 30, 20); VahvaLtk vahval(40, 30, 20);
33 Virtuaalifunktiot ja polymorfismi Astia* pastiat[] = &altk, &akan, &apahviltk, &vahval ; Taulukon alkiot alustetaan neljällä määrittelemällämme eri tyyppisellä Astia-tyypillä: tämä esittelee ja alustaa nelialkioisen taulukon. Tämän jälkeen kutsumme virtuaalista tilavuus()- funktiota for-silmukassa: for(int i = 0 ; i < sizeof pastiat / sizeof(pastiat[0]) ; i++) cout << "Tilavuus on " << pastiat[i]->tilavuus() << endl; Tässä kutsumme tilavuus()-funktiota kaikilla neljällä pastiat-taulukon alkioina olevan osoittimen avulla. Kuten tulostuksesta huomaat, funktiot sidotaan dynaamisesti. Olioiden tuhoaminen osoittimien avulla Kantaluokan osoittimien käyttö periytettyjen luokkien olioiden käsittelyssä on varsin yleistä, koska näin hyödynnetään virtuaalisia funktioita. Tässä piilee kuitenkin ongelma, kun haluat tuhota olion kantaluokan osoittimen avulla. Näemme tämän ongelman, kun lisäämme edellisen esimerkin luokkiin tuhoajafunktiot, jotka tulostavat viestin. Kokeile itse - Olioiden tuhoaminen osoittimien avulla Lisää Astia-luokkaan tuhoajafunktio, joka tulostaa viestin, kun sitä kutsutaan: class Astia public: virtual double tilavuus() const = 0; ~Astia(); ; // Astia.cpp #include <iostream> #include "Astia.h" using namespace std; Astia::~Astia() cout << "Astian tuhoajafunktio" << endl; Tee sama luokille Kannu, Laatikko ja VahvaLtk ja lisää tulostuslause PahviLaatikko-luokan tuhoajafunktioon. Lisäksi sinun tulee lisätä #include-komento iostream-otsikkotiedostoa varten useisiin.cpp-tiedostoihin. Nyt meidän täytyy muuttaa vielä edellisen esimerkin main()-funktiota, jotta pastiat-taulukon alkiot alustetaan vapaasta muistista luotujen olioiden osoitteilla. Lisäksi meidän tulee käyttää delete-operaattoria vapauttamaan muisti, kun olemme lopettaneet. 653
34 C++ Ohjelmoijan käsikirja // Esimerkki 16.8 Olioiden tuhoaminen osoittimien avulla #include <iostream> #include "Laatikko.h" // Laatikko-luokka #include "VahvaLtk.h" // VahvaLtk-luokka #include "Pahvil.h" // PahviLaatikko-luokka #include "Kannu.h" // Kannu-luokka using namespace std; int main() Astia* pastiat[] = new Laatikko(40, 30, 20), new Kannu(10, 3), new PahviLaatikko(40, 30, 20), new VahvaLtk(40, 30, 20) ; cout << endl; for(int i = 0 ; i < sizeof pastiat / sizeof(pastiat[0]) ; i++) cout << "Tilavuus on " << pastiat[i]->tilavuus() << endl; // Poistetan oliot vapaasta muistista for(int i = 0 ; i < sizeof pastiat / sizeof(pastiat[0]) ; i++) delete pastiat[i]; return 0; Kun suoritat ohjelman, se tulostaa: Tilavuus on Tilavuus on Tilavuus on Tilavuus on Astian tuhoajafunktio Astian tuhoajafunktio Astian tuhoajafunktio Astian tuhoajafunktio Kuinka se toimii Tässä on selvästikin vielä virhe: jokaisessa tapauksessa kutsutaan väärää tuhoajafunktiota. Jälleen kerran virhe syntyy siitä, että tuhoajafunktio sidotaan jo käännösaikana, ja koska käytämme delete-operaattoria Astia-olioon osoittavan osoittimen avulla, Astia-luokan tuhoajafunktiota kutsutaan joka kerta. Tämän voimme korjata tuhoajafunktioiden dynaamisella sidonnalla. 654 Virtuaaliset tuhoajafunktiot Varmistaaksemme, että vapaasta muistista varattujen olioiden kohdalla kutsutaan oikeita tuhoajafunktioita, meidän tulee käyttää luokkien virtuaalisia tuhoajafunktioita. Virtuaalisen tuhoajafunktion toteutamme yksinkertaisesti lisäämällä virtual-avainsanan luokan tuhoajafunktion esittelyyn. Se kertoo kääntäjälle, että kun tuhoajafunktiota kutsutaan osoittimen tai viittausparametrin avulla, tulee käyttää dynaamista sidontaa, jotta tuhoajafunktio valitaan suoritusaikana. Tämä toimii siitä huolimatta, että tuhoajafunktioiden nimet ovat eri; tuhoajafunktioita käsitellään erikoistapauksina tässä tarkoituksessa.
35 Virtuaalifunktiot ja polymorfismi Kokeile itse - Virtuaaliset tuhoajafunktiot Voimme havainnollistaa tätä muuttamalla edellistä esimerkkiä: meidän tulee vain lisätä virtual-avainsana kantaluokan tuhoajafunktion esittelyyn: class Astia public: virtual double tilavuus() const = 0; virtual ~Astia(); ; Kaikkien kolmen periytetyn luokan tuhoajafunktiot ovat virtuaalisia, koska kantaluokan tuhoajafunktio on esitelty virtuaaliseksi. Jos suoritat esimerkin ohjelman uudelleen, saat seuraavanlaisen tulostuksen: Tilavuus on Tilavuus on Tilavuus on Tilavuus on Laatikon tuhoajafunktio Astian tuhoajafunktio Kannun tuhoajafunktio Astian tuhoajafunktio Pahvilaatikon tuhoajafunktio Laatikon tuhoajafunktio Astian tuhoajafunktio VahvaLtk:n tuhoajafunktio Laatikon tuhoajafunktio Astian tuhoajafunktio Kuinka se toimii Minkä suuren eron pieni avainsana saakaan aikaan! Tulostus kertoo, että Laatikko-olion tuhoaminen saa aikaan kahden tuhoajafunktion kutsun: Laatikko-luokan tuhoajafunktion ja Astia-luokan tuhoajafunktion. Kannu-olion tuhoaminen saa myös aikaan kahden tuhoajafunktion kutsumisen. Olioiden PahviLaatikko ja VahvaLtk tuhoamiset saavat kummatkin aikaan kolmen tuhoajafunktion kutsun: kyseisen olion luokan tuhoajafunktion, suoran kantaluokan tuhoajafunktion ja lopuksi epäsuoran kantaluokan tuhoajafunktion. Oliot tuhotaan aina tällä tavalla, jokaisen kantaluokan tuhoajafunktiota kutsutaan peräkkäin luokkahierarkiassa ylöspäin kunnes kaikkein ylin kantaluokka saavutetaan. Kutsujärjestys on vastakkainen kuin periytetyn luokan olion yhteydessä kutsuttujen muodostinfunktioiden kutsujärjestys. Kun käytät periytymistä, sinun tulisi aina esitellä kantaluokan tuhoajafunktio virtuaaliseksi. Tästä seuraa pieni lisätyö tuhoajafunktion suorituksessa, mutta et huomaa sitä useimmissa tapauksissa. Virtuaalisen tuhoajafunktion käyttö varmistaa, että oliot tuhotaan oikein ja näin vältetään muutoin mahdollisesti muodostuvat ongelmat. 655
36 C++ Ohjelmoijan käsikirja Tyyppien tunnistus suoritusaikana Kuten jo tiedätkin, kantaluokan osoitin voi sisältää minkä tahansa periytetyn luokan olion osoitteen. Tämä tarkoittaa sitä, että tietyllä hetkellä ei välttämättä ole aivan selvää, minkä tyyppiseen olioon osoitin osoittaa. Osoitin voidaan välittää parametrinä tai se voi olla luokan jäsen; kummassakin tapauksessa osoite asetetaan muualla. Sama koskee viittausparametrejä - parametriä, joka on tyyppiä viittaus kantaluokkaan, voidaan käyttää minkä tahansa kantaluokasta perityn luokan olion kanssa. Tällaisissa tilanteissa voi olla hyödyllistä tietää osoittimen tai viittauksen tyyppi - ohjelman jatkosuoritus saattaa riippua tyypistä. Tämä on erityisen hyödyllistä silloin, kun luokkahierarkiassa on kaksi tai useampia oksia, kuten aikaisemmin käyttämässämme hierarkiassa Kannu oli toisessa oksassa ja PahviLaatikko toisessa. Operaattorilla typeid() saat tyypin selville suoritusaikana. Jotta voit käyttää operaattoria, sinun tulee sisällyttää lähdetekstiin otsikkotiedosto typeinfo, joka sisältää type_info-luokan määrittelyn. Operaattori typeid() palauttaa type_info-tyyppisen olion - tai tarkemmin sanottuna tyyppiä const std::type_info olevan olion. type_info-luokassa on toteutettu ==-operaattori, joten voit verrata type_info-olioita. Tietyn tyypin kohdalla saat type_info-olion lausekkeella typeid(tyyppi). Esimerkiksi lauseke typeid(laatikko*) palauttaa type_info-olion, joka kuvaa Laatikko*-tyyppiä. Voit testata, osoittaako esimerkiksi osoitin pastia PahviLaatikko-luokasta periytetyn luokan olioon, lauseella: if(typeid(*pastia) == typeid(pahvilaatikko)) cout << Osoitin on tyyppiä PahviLaatikko << endl; else cout << Osoitin ei ole tyyppiä PahviLaatikko << endl; Käytännössä käytät typeid()-operaattoria tätä monimutkaisemmissa tilanteissa: esimerkiksi silloin, kun olet kutsumassa tietyn luokan ei-virtuaalista funktiota. Ylimmän tason const-määre jätetään tässä huomioimatta, aivan samaan tapaan kuin funktioiden uudelleenmäärittelyssä. Eli tyyppi const PahviLaatikko palauttaa aivan saman type_info-olion kuin tyyppi PahviLaatikko. Kun käytät typeid()-operaattoria saadaksesi selville dynaamisen tyypin, sinun tulee yleensä käyttää *-operaattoria, jotta todella käytät osoittimen osoittavaa oliota, eikä kantaluokan oliota. Näin teimme yllä olevassa esimerkissäkin. Viittausparametrien kohdalla käytät tietysti vain parametrin nimeä: void Laatikko::teeTuo(Astia& rastia)... if(typeid(rastia) == typeid(pahvilaatikko)) cout << PahviLaatikko-tyyppinen arvo välitettiin << endl; else cout << PahviLaatikko-tyyppistä arvoa ei välitetty << endl;
37 Virtuaalifunktiot ja polymorfismi Tässä teetuo() on keksitty Laatikko-luokassa määritelty funktio, joka hyväksyy minkä tahansa Astia-luokasta periytetyn luokan olion. Koodi testaa, onko parametrin viittaaman olion tyyppi PahviLaatikko. Tietysti voisit testata mitä tahansa Astia-luokasta periytettyä luokkatyyppiä. Muista, että jos haluat käyttää typeid()-operaattoria, sinun tulee sisällyttää lähdetekstitiedostoon otsikkotiedosto typeinfo. Sinun ei tarvitse käyttää typeid()-operaattoria ohjelmissasi kovinkaan runsaasti. Jos käytät typeid()-operaattoria useasti, et ehkä käytä polymorfismia aina kuin pitäisi. typeid()- operaattorin käyttö tekee ohjelmastasi usein varsin jäykän - kirjoitat ohjelmaan tiettyjen luokkien testauksia, jotka tulee muuttaa, jos lisäät uusia luokkia luokkahierarkiaasi. Osoittimet luokan jäseniin Olemme jo nähneet, miten määrittelemme osoittimen mihin tahansa muuttujaan. Olemme myöskin nähneet, miten määrittelemme osoittimen funktioihin. Voit käyttää osoitinta tallettamaan myös tietyn luokan olion jäsenmuuttujan osoitteen (jos siihen päästään käsiksi). Et kuitenkaan voi käyttää funktio-osoitinta jäsenfunktion osoitteen tallettamiseen, vaikka osoittimen tyyppi vastaisikin jäsenfunktion allekirjoitusta. Tämä siitä syystä, että jäsenfunktio käyttää luokan tyyppiä osana omaa tyyppiään, joten jäsenfunktion tyyppi on aina eri kuin tavallisen funktion, jolla on sama parametriluettelo. Voit kuitenkin määritellä osoittimia luokan jäseniin ja tällainen osoitin voi osoittaa jäsenmuuttujiin tai jäsenfunktioihin. Luokan jäseniin osoittavien osoittimien avulla voit tehdä ohjelmistasi yleisempiä. Osoitin luokan jäseneen voi osoittaa useampaan yhteensopivaan jäseneen ja sitä voidaan käyttää osoittamaan kyseisen luokan minkä tahansa olion jäseneen. Katsotaan ensiksi osoittimia jäsenmuuttujiin, koska ne ovat yksinkertaisempia kuin osoittimet jäsenfunktioihin. Osoittimet jäsenmuuttujiin Tavallinen osoitin sisältää tietyn muuttujan osoitteen. Kyseessä voi olla tavallinen muuttuja tai luokan olion jäsenmuuttuja, mutta molemmissa tapauksissa osoitin sisältää tietyn muistiosoitteen. Osoitin luokan jäsenmuuttujaan toimii eri tavalla. Se sisältää luokkatyypin jäsenmuuttujan osoitteen ja se viittaa tiettyyn muistiosoitteeseen vain silloin, kun se on yhdessä luokan olion kanssa. 657
38 C++ Ohjelmoijan käsikirja Havainnollistaaksemme, mitä tämä tarkoittaa, palataan esimerkin 16.5 koodiin ja muutetaan Laatikko-luokan määrittelyä, jotta näemme, miten osoittimet jäsenmuuttujiin toimivat. Muutetaan jäsenmuuttujien saantitapa julkiseksi. (Teemme tämän vain väliaikaisesti; palautamme sen suojatuksi aivan kohta.) Luokan määrittely näyttää nyt seuraavalta: class Laatikko public: Laatikko(double pitarvo = 1.0, double syvarvo = 1.0, double korkarvo = 1.0); // Funktio, joka laskee Laatikko-olion tilavuuden virtual double tilavuus() const; public: double pituus; double syvyys; double korkeus; ; Koska kaikki jäsenmuuttujat ovat tyyppiä double, voimme esitellä osoittimen jäsenmuuttujaan, jota voidaan käyttää niiden kaikkien kanssa. Osoittimen tyyppi on double Laatikko::*. Esittely on seuraavanlainen: double Laatikko::* pdata; Näin monimutkaisen näköisen tyypinnimen kohdalla kannattaa tehdä typedefkomennolla nimen synonyymi. Voit käyttää seuraavanlaista lausetta: typedef double Laatikko::* pltkjasen; Tällä luot synonyymin pltkjasen, jota voit käyttää tyypin double Laatikko::* sijaan. Voit nyt sijoittaa luokan syvyys-jäsenen osoitteen tähän osoittimeen lauseella: pdata = &Laatikko::syvyys; pdata ei tietystikään osoita mihinkään tiettyyn tietoon, koska se ei ole osoitin luokan olion jäsenmuuttujaan. Se on osoitin luokan jäseneen yleisellä tasolla. Se viittaa tiettyyn muistiosoitteeseen vain, kun sitä käytetään Laatikko-tyyppisen olion yhteydessä. 658! Osoitin jäsenmuuttujaan on erittäin hyödyllinen silloin, kun luokassasi on useita saman tyyppisiä jäsenmuuttujia. Jos samantyyppisiä jäsenmuuttujia on vain yksi, voit viitata siihen yhtä hyvin suoraankin. Hyödyntääksemme osoittimia luokan jäsenmuuttujiin, meidän tulee ensin tarkastella joitakin uusia operaattoreita.
39 Osoitin jäseneen -valintaoperaattori Virtuaalifunktiot ja polymorfismi Tähän saakka olemme käyttäneet osoittimia luokan jäsenmuuttujiin. Kuten aikaisemmin mainitsimme, voit käyttää sitä ainoastaan tietyn olion jäsenten käsittelyyn. Tällaista osoitinta täytyy aina käyttää yhdessä olion, viittauksen olioon tai osoittimen olioon kanssa. Esitellään ja määritellään Laatikko-tyyppinen olio: Laatikko omaltk(20.0, 30.0, 40.0); // esitellään laatikko Oletetaan, että pdata on määritelty kuten edellä. Tällöin voimme käyttää sitä viittaamaan omaltk-olion syvyys-jäseneen. Ensiksi käytämme omaltk-olion nimeä suoraan. Voimme viitata omaltk-olion syvyys-jäseneen lausekkeella omaltk.*pdata. Voimme käyttää tätä lauseketta esimerkiksi tulostaessamme omaltk-olion syvyys-jäsenen lauseessa: cout << Jäsenmuuttujan arvo on << omaltk.*pdata << endl; Tässä käytämme suora osoitin jäseneen -valintaoperaattoria.*. Kuten näet, se on yhdistelmä jäsenen valinta -operaattorista (.) ja *-operaattorista sovellettuna jäsenmuuttujan osoittimeen..*-operaattoria käytetään aina valitsemaan luokan olion jäsen yhdistämällä luokan olio (tai viittaus luokan olioon) jäsenmuuttujan osoittimeen. Tässä osoittimeen pdata sovelletaan ensin *- operaattoria ja sitten valitaan jäsen - eli itse asiassa lauseke on sama kuin omaltk.syvyys. Seuraavaksi voimme työskennellä omaltk-olion osoittimen kanssa. Jotta voimme tehdä näin, meidän tulee esitellä ja alustaa osoitin Laatikko-olioon seuraavanlaisella esittelyllä: Laatikko* pltk = &omaltk; Käsittelemme omaltk-olion jäsenmuuttujaa tämän osoittimen avulla sekä pdata-jäsenen osoittimen avulla. Jotta voimme tehdä tämän, meidän tulee käyttää epäsuoraa osoitinta jäseneen -valintaoperaattoria ->* seuraavasti: cout << Jäsenmuuttujan arvo on << pltk->*pdata << endl; Tässä yhdistetään epäsuora jäseneen viittaus -operaattori (->) ja *-operaattori. Katsotaan nyt, miten tämä kaikki rakennetaan esimerkkiohjelman muotoon. Kokeile itse - Osoitin jäsenmuuttujaan Kun Laatikko-luokan jäsenmuuttujat on määritelty julkisiksi edellä käsitellyllä tavalla, voimme kokeilla osoittimia jäsenmuuttujiin seuraavalla ohjelmalla: // Esimerkki 16.9 Jäsenmuuttujiin osoittavien osoittimien käyttö #include <iostream> #include "Laatikko.h" // Laatikko-luokka #include "VahvaLtk.h" // VahvaLtk-luokka #include "Pahvil.h" // PahviLaatikko-luokka using namespace std; 659
40 C++ Ohjelmoijan käsikirja typedef double Laatikko::* pltkjasen; // Osoitin jäsenmuuttujaan int main() Laatikko omaltk(20.0, 30.0, 40.0); // Esitellään kantalaatikko VahvaLtk vahval(35.0, 45.0, 55.0); // Esitellään periytetty laatikko PahviLaatikko apahviltk(48.0, 58.0, 68.0);// Toinen periytetty laatikko pltkjasen pdata = &Laatikko::pituus; // Osoitin laatikon jäsenmuuttujaan cout << endl; // Käytetään osoitinta luokan jäsenmuuttujaan yhdessä luokan olion kanssa cout << "omaltk:n pituus-jäsen on " << omaltk.*pdata << endl; pdata = &Laatikko::syvyys; cout << "omaltk:n syvyys-jäsen on " << omaltk.*pdata << endl; pdata = &Laatikko::korkeus; cout << "omaltk:n korkeus-jäsen on " << omaltk.*pdata << endl; cout << "vahval:n korkeus-jäsen on " << vahval.*pdata << endl; cout << "apahviltk:n korkeus-jäsen on " << apahviltk.*pdata << endl; Laatikko* pltk = &omaltk; // Osoitin laatikkoon // Käytetään osoitinta luokan jäsenmuuttujaan kantaluokan osoittimen kanssa cout << "omaltk:n korkeus-jäsen on " << pltk->*pdata << endl; pltk = &vahval; cout << "vahval:n korkeus-jäsen on " << pltk->*pdata << endl; cout << endl; return 0; Jos käännät ja suoritat tämän ohjelman, saat tulostuksen: omaltk:n pituus-jäsen on 20 omaltk:n syvyys-jäsen on 30 omaltk:n korkeus-jäsen on 40 vahval:n korkeus-jäsen on 55 apahviltk:n korkeus-jäsen on 68 omaltk:n korkeus-jäsen on 40 vahval:n korkeus-jäsen on 55 Kuinka se toimii Ennen main()-funktion määrittelyä määrittelemme tyypin pltkjasen seuraavalla lauseella: typedef double Laatikko::* pltkjasen; // Osoitin jäsenmuuttujaan 660 Tämä määrittelee pltkjasen tyypin osoitin Laatikko-olion double-tyyppiseen jäseneen synonyymin. Määriteltyämme luokkien Laatikko, PahviLaatikko ja VahvaLtk oliot, esittelemme ja alustamme osoittimen Laatikko-luokan double-tyyppiseen jäseneen esittelyllä: pltkjasen pdata = &Laatikko::pituus; // Osoitin laatikon jäsenmuuttujaan
41 Virtuaalifunktiot ja polymorfismi pdata osoittaa nyt Laatikko-luokan pituus-jäseneen, joten voimme käyttää sitä viittaamaan minkä tahansa Laatikko-olion pituus-jäseneen. Tulostamme omaltk-olion pituus-jäsenen lauseella: cout << "omaltk:n pituus-jäsen on " << omaltk.*pdata << endl; Koska pdata on osoitin, voimme asettaa sen osoittamaan Laatikko-luokan toiseen jäseneen: pdata = &Laatikko::syvyys; Nyt pdata osoittaa luokan syvyys-jäseneen. Muista, että pdata-osoittimeen voi sijoittaa vain double-tyyppisen jäsenmuuttujan osoitteen. Jos luokalla olisi muun tyyppisiä jäseniä, esimerkiksi string-tyyppisiä, meidän tulisi määritellä sitä varten oma osoitin. Voimme nyt tulostaa omaltk-olion syvyys-jäsenen arvon varsin samanlaisella lauseella kuin aikaisemminkin: cout << "omaltk:n syvyys-jäsen on " << omaltk.*pdata << endl; Jotta asia varmasti tulisi selväksi, talletamme Laatikko-olion pituus-jäsenen osoitteen ja tulostamme omaltk-olion tämän jäsenen arvon seuraavilla lauseilla: pdata = &Laatikko::korkeus; cout << "omaltk:n korkeus-jäsen on " << omaltk.*pdata << endl; Kaikki Laatikko-luokasta periytettyjen luokkien oliot sisältävät tietysti Laatikko-aliolion, joten voimme käyttää tätä osoitinta jäsenmuuttujaan myös PahviLaatikko- ja VahvaLtk-olioiden kohdalla. Kun olemme esitelleet ja alustaneet pltk-osoittimen, käytämme epäsuoraa osoitinta jäseneen - valintaoperaattoria tulostamaan omaltk-olion korkeus-jäsenen arvon: Laatikko* pltk = &omaltk; cout << "omaltk:n korkeus-jäsen on " << pltk->*pdata << endl; Tämä toimii tietysti myös silloin, kun kantaluokan osoitin sisältää periytetyn luokan olion osoitteen: pltk = &vahval; cout << "vahval:n korkeus-jäsen on " << pltk->*pdata << endl; Tämä tulostaa pltk-osoittimen osoittaman olion korkeus-jäsenen arvon, joten saamme itse asiassa arvon vahval.korkeus. Osoittimet jäsenfunktioihin Tyyppiin osoitin luokan jäsenfunktioon kuuluu luokan tyyppi sekä funktion parametriluettelo ja paluuarvon tyyppi. Tämä tarkoittaa sitä, että tällainen osoitin kuuluu tietylle luokalle, eikä sitä voida käyttää tallettamaan minkään muun luokan jäsenfunktioiden osoitteita. Tätä lukuunottamatta ne noudattavat samoja periaatteita kuin luvussa 9 käsittelemämme osoittimet funktioihin. Osoittimen jäsenfunktioon esittely voi näyttää hieman monimutkaiselta, joten käsitellään nyt tiettyä ilmentymää. 661
42 C++ Ohjelmoijan käsikirja Palautetaan Laatikko-luokan jäsenmuuttujat suojatuiksi ja lisätään julkiset jäsenmuuttujat, joilla luemme jäsenmuuttujien arvot: class Laatikko public: Laatikko(double pitarvo = 1.0, double syvarvo = 1.0, double korkarvo = 1.0); // Funktio, joka näyttää olion tilavuuden void naytatilavuus() const; // Funktio, joka laskee Laatikko-olion tilavuuden virtual double tilavuus() const; // Luetaan jäsenmuuttujien arvot double luepituus() const return pituus; double luesyvyys() const return syvyys; double luekorkeus() const return korkeus; protected: double pituus; double syvyys; double korkeus; ; Voimme nyt esitellä osoittimen jäsenfunktioon, jota voimme sitten käyttää tallettamaan minkä tahansa kolmesta lisäämästämme funktiosta osoitteen. Osoitin esitellään seuraavasti: double(laatikko::*plue)() const; Kuten osoitin jäsenmuuttujaankin, tämä osoittaa luokan jäsenfunktioon ja tulkitaan tietyn luokkaolion funktion osoittimeksi. Luokan nimellä ja osoittimella määritellään luokan osoitin. Yleisesti ottaen, kun haluat esitellä osoittimen luokka_tyyppi-luokan funktioon, voit kirjoittaa: paluuarvon_tyyppi(luokka_tyyppi::*osoittimen_nimi)(parametrien_tyyppi_luettelo); Tämä on varsin monimutkainen esittely. Tästä syystä tällaiset tyypit esitellään usein typedef:n avulla. Yllä olevan esittelyn sijaan voimme aloittaa ensin määrittelemällä synonyymi osoittimen plue tyypille lauseella: typedef double(laatikko::*pltkfunktio)() const; Jäsenfunktion osoittimen esittelyn yleinen muoto typedef:n avulla on: typedef paluuarvon_tyyppi(luokka_tyyppi::*osoittimen_tyyppi)(parametrien_tyyppi_luettelo); 662 Tämä määrittelee tyypin osoittimen_tyyppi. Tämän tyyppiset osoittimet voivat tallettaa minkä tahansa luokka_tyyppi-luokan funktion osoitteen, jos funktion paluuarvon tyyppi on paluuarvon_tyyppi ja parametrien tyypit on lueteltu parametrien_tyyppi_luettelo:ssa. Nyt voimme käyttää yllä määriteltyä tyyppiä pltkfunktio osoittimen esittelyssä: pltkfunktio plue = &Laatikko::luePituus;
43 Virtuaalifunktiot ja polymorfismi Sen lisäksi, että tämä lause esittelee osoittimen plue, se myöskin alustaa sen funktion luepituus() osoitteella. plue voi tietysti osoittaa ainoastaan Laatikko-luokan jäsenfunktioihin ja tällöinkin vain silloin, kun niiden paluuarvon tyyppi ja parametriluettelo vastaa typedef-määrittelyä. Tämä tarkoittaa myös sitä, että plue voi osoittaa ainoastaan const-tyyppisiin jäsenfunktioihin. Jäsenfunktioiden osoittimia käytetään yhdessä luokan olion, olion viittauksen tai osoittimen kanssa. Tällöin käytetään samoja operaattoreita kuin näimme jäsenmuuttujien osoittimien yhteydessä. Katsotaan jäsenfunktioiden osoittimia toisessa esimerkissä. Kokeile itse - Jäsenfunktioiden osoittimet Jos olemme määritelleet Laatikko-luokan edellä kerrotulla tavalla, voimme kutsua Laatikkoluokan jäseniä jäsenfunktioiden osoittimilla seuraavaan tapaan: // Esimerkki Osoitin jäsenfunktioon #include <iostream> #include "Laatikko.h" #include "VahvaLtk.h" #include "pahvil.h" // Laatikko-luokka // VahvaLtk-luokka // PahviLaatikko-luokka using namespace std; typedef double(laatikko::*pltkfunktio)() const; // Osoitin jäsenfunktioon -tyyppi int main() Laatikko omaltk(20.0, 30.0, 40.0); VahvaLtk vahval(35.0, 45.0, 55.0); PahviLaatikko apahviltk(48.0, 58.0, 68.0); pltkfunktio plue = &Laatikko::luePituus; // Esitellään kantalaatikko // Esitellään periytetty luokka // Toinen periytetty laatikko // Osoitin jäsenfunktioon cout << endl; // Kutsutaan olion jäsenfunktiota osoittimen avulla cout << "omaltk:n pituus-jäsen on " << (omaltk.*plue)() << endl; plue = &Laatikko::lueSyvyys; cout << "omaltk:n syvyys-jäsen on " << (omaltk.*plue)() << endl; plue = &Laatikko::lueKorkeus; cout << "omaltk:n korkeus-jäsen on " << (omaltk.*plue)() << endl; // Toimii myös periytetyn luokan olioiden kohdalla cout << "vahval:n korkeus-jäsen on " << (vahval.*plue)() << endl; cout << "apahviltk:n korkeus-jäsen on " << (apahviltk.*plue)() << endl; Laatikko* pltk = &omaltk; // Osoitin kantaluokkaan // Kutsutaan funktiota luokan olion osoittimen avulla cout << "omaltk:n korkeus-jäsen on " << (pltk->*plue)() << endl; pltk = &vahval; cout << "vahval:n korkeus-jäsen on " << (pltk->*plue)() << endl; 663
44 C++ Ohjelmoijan käsikirja cout << endl; return 0; Ohjelman tulostus näyttää seuraavalta: omaltk:n pituus-jäsen on 20 omaltk:n syvyys-jäsen on 30 omaltk:n korkeus-jäsen on 40 vahval:n korkeus-jäsen on 55 apahviltk:n korkeus-jäsen on 68 omaltk:n korkeus-jäsen on 40 vahval:n korkeus-jäsen on 55 Kuinka se toimii typedef määrittelee synonyymin Laatikko-luokan jäsenfunktion osoitin -tyypille: typedef double(laatikko::*pltkfunktio)() const; // Osoitin jäsenfunktioon -tyyppi Tämän tyyppiset osoittimet voivat osoittaa ainoastaan Laatikko-luokan jäsenfunktioihin, joilla ei ole parametrejä, paluuarvon tyyppi on double ja ovat const-tyyppisiä. Funktio, joka ei täytä näitä kaikkia ehtoja, tarvitsee oman osoitintyyppinsä. Käytämme main()-funktiossa määrittelemäämme tyyppiä osoittimen plue esittelyssä: pltkfunktio plue = &Laatikko::luePituus; // Osoitin jäsenfunktioon Voimme käyttää plue-osoitinta tallettamaan minkä tahansa Laatikko-luokan kolmesta jäsenfunktiosta, jotka lukevat jäsenmuuttujan arvon, osoitteen. Tämä siksi, että niillä kaikilla on samat funktion allekirjoitukset ja paluuarvon tyypit. Tässä se alustetaan jäsenfunktion luepituus() osoitteella. Kutsuaksemme omaltk-olion luepituus()-funktiota, käytämme suoraa osoitin jäsenfunktioonvalintaoperaattoria: cout << "omaltk:n pituus-jäsen on " << (omaltk.*plue)() << endl; Sulkeet lausekkeen omaltk.*plue ympärillä ovat pakolliset - ilman niitä lause ei käänny. Tämä siitä syystä, että funktion kutsu -operaattori () on suoritusjärjestyksessä.*-operaattoria ennen. Ilman sulkeita lauseke vastaa lauseketta omaltk.*(plue()). Tällöin yritämme soveltaa suoraa jäsenen viittausoperaattoria globaalissa nimiavaruudessa olevan plue()-funktion palauttamaan paluuarvoon. Sama pätee myöhemmin ohjelmassa, kun käytämme epäsuoraa jäsenen viittausoperaattoria kutsuessamme jäsenfunktiota luokan olion kautta: cout << "omaltk:n korkeus-jäsen on " << (pltk->*plue)() << endl; 664 Jälleen funktion kutsu -operaattori on suoritusjärjestyksessä ennen epäsuoraa jäseneen viittausoperaattoria, joten meidän täytyy lisätä sulkeet. Kuten huomaat esimerkin lopputulostuksesta, voimme kutsua periytetyn luokan funktiota osoittimen plue avulla samaan tapaan kuin teimme jäsenmuuttujan osoittimen kohdalla.
45 Osoittimen jäseneen välittäminen funktiolle Virtuaalifunktiot ja polymorfismi Jäsenmuuttujan parametri voi olla osoitin jäseneen. Ne voivat olla osoittimia jäsenmuuttujiin tai osoittimia jäsenfunktioihin. Tarkastellaan esimerkkiä jälkimmäisestä. Jos sananlaskun tapaan käytämme moukaria pähkinän särkemiseen, voimme kirjoittaa funktion, joka laskee Laatikko-olion minkä tahansa sivun pinta-alan Laatikko-luokan jäsenfunktioksi, jolla on kaksi parametriä, jotka ovat osoittimia jäsenfunktioihin: class Laatikko public: double sivupinta_ala(double (Laatikko::*pLueSivu1)() const. double (Laatikko::*pLueSivu2)() const) return (this->*pluesivu1)() * this->*pluesivu2)(); // Loput luokasta kuten ennenkin ; Yllä oleva koodi näyttää parametrit koko komeudessaan, vaikka saat koodin näyttämään varsin yksinkertaiselta määrittelemällä ensin tyypin pltkfunktio edellisen esimerkin mukaan: typedef double(laatikko::*pltkfunktio)() const; // Osoitin jäsenfunktioon -tyyppi Nyt funktion määrittely muuttuu muotoon: class Laatikko public: double sivupinta_ala(double (pltkfunktio pluesivu1, pltkfunktio pluesivu2) return (this->*pluesivu1)() * this->*pluesivu2)(); // Loput luokasta kuten ennenkin ; Esimerkiksi Laatikko-luokan olion omaltk sivun pinta-alan saat selville lauseella: cout << Sivun pinta_ala on << omaltk.sivupinta_ala(&laatikko::luekorkeus, &Laatikko::luePituus) << endl; Tämä välittää jäsenfunktioiden luekorkeus() ja luepituus() osoitteet parametreinä, joten niitä käytetään sivun mittojen selville saamiseen pinta-alan laskennassa. Voit yhtä hyvin kutsua tätä funktiota periytetyn luokan olion kohdallakin, koska funktio peritään. Voit tietysti kutsua sitä myös Laatikko-olion kantaluokan osoittimen kautta tai luokkien PahviLaatikko ja VahvaLtk olioiden kautta. 665
46 C++ Ohjelmoijan käsikirja Yhteenveto Tässä luvussa käsittelimme periytyvyyden perusideat. Seuraavat perusasiat tulisi pitää kirkkaina mielessä: Polymorfismiin kuuluu funktion kutsuminen osoittimen tai viittauksen kautta. Tällöin kutsu sidotaan dynaamisesti - eli ohjelman suoritusaikana. Funktio voidaan määritellä kantaluokassa virtuaaliseksi (virtual). Tällöin kaikki tällaiset perityt funktiot ovat virtuaalisia myös periytetyissä luokissa. Kun virtuaalifunktiota kutsutaan osoittimen tai viittauksen avulla, kutsu sidotaan dynaamisesti: kutsussa käytettävän olion tyyppi määrittelee käytettävän funktion. Virtuaalifunktion kutsu, joka tapahtuu olion ja suoran jäseneen viittausoperaattorin avulla, sidotaan staattisesti - eli käännösaikana. Jos kantaluokassa on virtuaalifunktioita, kantaluokan tuhoajafunktio tulisi esitellä myöskin virtuaaliseksi. Näin varmistetaan oikean tuhoajafunktion käyttö dynaamisesti luotujen periytetyn luokan olioiden kohdalla. Puhtaalla virtuaalifunktiolla ei ole määrittelyä. Kantaluokan virtuaalifunktio voidaan määritellä puhtaaksi virtuaalifunktioksi lisäämällä = 0 funktion esittelyn perään. Luokkaa, jolla on yksi tai useampia puhtaita virtuaalifunktioita, kutsutaan abstraktiksi luokaksi, jonka ilmentymiä (olioita) ei voi luoda. Periytetyssä luokassa pitää määritellä kaikki perityt puhtaat virtuaalifunktiot. Jos niitä ei määritellä, periytetystä luokasta tulee myöskin abstrakti luokka, eikä luokan ilmentymiä voi luoda. Virtuaalifunktion parametrien oletusarvot sijoitetaan staattisesti. Eli jos virtuaalifunktion kantaluokan versiolla on parametrien oletusarvoja, periytetyssä luokassa määriteltyjä parametrien oletusarvoja ei huomioida dynaamisesti sidotuissa funktion kutsuissa. Luokan jäseniin osoittavia osoittimia on mahdollista määritellä. Ne voivat olla osoittimia jäsenmuuttujiin tai osoittimia jäsenfunktioihin. Tällaista osoitinta voidaan käyttää yhdessä olion, olion viittauksen tai olion osoittimen yhteydessä, kun viitataan sellaisen olion jäseneen, joka on määritelty osoittimen avulla. 666
47 Harjoituksia Virtuaalifunktiot ja polymorfismi 16.1 Kuten edellisenkin luvun harjoituksissa, luo kantaluokka Elain, joka sisältää kaksi jäsenmuuttujaa: string-tyyppinen jäsen nimi, joka sisältää eläimen nimen (kuten Fido ) ja kokonaisluku paino, joka sisältää Elain-olion painon paunoissa. Lisäksi lisää julkinen virtuaalinen jäsenfunktio kuka(), joka palauttaa string-tyyppisen merkkijonon, joka sisältää Elain-olion nimen ja painon. Lisää myöskin puhdas virtuaalifunktio aani(), joka periytetyssä luokassa palauttaa string-tyyppisen merkkijonon, joka kertoo, millaista ääntä kyseinen eläin pitää. Periytä Elain-luokasta vähintään kolme luokkaa Lammas, Koira ja Lehma, joiden julkinen kantaluokka on Elain. Toteuta kunkin luokan aani()-funktiot. Määrittele luokka Elaintarha, johon voidaan taulukkoon tallettaa korkeintaan 50 erityyppistä eläintä (käytä osoitintaulukkoa). Kirjoita main()-funktio, joka luo satunnaisessa järjestyksessä annetun määrän periytettyjen luokkien olioita ja talleta osoittimet Elaintarha-olioon. Käytä Elaintarha-olion jäsenfunktiota tulostamaan kunkin Elaintarha-olion tiedot ja äänen Määrittele luokka KantaPituus, joka tallettaa pituuden kokonaisina millimetreinä ja jolla on jäsenfunktio pituus(), joka palauttaa double-tyyppisen arvon, joka kuvaa pituutta. Periytä luokat Tuumia, Metreja ja Jaardeja KantaPituus-luokasta, jotka kaikki määrittelevät uudelleen kantaluokan pituus()-funktion, joka palauttaa double-tyyppisen pituuden luokan mukaisessa yksikössä. (1 tuuma on 25.4 millimetriä; 1 metri on 1000 millimetriä; 1 jaardi on 36 tuumaa) Määrittele main()-funktio, joka lukee joukon pituuksia eri yksiköissä ja luo oikean tyyppinen periytetyn luokan olio. Talleta luotujen olioiden osoitteet taulukkoon KantaPituus*. Tulosta kaikki pituudet millimetreinä alkuperäisen yksikön lisäksi Määrittele muunnosoperaattorit, jotka muuntavat edellisen harjoituksen periytettyjen tyyppien yksiköiden välillä. Määrittele esimerkiksi Tuumia-luokalle jäsenet operator Metria() ja operator Jaardeja(). Lisää main()-funktioon koodi, joka tulostaa kunkin mitan kaikissa yksiköissä. (Muista, että muunnosoperaattoreiden paluuarvon tyyppiä ei tarvitse määritellä, koska se näkyy suoraan niiden nimistä.) 16.4 Tee edellinen harjoitus uudelleen käyttämällä muodostinfunktioita muunnoksessa muunnosoperaattoreiden sijaan. 667
48 668 C++ Ohjelmoijan käsikirja
Periytyminen. Luokat ja olio-ohjelmointi
Periytyminen 15 Periytyminen Tässä luvussa käsittelemme aihetta, joka on olio-ohjelmoinnin kaikkein tärkein osa - periytyvyys. Periytyvyyden avulla voimme luoda uusia luokkia uudelleenkäyttämällä ja laajentamalla
Luokat. Luokat ja olio-ohjelmointi
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
T740103 Olio-ohjelmointi Osa 5: Periytyminen ja polymorfismi Jukka Jauhiainen OAMK Tekniikan yksikkö 2010
12. Periytyminen Johdantoa Käytännössä vähänkään laajemmissa ohjelmissa joudutaan laatimaan useita luokkia, joiden pitäisi pystyä välittämään tietoa toisilleen. Ohjelmien ylläpidon kannalta olisi lisäksi
Osoitin ja viittaus C++:ssa
Osoitin ja viittaus C++:ssa Osoitin yksinkertaiseen tietotyyppiin Osoitin on muuttuja, joka sisältää jonkin toisen samantyyppisen muuttujan osoitteen. Ohessa on esimerkkiohjelma, jossa määritellään kokonaislukumuuttuja
Ohjelman virheet ja poikkeusten käsittely
Ohjelman virheet ja poikkeusten käsittely 17 Ohjelman virheet ja poikkeusten käsittely Poikkeukset ovat tapa ilmoittaa virheistä ja odottamattomista tilanteista C++-ohjelmassasi. Poikkeusten käyttö virheiden
815338A Ohjelmointikielten periaatteet 2015-2016. Harjoitus 5 Vastaukset
815338A Ohjelmointikielten periaatteet 2015-2016. Harjoitus 5 Vastaukset Harjoituksen aiheena ovat aliohjelmat ja abstraktit tietotyypit sekä olio-ohjelmointi. Tehtävät tehdään C-, C++- ja Java-kielillä.
815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset
815338A Ohjelmointikielten periaatteet 2015-2016. Harjoitus 3 vastaukset Harjoituksen aiheena ovat imperatiivisten kielten muuttujiin liittyvät kysymykset. Tehtävä 1. Määritä muuttujien max_num, lista,
Operaattoreiden ylikuormitus. Operaattoreiden kuormitus. Operaattoreiden kuormitus. Operaattoreista. Kuormituksesta
C++ - perusteet Java-osaajille luento 5/7: operaattoreiden ylikuormitus, oliotaulukko, parametrien oletusarvot, komentoriviparametrit, constant, inline, Operaattoreiden ylikuormitus Operaattoreiden kuormitus
C++11 Syntaksi. Jari-Pekka Voutilainen Jari-Pekka Voutilainen: C++11 Syntaksi
1 C++11 Syntaksi Jari-Pekka Voutilainen 13.4.2012 2 Range-for Iteroi säiliön kaikki alkiot for-silmukassa. Säiliöltä vaaditaan begin- ja end-iteraattorit. Pätee kaikille C++11 STL-säiliöille, taulukoille,
Luokassa määriteltävät jäsenet ovat pääasiassa tietojäseniä tai aliohjelmajäseniä. Luokan määrittelyyn liittyvät varatut sanat:
1. Luokan jäsenet Luokassa määriteltävät jäsenet ovat pääasiassa tietojäseniä tai aliohjelmajäseniä. Luokan määrittelyyn liittyvät varatut sanat: class luokan_nimi tyypit: enum, struct, class, typedef
Mallit standardi mallikirjasto parametroitu tyyppi
Mallit 18 Mallit Malli on tehokas mekanismi uusien luokkien generoimiseksi automaattisesti. Standardikirjaston suuri osa, standardi mallikirjasto, rakentuu kokonaan mallien määrittelymahdollisuuden ympärille,
Luokan operaatiot. Osoittimet ja viittaukset luokan olioihin
Luokan operaatiot 13 Luokan operaatiot Luokkien olioiden luomiseen ja tuhoamiseen liittyy monia hienouksia, joista sinun tulee olla selvillä, jotta luokkiesi olioiden operaatiot toimivat turvallisesti
12 Mallit (Templates)
12 Mallit (Templates) Malli on määrittely, jota käyttämällä voidaan luoda samankaltaisten aliohjelmien ja luokkien perheitä. Malli on ohje kääntäjälle luoda geneerisestä tyyppiriippumattomasta ohjelmakoodista
Ohjelmoinnin jatkokurssi, kurssikoe 28.4.2014
Ohjelmoinnin jatkokurssi, kurssikoe 28.4.2014 Kirjoita jokaiseen palauttamaasi konseptiin kurssin nimi, kokeen päivämäärä, oma nimi ja opiskelijanumero. Vastaa kaikkiin tehtäviin omille konsepteilleen.
Harjoitus 7. 1. Olkoon olemassa luokat Lintu ja Pelikaani seuraavasti:
Harjoitus 7 1. Olkoon olemassa luokat Lintu ja Pelikaani seuraavasti: class Lintu //Kentät private int _siivenpituus; protected double _aivojenkoko; private bool _osaakolentaa; //Ominaisuudet public int
Esimerkkiprojekti. Mallivastauksen löydät Wroxin www-sivuilta. Kenttä Tyyppi Max.pituus Rajoitukset/Kommentit
Liite E - Esimerkkiprojekti E Esimerkkiprojekti Olet lukenut koko kirjan. Olet sulattanut kaiken tekstin, Nyt on aika soveltaa oppimiasi uusia asioita pienen, mutta täydellisesti muotoiltuun, projektiin.
Olio-ohjelmoinnissa luokat voidaan järjestää siten, että ne pystyvät jakamaan yhteisiä tietoja ja aliohjelmia.
4. Periytyminen 4.1. Johdantoa Käytännössä vähänkään laajemmissa ohjelmissa joudutaan laatimaan useita luokkia, joiden pitäisi pystyä välittämään tietoa toisilleen. Ohjelmien ylläpidon kannalta olisi lisäksi
Taulukot. Jukka Harju, Jukka Juslin 2006 1
Taulukot Jukka Harju, Jukka Juslin 2006 1 Taulukot Taulukot ovat olioita, jotka auttavat organisoimaan suuria määriä tietoa. Käsittelylistalla on: Taulukon tekeminen ja käyttö Rajojen tarkastus ja kapasiteetti
T Olio-ohjelmointi Osa 3: Luokka, muodostin ja hajotin, this-osoitin Jukka Jauhiainen OAMK Tekniikan yksikkö 2010
11. Luokka Opetellaan seuraavaksi, miten omia luokkia kirjoitetaan. Aikaisemmin olikin jo esillä, että luokka on tietorakenne, joka sisältää sekä tiedot (attribuutit) että niitä käsittelevät aliohjelmat
Omat tietotyypit. Mikä on olio?
Omat tietotyypit 11 Omat tietotyypit C++:n suuri vahvuus on sen oliopohjaisuudessa. Siihen liittyy runsaasti asiaa ja kulutammekin seuraavat viisi lukua tässä aiheessa. Tässä ja seuraavassa luvussa käsittelemme
9. Periytyminen Javassa 9.1
9. Periytyminen Javassa 9.1 Sisällys Periytymismekanismi Java-kielessä. Piirteiden näkyvyys periytymisessä. Ilmentymämetodien korvaaminen. Luokkametodien peittäminen. Super-attribuutti. Override-annotaatio.
Metodit. Metodien määrittely. Metodin parametrit ja paluuarvo. Metodien suorittaminen eli kutsuminen. Metodien kuormittaminen
Metodit Metodien määrittely Metodin parametrit ja paluuarvo Metodien suorittaminen eli kutsuminen Metodien kuormittaminen 1 Mikä on metodi? Metodi on luokan sisällä oleva yhteenkuuluvien toimintojen kokonaisuus
Geneeriset luokat. C++ - perusteet Java-osaajille luento 6/7: Template, tyyppi-informaatio, nimiavaruudet. Geneerisen luokan käyttö.
Geneeriset luokat C++ - perusteet Java-osaajille luento 6/7: Template, tyyppi-informaatio, nimiavaruudet Geneerinen luokka tarkoittaa parametroitua luokkamallia, jonka avulla voidaan muodostaa useita,
13 Operaattoreiden ylimäärittelyjä
248 13 C++-kielessä voidaan operaattoreita ylimäärittää. Ylimääriteltävää operaattoria voidaan pitää ikäänkuin metodina, joka esitellään luokan esittelyssä ja määritellään luokan ulkopuolella kuten metoditkin.
Tehtävä 1. TL5302 Olio-ohjelmointi Koe Malliratkaisuja. Tässä sekä a)- että b)-kohdan toimiva ratkaisu:
TL5302 Olio-ohjelmointi Koe 19.4.2005 Malliratkaisuja Tehtävä 1 Tässä sekä a)- että b)-kohdan toimiva ratkaisu: #include using namespace std; int main() int taul[5]=1,2,3,4,5; int *p,&r=taul[0];
2. Lisää Java-ohjelmoinnin alkeita. Muuttuja ja viittausmuuttuja (1/4) Muuttuja ja viittausmuuttuja (2/4)
2. Lisää Java-ohjelmoinnin alkeita Muuttuja ja viittausmuuttuja Vakio ja literaalivakio Sijoituslause Syötteen lukeminen ja Scanner-luokka 1 Muuttuja ja viittausmuuttuja (1/4) Edellä mainittiin, että String-tietotyyppi
Olio-ohjelmointi Javalla
1 Olio-ohjelmointi Javalla Olio-ohjelmointi Luokka Attribuutit Konstruktori Olion luominen Metodit Olion kopiointi Staattinen attribuutti ja metodi Yksinkertainen ohjelmaluokka Ohjelmaluokka 1 Olio-ohjelmointi
Kääntäjän virheilmoituksia
OHJ-1101 Ohjelmointi 1e 2008-09 1 Kääntäjän virheilmoituksia Kun progvh2 ohjelma käännetään antaa tutg++ seuraavat virheilmoitukset ja varoitukset: proffa> tutg++ progvh2.cc progvh2.cc:29:13: warning:
ITKP102 Ohjelmointi 1 (6 op)
ITKP102 Ohjelmointi 1 (6 op) Tentaattori: Antti-Jussi Lakanen 20. huhtikuuta 2018 Vastaa kaikkiin tehtäviin. Tee kukin tehtävä omalle konseptiarkille. Noudata ohjelmointitehtävissä kurssin koodauskäytänteitä.
15. Ohjelmoinnin tekniikkaa 15.1
15. Ohjelmoinnin tekniikkaa 15.1 Sisällys For-each-rakenne. Lueteltu tyyppi enum. Override-annotaatio. Geneerinen ohjelmointi. 15.2 For-each-rakenne For-rakenteen variaatio taulukoiden ja muiden kokoelmien
Sisällys. Metodien kuormittaminen. Luokkametodit ja -attribuutit. Rakentajat. Metodien ja muun luokan sisällön järjestäminen. 6.2
6. Metodit 6.1 Sisällys Metodien kuormittaminen. Luokkametodit ja -attribuutit. Rakentajat. Metodien ja muun luokan sisällön järjestäminen. 6.2 Oliot viestivät metodeja kutsuen Olio-ohjelmoinnissa ohjelma
Oliosuunnitteluesimerkki: Yrityksen palkanlaskentajärjestelmä
Oliosuunnitteluesimerkki: Yrityksen palkanlaskentajärjestelmä Matti Luukkainen 10.12.2009 Tässä esitetty esimerkki on mukaelma ja lyhennelmä Robert Martinin kirjasta Agile and Iterative Development löytyvästä
11. oppitunti III. Viittaukset. Osa. Mikä on viittaus?
Osa III 11. oppitunti Viittaukset Kahdessa viime luvussa opit käyttämään osoittimia kohteiden käsittelyyn vapaalla muistialueella sekä viittaamaan noihin kohteisiin epäsuorasti. Tässä luvussa käsiteltävät
Osa III. Olioiden luominen vapaalle muistialueelle
Osa III 10. oppitunti Kehittyneet osoittimet Eräs tehokkaimpia C++ -työkaluja on mahdollisuus käsitellä tietokoneen muistia suoraan osoittimien avulla. Tässä luvussa käsitelläänkin seuraavia aiheita: Kuinka
Olion elinikä. Olion luominen. Olion tuhoutuminen. Olion tuhoutuminen. Kissa rontti = null; rontti = new Kissa();
Sisällys 7. Oliot ja viitteet Olio Java-kielessä. Olion luominen, elinikä ja tuhoutuminen. Viitteiden käsittelyä: sijoitus, vertailu ja varautuminen null-arvoon. Viite metodin paluuarvona.. 7.1 7.2 Olio
Sisällys. JAVA-OHJELMOINTI Osa 6: Periytyminen ja näkyvyys. Luokkahierarkia. Periytyminen (inheritance)
Sisällys JAVA-OHJELMOINTI Osa 6: Periytyminen ja näkyvyys Periytyminen (inheritance) Näkyvyys (visibility) Eero Hyvönen Tietojenkäsittelytieteen laitos Helsingin yliopisto 13.10.2000 E. Hyvönen: Java Osa
4. Luokan testaus ja käyttö olion kautta 4.1
4. Luokan testaus ja käyttö olion kautta 4.1 Olion luominen luokasta Java-kielessä olio määritellään joko luokan edustajaksi tai taulukoksi. Olio on joukko keskusmuistissa olevia tietoja. Oliota käsitellään
Ohjelmassa muuttujalla on nimi ja arvo. Kääntäjä ja linkkeri varaavat muistilohkon, jonne muuttujan arvo talletetaan.
Osoittimet Ohjelmassa muuttujalla on nimi ja arvo. Kääntäjä ja linkkeri varaavat muistilohkon, jonne muuttujan arvo talletetaan. Muistilohkon koko riippuu muuttujan tyypistä, eli kuinka suuria arvoja muuttujan
C-kielessä taulukko on joukko peräkkäisiä muistipaikkoja, jotka kaikki pystyvät tallettamaan samaa tyyppiä olevaa tietoa.
Taulukot C-kielessä taulukko on joukko peräkkäisiä muistipaikkoja, jotka kaikki pystyvät tallettamaan samaa tyyppiä olevaa tietoa. Taulukon muuttujilla (muistipaikoilla) on yhteinen nimi. Jokaiseen yksittäiseen
Osoittimet. Mikä on osoitin?
Osoittimet 7 Osoittimet On aika siirtyä käsittelemään osoittimia, C++:lle elintärkeätä ominaisuutta. Osoittimet ovat tärkeitä, koska ne luovat perustan muistin dynaamiselle varaukselle ja käytölle. Ne
Sisällys. 1. Omat operaatiot. Yleistä operaatioista. Yleistä operaatioista
Sisällys 1. Omat operaatiot Yleistä operaatioista. Mihin operaatioita tarvitaan? Oman operaation määrittely. Yleisesti, nimeäminen ja hyvä ohjelmointitapa, määreet, parametrit ja näkyvyys. HelloWorld-ohjelma
Ohjelmoinnin perusteet Y Python
Ohjelmoinnin perusteet Y Python T-106.1208 2.3.2009 T-106.1208 Ohjelmoinnin perusteet Y 2.3.2009 1 / 28 Puhelinluettelo, koodi def lue_puhelinnumerot(): print "Anna lisattavat nimet ja numerot." print
1. Omat operaatiot 1.1
1. Omat operaatiot 1.1 Sisällys Yleistä operaatioista. Mihin operaatioita tarvitaan? Oman operaation määrittely. Yleisesti, nimeäminen ja hyvä ohjelmointitapa, määreet, parametrit ja näkyvyys. HelloWorld-ohjelma
Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op Taulukot & Periytyminen
Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op Taulukot & Periytyminen Taulukot: Array Taulukko Javassa pitää aina perustaa (new) Yksinkertaisessa tilanteessa taulukon koko tiedetään etukäteen ja
Tutoriaaliläsnäoloista
Tutoriaaliläsnäoloista Tutoriaaliläsnäolokierroksella voi nyt täyttää anomuksen läsnäolon merkitsemisestä Esim. tagi ei toiminut, korvavaltimon leikkaus, yms. Hyväksyn näitä omaa harkintaa käyttäen Tarkoitus
Rajapinta (interface)
1 Rajapinta (interface) Mikä rajapinta on? Rajapinta ja siitä toteutettu luokka Monimuotoisuus ja dynaaminen sidonta Rajapinta vs periytyminen 1 Mikä rajapinta on? Rajapintoja käytetään, kun halutaan määritellä
C++ rautaisannos. Kolme tapaa sanoa, että tulostukseen käytetään standardikirjaston iostreamosassa määriteltyä, nimiavaruuden std oliota cout:
C++ rautaisannos Kolme tapaa sanoa, että tulostukseen käytetään standardikirjaston iostreamosassa määriteltyä, nimiavaruuden std oliota cout: # include #include main ( ) main (
Olio-ohjelmointi 2. välikoe HYV5SN
Olio-ohjelmointi 2. välikoe 27.4.2007 HYV5SN 1. Tee ohjelma, joka sisältää laatikko-luokan. Luokan tietojäseninä ovat laatikon syvyys, leveys ja korkeus. Toteuta luokkaan muodostin, jonka avulla olio voidaan
Rekursiolause. Laskennan teorian opintopiiri. Sebastian Björkqvist. 23. helmikuuta Tiivistelmä
Rekursiolause Laskennan teorian opintopiiri Sebastian Björkqvist 23. helmikuuta 2014 Tiivistelmä Työssä käydään läpi itsereplikoituvien ohjelmien toimintaa sekä esitetään ja todistetaan rekursiolause,
Java kahdessa tunnissa. Jyry Suvilehto
Java kahdessa tunnissa Jyry Suvilehto Ohjelma Ohjelmointiasioita alkeista nippelitietoon n. 45 min Tauko 10 min Oliot, luokat ja muut kummajaiset n. 45 min Kysykää Sisältöä ei oikeasti ole 2x45 min täytteeksi,
Ohjelmointi 2. Jussi Pohjolainen. TAMK» Tieto- ja viestintäteknologia , Jussi Pohjolainen TAMPEREEN AMMATTIKORKEAKOULU
Ohjelmointi 2 Jussi Pohjolainen TAMK» Tieto- ja viestintäteknologia Tietotyypeistä C++ - kielessä useita tietotyyppejä Kirjaimet: char, wchar_t Kokonaisluvut: short, int, long Liukuluvut: float, double
Ohjelmointikieli TIE Principles of Programming Languages Syksy 2017 Ryhmä 19
Ohjelmointikieli TIE-20306 Principles of Programming Languages Syksy 2017 Ryhmä 19 Juho Kärnä Ville Mäntysaari 1. Johdanto D on yleiskäyttöinen, strukturoitu, staattisesti tyypitetty, käännettävä ohjelmointikieli
on ohjelmoijan itse tekemä tietotyyppi, joka kuvaa käsitettä
LUOKAN MÄÄRITTELY Luokka, mitä se sisältää Luokan määrittely Olion ominaisuudet eli attribuutit Olion metodit Olion muodostimet ja luonti Olion tuhoutuminen Metodin kutsu luokan ulkopuolelta Olion kopioiminen
Aalto Yliopisto T-106.2001 Informaatioverkostot: Studio 1. Oliot ja luokat Javaohjelmoinnissa
Aalto Yliopisto T-106.2001 Informaatioverkostot: Studio 1 Oliot ja luokat Javaohjelmoinnissa Vesa Laakso 22.9.2012 Sisällysluettelo Sisällysluettelo... 1 Johdanto... 2 1. Luokka... 2 2. Olio... 2 3. Luokan
9. Periytyminen Javassa 9.1
9. Periytyminen Javassa 9.1 Sisällys Periytymismekanismi Java-kielessä. Piirteiden näkyvyys periytymisessä. Metodien korvaaminen ja super-attribuutti. Attribuutin peittäminen periytymisen kautta. Rakentajat
Solidity älysopimus ohjelmointi. Sopimus suuntautunut ohjelmointi
Solidity älysopimus ohjelmointi Sopimus suuntautunut ohjelmointi Merkle puu Kertausta eiliseltä Solidity on korkean tason älysopimus ohjelmointikieli Muistuttaa olio-ohjelmointia Javalla Sopimuskoodi on
Harjoitustyö: virtuaalikone
Harjoitustyö: virtuaalikone Toteuta alla kuvattu virtuaalikone yksinkertaiselle olio-orientoituneelle skriptauskielelle. Paketissa on testaamista varten mukana kaksi lyhyttä ohjelmaa. Ohjeita Noudata ohjelman
Sisällys. 9. Periytyminen Javassa. Periytymismekanismi Java-kielessä. Periytymismekanismi Java-kielessä
Sisällys 9. Periytyminen Javassa Periytymismekanismi Java-kielessä. Piirteiden näkyvyys periytymisessä. Metodien korvaaminen ja super-attribuutti. Attribuutin peittäminen periytymisen kautta. Rakentajat
ITKP102 Ohjelmointi 1 (6 op)
ITKP102 Ohjelmointi 1 (6 op) Tentaattori: Antti-Jussi Lakanen 12. huhtikuuta 2019 Tee kukin tehtävä omalle konseptiarkille. Noudata ohjelmointitehtävissä kurssin koodauskäytänteitä. Yksi A4-kokoinen lunttilappu
Sisällys. 6. Metodit. Oliot viestivät metodeja kutsuen. Oliot viestivät metodeja kutsuen
Sisällys 6. Metodit Oliot viestivät metodeja kutsuen. Kuormittaminen. Luokkametodit (ja -attribuutit).. Metodien ja muun luokan sisällön järjestäminen. 6.1 6.2 Oliot viestivät metodeja kutsuen Oliot viestivät
Sisällys. Yleistä attribuuteista. Näkyvyys luokan sisällä ja ulkopuolelta. Attribuuttien arvojen käsittely aksessoreilla. 4.2
4. Attribuutit 4.1 Sisällys Yleistä attribuuteista. Näkyvyys luokan sisällä ja ulkopuolelta. Attribuuttien arvojen käsittely aksessoreilla. 4.2 Yleistä Luokan lohkossa, mutta metodien ulkopuolella esiteltyjä
Sisällys. Yleistä attribuuteista. Näkyvyys luokan sisällä. Tiedonkätkentä. Aksessorit. 4.2
4. Attribuutit 4.1 Sisällys Yleistä attribuuteista. Näkyvyys luokan sisällä. Tiedonkätkentä. Aksessorit. 4.2 Yleistä Luokan lohkossa, mutta metodien ulkopuolella esiteltyjä muuttujia ja vakioita. Esittely
Metodien tekeminen Javalla
1 Metodien tekeminen Javalla Mikä metodi on? Metodin syntaksi Metodi ja sen kutsuminen Parametreista Merkkijonot ja metodi Taulukot ja metodi 1 Mikä metodi on? Metodilla toteutetaan luokkaan toiminnallisuutta.
C# olio-ohjelmointi perusopas
Paavo Räisänen C# olio-ohjelmointi perusopas www.ohjelmoimaan.net Tätä opasta saa vapaasti kopioida, tulostaa ja levittää ei kaupallisissa tarkoituksissa. Kuitenkaan omille nettisivuille opasta ei saa
1 Tehtävän kuvaus ja analysointi
Olio-ohjelmoinnin harjoitustyön dokumentti Jyri Lehtonen (72039) Taneli Tuovinen (67160) 1 Tehtävän kuvaus ja analysointi 1.1 Tehtävänanto Tee luokka, jolla mallinnetaan sarjaan kytkettyjä kondensaattoreita.
Ohjelmoinnin perusteet, kurssikoe
Ohjelmoinnin perusteet, kurssikoe 18.6.2014 Kirjoita jokaiseen konseptiin kurssin nimi, kokeen päivämäärä, nimi, TMC-tunnus ja opiskelijanumero tai henkilötunnus. Vastaukset palautetaan tehtäväkohtaisiin
Osa. Listaus 2.1. HELLO.CPP esittelee C++ -ohjelman osat. 14: #include <iostream.h> 15: 16: int main() 17: {
Osa I 2. oppitunti C++-ohjelman osat Ennen kuin menemme yksityiskohtaisemmin sisälle C++-luokkiin, -muuttujiin jne, katsokaamme ensin, millaisista osista C++-ohjelma koostuu. Tämän tunnin aikana opit seuraavat
A) on käytännöllinen ohjelmointitekniikka. = laajennetaan aikaisemmin tehtyjä luokkia (uudelleenkäytettävyys)
1(37) PERIYTYMINEN (inheritance) YLILUOKKA (superclass) ALILUOKKA (subclass) A) on käytännöllinen ohjelmointitekniikka = laajennetaan aikaisemmin tehtyjä luokkia (uudelleenkäytettävyys) B) on käsitteiden
Alkuarvot ja tyyppimuunnokset (1/5) Alkuarvot ja tyyppimuunnokset (2/5) Alkuarvot ja tyyppimuunnokset (3/5)
Alkuarvot ja tyyppimuunnokset (1/5) Aiemmin olemme jo antaneet muuttujille alkuarvoja, esimerkiksi: int luku = 123; Alkuarvon on oltava muuttujan tietotyypin mukainen, esimerkiksi int-muuttujilla kokonaisluku,
C++11 lambdat: [](){} Matti Rintala
C++11 lambdat: [](){} Matti Rintala bool(*)(int) Tarve Tarve välittää kirjastolle/funktiolle toiminnallisuutta Callback-funktiot Virhekäsittely Käyttöliittymät Geneeristen kirjastojen räätälöinti STL:n
JAVA-PERUSTEET. JAVA-OHJELMOINTI 3op A274615 JAVAN PERUSTEET LYHYT KERTAUS JAVAN OMINAISUUKSISTA JAVAN OMINAISUUKSIA. Java vs. C++?
JAVA-OHJELMOINTI 3op A274615 JAVAN PERUSTEET LYHYT KERTAUS Teemu Saarelainen [email protected] Lähteet: http://java.sun.com/docs/books/tutorial/index.html Vesterholm, Kyppö: Java-ohjelmointi,
Ohjelmointi 1 Taulukot ja merkkijonot
Ohjelmointi 1 Taulukot ja merkkijonot Jussi Pohjolainen TAMK Tieto- ja viestintäteknologia Johdanto taulukkoon Jos ohjelmassa käytössä ainoastaan perinteisiä (yksinkertaisia) muuttujia, ohjelmien teko
815338A Ohjelmointikielten periaatteet
815338A Ohjelmointikielten periaatteet 2015-2016 V Abstraktit tietotyypit ja olioohjelmointi Sisältö I. Abstraktit tietotyypit II. 1. Johdatus abstrakteihin tietotyyppeihin 2. Abstraktit tietotyypit Adassa
Ohjelmointi funktioiden avulla
Ohjelmointi funktioiden avulla 8 Ohjelmointi funktioiden avulla Ohjelman jakaminen hallittaviin osiin on idea, joka on perustana kaikille ohjelmointikielille. Funktio on kaikkien C++-ohjelmien perusosa.
Ohjelmointitaito (ict1td002, 12 op) Kevät 2008. 1. Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen raine.kauppinen@haaga-helia.
Ohjelmointitaito (ict1td002, 12 op) Kevät 2008 Raine Kauppinen [email protected] 1. Java-ohjelmoinnin alkeita Tietokoneohjelma Java-kieli ja Eclipse-ympäristö Java-ohjelma ja ohjelmaluokka
AS-0.1103 C-ohjelmoinnin peruskurssi 2013: C-kieli käytännössä ja erot Pythoniin
AS-0.1103 C-ohjelmoinnin peruskurssi 2013: C-kieli käytännössä ja erot Pythoniin Raimo Nikkilä Aalto-yliopiston sähkötekniikan korkeakoulu - Automaation tietotekniikan tutkimusryhmä 17. tammikuuta 2013
2. Olio-ohjelmoinista lyhyesti 2.1
2. Olio-ohjelmoinista lyhyesti 2.1 Sisällys Yleistä. Oliot ja luokat. Attribuutit. Olioiden esittely ja alustus. Rakentajat. Olion operaation kutsuminen. 2.2 Yleistä Olio-ohjelmointia käsitellään hyvin
Tietueet. Tietueiden määrittely
Tietueet Tietueiden määrittely Tietue on tietorakenne, joka kokoaa yhteen eri tyyppistä tietoa yhdeksi asiakokonaisuudeksi. Tähän kokonaisuuteen voidaan viitata yhteisellä nimellä. Auttaa ohjelmoijaa järjestelemään
Tässä tehtävässä käsittelet metodeja, listoja sekä alkulukuja (englanniksi prime ).
Tehtävä 1: Metodit, listat, alkuluvut (4p) Tässä tehtävässä käsittelet metodeja, listoja sekä alkulukuja (englanniksi prime ). Alkuluvut ovat lukuja, jotka ovat suurempia kuin yksi ja jotka ovat jaollisia
7. Oliot ja viitteet 7.1
7. Oliot ja viitteet 7.1 Sisällys Olio Java-kielessä. Olion luominen, elinikä ja tuhoutuminen. Viitteiden sijoitus. Viitteiden vertailu. Varautuminen null-arvoon. Viite metodin paluuarvona. Viite metodin
Ohjelmointi 2 / 2010 Välikoe / 26.3
Ohjelmointi 2 / 2010 Välikoe / 26.3 Välikoe / 26.3 Vastaa neljään (4) tehtävään ja halutessa bonustehtäviin B1 ja/tai B2, (tuovat lisäpisteitä). Bonustehtävät saa tehdä vaikkei olisi tehnyt siihen tehtävään
Loppukurssin järjestelyt
C! Loppukurssin järjestelyt 29.3.2018 Ohjelmassa Yhteenvetoa palautteesta Ohjelmontitehtävän järjestelyt Tietokonetentin järjestelyt Kysyttävää / kerrattavaa 10-kierroksen asioista? Aikatauluista 10. kierroksen
Sisällys. 7. Oliot ja viitteet. Olion luominen. Olio Java-kielessä
Sisälls 7. Oliot ja viitteet Olio Java-kielessä. Olion luominen, elinikä ja tuhoutuminen.. Viitteiden vertailu. Varautuminen null-arvoon. Viite metodin paluuarvona.. Muuttumattomat ja muuttuvat merkkijonot.
Oliot viestivät metodeja kutsuen
6. Metodit 6.1 Sisällys Oliot viestivät metodeja kutsuen. Kuormittaminen. Luokkametodit (ja -attribuutit). Rakentajat. Metodien ja muun luokan sisällön järjestäminen. 6.2 Oliot viestivät metodeja kutsuen
1. Miten tehdään peliin toinen maila?
Muilla kielillä: English Suomi Pong-peli, vaihe 4 Tässä oppaassa teemme toisenkin mailan. 1. Miten tehdään peliin toinen maila? Maila tehtiin edellisessä vaiheessa, aliohjelmassa LuoKentta, seuraavasti:
Ohjelmoinnin perusteet, syksy 2006
Ohjelmoinnin perusteet, syksy 2006 Esimerkkivastaukset 1. harjoituksiin. Alkuperäiset esimerkkivastaukset laati Jari Suominen. Vastauksia muokkasi Jukka Stenlund. 1. Esitä seuraavan algoritmin tila jokaisen
Loppukurssin järjestelyt C:n edistyneet piirteet
C! Loppukurssin järjestelyt C:n edistyneet piirteet 30.3.2017 Ohjelmassa Ohjelmontitehtävän järjestelyt Tietokonetentin järjestelyt Esikääntäjä Parametrilistat Funktio-osoittimet Kunniamainintoja Kuura
Ohjelmassa henkilön etunimi ja sukunimi luetaan kahteen muuttujaan seuraavasti:
1 (7) Tiedon lukeminen näppäimistöltä Scanner-luokan avulla Miten ohjelma saa käyttöönsä käyttäjän kirjoittamaa tekstiä? Järjestelmässä on olemassa ns. syöttöpuskuri näppäimistöä varten. Syöttöpuskuri
IDL - proseduurit. ATK tähtitieteessä. IDL - proseduurit
IDL - proseduurit 25. huhtikuuta 2017 Viimeksi käsiteltiin IDL:n interaktiivista käyttöä, mutta tämä on hyvin kömpelöä monimutkaisempia asioita tehtäessä. IDL:llä on mahdollista tehdä ns. proseduuri-tiedostoja,
ATK tähtitieteessä. Osa 3 - IDL proseduurit ja rakenteet. 18. syyskuuta 2014
18. syyskuuta 2014 IDL - proseduurit Viimeksi käsiteltiin IDL:n interaktiivista käyttöä, mutta tämä on hyvin kömpelöä monimutkaisempia asioita tehtäessä. IDL:llä on mahdollista tehdä ns. proseduuri-tiedostoja,
