260 likes | 431 Views
Olio-ohjelmoinnin perusteet luento 5. Päivi Ovaska LTY/Tietotekniikan osasto. Sisältö. Luokkakohtaiset tieto- ja aliohjelmajäsenet Monimuotoisuus Yleistä Virtuaalinen aliohjelma ja dynaaminen sidonta Operaattoreiden ylikuormitus. Luokkakohtaiset tiedot.
E N D
Olio-ohjelmoinnin perusteetluento 5 Päivi Ovaska LTY/Tietotekniikan osasto
Sisältö • Luokkakohtaiset tieto- ja aliohjelmajäsenet • Monimuotoisuus • Yleistä • Virtuaalinen aliohjelma ja dynaaminen sidonta • Operaattoreiden ylikuormitus
Luokkakohtaiset tiedot • Luokkakohtaiset tietojäsenet (static data members) sisältävät luokkaa kuvailevia tietoja • Tiedot luokan kaikkien olioiden käytettävissä luokan metodeissa • Luokkakohtaisesta tiedosta on luokassa vain yksi ilmentymä • Luokkakohtainen tieto on ikään kuin globaali tieto luokan sisällä • Luokkakohtainen tieto on muistinvarausluokaltaan staattinen, joten järjestelmä varaa sille tilan globaalien muuttujien tilanvarauksen yhteydessä
Henkilo.hpp #ifndef HENKILOHPP #define HENKILOHPP class Henkilo { private: char etunimi[15]; char sukunimi[25]; char sotu[12]; static int lkm; static Henkilo *henkilot[10]; public: Henkilo(); ~Henkilo(); void Kysy(); void Nayta() const; static void Montako(); static int Lisaa(); static void TulostaKaikki(); static void TuhoaKaikki(); }; #endif Henkilo.cpp #include <iostream> using namespace std; #include <cstring> #include "henkilo.hpp" int Henkilo::lkm = 0; //luokkakohtaisen tiedon //määrittely ja alustus Henkilo *Henkilo::henkilot[10] = {NULL}; Henkilo::Henkilo() { strcpy(etunimi, " "); strcpy(sukunimi, " "); strcpy(sotu, " "); lkm++; } Henkilo::~Henkilo() { strcpy(etunimi, " "); strcpy(sukunimi, " "); strcpy(sotu, " "); } void Henkilo::Kysy() { cout<<"\nSYÖTÄ HENKILÖN TIEDOT\n"; cout<<"\nEtunimi: ............"; cin>>ws>>etunimi; cout<<"Sukunimi: ..........."; cin>>ws>>sukunimi; cout<<"Sosiaaliturvatunnus: "; cin>>ws>>sotu; } Esimerkki luokkakohtaisesta tiedosta
void Henkilo::Nayta() const { cout<<"\n\nHENKILÖN TIEDOT\n"; cout<<"\nNimi: "<<etunimi<<" "<<sukunimi; cout<<"\nSosiaaliturvatunnus: "<<sotu; } void Henkilo::Montako() { cout<<"\nHenkilöitä on yhteensä: "<<lkm; } int Henkilo::Lisaa() { int ind = 0; while (ind < 10 && henkilot[ind] != NULL) ind++; if (ind < 10) { henkilot[ind] = new Henkilo; henkilot[ind]->Kysy(); cout<<"\nLisätty henkilö: "; cout<<henkilot[ind]->sukunimi; return 1; } else return 0; } void Henkilo::TulostaKaikki() { int ind = 0; while (ind < 10 && henkilot[ind] != NULL) { henkilot[ind]->Nayta(); ind++; } } void Henkilo::TuhoaKaikki() { int ind = 0; while (ind < 10 && henkilot[ind] != NULL) { delete henkilot[ind]; henkilot[ind] = NULL; lkm--; ind++; } } Esimerkki jatkuu
Pääohjelma #include <iostream> using namespace std; #include "henkilo.hpp" int KysyValinta(); int main() { int vastaus, ok; vastaus = KysyValinta(); while (vastaus != 0) { switch(vastaus) { case 1: ok = Henkilo::Lisaa(); if (ok == 0) cout<<"\nTietorakenne täyttyi, henkilöä ei lisätty"; break; case 2: Henkilo::TulostaKaikki(); break; default: cout<<"\nKo. toimintoa ei ole"; } vastaus = KysyValinta(); } Henkilo::TuhoaKaikki(); return 0; } int KysyValinta() { int vastaus; cout<<"\nVALIKKO"; cout<<"\n1) Henkilötietojen lisäys"; cout<<"\n2) Henkilöiden tietojen tulostus"; cout<<"\n0) Lopetus\n"; cin>>ws>>vastaus; return vastaus; } Esimerkki jatkuu
Monimuotoisuus • Monimuotoisuus (polymorphism) tarkoittaa eri olioiden kykyä vastata samaan viestiin eri tuloksella • olion käyttäytymistavan määrää olion määrittelyluokkaan toteutettu metodi • ajonaikainen eli dynaaminen sidonta • Monimuotoisuus toteutetaan seuraavilla C++ mekanismeilla: • Virtuaalinen aliohjelma ja dynaaminen sidonta (korvaaminen,overriding) • Aliohjelmien ja operaattoreiden ylikuormitus (overloading) ja staattinen sidonta
Staattinen sidonta ja tavalliset metodit using namespace std; class Henkilo { public: void LuokanNimi() {cout<<"\nHenkilo";} }; class Tyontekija : public Henkilo { public: void LuokanNimi() {cout<<"\nTyontekija";} }; class Osakas : public Henkilo { public: void LuokanNimi() {cout<<"\nOsakas";} }; int main() { Henkilo *olio1 = new Henkilo; Tyontekija *olio2 = new Tyontekija; Osakas *olio3 = new Osakas; olio1->LuokanNimi(); olio2->LuokanNimi(); olio3->LuokanNimi(); return 0; } Dynaaminen sidonta ja virtuaalimetodit #include <iostream> using namespace std; class Henkilo { public: virtual void LuokanNimi() {cout<<"\nHenkilo";} }; class Tyontekija : public Henkilo { public: void LuokanNimi() {cout<<"\nTyontekija";} }; class Osakas : public Henkilo { public: void LuokanNimi() {cout<<"\nOsakas";} }; int main() { Henkilo *oliot[5]; oliot[0] = new Henkilo; oliot[1] = new Tyontekija; oliot[2] = new Osakas; for (int ind = 0; ind < 3; ind++) oliot[ind]->LuokanNimi(); return 0; } Esimerkki virtuaalisesta aliohjelmasta ja dynaamisesta sitomisesta
Aliohjelmien kuormitus • Aliohjelmien kuormitus (overloading) eli ylimäärittely tarkoittaa samannimisen aliohjelman esittelyä ja määrittelyä useaan kertaan • Käytetään, kun aliohjelman nimi halutaan standardoida tiettyyn tarkoitukseen riippumatta käsiteltävän tiedon tyypistä ja käsittelyn logiikasta • Kuormitetut aliohjelmat poikkeavat toisistaan tyyppien ja/tai lukumäärien perusteella • Kääntäjä tarkistaa käännösaikana, mikä aliohjelma sopii käytettyyn aliohjelmakutsuun parhaiten (staattinen sidonta) • Sopivuuden ratkaiseminen • suora sopivuus • sopivuus standardikonversion jälkeen • sopivuus käyttäjän määrittämän tyyppimuunnoksen jälkeen
Esimerkki kuormitetusta aliohjelmasta #include <iostream> using namespace std; void KerroTyyppi(int); void KerroTyyppi(int, int); void KerroTyyppi(double); int main() { KerroTyyppi(5); KerroTyyppi(2, 4); KerroTyyppi(2.5); return 0; } void KerroTyyppi(int luku) { cout<<"\nParametri on int "<<luku; } void KerroTyyppi(int luku1, int luku2) { cout<<"\n1. parametri on int "<<luku1; cout<<"\n2. parametri on int "<<luku2; } void KerroTyyppi(double luku) { cout<<"\nParametri on double "<<luku; }
Operaattoreiden ylikuormitus • Aliohjelman toteuttamista siten, että aliohjelman nimi on jokin operaattorimerkki • Ylikuormitusohjelman nimi on operatorop (op=operaattorimerkki) • Ylikuormitettavia operaattoreita: + - * / % & | ! = < > += -= *= /= %= = &= /= << >> >>= <<= == != <= >= && // ++ - ->* , () new delete
Operaattoreiden ylikuormituksen rajoituksia • merkkien suoritusjärjestystä ei ole mahdollista muuttaa • alkuperäistä syntaksia ei voi muuttaa • % ja ! ei ole mahdollista määritellä • uusia merkkejä ei voi määritellä • sizeof . .* :: ?: ei voi ylikuormittaa • Seuraavia operattoreita voidaan ylikuormittaa ainoastaan luokkien aliohjelmajäsenillä: =, (),[] ,-> • operaattoreita ei voi ylikuormittaa static-aliohjelmassa • Jos yliluokkaan on määritelty jokin ylikuormitettu operaattori, se periytyy aliluokille tavallisten aliohjelmien tapaan (poikkeus: sijoitusoperaattori =)
Esimerkkejä operaattoreiden ylikuormituksesta • kaksioperandisen operaattorialiohjelman kutsu • esim. luku1 + luku2; tulkintatavat: luku1.operator+(luku2); //luokka1 operator+ -viesti operator+(luku1,luku2); //luokkaan kuulumattoma operator+-aliohjelman kutsu, toteutetaan ystäväaliohjelmina, ei kuulu kurssin sisältöön • yksioperandisen operaattorin kutsu prefix-muodossa ++luku1; • yksioperandisen operaattorin kutsu postfix-muodossa luku1++;
Operaattoreiden esittelyt • Esittelyt: Luokka operator++(); // prefix-kutsumuoto Luokka operator++(int); //postfix-kutsumuoto
#include <iostream.h> class Luku { private: int x; public: Luku(const int p_x) {x = p_x;} Luku operator++(); //prefix Luku operator++(int); //postfix void Nayta() {cout<<x;} }; Luku Luku::operator++() { x++; return (*this); } Luku Luku::operator++(int) { Luku apu = *this; x++; return apu; } int main(void) { Luku Luku1(2); Luku Luku2 = Luku1++; cout<<"Luku2 on: "; Luku2.Nayta(); cout<<"\nLuku2 on muutettuna: "; Luku1.Nayta(); Luku Luku3 = ++Luku1; cout<<"\nLuku3 on: "; Luku3.Nayta(); return 0; } Tulos: Luku2 on: 2 Luku2 on muutettuna: 3 Luku3 on : 4 Esimerkki ++
Binäärinen lisäysoperaattori + • käsittelee kahta operandia Luokka olio1, olio2, olio3; olio3 = olio1 + olio2; • Kutsu voidaan purkaa: olio3.operator=(olio1.operator+(olio2));
#include <iostream> class Luku { private: int x; public: Luku() {x = 0;} Luku(const int p_x) {x = p_x;} Luku operator+(Luku &); Luku operator+(int); void Nayta() {cout<<x;} }; Luku Luku::operator+(Luku &p_olio) { Luku apu = *this; apu.x = apu.x + p_olio.x; return apu; } Luku Luku::operator+(int p_x) { Luku apu = *this; apu.x = apu.x + p_x; return apu; } int main(void) { Luku Luku1(2); Luku Luku2(3); Luku Luku3 = Luku1 + Luku2; //Luku1.operator+(Luku2) Luku3 = Luku3 + 2; //Luku3.operator+(2); cout<<"\nLuku3 on: "; Luku3.Nayta(); return 0; } tulos: Luku3 on:7 Esimerkki +
Unaarinen operaattori [] • Voidaan käyttää esimerkiksi merkkijonon tietyn merkin palauttamiseen • Operaattorin käyttö: olioparametri
class Merkkijono { char *jono; public: Merkkijono() { jono = NULL;} Merkkijono(const char []); Merkkijono(const Merkkijono &); Merkkijono &operator=(const Merkkijono &); ~Merkkijono(); void Nayta() const {if (jono) cout<<jono;} Merkkijono operator+(const Merkkijono &); char operator[](const int); }; Merkkijono Merkkijono::operator+(const Merkkijono &P_jono) { Merkkijono apu; apu.jono = new char[strlen(jono) + strlen(P_jono.jono) + 1]; strcpy(apu.jono, jono); strcat(apu.jono, P_jono.jono); return apu; } char Merkkijono::operator[](const int p_ind) { if (p_ind < strlen(jono) && p_ind > -1) return jono[p_ind]; else return 0; } Merkkijono::Merkkijono(const char p_jono[]) { int pituus = strlen(p_jono) + 1; jono = new char[pituus]; strcpy(jono, p_jono); } Merkkijono::Merkkijono(const Merkkijono &P_jono) { int pituus = strlen(P_jono.jono) + 1; jono = new char[pituus]; strcpy(jono, P_jono.jono); } Merkkijono &Merkkijono::operator=(const Merkkijono &P_jono) { int pituus = strlen(P_jono.jono) + 1; if (jono) delete [] jono; jono = new char[pituus]; strcpy(jono, P_jono.jono); return (*this); } Merkkijono::~Merkkijono() { if (jono) { delete [] jono; jono = NULL; } } int main(void) { Merkkijono olio1("Alku"); Merkkijono olio2("Loppu"); Merkkijono uusi = olio1 + olio2; uusi.Nayta(); cout<<"\n4. merkki on: "<<uusi[3]; return 0; } Esimerkki []
Tyyppimuunnosoperaattorit • tieto voidaan muuntaa luokan olioksi yksiparametrisen muodostimen avulla Luokka(tietotyyppi); Luokka olio =2; olio=Luokka(2); //järjestelmä tulkitsee sijoituksena • Olio voidaan muuntaa muuntyyppiseksi tiedoksi tyyppimuunnosoperaattorin avulla operator tyyppi(); • tyyppimuunnosoperattori on toteutettava luokan metodina, ei paluuttyyppiä eikä parametreja
class Luku { private: int x; public: Luku() {x = 0;} Luku(const int p_x) {x = p_x;} Luku(const double p_x) {x = (int)p_x;} operator int() {return x;} Luku operator+(Luku &); Luku operator+(int); void Nayta() {cout<<x;} }; Luku Luku::operator+(Luku &p_olio) { Luku apu = *this; apu.x = apu.x + p_olio.x; return apu; } Luku Luku::operator+(int p_x) { Luku apu = *this; apu.x = apu.x + p_x; return apu; } int main(void) { Luku Luku1; Luku1 = 2; Luku Luku2; Luku2 = 4.5; cout<<"Luku1: "; Luku1.Nayta(); cout<<"\nLuku2: "; Luku2.Nayta(); cout<<"\nMuunnettuna kokonaisluvuksi: "; int numero = (int)Luku2; cout<<numero; return 0; } tulos: Luku1:2 Luku2:4 Muunnettuna kokonaisluvuksi: 4 Esimerkki tyyppimuunnosoperaattorista
Myöhäisen sidonnan käyttötapoja • Käsitteellinen erikoistus/yleistys-hierarkkia • uudelleenkäyttö • rajapinta, jolla on useita toteutuksia • abstrakti rajapinta voidaan määritellä oma luokkanaan (pelkästään operaatiot avoimina virtuaaalioperaatioina), joka toteutuksen tarjoava luokka perii