Jazyk C++ 1 Blok 3 Objektové typy jazyka C++ Studijní cíl Ve třetím bloku bude představen a rozebrán nejdůležitější objektový typ jazyka C++ a to sice třída. Po absolvování bloku bude student schopen navrhovat základní třídy v programovacím v jazyce C++.
Doba nutná k nastudování 2 - 3 hodiny
Průvodce studiem Pro studium tohoto bloku jsou u studentů požadovány předchozí znalosti z oblasti programování v jazyce C a znalost základů objektově orientovaného programování. Pro úspěšné zvládnutí bloku se u studentů předpokládá základní znalost běžných výpočetních prostředků a principů programování (počítač, vývojové prostředí, zdrojový kód, …).
Třída Třída představuje základní pojem z oblasti objektově orientovaného programování. Anglicky class. Ve smyslu objektově orientovaného programování lze za třídy v jazyce C++ považovat jednak třídy, ale jednak také struktury a unie. Tyto objektové datové typy se deklarují pomocí klíčových slov struct (struktura), union (unie) a class (třída). V dalším textu bude pojem třída představovat obecný pojem pro objektový typ. Třída ve smyslu datového typu deklarovaného pomocí klíčového slova class bude vždy upřesněna blíže. Struktury (struct) a třídy deklarované pomocí class jsou si ve své podstatě velice podobné. Hlavní rozdíly jsou v možnostech dědění a ve výchozích přístupových právech. Unie (union) se potom od těchto liší trochu více. Detailněji budou popsány později. Jazyk C++ 1
1
Syntakticky korektní zápis pro třídy v jazyce C++ je následující: informativní deklarace: klíč jmenovka; definiční deklarace klíč jmenovkanep { tělo_třídy } seznam_deklarátorůnep; klíč jmenovka: seznam_předků { tělo_třídy } seznam_deklarátorůnep; klíč – určuje konkrétní typ třídy (class, struct, union). tělo_třídy – představuje specifikace přístupových práv (public, protected, private), deklarace a definice atributů, metod, typů šablon či přátel. Obsahuje tedy seznam deklarací složek (členů) třídy. V angl. literatuře se uvádí výraz class member. Za složky třídy jsou považovány:
atributy představující datové složky, metody představující funkční složky (členské funkce), typy představující takzvané vnořené (angl. nested) typy, šablony – budou probrány později.
Deklarace mohou být v libovolném pořadí a před každou deklarací může být uvedena specifikace přístupových práv. jmenovka – představuje identifikátor deklarovaného typu. seznam_deklarátorů – deklarátory proměnných tohoto typu, polí, ukazatelů na tento typ, … seznam_předků – určuje od jakých objektových typů je deklarovaná třída odvozena. Proměnné, které jsou deklarovány objektovým typem (class, union, struct) se nazývají instance. Za objekt je pak považována manipulovatelná oblast paměti – proměnná základního typu, objektového typu, pole hodnot, ukazatele na funkci, … Lze tedy též říci, že objekt je instance třídy. Deklarace objektových typů mohou být jednak globální, jednak lokální v deklaraci jiné třídy (vzniká tzv. vnořená třída) nebo lokální ve funkci (lokální třída).
Jazyk C++ 1
2
V deklaraci lze vynechat jmenovku třídy, potom se jedná o takzvanou nepojmenovanou třídu. Pokud se deklaruje nepojmenovaná třída, nesmí být vynechán seznam deklarátorů. Výjimku tvoří anonymní unie a deklarace vnořené třídy. Ke složkám třídy lze přistupovat pomocí operátorů:
. (tečka) – kvalifikace – pro přístup pomocí instance třídy nebo reference na třídu. -> – nepřímá kvalifikace – pro přístup pomocí ukazatele na třídu.
Atributy Deklarace atributu jakožto datových složek třídy je obdobná deklaraci klasických proměnných. Navíc lze u atributů deklarovat některé paměťové třídy (static pro statické atributy a mutable pro měnitelné atributy). Nestatickým atributem třídy TA nemůže být:
instance (nebo pole instancí) třídy TA, instance jiné třídy, kde je jako atribut instance (nebo pole instancí) třídy TA, instance třídy, která je odvozena od třídy TA.
Může jím však být ukazatel či reference na třídu TA. Příklad: struct TA{ TA A; //Chyba TA &RefA, *UkazA; TB B; //OK, pokud //nemá jako TB &RefB, *UkazB; }
//OK TB není odvozena od TA a/nebo atribut instanci TA. //OK
Metody Funkční složky třídy se deklarují buď informativní deklarací (prototypem) nebo definiční deklarací. V případě, že je uveden pouze prototyp metody, je nezbytné její tělo definovat později. Tělo metody lze definovat i mimo tělo třídy, je pak ovšem nutné její jmenovku uvést jmenovkou třídy a rozlišovacím operátorem. Příklad: void TA::Metoda() { … }
Kde TA představuje jmenovku třídy. Jazyk C++ 1
3
Za funkční složku třídy (metodu) jsou též považovány konstruktory, destruktory a přetížené operátory. Funkční složky třídy lze deklarovat též s paměťovou třídou static (vznikají takzvané statické metody). Lze také uvést specifikátory inline, explicit (pouze u konstruktorů) nebo virtual. Pokud má být některá metoda deklarovaná jako vložená (obdoba inline funkce), musí být v těle třídy uvedena kompletní (definiční) deklarace nebo musí být u prototypu uvedeno klíčové slovo inline. Příklad: struct TA { int x, y; int VratSoucet { return x + y; } //vložená metoda inline int VratSoucin(); //vložená metoda void Vypis(); //není vložená }; inline int TA::VratSoucin { //definice vložené metody return x*y; } void TA::Vypis() { std::cout << x << “ ” << y; }
Přístupová práva Pomocí specifikátorů public, protected a private lze docílit omezení přístupu různých částí programu ke složkám třídy. Pokud je uvedena specifikace přístupu, platí pro všechny následující deklarace až po nový výskyt specifikace přístupu. public – určuje složky třídy, které jsou přístupné všem částem programu bez omezení. Jedná se tedy o veřejné složky. protected – označuje chráněné složky, které jsou přístupné pouze metodám této třídy a jejím potomkům a spřáteleným třídám. private – složky určené tímto specifikátorem jsou přístupné pouze metodám dané třídy a třídám spřáteleným. Jedná se o soukromé složky. Příklad: class TB { int a; //soukromý atribut int b; //soukromý atribut protected: int c; //chráněný atribut Jazyk C++ 1
4
int d; //chráněný atribut public: int Soucet() { return a + b; } //veřejná metoda protected: void Init(); //chráněná metoda };
This ukazatel Konstantní ukazatel na danou třídu je určen klíčovým slovem this. Například pro třídu TA by se jednalo o ukazatel typu TA* const. Ukazatel this se používá v nestatických metodách třídy, které jej mají jako skrytý parametr. Například pro: TA A; A.Vypis();
Metodu Vypis() lze definovat takto: void TA::Vypis() { std::cout << this->x << ‘ ’ << this->y; }
Lokální proměnné Pokud se deklaruje lokální proměnná v metodě třídy (včetně parametru této metody), může se deklarovat se stejnou jmenovkou, jakou má atribut třídy. V takovém případě pak lokální proměnná zastiňuje atribut třídy. V případě potřeby přistoupit k datové složce třídy, je nutné uvést před jmenovkou atributu identifikátor této třídy a rozlišovací operátor nebo k němu přistoupit přes ukazatel this. Uvažujme příklad, kde struktura TA obsahuje atribut x a metodu SetX: // do atributu x se uloží hodnota parametru x void SetX(int x) { TA::x = x; } // opět se do atributu x uloží hodnota parametru x void SetX(int x) { this->x = x; }
Statické atributy Datové složky třídy, které jsou specifikovány paměťovou třídou static jsou společné pro všechny instance třídy a existují i ve chvíli, kdy žádné instance třídy neexistují. Na statické atributy lze nahlížet jako na globální proměnné v dané třídě, na které se vztahují přístupová práva (private, public, protected). Jazyk C++ 1
5
Statické datové složky nemusí být v metodách dané třídy nikterak kvalifikované. Je nutné je však kvalifikovat mimo metody této třídy, a to buď jmenovkou třídy a rozlišovacím operátorem nebo jmenovkou instance třídy a operátorem kvalifikace respektive nepřímé kvalifikace. Deklarace statické datové složky v rámci definice třídy má pouze informativní charakter a nevyhrazuje místo v paměti pro tento atribut. V definiční deklaraci je potřeba jmenovku statického atributu kvalifikovat jmenovkou třídy a rozlišovacím operátorem bez specifikátoru static. Lze jej zde též inicializovat. Statickým atributem nemůže být bitové pole, naopak oproti nestatickým atributům, může statickým atributem třídy TA být:
instance (nebo pole instancí) třídy TA, instance jiné třídy, kde je jako atribut instance (nebo pole instancí) třídy TA, instance třídy, která je odvozena od třídy TA.
Statické atributy nemohou být součástí lokální třídy a nemohou být zároveň měnitelné (mutable). Pokud je statická datová složka konstantního celočíselného (nebo výčtového) typu (včetně bool a char), může se inicializovat přímo v těle třídy a nemusí být definován mimo tělo třídy. Příklad: class TIntArr { int *a, n; public: static int PocAlokPrvku; //statický atribut udávající //celkový počet alokovaných //prvků static const int MaxPocPrvku = 1000; // statický //atribut konstantního //celočíselného typu //inicializovaný celočíselným //konstantním výrazem void Alokuj(int _n); void Dealokuj() { PocAlokPrvku -= n; /* ... */ } }; void TIntArr::Alokuj(int _n) { if (_n <= MaxPocPrvku) { PocAlokPrvku += n; // ... } }
Jazyk C++ 1
6
int TIntArr::PocAlokPrvku = 0; // static se před int // neuvádí TIntArr A; //výpis počtu alokovaných prvků dvěma způsoby cout << A.PocAlokPrvku; cout << TIntArr::PocAlokPrvku;
Statické metody Funkční složky třídy, které jsou specifikovány paměťovou třídou static jsou společné pro všechny instance třídy a existují i ve chvíli, kdy žádné instance třídy neexistují. Statické metody třídy TA mohou tak přímo využívat pouze statické atributy třídy TA. Nemají tedy ani k dispozici ukazatel this. Statické funkční složky nemusí být v metodách dané třídy nikterak kvalifikované. Je nutné je však kvalifikovat mimo metody této třídy, a to buď jmenovkou třídy a rozlišovacím operátorem nebo jmenovkou instance třídy a operátorem kvalifikace respektive nepřímé kvalifikace. Statickými metodami nemůžou být konstruktory a destruktory, ani virtuální metody. Příklad: class TIntArr { int *a, n; static int PocAlokPrvku; pulic: void Alokuj(int _n) { PocPrvku += _n; /* ... */ } void Dealokuj() { PocPrvku -= n; /* ... */ } //statické metody static int DejPocAlokPrvku() { return PocAlokPrvku; } static int DejPocAlokBytu(); }; int TIntArr::PocAlokPrvku = 0; int TIntArr::DejPocAlokBytu() // static se před int //neuvádí { return PocAlokPrvku * sizeof(int); } TIntArr A; //výpis počtu alokovaných prvků dvěma způsoby cout << A.PocAlokPrvku; cout << TIntArr::PocAlokPrvku;
Jazyk C++ 1
7
Konstantní a nestálé instance – metody Instance tříd mohou být deklarovány s tzv. cv-modifikátory: modifikátor pro konstantní instanci (const), modifikátor pro nestálou instanci (volatile) nebo modifikátor pro konstantní nestálou instanci (const voaltile). Příklad: //deklarace konstantní instance A třídy TA const TA A;
Konstantní instance třídy nemohou měnit její nestatické atributy s výjimkou atributů označených jako měnitelných (mutable). Konstantní instance třídy mohou volat pouze statické a konstantní metody této třídy. Nestálé instance třídy mohou volat pouze statické a nestálé metody této třídy. Pro konstantní nestálé metody platí kombinace obou výše uvedených omezení. Konstantní metoda třídy se deklaruje s modifikátorem const uváděným za kulatou závorkou uzavírající seznam formálních parametrů metody. Příklad: struct TA { // ... void Vypis() const; }; void TA::Vypis() const { /* ... */ }
Obdobným způsobem (užitím modifikátorů volatile a const volatile) se deklarují i nestálé a konstantní nestálé metody. CVmodifikátory jsou součástí deklarace metody, je nutné je tedy uvádět jak v prototypu, tak v definici metody. V jedné třídě lze definovat dvě metody se stejným názvem (identifikátorem) a stejnými parametry, které se liší jen modifikátorem const. Pro konstantní instanci se pak volá konstantní metoda a pro nekonstantní instanci se volá nekonstantní varianta této metody. Pro konstantní a nestálé metody lze volat nekonstantní a nestálé konstruktory, destruktory a statické metody. Ty nelze deklarovat jako konstantní nebo nestálé.
Jazyk C++ 1
8
Příklad: struct TA { int a; mutable int b; static int x; static void MocninaX() { x *= x; } int NasobekA(int n) { return a *= n; } int NasobekA(int n) const { return a*n; } void Set(int _a, int _b) { a = _a, b = _b; } void SoucetA(int n) const { a += n; } // Chyba void SoucetB(int n) const { b += n; } // OK }; const TA A = { 10, 20 }; // konstatní instance, // a = 10, b = 20 TA B; // nekonstatní instance //příklady použití A.a = 100; // Chyba A.b = 30; // OK A.x = 40; // OK - kontantní instance může měnit // statické atributy B.a = 1; // OK A.MocninaX(); // OK - konstantní instance může // volat statické metody A.Set(10, 20); // Chyba B.Set(1, 2); // OK A.NasobekA(10); // OK - volá se konstatní metoda B.NasobekA(10); // OK - volá se nekonstatní metoda void f(TA& c, const TA& d, const TA* e, int a) { c.Set(a, a); // OK d.Set(a, a); // Chyba e->Set(a, a); // Chyba }
Přátelé třídy Ke všem složkám dané třídy mohou přistupovat i třídy a funkce, které nejsou součástí této třídy. Tyto funkce a třídy musí být tzv. spřátelené funkce (třídy). Syntaktický zápis spřátelených tříd: friend prototyp_funkce; friend definiční_deklarace_funkce; friend klíč identifikátor_objektového_typu; V dané třídě lze definovat prototyp spřátelené funkce nebo celou její definici. Pokud se uvede definice, bude překladač s takovou funkcí zacházet jako s funkcí vloženou. A to bez ohledu na to, zda je či není deklarována s modifikátorem inline. Jazyk C++ 1
9
Jestliže je definice funkce uvedena mimo tělo třídy, musí být uvedena modifikátorem inline, pokud chceme, aby byla vložená. Za spřátelenou třídu lze považovat třídu, která má všechny metody deklarované jako spřátelené. Specifikace přístupu může být před deklarací spřátelené metody uvedena, avšak na danou metodu se nevztahuje. Přátelství tříd není ani tranzitivní, ani dědičné. Přátelství se nevztahuje ani na třídy vnořené. Příklad: Pokud je třída TB přítelem třídy TA a třída TC je přítelem třídy TB, neznamená to, že by třída TC byla automaticky přítelem třídy TA. Obdobně, je-li třída TB přítelem třídy TA a třída TD je potomkem třídy TB, neznamená to, že by třída TD byla automaticky přítelem třídy TA. Příklad: class TKomplexCislo { double r, i; public: void Set(double _r, double _i) { r = _r; i = _i; } friend double abs(const TKomplexCislo& t) // vložená // funkce { return sqrt(t.r*t.r + t.i*t.i); } friend void FriendSet(TKomplexCislo& t, double r, double i); friend class TKomplexCisloArr; // v metodách třídy TKomplexCisloArr lze přistupovat // ke všem složkám třídy TKomplexCislo friend void TKomplexCisloList::SetAll(double r, double i); // metoda SetAll třídy TKomplexCisloList může // přistupovat ke všem složkám třídy TKomplexCislo }; void FriendSet(TKomplexCislo& t, double r, double i) { t.r = r; t.i = i; } TKomplexCislo kc; kc.Set(10, 20); double a = abs(kc); FriendSet(kc, 1, 2); double b = abs(kc);
Shrnutí studijního bloku Tento studijní blok seznámil studenta se základními objektovými typy programovacího jazyka C++. S metodami a atributy tříd, ukazatelem this, lokálními, statickými, konstantními a nestálými proměnnými a metodami.
Jazyk C++ 1
10
Byly zavedeny termíny pro třídy, struktury a unie.
Otázky k procvičení pasáže 1. 2. 3. 4. 5.
Uveďte, jaké známe cv-modifikátory. Co jsou to statické atributy? Vysvětlete pojem objekt. Jakým klíčovým slovem se deklarují spřátelené funkce? Co znamená, že je nějaká třída spřátelená?
Odkazy na další studijní materiály
http://msdn.microsoft.com/en-us/library/vstudio/zdbe067e.aspx (oficiální stránky Microsoftu pro vývoj v programovacím jazyce C++ v prostředí Micsrosoft Visual Studio) http://www.iso.org/iso/iso_catalogue/catalogue_tc/catalogue_detail. htm?csnumber=50372 (ISO norma C++ 2011)
Použité zdroje a literatura [1] PRATA, Stephen. Mistrovství v C++. 3. aktualiz. vyd. Překlad Boris Sokol. Brno: Computer Press, 2007, 1119 s. ISBN 978-80-251-1749-1. [2] Přednášky Ing. Karla Greinera, Ph.D. na předmět Jazyk C++ vyučovaný na Dopravní fakultě Jana Pernera, Univerzity Pardubice.
Jazyk C++ 1
11