3.86k likes | 4k Views
Objektov ě-orientované p rogramování v C++. David Bednárek ulita.ms.mff.cuni.cz. Pravidla studia. PRG0 32 2/2 Z,Zk. Zápis na cvičení. Elektronický zápis do jednotlivých skupin www.mff.cuni.cz/vnitro/is/sis Grupíček Skupiny budou naplněny podle příslušnosti ke studijním skupinám
E N D
Objektově-orientované programování v C++ David Bednárek ulita.ms.mff.cuni.cz
Pravidla studia PRG032 2/2 Z,Zk
Zápis na cvičení • Elektronický zápis do jednotlivých skupin • www.mff.cuni.cz/vnitro/is/sis • Grupíček • Skupiny budou naplněny podle příslušnosti ke studijním skupinám • Asi 30% studentů žije mimo studijní skupiny a nebyli takto zapsáni • Zapsáni musejí být všichni • Do 13.10. • Kdo se neobjeví na prvním ani druhém cvičení, bude vyškrtnut • Kapacita laboratoře je omezena, skupiny nelze přeplňovat • Udělit zápočet může jen cvičící,ke kterému je student zapsán • Kdo nebude do 13.10. zapsán, zápočet v tomto šk. roce nedostane
Udělení zápočtu • Přesné podmínky udělení zápočtu určuje cvičící • Cvičící může podmínky individuálně upravit, pokud se s ním student na začátku semestru dohodne • Vzorové podmínky • Přiměřená účast na cvičeních • Úspěšné odevzdání domácího úkolu • Úspěšné složení zápočtového testu • 1. a 2. pokusy ve zkouškovém období ... 3. pokusy v dubnu • 2-3 hodiny v laboratoři, společně pro všechny skupiny • Vypracování zápočtového programu • Dohoda o tématu - do konce října • Předvedení cvičícímu do 15.3.2005 • Doladění do konce výuky v letním semestru
Zkouška • Zkouška bude provedena formou abc-testu • Vlastnosti a pravidla jazyka C++ • Základy používání knihoven C++ (STL, iostream) • Typické konstrukce objektového programování • Exception-safe programming • Termíny • Ve zkouškovém období ZS • Během výuky v LS
Pravidla pro budoucí neúspěšné • Zkouška • Pokud letos složíte obě zkoušky z PRG029 a PRG032 se známkou výborně nebo velmi dobřea nedostanete některý zápočet, budou vám příští rok obě automaticky uznány • Tento mechanismus je implementován zkoušejícími, nikoliv studijním oddělěním • Příští rok se bude měnit rozdělení syllabu mezi semestry, proto toto pravidlo neplatí pro jednotlivé předměty • Zápočet • Pokud nedostanete zápočet, budete příští rok opakovat ty části, které jste letos nesplnili • Podmínky splněné letos se automaticky uznávají • V příštím roce se musíte na začátku semestru přihlásit na některé cvičení a dohodnout se s jeho cvičícím na konkrétních podmínkách • Mohou být vypsána speciální cvičení pro repetenty
Obsah cvičení • V laboratoři SW2 / v učebně • Microsoft Visual Studio .NET 2003 • Demonstrace konstruktorů, destruktorů a virtuálních funkcí • Příklady dědičnosti a virtuálních funkcí • Demonstrace operátorů a copy-constructoru • Příklady datových typů (Complex, String, Vector, Matrix) • Praktická implementace vlastního typu string • Šablony, implementace vlastních kontejnerů • STL: deque, vector, map • fstream, open etc. • iostream, manipulátory, implementace vlastního operatoru << (Complex) • Experimenty s výjimkami • Exception-safe programování • Debugging • Vlastní nástroje pro ladění
Literatura • Pro začátečníky • Bruce Eckel:Thinking in C++ (2000)Myslíme v jazyku C++ (Grada 2000) • Miroslav Virius:Pasti a propasti jazyka C++Programování v C++ (ČVUT 2001) • Andrew Koenig, Barbara E. Moo:Accelerated C++ (2000) • Stanley B. Lippman:Essential C++ (2000)
Literatura • Pro středně pokročilé • Andrei Alexandrescu, Herb Sutter:C++ Coding Standards (2005) • Scott Meyers:Effective C++ (1998)More Effective C++ (1996)Effective STL (2001) • Herb Sutter:Exceptional C++ (2000)More Exceptional C++ (2002)Exceptional C++ Style (2004) • Nicolai M. Josuttis:Object-Oriented Programming in C++ (2002)The C++ Standard Library (1999)
Literatura • Až si budete myslet, že všechno umíte • Andrei Alexandrescu:Modern C++ Design (2001)Moderní programování v C++ (Computer Press 2004) • David Vandevoorde, Nicolai M. Josuttis:C++ Templates (2003)
Normy • C++ • ISO/IEC JTC1/SC22/WG21:14882 - Programming languages - C++ (2003) • C++ 2003 TR1 (2005) • Nové rozšíření knihoven (částečně podporováno GCC 4.0) • Založeno na knihovně Boost • C++0x • Návrhy lze sledovat na http://www.open-std.org/jtc1/sc22/wg21/ • C: • ISO/IEC 9899:Programming languages - C (1999) • C99 • Významně není podmnožinou C++ • Řada překladačů jej nezvládá (včetně MSVC 2005)
www • http://www.open-std.org/jtc1/sc22/wg21/ • ISO • http://www.gotw.ca/ • Herb Sutter: Guru of the Week • http://www.boost.org/ • Knihovna Boost • http://gcc.gnu.org/ • GCC
Třída a objekt • Třída (class) • Zobecnění pojmu struktura (struct) • Rozdíl mezi class a struct v C++ je nepatrný • Užívání class místo struct 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) • Objekt (instance třídy) • Běhová reprezentace jednoho exempláře třídy • Reprezentace objektu v paměti obsahuje • Datové položky • Skryté pomocné položky umožňující funkci • virtuálních metod, výjimek a RTTI • virtuální dědičnosti
Třída a objekt • Instanciace třídy = vznik objektu tohoto typu • Jako globální proměnná • V rámci startu programu (před main) • Jako lokální proměnná • V okamžiku průchodu řízení deklarací • Jako parametr předávaný hodnotou • Těsně před voláním funkce • Jako pomocná proměnná při výpočtu výrazu • V okamžiku, kdy je vypočtena její hodnota • Dynamickou alokací • V okamžiku volání operátoru new • Jako položka jiné třídy nebo součást pole • V okamžiku vzniku celku • Jako předek jiné třídy • V okamžiku vzniku instance potomka
Objekt a ukazatel na objekt • C++ důsledně odlišuje objekt a ukazatel na něj • Triviální důsledek: • class T { /*...*/ }; • T * p; // zde nevzniká objekt typu T
Objekt a ukazatel na objekt • C++ důsledně odlišuje objekt a ukazatel na něj • Triviální důsledek: • class T { /*...*/ }; • T * p; // zde nevzniká objekt typu T • Netriviální důsledek: • Proměnná typu T je vždy objekt typu T a žádného jiného, přestože do ní lze přiřadit i objekt odvozeného typu • class U : public T { /*...*/ }; • U y; • T x = y;// toto je kopie části objektu y do vznikajícího objektu x
Objekt a ukazatel na objekt • C++ důsledně odlišuje objekt a ukazatel na něj • Triviální důsledek: • class T { /*...*/ }; • T * p; // zde nevzniká objekt typu T • Netriviální důsledek: • Proměnná typu T je vždy objekt typu T a žádného jiného, přestože do ní lze přiřadit i objekt odvozeného typu • class U : public T { /*...*/ }; • U y; • T x = y;// toto je kopie části objektu y do vznikajícího objektu x • Poznámka pro znalce pravidel: • K tomuto přiřazení může dojít díky existenci automaticky vytvořeného copy-constructoru • T::T( const T &); • a díky možnosti konvertovat odkaz na potomka na odkaz na předka:U => U & => T & => const T &
Objekt a ukazatel na objekt • C++ důsledně odlišuje objekt a ukazatel na něj • Triviální důsledek: • class T { /*...*/ }; • T * p; // zde nevzniká objekt typu T • Netriviální důsledek: • Proměnná typu T je vždy objekt typu T a žádného jiného, přestože do ní lze přiřadit i objekt odvozeného typu • class U : public T { /*...*/ }; • U y; • T x = y;// toto je kopie části objektu y do vznikajícího objektu x • Poznámka pro znalce implementace: • Zde (ani nikde jinde) se nekopírují odkazy na tabulky virtuálních funkcí • Proměnná typu T tedy zůstane typem T včetně přiřazení těl virtuálních funkcí • Jiné chování by nemělo smysl
Objekt a ukazatel na objekt • C++ důsledně odlišuje objekt a ukazatel na něj • Triviální důsledek: • class T { /*...*/ }; • T * p; // zde nevzniká objekt typu T • Netriviální důsledek: • Proměnná typu T je vždy objekt typu T a žádného jiného, přestože do ní lze přiřadit i objekt odvozeného typu • class U : public T { /*...*/ }; • U y; • T x = y;// toto je kopie části objektu y do vznikajícího objektu x • V tomto odlišování se C++ liší od většiny jazyků s objekty (Java, JavaScript, PHP, VisualBasic, ...)
Objekt a ukazatel na objekt • C++ důsledně odlišuje objekt a ukazatel na něj • Nefunguje naivní implementace polymorfního typu: • class Variant { enum { REAL, COMPLEX } t; }; • class Real : public Variant { public: double Re; }; • class Complex : public Variant { public: double Re, Im; }; • Variant max( Variant a, Variant b); • Real x, y, z = max( x, y);// nelze přeložit • Complex u, v, w = max( u, v);// nelze přeložit
Objekt a ukazatel na objekt • C++ důsledně odlišuje objekt a ukazatel na něj • Nefunguje naivní implementace polymorfního typu: • class Variant { enum { REAL, COMPLEX } t; }; • class Real : public Variant { public: double Re; }; • class Complex : public Variant { public: double Re, Im; }; • Variant max( Variant a, Variant b); • Real x, y, z = max( x, y);// nelze přeložit • Complex u, v, w = max( u, v);// nelze přeložit • Parametry a, b nedokážou přenést atributy Re, Im • Návratovou hodnotu nelze (ani explicitně) přetypovat na potomka • Real x, y, z = (Real)max( x, y); // nelze přeložit • Complex u, v, w = (Complex)max( u, v);// nelze přeložit • I kdyby to šlo, typ Variant vracený hodnotou nedokáže přenést atributy Re, Im
Objekt a ukazatel na objekt • C++ důsledně odlišuje objekt a ukazatel na něj • Nefunguje naivní implementace polymorfního typu: • class Variant { enum { REAL, COMPLEX } t; }; • class Real : public Variant { public: double Re; }; • class Complex : public Variant { public: double Re, Im; }; • V tomto případě lze tento problém řešit referencemi: • Variant & max( Variant & a, Variant & b); • vyžaduje ovšem explicitní přetypování, které je nebezpečné • Real x, y, z = (Real&)max( x, y); // funguje • Complex u, v, w = (Complex &)max( u, v);// funguje • Vracení referencí ovšem funguje pouze pro funkce max a min
Objekt a ukazatel na objekt • C++ důsledně odlišuje objekt a ukazatel na něj • Nefunguje naivní implementace polymorfního typu: • class Variant { enum { REAL, COMPLEX } t; }; • class Real : public Variant { public: double Re; }; • class Complex : public Variant { public: double Re, Im; }; • V tomto případě lze tento problém řešit referencemi: • Variant & max( Variant & a, Variant & b); • vyžaduje ovšem explicitní přetypování, které je nebezpečné • Real x, y, z = (Real&)max( x, y); // funguje • Complex u, v, w = (Complex &)max( u, v);// funguje • Vracení referencí ovšem funguje pouze pro funkce max a min • Tyto funkce mají speciální vlastnost: vrací jeden ze svých parametrů
Objekt a ukazatel na objekt • Funkce jako add nemůže vracet referenci • add vrací hodnotu různou od všech svých parametrů • hodnotu parametrů nesmí měnit • reference nemá na co ukazovat • Špatné řešení č. 1: Lokální proměnná • Complex & add( const Complex & a, const Complex & b) • { • Complex r( a.Re + b.Re, a.Im + b.Im); • return r; • } • BĚHOVÁ CHYBA: r zaniká při návratu z funkce
Objekt a ukazatel na objekt • Funkce jako add nemůže vracet referenci • add vrací hodnotu různou od všech svých parametrů • hodnotu parametrů nesmí měnit • reference nemá na co ukazovat • Špatné řešení č. 2: Dynamická alokace • Complex & add( const Complex & a, const Complex & b) • { • Complex * r= new Complex( a.Re + b.Re, a.Im + b.Im); • return * r; • } • PROBLÉM: kdo to odalokuje ?
Objekt a ukazatel na objekt • Funkce jako add nemůže vracet referenci • add vrací hodnotu různou od všech svých parametrů • hodnotu parametrů nesmí měnit • reference nemá na co ukazovat • Špatné řešení č. 3: Globální proměnná • Complex g; • Complex & add( const Complex & a, const Complex & b) • { • g = Complex( a.Re + b.Re, a.Im + b.Im); • return g; • } • CHYBA: globální proměnná je sdílená • Complex a, b, c, d, e = add( add( a, b), add( c, d));
Třídy v C++ • Konstrukce class, dědičnost a virtuální funkce jsou silný mechanismus, užívaný k různým účelům • Různé pohledy na třídy a různá pojmenování • Abstraktní a konkrétní třídy • Třídy jako datové typy • Kontejnery (třídy logicky obsahující jiné objekty) • Singletony (jednou instanciované třídy) • Traits (neinstanciované třídy) • Různé účely dědičnosti • Rozšíření požadovaného rozhraní • Implementace požadovaného rozhraní • Rozšíření implementované funkčnosti • Využití k implementaci
Nesprávné užití dědičnosti • Nesprávné užití dědičnosti č. 1 • class Real { public: double Re; }; • class Complex : public Real { public: double Im; }; • Vypadá jako reusabilita kódu
Nesprávné užití dědičnosti • Nesprávné užití dědičnosti č. 1 • class Real { public: double Re; }; • class Complex : public Real { public: double Im; }; • Vypadá jako reusabilita kódu • Porušuje pravidlo "každý potomek má všechny vlastnosti předka" • např. pro vlastnost "má nulovou imaginární složku"
Nesprávné užití dědičnosti • Nesprávné užití dědičnosti č. 1 • class Real { public: double Re; }; • class Complex : public Real { public: double Im; }; • Vypadá jako reusabilita kódu • Porušuje pravidlo "každý potomek má všechny vlastnosti předka" • např. pro vlastnost "má nulovou imaginární složku" • Důsledek - slicing: • doubleabs( const Real & p) { return p.Re > 0 ? p.Re : - p.Re; } • Complex x; • double a = abs( x);// tento kód LZE přeložit, a to je špatně • Důvod: Referenci na potomka lze přiřadit do reference na předka • Complex => Complex & => Real & => const Real &
Nesprávné užití dědičnosti • Nesprávné užití dědičnosti č. 1 • class Real { public: double Re; }; • class Complex : public Real { public: double Im; }; • Slicing nastává i u předávání hodnotou • doubleabs( Realp) { return p.Re > 0 ? p.Re : - p.Re; } • Complex x; • double a = abs( x);// tento kód LZE přeložit, a to je špatně • Důvod: Předání hodnoty x do parametru p je provedeno implicitně vytvořeným konstruktorem: • Real::Real( const Real & y){ Re = y.Re; } • Parametr x typu Complex do tohoto konstruktoru lze předat • Complex => Complex & => Real & => const Real &
Nesprávné užití dědičnosti • Nesprávné užití dědičnosti č. 2 • class Complex { public: double Re, Im; }; • class Real : public Complex { public: Real( double r); }; • Vypadá jako korektní specializace:"každé reálné číslo má všechny vlastnosti komplexního čísla"
Nesprávné užití dědičnosti • Nesprávné užití dědičnosti č. 2 • class Complex { public: double Re, Im; }; • class Real : public Complex { public: Real( double r); }; • Vypadá jako korektní specializace:"každé reálné číslo má všechny vlastnosti komplexního čísla" • Chyba: Objekty v C++ nejsou hodnoty v matematice • Třída Complex má vlastnost "lze do mne přiřadit Complex" • Tuto vlastnost třída Real logicky nemá mít, s touto dědičností ji mít bude
Nesprávné užití dědičnosti • Nesprávné užití dědičnosti č. 2 • class Complex { public: double Re, Im; }; • class Real : public Complex { public: Real( double r); }; • Vypadá jako korektní specializace:"každé reálné číslo má všechny vlastnosti komplexního čísla" • Chyba: Objekty v C++ nejsou hodnoty v matematice • Třída Complex má vlastnost "lze do mne přiřadit Complex" • Tuto vlastnost třída Real logicky nemá mít, s touto dědičností ji mít bude • void set_to_i( Complex& p) { p.Re = 0; p.Im = 1; } • Real x; • set_to_i( x);// tento kód LZE přeložit, a to je špatně • Důvod: Referenci na potomka lze přiřadit do reference na předka • Real => Real & => Complex &
Nesprávné užití dědičnosti • Nesprávné užití dědičnosti č. 2 • class Complex { public: double Re, Im; }; • class Real : public Complex { public: Real( double r); }; • Vypadá jako korektní specializace:"každé reálné číslo má všechny vlastnosti komplexního čísla" • Chyba: Objekty v C++ nejsou hodnoty v matematice • Třída Complex má vlastnost "lze do mne přiřadit Complex" • Tuto vlastnost třída Real logicky nemá mít, s touto dědičností ji mít bude • Poznámka: při přímem přiřazování tento problém nenastane • Complex y; • Real x; • x = y;// tento kód NELZE přeložit • Důvod: operátor = se nedědí • Complex & Complex::operator=( const Complex &); // nezdědí se • Real & Real::operator=( const Real &);// nesouhlasí typ argumentu
Třídy v C++ • Třídy sloužící jako datové typy • Proměnné typu T • Časté kopírování, vracení hodnotou • Přiřazení bývá jediný způsob změny stavu objektu • Dědičnost nemá smysl • Bez virtuálních funkcí • Třídy reprezentující „živé“ objekty • Proměnné typu T *, případně T & • Objekty alokovány dynamicky • Kopírování nemívá smysl • Metody měnící stav objektu • Většinou s dědičností a virtuálními funkcemi
Třídy v C++ • Abstraktní třída • Definice v C++: Třída obsahující alespoň jednu čistě virtuální funkci • Běžná definice: Třída, která sama nebude instanciována • Představuje rozhraní, které mají z ní odvozené třídy (potomci) implementovat • Konkrétní třída • Třída, určená k samostatné instanciaci • Implementuje rozhraní, předepsané abstraktní třídou, ze které je odvozena
class UUU { public: virtual ~UUU(); }; class XXX : public UUU { public: virtual ~XXX(); }; XXX * px = new XXX; // konverze potomek-předek UUU * pu = px; delete pu; Pokud je objekt destruován operátorem delete aplikovaným na ukazatel na předka, musí být destruktor v tomto předku deklarován jako virtuální Odvozené pravidlo: Každá abstraktní třída má mít virtuální destruktor Je to zadarmo Může se to hodit Dědičnost a destruktor
Ideální užití dědičnosti a virtuálních funkcí • Abstraktní třída • Definuje rozhraní objektu jako množinu předepsaných virtuálních funkcí • class GraphicObject { • public: • virtual ~GraphicObject();// každá abstraktní třída má mít v.d. • virtual void paint() = 0; // čistě virtuální funkce • virtual void move( int dx, int dy) = 0; // čistě virtuální funkce • };
Ideální užití dědičnosti a virtuálních funkcí • Abstraktní třída • Definuje rozhraní objektu jako množinu předepsaných virtuálních funkcí • Abstraktní třídy se mohou dědit • Dědičnost jako rozšiřování předepsaného rozhraní • class ClickableObject : public GraphicObject { • public: • virtual void click( int x, int y) = 0; // čistě virtuální funkce • };
Ideální užití dědičnosti a virtuálních funkcí • Abstraktní třída • Definuje rozhraní objektu jako množinu předepsaných virtuálních funkcí • Konkrétní třída • Implementuje předepsané virtuální funkce • Je potomkem abstraktní třídy • Dědičnost jako vztah rozhraní-implementace
Ideální užití dědičnosti a virtuálních funkcí • Abstraktní třída • Definuje rozhraní objektu jako množinu předepsaných virtuálních funkcí • Konkrétní třída • Implementuje předepsané virtuální funkce • class Button : public ClickableObject { • public: • Button( int x, int y, const char * text); • protected: • virtual void paint(); • virtual void move( int dx, int dy); • virtual void click( int x, int y); • private: • int x_, y_; char * text_; • };
Ideální užití dědičnosti a virtuálních funkcí • Abstraktní třída • Definuje rozhraní objektu jako množinu předepsaných virtuálních funkcí • Konkrétní třída • Implementuje předepsané virtuální funkce • Polotovar třídy • Mezi abstraktní a konkrétní třídou - třída implementující část předepsaných virtuálních funkcí • Jejím potomkem je konkrétní třída nebo jiný polotovar • Dědičnost jako reusabilita kódu
Ideální užití dědičnosti a virtuálních funkcí • Polotovar třídy • Mezi abstraktní a konkrétní třídou - třída implementující část předepsaných virtuálních funkcí • class PositionedObject : public ClickableObject { • public: • PositionedObject( int x, int y); • protected: • int get_x() const { return x_; } • int get_y() const { return y_; } • virtual void move( int dx, int dy); • private: • int x_, y_; • };
Ideální užití dědičnosti a virtuálních funkcí • Konkrétní třída • Implementuje předepsané virtuální funkce • Konkrétní třídy mohou mít potomky - jiné konkrétní třídy se změněnými vlastnostmi (redefinovanými virtuálními funkcemi) • Dědičnost jako reusabilita kódu se změnou chování • class IconButton : public Button { • public: • IconButton( int x, int y, const char * text, BitMap icon); • protected: • virtual void paint(); • private: • BitMap icon_; • };
Ideální užití dědičnosti a virtuálních funkcí • Různé významy dědičnosti • Rozšiřování předepsaného rozhraní • GraphicObject => ClickableObject • Vztah rozhraní-implementace • ClickableObject => PositionedObject, Button • Reusabilita kódu • PositionedObject => Button • Reusabilita se změnou chování (overriding) • Button => IconButton • A to není zdaleka všechno... • C++ pro odlišné účely využívá tytéž mechanismy • Některé jazyky tyto účely rozlišují (Java)