olio-ohjelmoinnin perusteet luento 4: perinnästä

Post on 20-Jan-2016

45 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

DESCRIPTION

Olio-ohjelmoinnin perusteet luento 4: Perinnästä. Jani Rönkkönen LTY/Tietotekniikan osasto Kalvot on muokattu Sami Jantusen luentokalvoista viime vuodelta. Sisältö. Johdanto Kertausta Esimerkki Yhteenveto Luokkien näkyvyysmääreet Perintä Toiminnan korvaaminen Moniperintä Yhteenveto. - PowerPoint PPT Presentation

TRANSCRIPT

Olio-ohjelmoinnin perusteetluento 4: Perinnästä

Jani RönkkönenLTY/Tietotekniikan osasto

Kalvot on muokattu Sami Jantusen luentokalvoista viime vuodelta.

Sisältö Johdanto

Kertausta Esimerkki Yhteenveto

Luokkien näkyvyysmääreet Perintä Toiminnan korvaaminen Moniperintä Yhteenveto

PerintäMuistatko vielä….?

Perintä tarkoittaa periaatetta siitä, että yleisempi määrittely on myös voimassa erikoistuneissa olioissa

Sanomme, että kukkakauppias perii myös kauppiaan ja ihmisen toiminnallisuuden

Ihminen

Kauppias

Kukkakauppias

PerintäMuistatko vielä….?

Perinnän idea: Luokat voidaan organisoida

hierarkkisiin perintäpuihin Lapsiluokka perii vanhempiensa

tiedon ja toiminnallisuuden Abstrakti isäluokka on sellainen,

mistä ei voidan tehdä omaa oliota, mutta mitä käytetään lapsiluokkien määrittelyssä

PerintäMuistatko vielä….?

PerintäMuistatko vielä….?

Eläin

...Niveljalkainen Selkäjänteinen

Hämähäkkieläin Hyönteinen Matelija Nisäkäs Lintu

...

......

... ......

Leppäkerttu Kissa Ihminen

...

LuokkahierarkiaMammal

Land-Mammal

int weight

int numLegs

Dogboolean rabid

Chihuahua

giveBirth( )

SheepDog

Kuinka monta attribuuttia koiralla on?

...

Huomaathan että:

Land-Mammal on Dog- luokan isäluokka (superclass), mutta Mammal –luokan lapsiluokka (subclass)

Dog –luokalla on kolme attribuuttiaweight, numLegs ja rabidkaksi attribuuttia perinnän kautta ja yksi omassa luokassa

Muistatko vielä? Koirat eläintarhassa CDog.h

Vielä paljon kerrottavaa Ei vielä ihan tyylipuhdas luokan esittely.

CDogbool rabidOrNot

int weight

string name

void growl()void eat()

#ifndef CDog_H #define CDog_H#include <iostream.h>class CDog {

int weight;bool rabidOrNot;std:string name;

public:CDog (int x, std::string y);

~CDog(); //tuhoajan esittelybool getRabid ( )const;void setRabid (bool x);std::string getName ( )const;void setName (std::string z);int getWeight ( )const;void setWeight (int x);void eat( );void growl( )const;

}; #endif /* CDog_H */

Muistatko vielä? Koirat eläintarhassa CDog.cpp

#include <string.h>#include “CDog.h”

using namespace std;

// ConstructorCDog::CDog (int x, string y) {

rabidOrNot = false;weight = x;name = y;

}// destructorCDog::~CDog(){}void CDog::eat ( ) {

cout << name << “ is eating”<< endl;weight++;}void CDog::growl ( ) const{

cout << “Grrrr”;}

bool CDog::getRabid ( ) const{ return rabidOrNot;}void CDog::setRabid (bool x) { rabidOrNot = x;}int CDog::getWeight ( ) const{ return weight;}void CDog::setWeight (int y) { weight = y;}string CDog::getName ( ) const{ return name;}void setName (string z) { name = z;}

Ongelmia!

Mikä tahansa koira ei kelpaa!Puudeli se olla

pitää!

Miten saamme luotua puudelin siten, ettei tarvitsisi kirjoittaa paljon koodia uusiksi??

Perintä:Puudeli on koira

Puudeli on koira (muistathan “is-a” testin)

Käytetään hyväksi CDog-luokan toteutus perimällä siitä CPoodle-luokka

CDogbool rabidOrNot

int weight

string name

void growl()void eat()

CPoodle

No niin…. Ryhdytään hommiin!Luodaan puudeli-luokka (sekä .h, että .cpp tiedostot)

#include "CDog.h“ CPoodle.h

class CPoodle:public CDog {public:CPoodle(int x, std::string y);

};

Tässä suoritetaan perintä CDog -luokasta

#include <iostream>

#include "CPoodle.h"

using namespace std;

CPoodle::CPoodle(int x, string y) : CDog (x,y){

cout << “Tuli muuten tehtyä puudeli" << endl;}

CPoodle.cpp

Mitäs täällä tapahtuu?

Isäluokan rakentajaa kutsutaan aina!*

*Huomaa!: Jos isäluokan rakentajaa ei kutsuta eksplisiittisesti itse, kääntäjä yrittää kutsua automaattisesti isäluokan oletusrakentajaa (mitä ei tässä esimerkissä ole olemassa)

CPoodle.cpp

CPoodle::CPoodle(int x, string y) : CDog (x,y)

{

cout << “Tuli muuten tehtyä puudeli" << endl;

}

Normaalia rakentaja tavaraa

Mitä tuli taas tehtyä??

Loimme puudeliluokan jolla on kaikki attribuutit ja metodit kun CDog-luokallakin

Ongelmia!

Puudelit ei sano “Grrrrrrr”! Eihän??? Ne sanoo “Yip”!

void CDog::growl ( ) {cout << “Grrrr”;

}

Muistatko vielä?Toiminnallisuuden korvaaminen

Eläin

Nisäkäs Lintu

Selkäjänteinen

Nisäkkäät synnyttävät eläviä poikasia

Linnut munivat munia The Australian Platypus on

nisäkäs, mutta luo munia

Muistatko vielä?Toiminnallisuuden korvaaminen

On mahdollista korvata (override) isäluokassa määritelty toiminnallisuus toteuttamalla lapsiluokkaan saman niminen toiminnallisuus

Sopivan metodin etsintä aloitetaan aina lapsiluokasta. Jos lapsiluokassa ei ole toteutettuna haluttua toiminnallisuutta, siirrytään etsimään sitä isäluokasta

Tehdäänpä jotain puudelin murinalle!!!

Korvataan CDog –luokan growl -metodi

Yksinkertaista, kirjoitetaan Puudeliluokkaan vain samanniminen metodi

GRRRRR

Kiltti puudeli!

YIP

CPoodle.cpp

CPoodle::CPoodle(int x, string y) : CDog (x,y){

cout << “Tuli muuten tehtyä puudeli" << endl;

}

void CPoodle::growl( ) const{

cout << "Yip!" << endl;

}

#include "CDog.h“ CPoodle.h

class CPoodle:public CDog {

public:

CPoodle(int x, std::string y);

void growl() const;

};

Mitä juuri opimme?Perinnän määrittely

#include "CDog.h“ CPoodle.h

class CPoodle:public CDog {public:CPoodle(int x, std::string y);

};

Tässä suoritetaan perintä CDog -luokasta

Puhumme perinnästä hetken kuluttua lisää!

Mitä juuri opimme?Rakentajien käyttö perinnän yhteydessä

Isäluokan rakentajaa kutsutaan aina!*

CPoodle.cpp

CPoodle::CPoodle(int x, string y):CDog(x,y)

{

cout << “Tuli muuten tehtyä puudeli" << endl;

}

Normaalia rakentaja tavaraa

Luokkia perittäessä on rakentajien ja purkajien käytössä on paljon huomioitavaa Puhumme tästä hetken kuluttua lisää!

Mitä juuri opimme?Toiminnan korvaaminen

GRRRRYIPToiminnan korvaaminen on oleellinen osa perintää.

Puhumme tästä hetken kuluttua lisää!

Missä mennään Johdanto

Kertausta Esimerkki Yhteenveto

Luokkien näkyvyysmääreet Perintä Toiminnan korvaaminen Moniperintä Yhteenveto

Luokan näkyvyysmääreetclass CPoodle{public: //Tänne tulevat asiat näkyvät //luokasta ulosprotected: //Tänne tulevat asiat näkyvät //vain aliluokilleprivate: //Tänne tulevat asiat ei näy // ulospäin};

Sääntöjä

Oletusarvoinen näkyvyysmääre on private

Saatat nähdä koodia, missä jäsenmuuttujat on määritelty luokassa ensimmäisenä ilman näkyvyysmäärettä (=private:). Huonoa tyyliä! Selkeämpää kirjoittaa luokka public:,

protected:, private: -järjestyksessä

Public:

Julkinen rajapinta. Kaikki voi käyttää Luokka: Koira

•murise•syö•kerroPaino•kerroNimi•oletkoVesikauhuinen

Public: Ohjeita

Ole huolellinen julkisen rajapinnan suunnittelussa!

Rajapinta on lupaus luokan tarjoamista palveluista.

Rajapintaan kuuluvien asioiden tulisi säilyä muuttumattomina

Rajapinnan tulisi olla “minimaalinen, mutta täydellinen”

Ei ylimääräistä tavaraa “varmuuden vuoksi” Jos jotain puuttuu, niin luokan käyttäjällä ei mitään

mahdollisuuksia korjata puutettaMinun luokka tekee tätä eikä mitään muuta

Public: Ohjeita

Jäsenmuuttujat on syytä pitää visusti piilossa!

Tiedon piilottaminen on yksi olioajattelun perusajatuksista

Voi tulla tarve siirtää jäsenmuuttuja muualle tai korvata se jollain toisella rakenteella Et voi tehdä tätä jos olet jo julkaissut muuttujasi

On parempi, että oliolla on täysi kontrolli tietoonsa Jos muuttuja on julkinen, olio ei voi mitenkään

tietää milloin arvoa luetaan tai sitä muutetaanEt pääse “kopeloimaan” tietojani!

Protected:

Käytetään perittäessä luokkia Muulloin toimii samoin

kuin private: Sallii lapsiluokkien

käyttää jäseniään

Private: Kaikkein rajoittavin näkyvyysmääre Vain luokka itse voi käyttää private

jäseniä (myös ystäväfunktiot ja –luokat, mutta tästä lisää myöhemmin)

samantyyppinen olio pääsee myös käsiksi toisen olion privaatteihin tietoihin

Julista privaatiksi: jäsenmuuttujat apufunktiot

Missä mennään Johdanto

Kertausta Esimerkki Yhteenveto

Luokkien näkyvyysmääreet Perintä Toiminnan korvaaminen Moniperintä Yhteenveto

Perinnän määrittely#include "CDog.h“ CPoodle.h

class CPoodle:public CDog {public:CPoodle(int x, string y);

};

Tässä suoritetaan perintä CDog -luokasta

Perinnän määrittely class CPoodle:public CDog

C++:ssa on oletuksena yksityinen perintä class B : A {…} tarkoittaa samaa kuin class B : private A {…}

Tämä on hieman outoa, sillä julkinen (public:) on kuitenkin yleisintä

PerintätavoistaEsimerkki (Hannu Peltosen kirjasta Johdatus C++

ohjelmointikieleen)

Haluamme rakentaa yleiskäyttöisen luokan, jota apuna käyttäen voimme toteuttaa erilaisia 2-suuntaisia listojanextprevdata

nextprevdata

nextprevdata

2-suuntainen lista (Deque)

Ensimmäiseksi määrittelemme Deque luokan jonka alkioihin voi tallentaa mitä tahansa tietoa

Luokkaa ei käytetä suoraan, vaan siitä johdetaan uusia luokkia erityyppisten alkioiden tallentamista varten.

nextprevdata

nextprevdata

nextprevdata

2-suuntainen lista (Deque)Määritellään ensin jäsenmuuttujat

class Deque{

…private:

struct Item{

Item *prev;Item *next;void *data;

};Item *first;Item *last;Item *curr;

};

nextprevdata

nextprevdata

nextprevdata

2-suuntainen lista (Deque)Määritellään julkinen rajapinta

void Deque::goBeforeFirst(){

curr = first;}void Deque:: goAfterLast(){

curr = last;}void Deque:: forth(){

if (curr != last)curr = curr->next;

}void Deque:: back(){

if (curr != first)curr = curr->prev;

}int Deque:: isBeforeFirst() const{

return curr == first;}int Deque:: isAfterLast() const{

return curr == last;}next

prevdata

nextprevdata

nextprevdata

class Deque{public:

void goBeforeFirst();void goAfterLast();void forth();void back();int isBeforeFirst() const;int isAfterLast() const;...

};

2-suuntainen lista (Deque)Rakentaja

nextprevdata

nextprevdata

nextprevdata

Deque oli tarkoitettu yleiskäyttöiseksi luokaksi, joka ei voi esiintyä yksinään.

Miten voidaan varmistua siitä, että ohjelmassa ei pysty määrittelemään suoraan tyyppiä Deque olevia muuttujia?

2-suuntainen lista (Deque)Rakentaja

Deque::Deque(){

first = new Item; last = new Item; first->prev = NULL;first->next = last; last->prev = first;last->next = NULL;curr = first;

};

nextprevdata

nextprevdata

nextprevdata

class Deque{...

protected:Deque();...

};

Kun rakentaja on protected, ei sitä voi kutsua muualta kuin periytetystä luokasta

2-suuntainen lista (Deque)

Valmista?

Puuttuuko vielä jotain?

2-suuntainen lista (Deque)Alkion lisäys

class Deque{

...protected:

void insertBeforeCurrent(void*);void insertAfterCurrent(void*);

...};

void Deque::insertBeforeCurrent(void *p){

if (curr != first){

Item *newItem = new Item;newItem->data = p;newItem->next = curr;newItem->prev = curr->prev;curr->prev->next = newItem;curr->prev = newItem;curr = newItem;

}}void Deque::insertAfterCurrent(void *p){

if (curr != last){

forth();insertBeforeCurrent (p);

}}

nextprevdata

nextprevdata

nextprevdata

2-suuntainen lista (Deque)Alkion poisto

class Deque{

...protected:

void *removeCurrentAndGoBack();void *removeCurrentAndGoForth();

private:void *remove(Item *newCurr);...

};

void * Deque::removeCurrentAndGoBack(){

return remove(curr->prev);}

void * Deque::removeCurrentAndGoForth(){

return remove(curr->next);}

void * Deque::remove (Item *newCurr){

if (curr == first || curr == last )return NULL;

else{

void *res = curr->data;curr->prev->next = curr->next;curr->next->prev = curr->prev;delete curr;curr = newCurr;return res;

}}

nextprevdata

nextprevdata

nextprevdata

2-suuntainen lista (Deque)Nykyisen alkion saanti ja listan tuhoaminen

class Deque{

...protected:

void *current () const;~Deque(); ...

};

void * Deque::current() const{

return (curr == first || curr == last) ?NULL : curr->data;

}

Deque:: ~Deque (){

Item *p, *next;for (p = first; p != NULL; p = next){

next = p->next;delete p;

}}

nextprevdata

nextprevdata

nextprevdata

Hurraa!!!

VIHDOINKIN VALMISTA!

Mitä tuli tehtyä?

Loimme luokan joka on elegantti ja yleiskäyttöinen Ei käytetä yksin vaan tästä

johdetaan helposti erilaisia listaratkaisuja minimaallisella työllä

Koodin uudelleenkäyttö! Hyvä esimerkki

oliopohjaisuudesta ja perinnästä!

Listoja liukuhihnalta!(IntDeque)Otetaanpa Deque luokka hyötykäyttöön! Luodaan Int-pohjainen lista

class IntDeque: public Deque{public:

IntDeque();void insert (int);void remove();int current () const; ~IntDeque();

};

nextprevdata

nextprevdata

nextprevdata

IntDeque

void goBeforeFirst();void goAfterLast();void forth();void back();int isBeforeFirst() const;int isAfterLast() const;IntDeque();void insert (int);void remove();int current () const; ~IntDeque();

Deque-luokasta peritty

Uusi toiminnallisuus

IntDeque toteutus

IntDeque::IntDeque(){ //kutsutaan isäluokan }//oletusmuodostinta automaattisesti

void IntDeque::insert (int n){

int *ptr = new int;*ptr = n;insertAfterCurrent (ptr);

}

void IntDeque::remove (){

delete (int*) removeCurrentAndGoBack();}

int IntDeque::current() const{

int *ptr = (int*)Deque::current();return (ptr != NULL )? *ptr : -1;

}

IntDeque:: ~IntDeque(){

goBeforeFirst();forth();while (!isAfterLast()){

delete Deque::current();forth();

}}

IntDequeMitä opimme? Koodin uudelleenkäyttö on

helppoa ja mukavaa! Kun perit public: -määreellä perit

isäluokan rajapinnan ja saat sen public- ja protected –jäsenet käyttöösi

Listoja liukuhihnaltaIntStack

Seuraavaksi haluamme tehdä pinon

Jotain pielessä! Mitä?IntDeque

void goBeforeFirst();void goAfterLast();void forth();void back();int isBeforeFirst() const;int isAfterLast() const;IntStack();int empty () const; void push (int);int top () const; int pop (); ~IntStack();

Deque-luokasta peritty

Uusi toiminnallisuus

IntStackjulkinen perintä

IntDeque

void goBeforeFirst();void goAfterLast();void forth();void back();int isBeforeFirst() const;int isAfterLast() const;IntStack();int empty () const; void push (int);int top () const; void pop (); ~IntStack();

Deque-luokasta peritty

Uusi toiminnallisuus

Ku emmie haluu noin paljon tavaraa miun

julkiseen rajapintaan!!!!!

Mitäs nyt? Haluamme vain käyttää hyväksi olemassa olevaa

toiminnallisuutta. Emme halua periä isäluokan julkista rajapintaa

IntStack- yksityinen perintä class IntStack : private IntDeque

Perittiin toiminnallisuus, mutta ei rajapintaa Yksityisesti peritty luokka voi käyttää

kantaluokan suojattuja jäseniä aivan kuin julkisestikin johdettu luokka

Luokkien ulkopuolella IntStack:lla ja IntDequella ei näytä olevan mitään tekemistä keskenään.

Mitä saimme aikaan yksityisellä perinnällä?

protected perintä class IntStack : protected IntDeque

Yhteneväisyydet: Molemmat sallivat kantaluokan toiminnan korvaamisen Kumpikaan ei tunnusta sukulaisuuttaan isäluokkaan

Erot: protected perintä sallii lastenlasten tietävän perintäsuhteesta.

protected johtamastasi luokasta perityt luokat näkevät sisältösi.

protected perinnän hyöty: sallii lapsiluokkiesi käyttävän hyväksi isäluokkasi

toiminnallisuutta protected perinnän haitta:

protected perinnän muuttaminen saattaa hajoittaa jotain lapsiluokissasi.

Kuinka protected perintä eroaa private perinnästä?

private- ja protected perintää liittyvät näkyvyyssäännöt

Tarkastellaan seuraavia esimerkkejä:   class B                 { /*...*/ }; class D_priv : private   B { /*...*/ }; class D_prot : protected B { /*...*/ }; class D_publ : public    B { /*...*/ }; class UserClass  { B b; /*...*/ };

Mikään perityistä luokista ei pääse B-luokan yksityisiin jäseniin

B-luokan public- ja protected jäsenet ovat: D_priv- luokassa private D_prot –luokassa protected, näkyvät D_publ –luokassa samalla lailla kuin B-luokassakin

UserClass-luokka näkee vain B:n julkiset jäsenet

Jäsenkohtainen näkyvyyksien määrittely

Tarkastellaan edelleen seuraavaa esimerkkiä: class B                 { /*...*/ }; class D_priv : private   B { /*...*/ }; class D_prot : protected B { /*...*/ }; class D_publ : public    B { /*...*/ }; class UserClass  { B b; /*...*/ };

On mahdollista palauttaa private tai protected perinnässä muuttuneet jäsenten näkyvyydet. Suojausta ei voi muuttaa vapaammaksi tai tiukemmaksi kuin mikä se on kantaluokassa. Esimerkki:

Halutaan tietty B:n public jäsen näkyvän julkisena myös D_priv tai D_prot –luokassa.

     class D_prot : protected B {    public:       B::f;      };

Takaisin IntStack:n pariin Päätimme siis käyttää privaattia perintää. Uudelleenkäyttö on taas nopeaa ja helppoa. Ei

paljon mitään kirjoitettavaa:

IntStack::IntStack(){ //isäluokan rakentaja riittää}

int IntStack::empty() const{

return isBeforeFirst();}

void IntStack::push(int n) {

insert(n);}

int IntStack::top() const{

return current();}

void IntStack ::pop(){

remove();}

IntStack :: ~ IntStack(){ //isäluokan purkaja riittää}

On toinenkin tapa…

Usein yksityisen perinnän kaltainen lopputulos saadaan aikaan liittämällä kantaluokan olio toisen luokan jäsenmuuttujaksi.

Deque

IntStack

<<private>> IntDeque

IntStack

Deque

Muistatko vielä? AggregaatioAggregaatiot ovat erikoistuneita assosiaatioita kuvaamaan luokan koostumista muista luokista

Kokonaisuutta kuvaavaa puolta kutsutaan aggregaatiksi (aggregate)

Aggregaatio kuvataan assosiaation päässä olevalla timantilla.

****

****** Region

VehiclePart

Country

Vehicle

IntStack aggregaatiota hyväksi käyttäen

class IntStack

{

public:

IntStack();

int empty () const;

void push (int);

int top () const;

void pop ();

~IntStack();

private:

IntDeque q;

};

IntStack::IntStack(){ //q alustetaan IntDequen rakentajassa}

int IntStack::empty() const{

return q.isBeforeFirst();}

void IntStack::push(int n) {

q.insert(n);}int IntStack::top() const{

return q.current();}

void IntStack ::pop(){

q.remove();}

IntStack :: ~ IntStack(){ //isäluokan purkaja riittää}

IntStack (private perintä) vs. IntStack (aggregaatio)

Yhteistä: Molemmat kuvaavat (has-a) koostetta. Kummassakin tapauksessa yhteys Deque-luokkaan on piilotettu

Eroja: Kooste voi sisältää useita olioita Yksityisessä perinnässä johdettu luokka voi käyttää

kantaluokan suojattuja jäseniä Kumpaa tapaa kannattaa käyttää?

Käytä aggregaatiota aina kun pystyt Käytä yksityistä perintää kun on pakko Tyypillisesti et halua päästä käsiksi muiden luokkien

sisälmyksiin. Yksityinen perintä antaisi sinulle tällaista ylimääräistä voimaa (ja vastuuta)

Yksityinen perintä on rakaampaa ylläpitää, sillä silloin on suurempi vaara, että joku muuttaa koodia siten, että se menee rikki

Rakentajien käyttö perinnän yhteydessä

Isäluokan rakentajaa kutsutaan aina!*

CPoodle.cpp

CPoodle::CPoodle(int x, string y) : CDog (x,y)

{

cout << “Tuli muuten tehtyä puudeli" << endl;

}

Normaalia rakentaja tavaraa

PUHUTAAN TÄSTÄ LISÄÄ SEURAAVALLA LUENNOLLA!

Missä mennään Johdanto

Kertausta Esimerkki Yhteenveto

Luokkien näkyvyysmääreet Perintä Toiminnan korvaaminen Moniperintä Yhteenveto

Toiminnan korvaaminen

GRRRRYIP Joskus aliluokan olion on tarpeen

suorittaa kantaluokasta perimänsä palvelu hieman kantaluokasta poikkeavalla tavalla

Aliluokka haluaa siis periä rajapinnan, mutta ei toteutusta

C++ tarjoaa mahdollisuuden uudelleenmääritellä (override) kantaluokasta poikkeava toiminto. Määrittelet kyseisen funktion kantaluokassa vain virtuaaliseksi (avainsanalla virtual)

GRRR? YIP? VIRTUAL?? Hetkinen, eihän esimerkissä

määritelty growl –funktiota virtuaaliseksi!

Ei niin, esimerkissä oli itse asiassa kyseessä funktion peittäminen

Funktion peittäminen tapahtuu kirjoittamalla lapsiluokkaan täsmälleen saman niminen funktio

Funktion peittämisen yhteydessä lapsiluokka peittää kaikki samannimiset kantaluokan funktiot

Toiminnon peittäminen ja korvaaminen käyttäytyvät eri tavalla riippuen kutsutavasta.

Toiminnan peittäminen

Mitä tapahtuu?CPoodle *myPoodle;

myPoodle = new CPoodle();

CPoodle->growl();

((CDog*)myPoodle)->growl();

CDogbool rabidOrNot

int weight

string name

void growl()void eat()

CPoodlevoid growl() Vastaus:

YIPGRRRRRRRR

Toiminnan korvaaminen

Mitä tapahtuu?CPoodle *myPoodle;

myPoodle = new CPoodle();

CPoodle->growl();

((CDog*)myPoodle)->growl();

CDogbool rabidOrNot

int weight

string name

virtual void growl()void eat()

CPoodlevoid growl() Vastaus:

YIPYIP

Toiminnan korvaamisesta

Kutsutilanteesta riippuva jäsenfunkiton valinta ei ole toivottavaa

Tällainen tapahtuu helposti vahingossa silloin, kun kantaluokan jäsenfunktion esittelystä unohtuu avainsana virtual Silloin ei auta vaikka lapsiluokassa virtual

löytyisikin

Noniin… takaisin toiminnan korvaamisen pariin. Toiminta määritellään korvattavaksi siis virtual

avainsanaa käyttäen. Tämän jälkeen kantaluokasta periytettävillä aliluokilla on kaksi mahdollisuutta:

Hyväksyä kantaluokan tarjoama jäsenfunktion toteutus. Tällöin aliluokan ei tarvitse tehdä mitään

kantaluokan toteutus periytyy automaattisesti myös aliluokkaan

Kijoitaa oma toteutuksensa perimälleen jäsenfunktiolle. Tässä tapauksessa aliluokan esittelyssä esitellään jäsenfunktio uudelleen, ja sen jälkeen aliluokan toteutuksessa kirjoitetaan jäsenfunktiolle uusi toteutus aivan kuin normaalille aliluokan jäsenfunktiolle. Aliluokan esittelyssä avainsanan virtual toistaminen ei ole pakollista, mutta kylläkin hyvän ohjelmointityylin mukaista

Toiminnan korvaamisestaHuomioitavaa

On tärkeää, että korvaava funktio tarjoaa kantaluokan kannalta saman palvelun kuin alkuperäinenkin funktio.

Aliluokka voi muuttaa vain toteutusta, ei rajapintaa.

Päivitetään CDog.h:

Jäsenmuuttujille oma private

lohko

Growl funktiosta virtuaalinen

CDogbool rabidOrNot

int weight

string name

virtual void growl()void eat()

CPoodlevoid growl()

#ifndef CDog_H #define CDog_H#include <iostream.h>class CDog {public:

CDog (int x, std::string y); ~CDog(); //tuhoajan esittely

bool getRabid ( )const;void setRabid (bool x);std::string getName ( )const;void setName (std::string z);int getWeight ( )const;void setWeight (int x);void eat( );virtual void growl( )const;

private: int weight;

bool rabidOrNot;std:string name;

}; #endif /* CDog_H */

Ei muutoksia CDog.cpp:

#include <string.h>#include “CDog.h”

using namespace std;

// ConstructorCDog::CDog (int x, string y) {

rabidOrNot = false;weight = x;name = y;

}// destructorCDog::~CDog(){}void CDog::eat ( ) {

cout << name << “ is eating”<< endl;weight++;}void CDog::growl ( ) const{

cout << “Grrrr”;}

bool CDog::getRabid ( ) const{ return rabidOrNot;}void CDog::setRabid (bool x) { rabidOrNot = x;}int CDog::getWeight ( ) const{ return weight;}void CDog::setWeight (int y) { weight = y;}string CDog::getName ( ) const{ return name;}void setName (string z) { name = z;}

Myös puudeliin virtuallinen growl metodi:

YIP

CPoodle.cpp#include <iostream>

#include "CPoodle.h"

using namespace std;

CPoodle::CPoodle(int x, string y) : CDog (x,y){}

void CPoodle::growl( ) const{

cout << "Yip!" << endl;}

#include "CDog.h“ CPoodle.h

class CPoodle:public CDog {

public:

CPoodle(int x, std::string y);

virtual void growl() const;

};

Ja taas esimerkkiKoirat Kennelissä: Zoo.cpp

void main(){

CDog *kennel[5];CDog *valittuTyyppi;int valinta;

for (int i=0; i<5; i++){ cout<<“(1)Koira (2)Puudeli”; cin >> valinta; switch (valinta) { case 1: valittuTyyppi=new CDog(1,”koira”); break; case 2: valittuTyyppi=new CPoodle(1,”puudeli”); break; }

kennel[i]=valittuTyyppi;}for (i=0; i<5; i++)

kennel[i]->growl();}

GRRRR

Mitä tämä ohjelma tekee?

YIP

Koirat Kennelissä Syöte:12211

void main(){

CDog *kennel[5];CDog *valittuTyyppi;int valinta;

for (int i=0; i<5; i++){ cout<<“(1)Koira (2)Puudeli”; cin >> valinta; switch (valinta) { case 1: valittuTyyppi=new CDog(1,”koira”); break; case 2: valittuTyyppi=new CPoodle(1,”puudeli”); break; }

kennel[i]=valittuTyyppi;}for (i=0; i<5; i++)

kennel[i]->growl();}

Tuloste:GRRRYIPYIPGRRRGRRR

No mitäs kivaa tuossa oli? Esimerkki esitteli virtuaalifunktoiden

toiminnan puhtaimmillaan Täsmälleen sama koodirivi:

(kennel[i]->growl();) tuotti erilaisia tuloksia Kääntäjä ei kaikissa tapauksissa pysty vielä

käännösaikana päättelemään mitä rajapintafunktion totetusta on tarkoitus kutsua

Päätös tästä siirtyykin ajonaikaiseksi. Tästä käytetään nimitystä dynaaminen sitominen (dynamic binding)

Dynaaminen sitominen mahdollistaa siis sen, että sama jäsenfunktiokutsu käyttäytyy eri tavalla riippuen siitä, minkä tyyppinen olio osoittimen tai viittauksen päässä on

Dynaaminen sitominen Koska growl-funktio on

virtuaalinen, voidaan sen toteutus määritellä uudelleen missä tahansa periytetyssä luokassa.

Niinpä kääntäjä tietää vain, että siinä kutsutaan jotain jäsenfunktion growl toteutusta.

Kääntäjä tuottaa kyseiseen ohjelman kohtaan koodin, joka ensin tarkastaa osoittimen päässä olevan olion todellisen luokan ja vasta sen jälkeen kutsuu sille sopivaa jäsenfunktion toteutusta

void main(){

CDog *kennel[5];CDog *valittuTyyppi;int valinta;

for (int i=0; i<5; i++){ cout<<“(1)Koira (2)Puudeli”; cin >> valinta; switch (valinta) { case 1: valittuTyyppi=new CDog(1,”koira”); break; case 2: valittuTyyppi=new CPoodle(1,”puudeli”); break; }

kennel[i]=valittuTyyppi;}for (i=0; i<5; i++)

kennel[i]->growl();}

Virtuaalifunktoiden hyödyt

Virtuaalifunktiot ja dynaaminen sitominen tekevät mahdollisiksi todella joustavat ohjelmarakenteet jäsenfunktion kutsujan ei tarvitse tietää

yksityiskohtia siitä, mitä jäsenfunktion toteutusta kutsutaan

Ohjelman ylläpidettävyys, laajennettavuus ja luettavuus paranee

Virtuaalifunktioiden hinta Ohjelmakoodin täytyy aina

virtuaalifunktioiden yhteydessä: tarkastaa olion todellinen luokka valita oikea versio jäsenfunktion toteutuksesta

Em. tehtävät jää lähes aina ajonaikaiseksi. valinnan tekeminen hidastaa jäsenfunktion

kutsumista. Käytännön kokemusten mukaan

virtuaalifunktioiden käyttä on n. 4% hitaampaa

Virtuaalifunktioiden hinta Virtuaalifunktiot lisäävät myös muistin

kulutusta: Mikäli luokassa tai sen kantaluokassa on yksikin

virtuaalifunktio, täytyy luokan olioihin tallettaa jonnekkin tieto siitä, minkä luokan olioita ne ovat

Tähän käytetään yleensä virtuaalitaulua (v-taulu) Jokaista luokkaa kohden on yksi v-taulu ja jokaisella

luokan tyyppisellä oliolla on osoitin v-tauluun (virtuaalitaulujen toteutus riippuu kääntäjistä)

osoitin v-tauluun lisää olion muistin kulutusta n. 4 tavun verran. Ylimääräisten virtuaalifunktioiden lisääminen ei kasvata v-taulun osoittimien määrää.

Missä mennään Johdanto

Kertausta Esimerkki Yhteenveto

Luokkien näkyvyysmääreet Perintä Toiminnan korvaaminen Moniperintä Yhteenveto

Moniperintä

Kertausta: Periytymisessä uusi luokka

luodaan periyttämällä siihen jonkin olemassa olevan luokan ominaisuudet.

Miksei sitten periytetä kerralla ominaisuuksia useammasta luokasta?

Moniperintä C++ mahdollistaa moniperinnän. Syntaksi:

class Pegasus : public Bird, public Horse Moniperintä on hyvin kiistelty mekanismi:

Kaikki oliopohjaiset kielet ei tue moniperintää Pyri välttämään moniperinnän käyttöä

moniperinnän käyttö johtaa varsin usein ongelmiin asiat voidaan yleensä ratkaista muillakin tavoilla.

Joskus moniperintä on vain vähemmän työläämpää

Moniperintä -EsimerkkiKirjastonTeos

Hyllyluokka, lainaaika, yms.

KirjastonKirja

(Hyllyluokka, lainaaika, yms.)( Nimi, tekijä, yms.)Mahd. uudet ominaisuudet

Kirja

Nimi, tekijä, yms.

class KirjastonKirja : public KirjastonTeos, public Kirja{

…};

Moniperintä -käyttökohteita Rajapintojen yhdistäminen.

Halutaan oman luokan toteuttavan useiden eri rajapintojen toiminnallisuus

Luokkien yhdistäminen. Halutaan esimerkiksi käyttää hyväksi muutamaa yleiskäyttöistä

luokkaa oman luokan kehitystyössä. Luokkien koostaminen valmiista ominaisuuskokoelmista.

Esimerkki: Kaikki lainaamiseen liittyvät toiminnot on kirjoitettu Lainattava-

luokkaan. Vastaavasti kaikki tuotteen myymiseen liittyvät aisat ovat

luokassa Myytävät. Voimme luoda KirjastonKirja –luokan perimällä sen Kirja-

kantaluokasta ja maustamalla sen Lainattava-luokasta saaduilla ominaisuuksilla

Voimme yhtä lailla luoda KaupallinenCD-ROM-luokan perimällä sen CD-ROM kantaluokasta ja ottaa käyttöön ominaisuudet Myytävä-luokasta

Moniperitytymisen vaaroja Suuri osa moniperiytymisen vaaroista johtuu siitä, että se

on houkuttelevan helpontuntuinen vaihtoehto sellaisissakin tapauksissa, joissa se ei olioajattelun kannalta ole perusteltua.

Moniperityn luokan täytyy olla kaikein aikaa perittyjen kantaluokkiensa ilmentymä

Se ei vaan käy että välillä ollaan yhtä ja välillä toista Esim: Vesitaso ei pysty olemaan yhtäaikaa vene ja lentokone.

Tekee luokkarakenteen vaikeaselkoisiksi Aiheuttaa helposti ongelmia kuten rajapintojen

moniselitteisyyttä ja vaikeuksia elinkaaren hallinnassa. Älä siis käytä moniperintää ellei sille ole painavia

perusteita

Moniperintä ja moniselitteisyys

Kumpaa funktiota kutsutaan kun KirjastonKirjaa pyydetään tulostamaan tiedot?

KirjastonTeos

Hyllyluokka, lainaaika, yms.

KirjastonKirja

(Hyllyluokka, lainaaika, yms.)( Nimi, tekijä, yms.)Mahd. uudet ominaisuudet

Kirja

Nimi, tekijä, yms.

tulostaTiedot()tulostaTiedot()

Moniperintä ja moniselitteisyys Yritys kutsua kahdesta eri kantaluokasta periytynyttä

jäsenfunktiota aiheuttaa C++:ssa käännösaikaisen virheilmoituksen siitä, että jäsenfunktion kutsu on moniselitteinen (ambiguous)

Jos kummankin kantaluokan funktiot tekevät suunnilleen saman asian voidaan ongelma kiertää määrittelemällä samannimiset funktiot virtuaalisiksi (tästä puhutaan myöhemmin). Tällöin kutsutaa aliluokassa toteutettua funktiota

Jos kantaluokan funktiot taas ovat sisällöltään vahvasti erilaisia ajaudumme suurempiin ongelmiin. Tällöin on vaikea saada peritty luokka käyttäytymään siten, että se tyydyttää molemman kantaluokan tarpeet.

Moniperintä ja moniselitteisyys Jos moniselitteisille jäsenfunktioille ei

ole tarkoitus antaa uusia toteutuksia moniperiytetyssä aliluokassa, muodostuu ainoaksi ongelmaksi jäsenfunktion kutsuminen. Tämäkin vain silloin kun kutsutaan suoraan

aliluokan rajapinnan kautta Kantaluokkaosoittimien kautta

moniselitteisyyttä ei ole. Kullakin kantaluokalla on vain yksi

mahdollinen toteutus jäsenfunktiolle

Moniperintä ja moniselitteisyysRatkaisuja Kutsutaan moniselitteistä jäsenfunktiota aina

kantaluokkaosoittimien kautta tarvittaessa vaikkapa väliaikaisia osoitinmuuttujia käyttäen

helpoin, mutta kömpelöin ratkaisu Moniselitteisen jäsenfunktion kutsun yhteydessä on

mahdollista erikseen kertoa, minkä kantaluokan versiota halutaan kutsua. Tämä onnistuu ::-syntaksilla. Esimerkiksi Kirja-luokan tulostaTiedot-jäsenfunktiota voi kutsua syntaksilla:

KirjastonKirja k;k.Kirja::tulostaTiedot()

Tämä syntaksi on kuitenkin myös ehkä hieman oudon näköinen ja vaatii kantaluokan nimen kirjoittamista näkyviin kutsun yhteyteen

Kolmas vaihtoehto on kirjoittaa aliluokkaan uudet keskenään erinimiset jäsenfunktiot, jotka kutsuvat kunkin kantaluokan toteutusta moniselitteiselle jäsenfunktiolle ::-syntaksilla.

Esimerkki (3. vaihtoehto)

classs KirjastonKirja : public KirjastonTeos, public Kirja {public:

.

.

.void tulostaKTeostiedot (std::ostrea& virta) const;void tulostaKKirjatiedot (std::ostrea& virta) const;

}void KirjastonKirja::tulostaKTeostiedot (std::ostrea& virta) const{

KirjastonTeos::tulostaTiedot(virta);}void KirjastonKirja::tulostaKirjatiedot (std::ostrea& virta) const{

Kirja::tulostaTiedot(virta);}

Mitä tällä luennolla opimme? Puudeli-esimerkin avulla opimme laajentamaan

olemassa olevaa toteutusta perintää käyttämällä Kuinka jäsenmuuttujien ja –funktioiden

näkyvyyttä voidaa hallita luokan sisällä (public, protected, private

2-suuntainen linkitetty lista taas opetti koodin tekemisestä uudelleenkäytettäväksi uudelleenkäytettävän koodin hyväksikäyttöä mitä eroa on public- ja private perinnällä kuinka aggregaatio eroaa private perinnästä

Koirat kennelissä esimerkin avulla opimme dynaamisesta sidonnasta ja virtaalifunktioista

Moniperintä

top related