Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 1/32 Abstraktní třídy, polymorfní struktury Ing. Josef Vogel, CSc Katedra softwarového inženýrství Katedra teoretické informatiky, Fakulta informačních technologii, ČVUT v Praze Karel Müller, Josef Vogel, 2011 Programování a algoritmizace 2, BI-PA2, 2011, Přednáška 9 BI-PA2 Evropský sociální fond Praha & EU: Investujeme do vaší budoucnosti
Témata abstraktní třídy polymorfní datové struktury Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 2/32
Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 3/32 Abstraktní třídy Příklad: čítače různých typů hodnot Třídu Citac zavedeme bez datových položek, pouze s deklarovanými prototypy metod.
Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 4/32 Čítač jako abstraktní třída Citac zvetsit zmensit nastavit hodnota Operace jsou definované až pro potomky IntCitac int hodn int pochodn zvetsit zmensit nastavit hodnota ZnCitac char hodn char pochodn zvetsit zmensit nastavit hodnota DatCitac Datum hodn Datum pochodn zvetsit zmensit nastavit hodnota
p9\citac1\citac.h Abstraktní třída Citac (specifikace) Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 5/32 /* citac.h */ #ifndef _CITAC_ #define _CITAC_ struct Citac { Citac() { } virtual ~Citac() { } virtual void zvetsit() = 0; virtual void zmensit() = 0; virtual void nastavit() = 0; virtual void hodnota(char *val) = 0; }; #endif metody budou definovány až v podtřídách
p9\citac1\intcitac.h Konkrétní podtřída IntCitac Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 6/32 /* intcitac.h */ #include "citac.h" #include <stdio.h> class IntCitac : public Citac { int hodn; int pochodn; public: IntCitac(int ph) {pochodn = ph; nastavit();} virtual void zvetsit() {hodn++;} virtual void zmensit() {hodn--;} virtual void nastavit(){hodn = pochodn;} virtual void hodnota(char *val) {sprintf(val, "%d", hodn);} };
p9\citac1\zncitac.h Konkrétní podtřída ZnCitac Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 7/32 /* zncitac.h */ #include "citac.h #include <ctype.h> #include <stdio.h> class ZnCitac : public Citac { char hodn; char pochodn; public: ZnCitac(char ph) {pochodn = ph; nastavit();} virtual void zvetsit() {hodn++;} virtual void zmensit() {hodn--;} virtual void nastavit() {hodn = pochodn;} virtual void hodnota(char *val) { if (isprint(hodn)) sprintf(val, "%c", hodn); else sprintf(val, "\\%d", hodn); } };
p9\citac1\citani.cpp Úprava v proceduře pro čítání Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 8/32 void citani(citac *pc) { int volba; char hodn[100]; do { pc->hodnota(hodn); cout << "Hodnota = " << hodn << endl; volba = menu(); switch (volba) { case 1: pc->zvetsit(); break; case 2: pc->zmensit(); break; case 3: pc->nastavit(); break; } } while (volba > 0); cout << "konec citani" << endl; }
p9\citac1\main.cpp Program se dvěma čítači Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 9/32 #include "intcitac.h" #include "zncitac.h" #include "citani.h" #include <iostream> using namespace std; int main() { IntCitac ic(0); ZnCitac zc('a'); cout << "Citace\n"; // Citani cisel cout << "Citani cisel\n" << endl; citani(&ic); // čítá cisla // Citani znaku cout << "\ncitani znaku\n" << endl; citani(&zc); // čítá znaky cout << "Konec\n"; return 0; }
Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 10/32 Řešení 2 Jako abstraktní třídu zavedeme typ čitatelných hodnot CitHodnota zvetsit zmensit hodnota Cislo hodn zvetsit zmensit hodnota Znak hodn zvetsit zmensit hodnota Datum den mesic rok zvetsit zmensit hodnota
Řešení 2 V čítači nebude hodnota, ale ukazatel na čitatelnou hodnotu Čítač nebude abstraktní, ale konkrétní třída Citac CitHodnota *hodn CitHodnota *pochodn zvetsit zmensit nastavit hodnota objekt typu Cislo, Znak nebo Datum Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 11/32
p9\citac2\citac.h Třída Citac Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 12/32 /* citac.h */ #ifndef _CITAC_ #define _CITAC_ #include "cithodn.h" class Citac { CitHodnota *hodn; CitHodnota *pochodn; public: Citac(CitHodnota *ph) {pochodn = ph; nastavit();} void zvetsit() {hodn->zvetsit();} void zmensit() {hodn->zmensit();} void nastavit() {hodn = pochodn;} void hodnota(char *val){hodn->hodnota(val);} }; #endif
p9\citac2\cithod.h, cislo.h Abstraktní a konkrétní čitatelná hodnota Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 13/32 /* cithodn.h */ struct CitHodnota { virtual void zvetsit() = 0; virtual void zmensit() = 0; virtual void hodnota(char *val) = 0; }; /* cislo.h */ #include <stdio.h> #include "cithodn.h" class Cislo : public CitHodnota { int hodn; public: Cislo(int c) {hodn = c;} virtual void zvetsit() {hodn++;} virtual void zmensit() {hodn--;} virtual void hodnota(char *val) {sprintf(val, "%d", hodn);} };
p9\citac2\main.cpp Program pro čítání čísel a znaků Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 14/32 #include "citani.h" #include "citac.h" #include "cislo.h" #include "znak.h" #include <iostream> using namespace std; int main() { Cislo *pc=new Cislo(0); Znak *pz=new Znak('A'); Citac citcisel(pc); Citac citznaku(pz); cout << "\n\ncitace\n"; cout << "Citani cisel\n" << endl; // Citani cisel citani(&citcisel); cout << "\ncitani znaku\n" << endl; // Citani znaku citani(&citznaku); cout << "Konec\n"; delete pc; delete pz; return 0; } -- Nefunguje nastavit! Proč?
Oprava chyby Chyba je v tom, že nastavit uloží do hodn ukazatel na stejný objekt, na který ukazuje pochodn hodn pochodn 0 Je třeba přiřadit ukazatel na dynamicky vytvořenou kopii hodn pochodn 0 0 kopie počáteční hodnoty Vytvoření dynamické kopie sebe sama musí umět čitatelná hodnota Třídu CitHodnota doplníme o virtuální metodu CitHodnota *kopie() Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 15/32
Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 16/32 Doplněná třída CitHodnota CitHodnota zvetsit zmensit hodnota kopie Cislo hodn zvetsit zmensit hodnota kopie Znak hodn zvetsit zmensit hodnota kopie Datum den mesic rok zvetsit zmensit hodnota kopie
p9\citac3\cithod.h, cislo.h Doplněná třída CitHodnota a Cislo Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 17/32 /* cithodn.h */ struct CitHodnota { virtual void zvetsit() = 0; virtual void zmensit() = 0; virtual void hodnota(char *val) = 0; virtual CitHodnota *kopie() = 0; }; /* cislo.h */ #include <stdio.h> #include "cithodn.h class Cislo : public CitHodnota { int hodn; public: Cislo(int c) {hodn = c;} virtual void zvetsit() {hodn++;} virtual void zmensit() {hodn--;} virtual void hodnota(char *val) {sprintf(val, "%d", hodn);} virtual CitHodnota *kopie(){return new Cislo(hodn);} };
p9\citac3\citac.h Upravená třída Citac Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 18/32 /* citac.h */ #include "cithodn.h" class Citac { CitHodnota *hodn; CitHodnota *pochodn; public: Citac(CitHodnota *ph) }; { pochodn = ph; hodn = pochodn->kopie(); } ~Citac() { delete pochodn; delete hodn; } void zvetsit() { hodn->zvetsit(); } void zmensit() { hodn->zmensit(); } void nastavit() { delete hodn; hodn=pochodn->kopie(); } void hodnota(char *val) { hodn->hodnota(val); }
Polymorfní datové struktury Abstraktní datový typ (zásobník, fronta, tabulka apod.) je obvykle implementován datovou strukturou Datová struktura obsahuje prvky, pro které mohou být definovány určité operace Homogenní datová struktura obsahuje prvky stejného typu, tzn. každá operace se provádí pro všechny prvky stejně Polymorfní datová struktura obsahuje prvky různých typů, operace s prvky se mohou provádět různě v závislosti na typu prvku Polymorfní datové struktury je třeba implementovat s využitím abstraktní třídy, typy prvků jsou jejími podtřídami Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 19/32
Polymorfní zásobník Polymorfní zásobník bude implementován spojovým seznamem dynamických proměnných, které budou obsahovat ukazatel na prvek zásobníku Typy prvků zásobníku budou podtřídami abstraktní třídy Prvek Stack Elem Elem Elem Prvek Prvek Prvek Ve třídě Prvek bude pro všechny prvky bude přetížen operátor <<, který pro výpis použije dynamicky vázanou metodu Print, jejíž definice bude až v podtřídách Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 20/32
Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 21/32 Polymorfní zásobník Stack vrch void push(prvek*) Prvek *pop() int isempty() << Elem dalsi hodn Prvek void print(ostream&)=0 << Cislo int hodn void print(ostream&) Znak char hodn void print(ostream&)
Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 22/32 p9\polym1\stack.h Polymorfní zásobník /* stack.h */ #include <iostream> using namespace std; class Prvek { public: virtual void print(ostream&) const = 0; friend ostream& operator<<(ostream& s,const Prvek& p) {p.print(s); return s;} }; class Stack { struct Elem { Prvek *hodn; Elem *dalsi; Elem(Prvek *h, Elem *d){hodn = h; dalsi = d;} }; Elem *vrch; public: Stack() {vrch = 0;} ~Stack(); void push(prvek* p) {vrch = new Elem(p, vrch);} Prvek* pop(); int isempty() {return vrch == 0;} friend ostream& operator<<(ostream&, Stack&); };
p9\polym1\stack.cpp Polymorfní zásobník Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 23/32 /* stack.cpp */ #include "stack.h" Stack::~Stack() { Elem *p=vrch; while (p) { vrch=vrch->dalsi; delete p; p=vrch; } } Prvek* Stack::pop() { Elem *pom = vrch; Prvek *prvek = vrch->hodn; vrch = vrch->dalsi; delete pom; return prvek; } ostream& operator<<(ostream& s, Stack& stc) { Stack::Elem *pom = stc.vrch; while (pom) { s << *pom->hodn << endl; pom = pom->dalsi; } return s; }
p9\polym1\main.cpp Použití polymorfního zásobníku Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 24/32 /* main.cpp */ /* Stack s abstraktnim prvkem */ #include "stack.h" #include <iostream> using namespace std; class Cislo : public Prvek { int hodn; public: Cislo(int h) {hodn = h;} virtual void print(ostream& s) const {s << hodn;} }; class Znak : public Prvek { char hodn; public: Znak(char h) {hodn = h;} virtual void print(ostream& s) const {s << hodn;} };
p9\polym1\main.cpp Použití polymorfního zásobníku Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 25/32 void main() { Stack s; Znak z('b'); Cislo c(222); cout << "Polymorfní zásobník\n"; Cislo *pcislo=new Cislo(10); s.push(pcislo); Cislo *pznak=new Znak( a ); s.push(pznak); s.push(&z); s.push(&c); cout << "Vypis zásobníku\n" << s; cout << "Odebirani ze zásobníku\n"; while (!s.isempty()) cout << *s.pop() << endl; delete pcislo; delete pznak; }
K parametrům typu reference na nadtřídu Je-li parametr funkce (metody, konstruktoru) typu reference na třídu T, přípustným skutečným parametrem je objekt typu T, kde T je T nebo podtřídou T. Ve druhém případě se předá odkaz na objekt, aniž by se prováděla konverze na T. Bylo použito v předchozím příkladě: class Prvek { public: virtual void print(ostream&) const = 0; friend ostream& operator<<(ostream& s,const Prvek& p) {p.print(s); return s;} }; Funkce operator<< definovaná pro třídu Prvek se vyvolá i pro objekt typu Cislo nebo Znak; vlastní výpis provede metoda print, která je definovaná pro každou podtřídu jinak. Parametr typu reference na nadtřídu může být též výstupním parametrem Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 26/32
p9\polym2\main.cpp Výběr ukazatele z polymorfní datové struktury Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 27/32 Při výběru ukazatele na prvek polymorfní datové struktury je někdy třeba jej uložit do proměnné typu ukazatel Obsahuje-li datová struktura ukazatele na instance podtříd abstraktní třídy T, lze vybraný ukazatel uložit přímo (bez přetypování) pouze do proměnné typu T* Příklad 1: Stack s; s.push(new Cislo(10)); s.push(new Znak( a )); Znak *pz = s.pop(); Cislo *pc = s.pop(); Příklad 2: Stack s; s.push(new Cislo(10)); s.push(new Znak( a )); Prvek *p1 = s.pop(); Prvek *p2 = s.pop(); // chyba při překladu // chyba při překladu // O.K. // O.K.
p9\polym3\main.cpp Dynamické přetypování na ukazatel na podtřídu Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 28/32 Ukazatel na nadtřídu lze dynamicky přetypovat na ukazatel na podtřídu Příklad: Součet čísel v polymorfním zásobníku int main() { Stack s; s.push(new Cislo(10)); s.push(new Cislo(29)); s.push(new Znak('a')); cout << s; } Cislo *pc; int soucet = 0; while (!s.isempty()) { pc = dynamic_cast<cislo*>(s.pop()); if (pc!=null) soucet += pc->hodn; } cout << "soucet cisel v zasobniku = " << soucet << endl; return 0;
p9\polym4\main.cpp Dynamický test typu objektu Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 29/32 Někdy stačí pouze zjistit typ objektu, na který ukazuje proměnná typu nadtřída objektu Příklad: počet čísel a znaků v polymorfním zásobníku #include <typeinfo> int main() { Stack s; s.push(new Cislo(10)); s.push(new Cislo(29)); s.push(new Znak('a')); cout << s; Prvek *p; int pocetcisel = 0, pocetznaku = 0; while (!s.isempty()) { p = s.pop(); if (typeid(*p)==typeid(cislo)) pocetcisel++; else if (typeid(*p)==typeid(znak)) pocetznaku++; } cout << "pocet cisel = " << pocetcisel << endl; cout << "pocet znaku = " << pocetznaku << endl; return 0; }
RTTI RTTI - Run Time Type Information mechanizmus umožňující identifikaci typu objektu při běhu programu původně řešeno knihovnami (např. MFC), přičemž v každé jinak proto doplněno do poslední verze jazyka RTTI zahrnuje: dynamické přetypování (operátor dynamic_cast) identifikaci typu objektu (operátor typeid) třídu type_info obsahující informace o typu objektu Použití RTTI ve VC vyžaduje nastavení parametru překladače Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 30/32
RTTI Je-li p ukazatel na třídu s dynamicky vázanými metodami, pak výsledek operace dynamic_cast<t*>(p) je typu T* a je různý od NULL, ukazuje-li p na objekt, jehož třída je T nebo potomkem či předkem T, jinak je výsledkem NULL Je-li x proměnná nebo parametr typu reference na třídu s dynamicky vázanými metodami, pak výsledek operace dynamic_cast<t&>(x) je typu T, je-li x instancí třídy T nebo potomka či předka T, jinak vyvolána výjimka bad_cast Je-li x instance nebo identifikátor třídy T s dynamicky vázanými metodami, pak výsledkem operace typeid(x) je dynamická identifikace třídy T Výsledkem operátoru typeid je konstatní reference na objekt typu type_info, který identifikuje třídu při běhu programu Použití operátoru typeid vyžaduje vložení header-souboru <typeinfo.h> Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 31/32
Karel Müller, Josef Vogel (ČVUT FIT) Abstraktní třídy, polymorfní struktury BI-PA2, 2011, Přednáška 9 32/32 Operátory přetypování const_cast<t>(x) přidává nebo ubírá modifikátory const resp. volatile static_cast<t>(x) běžná přetypování, v rámci hierarchie tříd pouze se statickou kontrolou (bez dynamické kontroly typů) dynamic_cast<t>(x) přetypování ukazatelů nebo referencí v rámci hierarchie tříd s dynamickou kontrolou typů reinterpret_cast<t>(x) implementačně závislá a nepřenositelná přetypování