910 likes | 1.08k Views
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.
E N D
Olio-ohjelmoinnin perusteetluento 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
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ä
Eläin ... Niveljalkainen Selkäjänteinen ... ... Hämähäkkieläin Hyönteinen Matelija Nisäkäs Lintu ... ... ... ... ... Leppäkerttu Kissa Ihminen PerintäMuistatko 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??
Perintä: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 perintä 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?Perinnän määrittely Tässä suoritetaan perintä CDog -luokasta #include "CDog.h“ CPoodle.h class CPoodle:public CDog { public: CPoodle(int x, std::string y); }; 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 GRRRR YIP Toiminnan 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ää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 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 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ää 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 Tässä suoritetaan perintä CDog -luokasta #include "CDog.h“ CPoodle.h class CPoodle:public CDog { public: CPoodle(int x, string y); };
Perinnän määrittelyclass 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 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 • 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; last->prev = first; last->next = 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; } } 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; } } 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 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 () { 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ä? • 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