Objektově orientované programování RNDr. Jan Lánský, Ph.D. Katedra softwarového inženýrství MFF UK Katedra informatiky VŠFS (Autor původní verze slajdů: Mgr. Zbyněk Winkler) (Autor prapůvodní verze slajdů: RNDr. Filip Zavoral, Ph.D.) (Část slajdů převzata od: RNDr. David Bednárek)
[email protected] http://kocour.ms.mff.cuni.cz/~lansky/
Zápočet
Zdrojové kódy v minimálním rozsahu 1500 řádků, doporučeno 2500 řádků. Nejlépe: Vlastní ucelený příklad, který něco rozumného dělá Vlastní jednoduché příklady, zadání může být z knihy Nejhůře: Příklady probírané na cvičení
PS: Aktivní účast na cvičeních (8 účastí z 12 možných) KS: Aktivní účast na soustředěních, 4 účasti z 9 možných) Chybějící účast (pod stanovený limit) lze nahradit: PS: +200 řádků zdrojových kódů za 1 hodinu neúčasti KS: +400 řádků zdrojových kódů za 1 neúčast na soustředění. Pozor v jeden den bývají obvykle 2 až 3 soustředění) Příklad: PS student X se zúčastnil jedné hodiny cvičení. Na zápočet odevzdá 1500 + (8 – 1)*200 = 2900 řádků zdrojových kódů. Příklad: KS student Y se zúčastnil pouze výuky 1.4.2015 kdy proběhly 2 hodiny soustředění. Na zápočet odevzdá 1500 + (4 – 2)*400 = 2300 řádků zdrojového kódu.
Zkouška Diskuze nad zdrojovými kódy předloženými k získání zápočtu. Zkušební okruhy
Reference jako parametr funkce a návratová hodnota Přetěžování funkcí a operátorů, new a delete Bezparametrický konstruktor, copy konstruktor, operator =, destruktor Dědičnost, Virtuální funkce, abstraktní třídy Prostory jmen Streamy, práce se soubory Šablony funkcí a tříd STL kontejnery, iterátory a algoritmy Výjimky
Literatura Miroslav Virius: Programování v C++ Miroslav Virius: Pasti a propasti jazyka C++ Miroslav Virius: Od C k C++ Scott Meyers: Effective C++, More Effective C++, Effective STL Herb Sutter: Exceptional C++, More Exceptional C++ Que: ANSI/ISO C++ Professional Programmer's Handbook Bruce Eckel: Myslíme v jazyce C++ James O. Coplien: Advanced C++ Programming Styles and Idioms Bjarne Stroustrup: The C++ Programming Language ISO/IEC 14882, ANSI: Programming languages - C++ (1998, 2003)
Obsah předmětu ZS (PJC)
LS (OOP)
C++
C++
C
C
Paradigmata programování, OOP Objekty, zapouzdření, dědičnost, konstruktory a destruktory Přetěžování funkcí, předefinování operátorů Pozdní vazba, virtuální funkce Abstraktní datové typy Šablony, výjimky, prostory jmen. Objektové knihovny: streams, STL
RTTI, OO styly a idiomy...
Paradigmata programování Procedurální programování
jakou akci mám provést vstup – výpočet (algoritmus) – výstup black box: procedura / funkce
side effects, údržba
Modulární programování
rozdělení problému na komponenty procedury pracují nad daty - rozhraní black box: modul
Datová abstrakce
vytvoření vlastního datového typu (abstract/user defined datové typy) kompletní množina operací nad tímto typem nelze rozumně rozšiřovat black box: datový typ
Objektové programování
dědičnost – obecné / konkrétní vlastnosti Polymorfismus – odlišné chování potomků možnost pozdějších rozšíření zapouzdření
dále – generické programování šablony, STL
Třídy a objekty Koncepční pohled
objekt: entita reagující na vnější podněty třída: množina stejně reagujících entit
Technický pohled
objekt: struktura obsahující data a funkce, instance třídy (proměnná) třída: typ objektu – jednotná struktura dat, stejné operace nad daty Zobecnění pojmu struktura (struct) Rozdíl mezi class a struct v C++ je nepatrný, užívání class je pouze konvence
deklarace třídy obsahuje Deklarace datových položek (stejně jako v C) Funkce (metody), virtuální funkce a statické funkce Definice výčtových konstant a typů (včetně vnořených tříd)
Rozhraní – veřejné informace a služby pro uživatele Implementace – (neveřejná) interní data a metody (funkce)
Třída zvíře v C++ - rozhraní zvire.h definice třídy rozhraní (veřejné) konstruktor (inicializace)
metody
class Zvire { private: int zaludek;
vnitřní stav (privátní)
public: Zvire() { zaludek = 1; };
Datová položka
int zije() { return zaludek>0; }; int jez( int jidlo); int vymesuj( int objem); };
inline tělo funkce
Deklarace metody
Třída zvíře - implementace zvire.h class Zvire { Třída metody private: int zaludek; public: Zvire() { ... }; int zije() { ... }; int jez( int jidlo); int vymesuj( int objem); };
Přístup k datům metody
Středník !!!
zvire.cpp :: operátor kvalifikace
#include ”zvire.h” int Zvire::jez( int jidlo) { if( ! zije()) return 0; return zaludek += jidlo; }
int Zvire::vymesuj( int objem) { if( (zaludek -= objem) <= 0) zaludek = 0; return zaludek; }
Implementace (tělo) metody
Třída zvíře - použití zvire.h
mujprogram.cpp Import rozhraní
class Zvire { private: int zaludek; public: Zvire() { ... }; int zije() { ... }; int jez( int jidlo); int vymesuj( int objem); };
zaludek = 1
zaludek = 6
#include ”zvire.h” ..... Automatický konstruktor { ..... Zvire pytlik;
pytlik.jez(5); pytlik.vymesuj(3); zaludek = 3 if( ! pytlik.zije()) return -1; -1 0 pytlik.vymesuj(4); if( ! pytlik.jez(1)) return -2; ..... }
Instance třídy = objekt
Objekt - instance třídy
int Zvire::jez( int jidlo) { if( ! zije()) return 0; return zaludek += jidlo; } dvě instance třídy
..... Zvire pytlik, beruska; pytlik.jez( 5); beruska.jez( 1); .....
Metoda třídy - ke kterému objektu má přistupovat?
?
pytlik: beruska:
zaludek
6
zaludek
2
this Každá metoda dostane 'tajný' parametr this – ukazatel na objekt
C
zvire:: znamena zvire * this
C++
int jez( Zvire* this, int jidlo) { if( ! zije( this)) return 0; return this->zaludek += jidlo; }
int Zvire::jez( int jidlo) { if( ! zije()) return 0; return zaludek += jidlo; }
..... Zvire pytlik, beruska;
..... Zvire pytlik, beruska;
jez( &pytlik, 5); jez( &beruska, 1); .....
pytlik.jez( 5); beruska.jez( 1); .....
this->zije()
this->zaludek
this pytlik:
zaludek
6
beruska:
zaludek
2
Reference int x = 1, y = 2; int *px; px = &x; *px = 3;
reference pouze inicializace nelze měnit
int &ry = y; ry = 4;
x:
3
:px
y:
4
:ry
reference i ukazatele jsou reprezentovány adresou
return *px + ry;
x:
1
:a
y:
2
:b
swap( int& a, int& b) { int c = a; a = b; b = c; } int x = 1, y = 2; swap( x, y);
skutečné parametry odkazy na proměnné
zpřehlednění kódu přetěžování funkcí
Přetěžování funkcí Funkce je definována svým identifikátorem a počtem a typem parametrů int pocitej( int x) { return x+1; }
Funkce se stejným identifikátorem ale různým počtem parametrů
int pocitej( int a, int b) { return 2*a + b; } int pocitej( int a, const char* s) { return a + strlen( s); } pocitej( 1); pocitej( 1, 2); pocitej( 1, "ahoj");
Funkce se stejným počtem ale různým typem parametrů
Správná funkce podle počtu a typů skutečných parametrů
// int pocitej( int) // int pocitej( int, int) // int pocitej( int, char*)
Implicitní parametry Některé parametry funkce mohou mít implicitní hodnoty
pokud nejsou všechny parametry implicitní – implicitní parametry odzadu
Při volání funkce lze implicitní parametry vynechat
použije se implicitní hodnota
int fce( int a, int b = 2, int c = 4) { return 2*a + b - c; } fce( 1); // int fce( 1, 2, 4) fce( 1, 5); // int fce( 1, 5, 4) fce( 1, 5, 6); // int fce( 1, 5, 6)
Volá se stále stejná funkce int fce( int, int, int)
Kdy použít přetěžování a kdy implicitní parametry?
Stejný kód pro různý počet parametrů implicitní parametry Pro různé počty nebo typy parametrů různý kód přetěžování
Konstruktory Implicitní konstruktor
class Zvire { private: int zaludek; public: Zvire() { zaludek = 1; }; Zvire( int zal) { zaludek = zal; }; Zvire( const Zvire& vzor) { zaludek = vzor.zaludek; }; }; Zvire beruska; Zvire pytlik( 20); Zvire beberuska( beruska); Zvire tlustoch = pytlik;
bez parametrů
Konstruktor s parametry
Copy konstruktor X (const X&)
vytvoří objekt jako kopii jiného různé zápisy copy konstruktoru Pro U≠T nejsou zcela ekvivalentní: U u; T t(u); // T::T( U&) T t = u; // T::T( T(u)) nebo // T::T( u.operator T()) zatím lze ignorovat
Konstruktor s parametry Tímto zakážeme deklarovat objekt bez použití NEimplicitního konstruktoru
class Clovek { private: char jmeno[50]; Clovek();
Využití
public: //Clovek() { jmeno[0] = 0; }; Clovek( char * jmeno) { strcpy(this->jmeno, jmeno); }; }; Clovek honza("Honza"); //Clovek petr;
Nejde, zakázali jsme
Předání nastavení objektu Šetří řádky kódu
Zakázat implicitiní konstruktor
Dobře rozvážit zda zakázat Bezpečnost proti nezadání klíčové hodnoty
identifikátor
Přetěžování operátorů - deklarace
class Bod { private: int x, y; public: Bod( int xx=0, int yy=0) { x=xx; y=yy; }; Bod operator+( const Bod&); Bod operator=( const Bod&); };
implicitní parametry implicitní konstruktor
přetížení operátoru + a+b a=b
a.operator+(b) a.operator=(b)
Bod::Bod(0,0);
Bod a(1,2), b, c; c = a + b; c.operator=(a.operator+(b));
c.assign( a.add( b));
Přetěžování operátorů – těla metod
x
this->x
Bod Bod::operator=( const Bod& b) { x = b.x; y = b.y; return *this; } Bod Bod::operator+( const Bod& b) { return Bod( x+b.x, y+b.y); }
co to je ???
vytvoření dočasného objektu konstruktor Bod::Bod(int, int)
reference aktualizace stavu kopie objektu
(hodnotou přiřazení je přiřazovaná hodnota)
Přetěžování operátorů - pravidla
Většinu operátorů jazyka C++ lze definovat pro uživatelské datové typy
Nelze předefinovat tyto operátory:
.
.*
::
? :
sizeof
Alespoň jeden z operandů musí být třída nebo výčtový typ nebo reference na ně
Nelze předefinovat operace na číselných typech a ukazatelích
Předefinováním nelze měnit prioritu a asociativitu operátorů
Pro předefinované operátory nemusí platit identity definované pro základní typy ++a nemusí být ekvivalentní a=a+1 a[b] nemusí být ekvivalentní *(a+b) ani b[a]
je však velmi doporučeno dodržovat běžnou sémantiku
Pro předefinované operátory && a || neplatí pravidla o zkráceném vyhodnocování Typy skutečných operandů nemusejí přesně odpovídat typům formálních parametrů
stejná pravidla jako pro přetížené funkce
Přetěžování operátorů – ekvivalence Pozor! Pro předefinované operátory nemusí platit identity definované pro základní typy:
a=a+b
a+=b
Bod Bod::operator+=( const Bod& b) { x += b.x; y += b.y; return *this; }
a[b]
*(a+b)
Bod Bod::operator+=( const Bod& b) { return *this = *this + b; }
this->operator=( this->operator+( b))
copy konstruktor a operator= class Bod { private: int x, y; public: Bod( const Bod& b) { x=b.x; y=b.y; }; Bod operator=( const Bod& b) { x=b.x; y=b.y; return *this; }; }; Bod a(1,2); Bod k, m(a), n = a; k = m;
není-li copy konstruktor nebo operator= definován, automaticky se vygeneruje copy konstruktor resp. operator= všech složek
copy konstruktor definice nového objektu operator= přiřazení do existujícího objektu
Rozdíl mezi copy konstruktorem a přiřazením:
copy konstruktor se nemusí starat o předchozí stav objektu, přiřazení ano přiřazení vrací (přiřazovanou) hodnotu, copy konstruktor nevrací nic
Udržovatelnost kódu class Bod { public: Bod( const Bod & b) { dosad(b); }; Bod operator=( const Bod & b) { dosad(b); return *this; };
Těla operátorů a konstruktorů
Public část před private
};
Funkce Get a Set
Těla metod vždy v *.cpp souboru
private: int x, y; void dosad( const Bod & b) { x=b.x; y=b.y; };
Private položky nejsou zajímavé
Datové položky vždy private
int GetX() {return x; }; int GetY() {return y; }; void SetX(int x) { this->x = x; } void SetY(int y) { this->y = y; }
volání jiné funkce
Pro pozdější rozšíření Lépe se hledá kde je implementované
Jména tříd
ToJeMojeTrida
Jména funkcí proměnných
mojePrvniFunkce
Objekt a ukazatel na objekt C++ odlišuje objekt a ukazatel na něj
Rozdíl oproti jiným jazykům
Java, JavaScript, PHP, VisualBasic, ...
Analogie s chováním stuct v C Ukazatel nelze použít dokud není splněna jedna z možností: Přiřazen existující objekt Reference Dynamicky vytvořen nový objekt Operátor new
class Zvire { ..... }; Zvire * pytlik; Zvire beruska; pytlik = &beruska; pytlik = new Zvire;
Nevzniká tu žádný objekt
vzniká nový objekt
Operátory new a delete new: alokace paměti, zavolání konstruktoru
není nutno testovat úspěšnost – mechanismus výjimek
delete: zavolání destruktoru, dealokace paměti
jako parametr lze i 0 Bod a(1,2); Bod *pb = new Bod; *pb = a + a; a = *pb; delete pb; pb = new Bod( a); Bod *pc = new Bod( 3, 5); a = *pb + *pc; delete pc; delete pb; char* buf = new char[10]; strcpy( buf, “ahoj”); ... delete[] buf;
dynamická alokace, implicitní konstruktor
náhrada za malloc() uvolnění paměti
další alokace, explicitní konstruktory
alokace pole objektů uvolnění paměti u alokovaných polí nutno []
Chytré řetězce – nápad Práce s řetězci v C + efektivní – nepohodlá a těžkopádná – časté chyby Chtěl bych: přiřazování, zřetězení, automatická alokace místa
Str s1 = “ahoj”; Str s2 = “babi”; Str s3;
s3 = (char*) malloc( strlen(s1) + strlen(s2) + 2); strcpy( s3, s1); s3[ strlen(s1)] = „ „; strcpy( s3 + strlen(s1) + 1, s2);
s3 = s1 + „ „ + s2; s3 += “.”;
„obyčejné„ zřetězení – nechci se starat o to, kde sebrat místo
Chytré řetězce - třída
class Str { private: char* buf;
ukazatel na alokovaná data
public: Str() { buf = 0; }; Str( const Str& s); Str( const char* s);
implicitní konstruktor prázdný řetězec
destruktor objekt si musí po sobě uklidit
delete přežije i 0, nemusím testovat
~Str() { delete[] buf; }; Str& operator=( const Str& s); Str operator+( const Str& s);
operace s řetězci
int len() const { return buf ? strlen(buf) : 0; }; };
další metody (délka řetězce)
Konstantní funkce, nemodifikuje objekt
Destruktory
class Str { private: char* buf;
ukazatel na alokovaná data
public: alokace paměti Str() { buf = 0; }; pro řetězec Str( const char* s) { buf = new char[ strlen( s) + 1]; strcpy( buf, s); }; destruktor - automaticky ~Str() { delete[] buf; }; se volá při zrušení objektu };
nemá argumenty nic nevrací
Vyvolání destruktoru v kostruktoru s1 se alokuje paměť pro řetězec fce() { dynamická alokace sp2 Str s1 = “ahoj”; Str* s2 = new Str( “babi”); ..... delete zavolá destruktor delete s2; (a potom uvolní paměť) ..... } zde končí život s1 automaticky se vyvolá destruktor
Řetězce – implementace
Str& Str::operator=( const Str& s) { delete[] buf; if( ! s.len()) { buf = 0; } else { buf = new char[ s.len()+1]; strcpy( buf, s.buf); } return *this; } Str::Str( const Str& s) { .... }
uklidit po předchozím řetězci prázdný řetězec alokace paměti okopírování znaků přiřazená hodnota – objekt sám
reference kvůli efektivitě
copy konstruktor – totéž bez delete a return
O něco lepší implementace později si ukážeme ještě lepší – counted pointers class Str { private: char* buf; void copy( const char* s);
privátní metoda – alokace a kopírování
public: Str() { buf = 0; }; Str( const Str& s) { copy( s.buf); }; Str( const char* s) { copy( s); }; ~Str() { clear(); }; Str& operator=( const Str& s) { clear(); copy( s.buf); return *this; }; Str& operator=( const char* s) { clear(); copy( s); return *this; }; void clear() { delete[] buf; }; };
konstruktory: jen alokace a kopírování
přiřazení: i uklizení a návratová hodnota
často potřebujeme uklízet !!! buf = 0; nebo private !!!
Implementace kopírování class Str { private: char* buf; void copy( const char* s); public: Str() { buf = 0; }; Str( const Str& s) { copy( s.buf); }; Str( const char* s) { copy( s); }; ~Str() { clear(); }; Str& operator=( const Str& s) { clear(); copy( s.buf); return *this; }; Str& operator=( const char* s) { clear(); copy( s); return *this; }; void clear() { delete[] buf; }; };
Jde clear() přidat do copy() ???
předpokládáme prázdný buf
zařídí volající metoda - copy je private
void Str::copy( const char* s) { if( !s || !*s) { buf = 0; } else { buf = new char[ strlen( s)+1]; if( buf) strcpy( buf, s); } } alokace a kopírování
zkontrolovat prázdný řetězec
Zřetězení Str Str::operator+( const Str& s) { Str newstr; newstr.buf = new char[ len() + s.len() + 1]; strcpy( newstr.buf, buf); strcat( newstr.buf, s.buf); return newstr; } Str Str::operator+( const char* s) { Str newstr; newstr.buf = new char[ len() + strlen(s) + 1]; strcpy( newstr.buf, buf); strcat( newstr.buf, s); nelze návrat reference return newstr; (lokální dočasný objekt) } nové hodnoty VŽDY vracet hodnotou
nový prázdný řetězec místo na znaky první operand druhý operand
návratová hodnota
Připojení řetězce když už umíme + a = proč si neudělat += class Str { .... public: .... Str& operator=( const Str& s); Str& operator=( const char* s); Str operator+( const Str& s); Str operator+( const char* s); Str& operator+=( const Str& s) { *this = *this + s; return *this; }; Str& operator+=( const char* s) { *this = *this + s; return *this; }; }; lze vracet referencí existující hodnota
operator+( char*)
operator+( Str&)
Str a jednotlivé znaky class Str { ... public: Str(); Str( const Str&); Str( const char*); Str( char c) { buf = new char[ 2]; buf[0] = c; buf[1] = '\0'; }; Str& operator=( const Str&); Str& operator=( const char*); Str& operator=( char); Str operator+( int); Str operator+=( int); };
dodefinovat konstruktor, přiřazení a operace pro další typ
Výstup
neprázdný obsah na stdout
‘normálně’ spojím řetězce s mezerou ... a vytisknu
dočasný objekt
reference na s3
později si ukážeme elegantnější řešení - streams
class Str { ... public: int print() const { return buf ? printf( "%s", buf) : 0; }; }; Str s1 = "ahoj", s2("babi"), s3; s3 = s1 + ' ' + s2; s3.print(); Str("\n").print(); (s3 += ".\n").print();
... and together
class Str { private: char* buf; void copy( const char* s); void clear(); public: Str() { buf = 0; }; Str( const Str& s); Str( const char* s); Str( char c); ~Str();
Str& operator=( const Str& s); Str& operator=( const char* s); Str& operator=( char c); Str operator+( const Str& s); Str operator+( const char* s); Str operator+( char c); Str& operator+=( const Str& s); Str& operator+=( const char* s); Str& operator+=( char c); int len() const; int print() const; };
Dědičnost vztah tříd předek-potomek – hierarchie
přesnější názvosloví: základní (base) / odvozená třída (derived class)
vícenásobná dědičnost
dvakrát měř, jednou řež, protokoly
specializace
potomek má/umí něco navíc
reusabilita
jiné chování bez změny původní třídy
Zvíře jez, vyměšuj
Pes sedni, lehni
Člověk uč_se
Pitbul trhej
pes jako potomek zvířete - definice class Zvire { protected: int zaludek;
Přístup pro třídu a potomky
public: Zvire(); Zvire( int jidlo); int zije(); int jez( int jidlo); int vymesuj( int objem); };
potomek class Pes : public Zvire (odvozená třída od) { Zvířete private: enum t_stav { Stoji, Sedi, Lezi }; t_stav stav; přidaná položka public: Pes() { stav = Stoji; }; void sedni() { stav = Sedi; }; t_stav codela() { return stav; } };
položky předka
položky potomka
metody předka žaludek stav
jez, vyměšuj sedni
potomek obsahuje všechny položky a metody předka
metody potomka
Zvire pytlik; Pes azor; pytlik.jez(); azor.jez(); azor.sedni();
Konstruktor a destruktor předka class Zvire { ... ~Zvire() { printf( "zabijim zvire "); }; }; class Pes : public Zvire { ... public: Pes() { stav = Stoji; }; Pes( int jidlo) : Zvire( jidlo) { stav = Stoji; }; ~Pes() { printf( "zabijim psa "); }; }; {
implicitní konstruktor předka (automaticky)
explicitní konstruktor předka
konstruktory předků a vložených tříd se volají před konstruktorem potomka destruktor předka se vyvolá automaticky po ukončení destruktoru potomka
Pes azor; ... }
zabijim psa zabijim zvire
Kompatibilita předka a potomka Potomka lze přiřadit do předka (platí i pro ukazatele) Předka NELZE přiřadit do potomka (platí i pro ukazatele) pes umí jíst, brouk neumí štěkat
Zvire pytlik, *pz; Pes azor, *pp;
pytlik
žaludek
azor
žaludek stav
pytlik = azor; pz = &azor;
azor
azor = pytlik; pp = &pytlik;
pytlik
žaludek stav nelze
žaludek ???
Polymorfismus odlišné chování potomků – pozdní vazba (late binding)
Zvíře
Pes
jez
jez
Pitbul jez sní maso
najde něco v přírodě
Člověk jez
jde do restaurace
sní hodně masa
Polymorfismus - motivace class Zvire { jez() { priroda(); }; }; class Pes : public Zvire { jez() { maso(1); }; }; class Pitbul : public Pes { jez() { maso(10); }; };
Tohle není polymorfismus ! Zvire pytlik; Pes punta; Pitbul zorro; Clovek pepa; pytlik.jez(); // priroda(); punta.jez(); // maso(1); zorro.jez();// maso(10); pepa.jez(); // hospoda();
class Clovek : public Zvire { jez() { hospoda(); }; }; Každá třída má vlastní implementaci (tělo) metody jez
'normální' vlastnost tříd zakrývání metod Při překladu je známo ze které třídy se volá metoda
Polymorfismus – takto nelze do ukazatele na základní třídu (předka) dám ukazatel na nově vytvořený objekt odvozené třídy (potomka)
Zvire* z;
z je ukazatel na zvíře volá se Zvire::jez()
z = new Pes; z->jez(); // priroda(); z = new Clovek; z->jez(); // priroda();
pokus – 'na tvrdo' chci metodu potomka
Zvire* z; z = new Pes; z->Pes::jez();
nelze - syntaktická chyba
pes není předkem zvířete // priroda();
z = new Clovek; z->Clovek::jez(); // priroda();
Polymorfismus – takto bych to chtěl chtěl bych, aby se volaly 'správné' metody
Zvire* z;
Zvire* naseRodina[3];
z = new Pes; z->jez(); // maso(1);
naseRodina[0] = new Clovek; naseRodina[1] = new Pes; naseRodina[2] = new Pitbul;
z = new Clovek; z->jez(); // hospoda();
for( int i = 0; i < 3; i++) naseRodina[i]->jez();
Chci pokaždé se zavolat jinou metodu Rozlišení metody se musí dít za běhu
Virtuální funkce - deklarace magické klíčové slovo virtual class Zvire { virtual jez() { priroda(); }; }; class Pes : public Zvire { virtual jez() { maso(1); }; }; class Pitbul : public Pes { virtual jez() { maso(10); }; }; class Clovek : public Zvire { virtual jez() { hospoda(); }; };
každý objekt si s sebou nese informaci kterou virtuální funkci používá
Virtuální funkce - implementace Zvire * z; z = new Zvire;
z = new Pes; Pes
Zvire
žaludek
jez
tabulka virtuálních funkcí
žaludek
jez stav
Zvire::jez() { priroda(); };
z->jez();
Pes::jez() { maso(1); };
zavolá se správná metoda podle tabulky virtuálních funkcí
Virtuální funkce a konstruktory a destruktory v konstruktoru a destruktoru se vždy volá metoda vytvářeného/rušeného objektu
class A { public: virtual f(); A() { f(); }; // A::f ~A() { f(); }; // A::f g() { f(); }; // A/B::f };
určí se za běhu podle skutečného typu objektu
nejdřív se zavolá konstruktor předka
class B : public A { public: virtual f(); B() { f(); }; // A::A B::f ~B() { f(); }; // B::f A::~A g() { f(); }; // B::f };
nejdřív se provede kód destruktoru, pak se zavolá destruktor předka
Volání virtuálních funkcí
a
A::f
paa
pab b
B::f
pbb
// A::f // B::f
paa->f(); pab->f(); pbb->f();
// A::f // B::f // B::f
b.A::f(); b.B::f(); a.B::f(); paa->A::f(); pab->A::f(); pab->B::f(); pbb->A::f(); pbb->B::f();
// A::f // B::f // NE! // A::f // A::f // NE! // A::f // B::f
kvalifikované volání
A a; // A::A B b; // B::B A * paa = &a; A * pab = &b; B * pbb = &b; // B * pba = &a; nelze!! (předka do potomka)
a.f(); b.f();
pozdní vazba
class A { public: virtual f(); }; class B : public A { public: virtual f(); };
Abstraktní třída, čistě virtuální funkce int armada; class Vojak { public: enum THod { vojin, desatnik, porucik, general }; Vojak( THod hod = vojin) { hodnost=hod; armada++; }; virtual void pal() = 0; virtual ~Vojak() { armada--; }; private: THod hodnost; };
POZOR!!! Nutný virtuální destruktor
abstraktní třída nelze vytvořit objekt společný předek
pure virtual function ⇒ abstraktní třída společné rozhraní class Samopal {}; class Kalasnikov : public Samopal {};
class Pesak : public Vojak { private: Samopal* sam; public: Pesak( THod hod=vojin) : Vojak( hod) { sam = new Kalasnikov; }; virtual void pal() { sam->pal(); }; virtual ~Pesak() { delete sam; }; };
Abstraktní třídy, virtuální destruktory pokud by ~Vojak nebyl virtuální
// Vojak v; Pesak p; Pesak* pp = new Pesak; pp->pal(); Vojak* pv = new Pesak; pv->pal(); delete pp; delete pv;
// NELZE – abstraktní třída // OK – Pesak Vojin // OK // Pesak::pal // OK // Pesak::pal // OK, Pesak::~Pesak // !!! Vojak::~Vojak
POZOR!!! nejsou-li destruktory virtuální, nezruší se samopal Řešení: virtuální destruktor
class Vojak { virtual ~Vojak() { armada--; }; }; class Pesak : public Vojak { virtual ~Pesak() { delete sam; }; };
Nesprávné užití dědičnosti
Letadlo není potomkem svého motoru Důkaz: Co když má dva motory... Násobná dědičnost? Ne: Je třeba je odlišit
Jezevčík umí vyhnat lišku z nory... Myslivec s jezevčíkem tedy také...
Kompozice
Skládání velkých objektů z malých C++: Třída s datovými položkami
Myslivec není potomkem svého jezevčíka Důkaz: Nežere granule... Kompozice? Ne: Nerodí se zároveň
Mlok není potomkem ryby a savce Důkaz: Nemá dvě hlavy... Virtuální dědičnost? Ne: Nekojí
Tlačítko není potomkem obdélníku a textu
Delegace
Převedení funkčnosti na jiný objekt C++: Ukazatel
Společný abstraktní předek
Obratlovec Vizuální objekt
Prostory jmen (namespaces) zapouzdření identifikátorů prevence kolizí (velké projekty, knihovny) stejné identifikátory v různých prostorech jmen
namespace aa { int p; int f1( int x) { return x + p; } int f2( int x, int y); }
definice prostoru jmen přístup k identifikátoru ze stejného prostoru
int aa::f2( int x, int y) { return p * (x + y); }
definice funkce mimo prostor jmen
aa::f1( aa::f2( 5, 6));
přístup k identifikátorům přes ::
Prostory jmen prostor jmen se může opakovaně otevírat a zavírat explicitní přístup ke globálnímu identifikátoru ::id standardní knihovny – namespace std using namespace std; namespace aa { int p; int q; } int g( int n) { cout << (n + aa::p); }
namespace aa { int f3( int x) { return 1 + ::g( x); }
rozbalení std přístup do aa
přístup k identifikátorům std znovuotevření prostoru aa přístup ke globálnímu identifikátoru
Prostory jmen a standardní knihovny stará konvence: stdio.h, ctype.h, iostream.h identifikátory v globálním prostoru jmen
strlen, FILE
nová konvence: cstdio, cctype, iostream identifikátory uzavřené do namespace std
std::strlen, std::FILE
standardní knihovny C++ Základní knihovny z C přejmenované podle nové konvence Rozšířené C++ knihovny iostream: znakový formátovaný vstup a výstup STL: Standard Template Library
použití šablon kontejnery, iterátory, algoritmy
Vstup a výstup - proudy (streams) hierarchie tříd pro (formátovaný znakový) vstup a výstup jednotné rozhraní pro v/v do souborů a paměti, ... operátory << a >>, manipulátory motivace:
rozšiřitelnost bezpečnost
datum d( 12, 3, 2004); printf( "dnes je %?", d);
#include
using namespace std;
int main() { int n; cout << "Rekni cislo: "; cin >> n; cout << "Mam vic: " << (n+1) << ", hec!" << endl; }
int i; printf( "Jmenuji se %s", i);
definice základních tříd a manipulátorů ostream cout istream cin
FILE* stdout FILE* stdin
ostream& ostream::operator<< () istream& istream::operator>> ()
Streams – hierarchie tříd
Hlavičkové soubory
– základní operace, standardní v/v, manipulátory bez parametrů
cin, cout, <<, >>, endl, ws, ...
– manipulátory s parametry
setw, setfill, ...
– vstup a výstup do souborů
fstream, ifstream, ofstream, ...
<strstream> - vstup a výstup do paměti (chytré řetězce)
strstream, istrstream, ostrstream, ...
Manipulátory do proudu lze vkládat manipulátory – změní stav proudu endl left, right dec, hex ws setw(int) setfill(int)
pošle buffer na výstup a odřádkuje zarovnávej doleva / doprava v desítkové / šestnáctkové soustavě přeskoč bílé znaky (na vstupu) šířka výstupního pole (jen pro následující číselnou položku) výplňkový znak
... a spousty dalších
cout << "[" << setfill('.') << setw(5) << 17 << "]" << endl;
nastaví výplňový znak
nastaví šíři výstupu
vytiskne podle aktuálního nastavení
[...17]
výstup
Výstup do souboru
spojení proudu se souborem v konstruktoru
třída pro souborový proud
způsob otevření ios::in, out, app, trunc, binary, ... Př: ios::in | ios::binary
#include using namespace std; int main() { fstream f( "C:\\src\\pokus.txt", ios::out); if( ! f) error(); f << "bubu" << endl; }
soubory není třeba zavírat, zavře je automaticky destruktor
operator ! (ostream&) vrátí true když se operace nepodařila
Další metody vstupních proudů pro binární vstup a výstup nelze použít operátory << a >>
Vstup get( kam, délka, koncový_znak) getline( kam, délka, koncový_znak) ignore( délka, koncový_znak) read( pole_znaků, délka) tellg() seekg( posun, odkud) unget()
int i = 17; ofstream f( "pokus.txt", ios::binary); if( ! f) error(); f.write( (char*)&i, sizeof( i));
Výstup put( znak) write( pole_znaků, délka) tellp() seekp(posun, odkud) flush()
... a další
pole bajtů a jejich počet
Spřátelené funkce – vlastní výstup class Complx { private: int re, im; public: Complx( int _re = 0, int _im = 0) { re = _re; im = _im; }; friend ostream& operator<<( ostream& s, Complx& c) { return s << c.re << "+" << c.im << "i"; }; }; Complx x(1,2); cout << x << endl; spřátelená (friend) funkce může přistupovat k privátním položkám
POZOR! Toto není metoda třídy!
Šablony množina funkcí/tříd lišících se pouze typem parametrů/položek vzor, podle kterého překladač vytvoří funkci nebo třídu (instanci) pro konkrétní typ
Definice šablony funkce
Typový parametr T nahrazuje skutečný typ místo typename lze class
template T max( T a, T b) { return a > b ? a : b; }; int x = 10, y = 20; double m = 1.1, n = 2.2; cout << max(x,y) << max(m,n) << endl; int max( int a, int b)
double max( double a, double b)
Šablony tříd - definice template class Guma { private: int size; T* array; public: const int default_size = 10; Guma( int _size = default_size) { size = _size; array = new T[size]; }; ~Guma() { delete array; } T& operator[] (int n); };
size:
5
pole neznámého typu
array:
0
1
2
3
4
?
?
?
?
?
instance šablony třídy definice proměnné
int main() { Guma ip(5); ip[3] = 999;
přetížený operator[]
Šablony metod, instance šablon template class Guma { private: int size; T* array; public: T& operator[] (int n); }; template T& Guma::operator[] (int n) { if( n >= size) { T* na = new T[ n + 1]; for( int i = 0; i < size; i++) na[i] = array[i]; delete array; array = na; size = n + 1; } return array[n]; }
instance šablony třídy definice typu
struct Krabice { int a, b; char jm[10]; };
typedef Guma polekrab; int main(int argc, char* argv[]) { Guma ip(5); polekrab pk; ip[3] = 999; pk[12].a = ip[3]; definice šablony metody
pk[i] je typu Krabice&
STL – Standard Template Library kontejnery – datové struktury pro ukládání dat a manipulaci s nimi iterátory – třídy pro přístup k datům kontejnerů algoritmy – základní algoritmy nad kontejnery (třídění, procházení, hledání) další pomocné třídy – alokátory, komparátory, funktory ...
list sez; sez.push_front( 1); sez.push_back( 2); sez.push_front( 3); list::iterator i; for( i = sez.begin(); i != sez.end(); i++) cout << "[" << *i << "]";
obousměrný seznam přidání prvku zepředu ... zezadu ... zepředu iterátor seznamu průchod seznamem
přístup k datům přes iterátor – operator*
STL – kontejnery Sekvenční kontejnery
Asociativní kontejnery
uspořádané
setříděné
STL – kontejnery Sekvenční kontejnery deque – dvoustranná fronta [dek] umožňuje v konst. čase přidávat na začátek i konec implementace typicky pomocí polí adaptéry (specializované použití i rozhraní): stack, queue, priority_queue
vector – pole (gumové) přístup k prvku v konstantním čase jako vector se chová i string a standardní pole (T x[]) string – chytré řetězce =, +, += a mnoho dalších operací a metod
list – dvousměrný seznam implementace: spojový seznam umožňuje v konstantním čase přidávat prvky na libovolné místo
Asociativní kontejnery map, multimap – zobrazení, asociativní pole, slovník, mapa
uspořádaná struktura indexovaná libovolným typem, pair: klíč, hodnota)
set, multiset – množina, multimnožina
každý prvek nejvýše jednou / vícekrát
STL – iterátory a metody kontejnerů kontejner::iterator T& iterator::operator*
iterátor příslušného kontejneru přístup k prvku přes iterátor
begin(), end() push_front(), push_back() pop_front(), pop_back() front(), back() operator[], at() insert(iterator,T) size(), empty(), clear()
iterátor na začátek / za(!) konec kontejneru přidání prvku na začátek / konec odebrání prvku ze začátku / konce – nevrací hodnotu! prvek na začátku / konci přímý přístup k prvku vložení prvku na místo určené iterátorem velikost / neprázdost / smazání kontejneru
push(), pop(), top()
přidání / odebrání / prvek na vrcholu zásobníku
STL – použití iterátorů vytvoření celočíselného vectoru pole
pole.begin() vrátí iterátor na začátek pole
p je iterátor do vector
jestli p už nedosáhl konce
pole.end() vrátí iterátor za konec pole
vector pole; vector::iterator p; for( p = pole.begin(); p != pole.end(); p++) cout << "[" << *p << "]";
*p (overl.) vrátí hodnotu prvku na nějž ukazuje iterátor
p++ (overl.) zařídí, že p bude ukazovat na další prvek
STL – použití asociativního pole
map<string,string> ts; ts["Filip"] = "605123456"; ts["Petra"] = "721334455"; ts["David"] = "723654321"; ts["Kuba"] = "222333444"; cout << "Telefon Petry: " << ts["Petra"] << endl; map<string,string>::iterator ti; for( ti = ts.begin(); ti != ts.end(); ti++) cout << ti->first << ": " << ti->second << endl;
pair<string,string> iterator::operator* ti->first ≡ (*ti).first
operator [] (const string&) vyhledání podle first
ts: pair: string first string second
STL – algoritmy Inicializace fill Fills a sequence with an initial value fill_n Fills n positions with an initial value copy Copies a sequence into another sequence copy_backward Copies a sequence into another sequence generate Initializes a sequence using a generator generate_n Initializes n positions using a generator swap_ranges Swaps values from two parallel sequences Vyhledávání find Finds an element matching the argument find_if Finds an element satisfying a condition adjacent_find Finds consecutive duplicate elements find_first_of Finds one member of a seq. in another seq. find_end Finds the last occurr. of a sub-seq. in a seq. search Matches a sub-sequence within a sequence max_element Finds the maximum value in a sequence min_element Finds the minimum value in a sequence mismatch Finds first mismatch in parallel sequences Mazání remove unique
Removes elements that match condition Removes all but first of duplicate values
Ostatní for_each
Applies a function to each element
+ mnoho dalších Transformace prvků reverse Reverses the elements in a sequence replace Replaces specific values with new value replace_if Replaces elements matching predicate rotate Rotates elements in a sequence around a point next_permutation Generates permutations in sequence prev_permutation Generates permutations in reverse seq. inplace_merge Merges two adjacent sequences into one random_shuffle Randomly rearranges elements in a seq. Třídění sort Sorts all elements make_heap Converts a range into a heap Skalární výpočty count Counts number of elements matching value count_if Counts elements matching predicate accumulate Reduces sequence to a scalar value equal Checks two sequences for equality lexicographical_compare Compares two sequences Výpočty generující sekvence transform Transforms each element partial_sum Generates sequence of partial sums adjacent_difference Gen. sequence of adjacent differences
STL – použití algoritmů vlastní funkce pro jeden prvek vyplní se náhodnými čísly pro každý prvek se zavolá funkce najde max. prvek vrátí iterátor setřídí část pole od max. prvku do konce
odstraní duplicity
void tiskni( int x) { cout << " [" << x << "]"; } vector pole; vector::iterator b, e, p; generate( b = pole.begin(), e = pole.end(), rand); for_each( b, e, tiskni); p = max_element( b, e); !! pozor – může sort( p, e); zneplatnit iterátor e unique( p, e); for_each( b, pole.end(), tiskni);
STL – chybová hlášení \SRC\templ\templ.cpp(101) : error C2664: 'class std::_Tree,class std::allocator >,struct std::pair,class std::allocator > const ,class std::basic_string,class std::allocator > >,struct std::map,class std::allocator >,class std::basic_string,class std::allocator >,struct std::less,class std::allocator > >,class std::allocator,class std::allocator > > >::_Kfn,struct std::less,class std::allocator > >,class std::allocator,class std::allocator > > >::iterator __thiscall std::map,class std::allocator >,class std::basic_string,class std::allocator >,struct std::less,class std::allocator > >,class std::allocator,class std::allocator > > >::insert(class std::_Tree,class std::allocator >,struct std::pair,class std::allocator > const ,class std::basic_string,class std::allocator > >,struct std::map,class std::allocator >,class std::basic_string,class std::allocator >,struct std::less,class std::allocator > >,class std::allocator,class std::allocator > > >::_Kfn,struct std::less,class std::allocator > >,class std::allocator,class std::allocator > > >::iterator,const struct std::pair,class std::allocator > const ,class std::basic_string,class std::allocator > > &)' : cannot convert parameter 1 from 'char [6]' to 'class std::_Tree,class std::allocator >,struct std::pair,class std::allocator > const ,class std::basic_string,class std::allocator > >,struct std::map,class std::allocator >,class std::basic_string,class std::allocator >,struct std::less,class std::allocator > >,class std::allocator,class std::allocator > > >::_Kfn,struct std::less,class std::allocator > >,class std::allocator,class std::allocator > > >::iterator'
string constructors
Create or copy a string
size(), length()
Return the number of characters
destructor
Destroys a string
max_size()
=, assign()
Assign a new value
Returns the maximum possible number of characters
swap()
Swaps values between two strings
empty()
Returns whether the string is empty
+=,append(), push_back()
Append characters
capacity()
Returns the number of characters that can held without be reallocation
insert()
Inserts characters
[], at()
Access a character
erase()
Deletes characters
>>, getline()
Read the value from a stream
clear()
Removes all characters (makes it empty)
<<
Writes the value to a stream
resize()
Changes the number of characters (deletes or appends chars at the end)
copy()
Copies or writes the contents to a C-string
c_str()
Returns the value as C-string
replace()
Replaces characters
data()
Returns the value as character array
+
Concatenates strings
substr()
Returns a certain substring
==,!=,<,<=, >,>=, compare()
Compare strings
find functions
Search for a certain substring or character
begin(), end()
Provide normal iterator support
rbegin(), rend()
Provide reverse iterator support
get_allocator()
Returns the allocator
Výjimky Motivace: co dělat, když (knihovní) funkce zjistí chybu?
nedostatek paměti, nelze otevřít soubor, nulový ukazatel, ...
Vypsat zprávu na 'obrazovku' a skončit
Nastavit do globální funkce příznak chyby
FUJ! Nikdy!
problém s více vlákny, nutnost neustále testovat
Vrátit 'divnou' hodnotu takhle funguje většina knihovních funkcí C nepříliš praktické, testování každé funkce, vnořené testy divná hodnota nemusí existovat
Co chceme: oddělit detekci výjimečné situace od jejího zpracování po výskytu 'chyby' (výjimečné situace) automaticky skočit na zpracování kulturně po sobě uklidit (volání destruktorů) Řešení v C++: mechanismus výjimek
Výjimky - jednoduchý příklad void mojefce( char* str) { if( ! str) throw runtime_error( "Nic!"); cout << str; } int main(int argc, char* argv[]) { char* p = 0; try { mojefce( p); } catch( runtime_error& e) { cout << "Chyba: " << e.what() << endl; } return 0; }
vyvolání výjimky
typ výjimky standardní třída potomek exception pokusný blok try block handler(y) typ výjimky
standardní metoda třídy runtime_error řetězec z konstruktoru
Výjimky - jednoduchý příklad char* mojefce( long n) { char* bigbigbig = new char[n]; cout << "Proslo to" << endl; return bigbigbig; } int main(int argc, char* argv[]) { char* p = 0; try { mojefce( 2000000000); cout << "Vratil jsem se" << endl; } catch( runtime_error& e) { cout << "Chyba: " << e.what() << endl; } return 0; }
pokud se nepovede naalokovat, nastane výjimka při výjimce se dále nepokračuje, hledá se nejbližší volný handler
nalezený handler
Výjimky - pravidla
k try bloku může být několik handlerů s různými typy try bloky mohou být vnořené výjimka může být vyvolána v libovolně zanořené funkci po vyvolání výjimky se řízení předá handleru s odpovídajícím typem před odchodem ze všech bloků se zavolají destruktory lokálních objektů předávaná hodnota nese informaci o výjimce typické použití: potomek standardní třídy exception i pro výjimky platí, že potomek může nahradit předka konstruktor runtime_error(string&), metoda string what() po ošetření výjimky pokračuje program za handlery try bloku při běhu bez výjimky se handlery ignorují (přeskočí) neošetřená výjimka – unhandled exception, konec programu
Specifikace výjimek funkcí Problém: jak programátor pozná které výjimky má ošetřovat? Řešení: funkce může specifikovat výjimky, které může vyvolat
funkce může vyvolat výjimky těchto typů
void mojefce( char* s) throw (runtime_error, mojechyba); int jinafce( void) throw(); char* tretifce( char* s);
funkce může vyvolat libovolnou výjimku
funkce nevyvolává žádnou výjimku
... co jsme neprobrali
spoustu věcí jazyk protected, volatile, static, operátory .* a ->*, ukazatele na funkce a metody, ... vícenásobná dědičnost, protokoly RTTI, typeid, type_info static_cast, dynamic_cast, reinterpret_cast, const_cast podrobněji knihovny, zejména streams a STL, efektivní používání knihoven OOP counted pointers, mělké vs. hluboké kopie objektová paradigmata – zprávy, obálkové třídy, subtyping, forwarding hlouběji o objektovém návrhu, reusabilitě, efektivitě implementace funktory a jiné specialitky
spoustu věcí kdo chcete C++ opravdu profesionálně používat, přečtěte si literaturu (Meyers, Sutter) nebuďte líní – zkoušejte i jednoduché věci naprogramovat 'profesionálně'
Dodelat na priste Vice slajdu o pretezovani operatoru (i unarni) Vice slajdu o referenci Chytre retezce – pocitane odkazy