910 likes | 1.05k Views
Olio-ohjelmoinnin perusteet luento 4: Periytymisestä. Jani Rönkkönen jani.ronkkonen@lut.fi Luennot muokattu Sami Jantusen ja Kari Smolanderin aikaisempien vuosien luennoista. Sisältö. Johdanto Kertausta Esimerkki Yhteenveto Luokkien näkyvyysmääreet Periytyminen Toiminnan korvaaminen
E N D
Olio-ohjelmoinnin perusteetluento 4: Periytymisestä Jani Rönkkönen jani.ronkkonen@lut.fi Luennot muokattu Sami Jantusen ja Kari Smolanderin aikaisempien vuosien luennoista
Sisältö • Johdanto • Kertausta • Esimerkki • Yhteenveto • Luokkien näkyvyysmääreet • Periytyminen • Toiminnan korvaaminen • Moniperiytyminen • Yhteenveto
PeriytyminenMuistatko vielä….? • Periytyminen 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
PeriytyminenMuistatko vielä….? Perinnän idea: • Luokat voidaan organisoida hierarkkisiin periytymispuihin • Lapsiluokka perii vanhempiensa tiedon ja toiminnallisuuden • Abstrakti isäluokka on sellainen, josta ei voida tehdä omaa oliota, mutta jota käytetään lapsiluokkien määrittelyssä
Eläin ... Niveljalkainen Selkäjänteinen ... ... Hämähäkkieläin Hyönteinen Matelija Nisäkäs Lintu ... ... ... ... ... Leppäkerttu Kissa Ihminen PeriytyminenMuistatko vielä….?
Luokkahierarkia Mammal int weight giveBirth( ) Kuinka monta attribuuttia koiralla on? Land-Mammal int numLegs Dog boolean rabid ... Chihuahua SheepDog
Huomaathan että: • Land-Mammal on Dog- luokan isäluokka (superclass), mutta Mammal –luokan lapsiluokka (subclass) • Dog –luokalla on kolme attribuuttia • weight, numLegs ja rabid • kaksi attribuuttia perinnän kautta ja yksi omassa luokassa
Muistatko vielä? Koirat eläintarhassa CDog.h #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 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( ); void growl( )const; }; #endif /* CDog_H */ • Vielä paljon kerrottavaa Ei vielä ihan tyylipuhdas luokan esittely. CDog boolrabidOrNot intweight stringname void growl() void eat()
Muistatko vielä? Koirat eläintarhassa CDog.cpp 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; } #include <string.h> #include “CDog.h” using namespace std; // Constructor CDog::CDog (int x, string y) { rabidOrNot = false; weight = x; name = y; } // destructor CDog::~CDog() {} void CDog::eat ( ) { cout << name << “ is eating” << endl; weight++; } void CDog::growl ( ) const{ cout << “Grrrr”; }
Ongelmia! • Mikä tahansa koira ei kelpaa! Puudeli se olla pitää! Miten saamme luotua puudelin siten, ettei tarvitsisi kirjoittaa paljon koodia uusiksi??
periytyminen:Puudeli on koira • Puudeli on koira (muistathan “is-a” testin) • Käytetään hyväksi CDog-luokan toteutus perimällä siitä CPoodle-luokka CDog boolrabidOrNot intweight stringname void growl() void eat() CPoodle
No niin…. Ryhdytään hommiin!Luodaan puudeli-luokka (sekä .h, että .cpp tiedostot) Tässä suoritetaan periytyminen CDog -luokasta #include "CDog.h“ CPoodle.h class CPoodle: public CDog { public: CPoodle(int x, std::string y); }; #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!* CPoodle.cpp CPoodle::CPoodle(int x, string y) : CDog (x,y) { cout << “Tuli muuten tehtyä puudeli" << endl; } Normaalia rakentaja tavaraa *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)
Mitä tuli taas tehtyä?? • Loimme puudeliluokan jolla on kaikki attribuutit ja metodit kun CDog-luokallakin
Ongelmia! void CDog::growl ( ) { cout << “Grrrr”; } • Puudelit ei sano “Grrrrrrr”! Eihän??? • Ne sanoo “Yip”!
Eläin Selkäjänteinen Muistatko vielä?Toiminnallisuuden korvaaminen • Nisäkkäät synnyttävät eläviä poikasia • Linnut munivat munia • The Australian Platypus on nisäkäs, mutta luo munia Nisäkäs Lintu
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!!! GRRRRR • Korvataan CDog –luokan growl -metodi • Yksinkertaista, kirjoitetaan Puudeliluokkaan vain samanniminen metodi
#include "CDog.h“ CPoodle.h class CPoodle:public CDog { public: CPoodle(int x, std::string y); void growl() const; }; 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; }
Mitä juuri opimme?Periytymisen määrittely Tässä suoritetaan periytyminen CDog -luokasta #include "CDog.h“ CPoodle.h class CPoodle:public CDog { public: CPoodle(int x, std::string y); }; Puhumme periytymisestä hetken kuluttua lisää!
Mitä juuri opimme?Muodostimien käyttö periytymisen yhteydessä Isäluokan muodostinta 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 muodostinten ja purkajien käytössä on paljon huomioitavaa Puhumme tästä lisää ensi viikolla!
Mitä juuri opimme?Toiminnan korvaaminen GRRRR YIP Toiminnan korvaaminen on oleellinen osa periytymistä. Puhumme tästä hetken kuluttua lisää!
Missä mennään • Johdanto • Kertausta • Esimerkki • Yhteenveto • Luokkien näkyvyysmääreet • periytyminen • Toiminnan korvaaminen • Moniperiytyminen • Yhteenveto
Luokan näkyvyysmääreet • class CPoodle • { • public: • //Tänne tulevat asiat näkyvät • //luokasta ulos • protected: • //Tänne tulevat asiat näkyvät • //vain aliluokille • private: • //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 voivat 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 puutetta Minun 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ä muutetaan Et pääse “kopeloimaan” tietojani!
Protected: • Käytetään perittäessä luokkia • Muulloin toimii samoin kuin private: • Sallii lapsiluokkien käyttää yläluokan jäseniä • Lapsiluokka ei pääse suoraan käsiksi yläluokalta perimiinsä private jäseniin
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 • Periytyminen • Toiminnan korvaaminen • Moniperiytyminen • Yhteenveto
Periytymisen määrittely Tässä suoritetaan periytyminen CDog -luokasta #include "CDog.h“ CPoodle.h class CPoodle: public CDog { public: CPoodle(int x, string y); };
Periytymisen määrittelyclass CPoodle: public CDog • C++:ssa on oletuksena yksityinen periytyminen • class B : A {…} tarkoittaa samaa kuin class B : private A {…} • Kaikki isäluokasta perittävät asiat saavat private näkyvyysmääreen lapsiluokassa, riippumatta alkuperäisestä määrityksestä (eli poistuvat lapsiluokan julkisesta rajapinnasta) • Tämä on hieman outoa, sillä julkinen (public:) on kuitenkin yleisintä
Periytymisen tavoistaEsimerkki (Hannu Peltosen kirjasta Johdatus C++ ohjelmointikieleen) • Haluamme rakentaa yleiskäyttöisen luokan, jota apuna käyttäen voimme toteuttaa erilaisia 2-suuntaisia listoja next prev data next prev data next prev data
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. next prev data next prev data next prev data
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; }; next prev data next prev data next prev data
next prev data next prev data next prev data 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; } class Deque { public: void goBeforeFirst(); void goAfterLast(); void forth(); void back(); int isBeforeFirst() const; int isAfterLast() const; ... };
2-suuntainen lista (Deque)Rakentaja/muodostin • 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? next prev data next prev data next prev data
2-suuntainen lista (Deque)Rakentaja Deque::Deque() { first = new Item; last = new Item; first->prev = NULL; first->next = last; First->data = NULL; last->prev = first; last->next = NULL; Last->data = NULL; curr = first; }; class Deque { ... protected: Deque(); ... }; Kun rakentaja on protected, ei sitä voi kutsua muualta kuin periytetystä luokasta next prev data next prev data next prev data
2-suuntainen lista (Deque) Valmista? Puuttuuko vielä jotain?
next prev data next prev data next prev data 2-suuntainen lista (Deque)Alkion lisäys 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); } } class Deque { ... protected: void insertBeforeCurrent(void*); void insertAfterCurrent(void*); ... };
next prev data next prev data next prev data 2-suuntainen lista (Deque)Alkion poisto 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; //palautetaan data } } class Deque { ... protected: void *removeCurrentAndGoBack(); void *removeCurrentAndGoForth(); private: void *remove(Item *newCurr); ... };
2-suuntainen lista (Deque)Nykyisen alkion saanti ja listan tuhoaminen 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->data; delete p; } } class Deque { ... protected: void *current () const; ~Deque(); ... }; next prev data next prev data next prev data
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 • 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(); class IntDeque: public Deque { public: IntDeque(); void insert (int); void remove(); int current () const; ~IntDeque(); }; Deque-luokasta peritty Uusi toiminnallisuus next prev data next prev data next prev data
IntDeque toteutus • int IntDeque::current() const • { • int *ptr = (int*)Deque::current(); • return (ptr != NULL )? *ptr : -1; • } • IntDeque:: ~IntDeque() • { • goBeforeFirst(); • forth(); • while (!isAfterLast()) • { • delete (int*)Deque::current(); • forth(); • } • } IntDeque::IntDeque() { //kutsutaan isäluokan }//oletusmuodostinta automaattisesti void IntDeque::insert (int n) { int *ptr = newint; *ptr = n; insertAfterCurrent (ptr); } void IntDeque::remove () { //Kutsutaan delete myös data:lle delete (int*) removeCurrentAndGoBack(); }
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ä? • IntStack • 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