Jazyk C++ 1 Blok 1 Úvod do programovacího jazyka C++ Studijní cíl První blok kurzu je věnován úvodu do problematiky programovacího jazyka C++. V bloku budou rozebrány historické souvislosti programovacích jazyků C a C++ a budou představeny a zhodnoceny základní technologie, které se této oblasti dotýkají. Po absolvování bloku bude student ovládat základní pojmy z oblasti programování 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. 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, …).
Historie Dalo by se říci, že jazyk C++ je objektově orientovaným rozšířením jazyka C. Autorem programovacího jazyka C++ je dánský programátor, informatik a profesor na Texas A&M University Bjarne Stroustrup. V roce 1982 Stroustrup rozšířil jazyk C o objekty pomocí preprocesoru a nazval jej „C s třídami“ (C with classes). První verze C++ se pak objevila v roce 1985. V dalších letech se pak začaly objevovat první normy pro jazyk C++ (ISO/IEC 14882:1998; ISO/IEC 9899:1999; ISO/IEC 14882:2003; ISO/IEC 14882:2011).
Deklarace a definice Deklarace (informativní deklarace) informuje překladač o existenci nějakého objektu. Jazyk C++ 1
1
Definice (definiční deklarace) přikazuje překladači vytvořit daný objekt.
Neobjektové rozdíly mezi C a C++ Identifikátory Délka identifikátoru (např. název proměnné) v C++ není dle normy omezena a všechny znaky jsou v něm signifikantní (významné). V jazyce C je signifikantních pouze 31 znaků. Překladače však mívají délky identifikátoru v C++ omezeny. Ve vývojovém prostředí Microsoft Visual C++ je signifikantních implicitně 247 znaků.
Komentáře V jazyce C++ lze použít klasických C komentářů /* Komentáře na více řádcích. */
Podle nových norem lze uvádět jak v jazyce C, tak i v jazyce C++ také komentáře jednořádkové. // jednořádkový komentář
Víceřádkové komentáře ale nelze podle normy vnořovat do sebe.
Příkazy Za příkaz lze považovat také informativní i definiční deklaraci. To znamená, že deklarace se může vyskytnout na všech místech, kde syntaxe jazyka umožňuje příkaz. Rozdíl oproti jazyku C je vidět převážně u složených příkazů, tzv. bloků. V jazyce C++ je blok definován takto: {posloupnost_příkazůnep} V jazyce C je potom blok definován následovně: {posloupnost_deklaracínep posloupnost_příkazůnep}
Příklad int pole[10]; pole[5] = 10; int x = pole[5]*2; // v jazyce C++ korektní zápis
Deklarace proměnné je také povolena ve výrazu podmínka v příkazech:
if(podmínka) příkaz
Jazyk C++ 1
2
if(podmínka) příkaz else příkaz2 switch(podmínka) příkaz while(podmínka) příkaz for(inicializace; podmínka; výraz) příkaz
V případě, že se jedná o cyklus for, může být proměnná deklarována též v části inicializace. Podle normy by měla být proměnná deklarovaná v těchto příkazech vždy proměnnou lokální. Ale často záleží na konkrétním překladači. Pozor, proměnnou nelze deklarovat v cyklu do příkaz while (výraz); v části výraz.
Příklad if(int a = f()) Zpracuj(a); b = a; // Chyba! Proměnná a for(int i = 0; i < 10; i++) b = i; // Chyba! Proměnná i
// OK už neexistuje. a[i] = i; // OK už neexistuje.
Skoky Příkazů goto a switch se týká omezení možnosti přeskočit definici s inicializací, pokud nedojde k přeskočení celého bloku, v němž se tento příkaz vyskytuje.
Chybný zápis if(true) goto Pokracovani; int a = 1; //… Pokracovani: //…
Korektní zápis if(true) goto Pokracovani; int a; a = 1; //… Pokracovani: //…
Jazyk C++ 1
3
Korektní zápis if(true) goto Pokracovani; { int a = 1; //… } Pokracovani: //…
Chybný zápis switch(a) { case 0: int x = 1; //… break; case 1: //… }
Korektní zápis switch(a) { case 0: int x; x = 1; //… break; case 1: //… }
Korektní zápis switch(a) { case 0: { int x = 1; //… break; } case 1: //… }
Jazyk C++ 1
4
Datové typy Celočíselné typy Logický typ Pro logické hodnoty (pravda, nepravda) je v jazyce C++ k dispozici datový typ bool, který může nabývat hodnot true nebo false. Přičemž platí vztah true > false. Pokud překladač na místě, kde očekává logickou hodnotu, narazí na celé číslo, reálné číslo či znak, automaticky jej konvertuje, a to:
libovolné nenulové číslo => true, nulu => false.
Pokud překladač na místě, kde očekává číselnou hodnotu, narazí na hodnotu logickou, automaticky ji konvertuje:
true => 1, false => 0.
Znakové typy Jazyk C++ zná jednobytové znakové typy char, signed char a unsigned char a dvoubytový typ wchar_t, který lze použít pro práci s UNICODE a znaky asijských jazyků. Na rozdíl od programovacího jazyku C, kde jsou znakové konstanty (např. ‘a’) typu int, jsou v programovacím jazyce C++ znakové konstanty typu char. Pokud se znakové konstanty zapisují s prefixem L (např. L’a’), bude tato konstanta typu wchar_t. Znaková konstanta v UNICODE se zapisuje ‘\Uxxxxxxxx’ nebo ‘\uxxxx’, kde x představuje číslici šestnáctkové soustavy. Zápis ‘\uxxxx’ je potom zkratkou pro ‘\U0000xxxx’. Pro práci s dvoubytovými znaky (wide characters) se používají funkce s prefixem w, např. wprintf.
Typy pro celá čísla V jazyce C++ jsou k dispozici velké datové typy long long int a unsigned long long int, přičemž int lze vynechat. Rozsah těchto typů nesmí být menší než rozsah long int a unsigned long int. V prostředí Microsoft Visual Studio C++ jsou tyto typy 8bytové. Jejich rozsahy jsou tedy následující: Jazyk C++ 1
5
long long int unsigned long long int
-264/2 až 264/2-1 0 až 264-1
Další celočíselné typy jsou:
s přesně určenou šířkou: o znaménkové: int8_t, int16_t, int32_t, int64_t o bezznaménkové: uint8_t, uint16_t, uint32_t, uint64_t s minimální šířkou alespoň N bitů: o znaménkové: int_least8_t, int_least16_t, int_least32_t, int_least64_t o bezznaménkové: uint_least8_t, uint_least16_t, uint_least32_t, uint_least64_t s minimální šířkou a nejrychlejším výpočtem: o znaménkové: int_fast8_t, int_fast16_t, int_fast32_t, int_fast64_t o bezznaménkové: uint_fast8_t, uint_fast16_t, uint_fast32_t, uint_fast64_t typy, do nichž lze uložit void* o znaménkové: intptr_t o bezznaménkové: uintptr_t s maximální šířkou o znaménkové: intmax_t o bezznaménkové: uintmax_t
Hlavičkový soubor <stdint.h> obsahuje také definice maker, poskytující minimální a maximální hodnoty konkrétních typů:
INTN_MIN, INTN_MAX, UINTN_MAX (např. INT16_MAX) INT_LEASTN_MIN, INT_LEASTN_MAX, UINT_LEASTN_MAX (např. INT_LEAST16_MAX) INT_FASTN_MIN, INT_FASTN_MAX, UINT_FASTN_MAX (např. INT_FAST16_MAX) INTPTR_MIN, INTPTR_MAX, UINTPTR_MAX INTMAX_MIN, INTMAX_MAX, UINTMAX_MAX
Ve vývojovém prostředí Microsoft Visual Studio C++ celočíselné znaménkové typy s přesně určenou šířkou, které lze používat ve spojení s modifikátorem unsigned:
__int8, __int16, __int32, __int64
Jazyk C++ 1
6
Celočíselný konstantní výraz Jedná se o výraz, který dokáže překladač vyhodnotit už v době překladu. Jako operand v takovém výrazu lze použít:
literál (Představuje pevně danou hodnotu, vyjádřenou explicitně, bez použití jiných prvků jazyka. Mezi literály tedy nepatří proměnné programu ani konstanty, naopak k inicializaci jejich hodnot se zpravidla literály používají.), makro, konstanta deklarovaná pomocí const inicializovaná konstantním výrazem, výčtová konstanta, operátor sizeof.
Pole Oproti programovacímu jazyku C, kde lze pro zadání mezí polí využít pouze makro (manifestační konstantu) nebo literál, lze v jazyce C++ použít libovolný celočíselný konstantní výraz.
Příklad const int mez = 10; enum { e = 20 }; #define d = 30; int w[mez], x[e], y[mez+e], z[d+e]; // toto nelze použít v jazyce C double v[d]; // OK v C i C++
Ukazatele V programovacím jazyce C++ nelze přiřadit ukazateli s doménovým typem ukazatel bez doménového typu (void*), např.: void* p; int a, *b; p = &a; // Lze použít v C i C++ b = p; // Chyba v C++ (nutno přetypovat), v C lze použít b = (int*)p;
Ukazatel nikam V programovacím jazyce C se pro ukazatel na žádnou platnou adresu používá makro NULL. V programovacím jazyce C++ jej lze sice použít také, ale vzhledem k přísnější typové kontrole mohou nastat situace, kdy se jedná o chybu. Doporučuje se proto v jazyce C++ používat pro ukazatele nikam hodnotu nula (0).
Jazyk C++ 1
7
Ve vývojovém prostředí Microsoft Visual Studio C++ je dokonce výraz NULL chápan jako synonymum pro 0. Ukazatele lze použít i v místech, kde je očekávána logická hodnota. Dochází k automatické konverzi:
0 => false, jiná hodnota => true.
Standardní vstupy a výstupy Výchozím standardním výstupem (stdout) je obrazovka a lze k ní přistupovat zápisem: cout << výraz1 << výraz2 …;
Výchozím standardním vstupem (stdin) je klávesnice a lze z ní načítat prostřednictvím zápisu: cin >> proměnná1 >> proměnná2 …;
Před použitím cin a cout je potřeba zahrnout do zdrojového souboru hlavičkový soubor
a zahrnout jmenný prostor std: using namespace std;
Příklad: int a, b; cin >> a >> b; cout << “Obvod obdélníku je: ” << 2*(a+b);
Reference Reference představuje proměnnou odkazující na jinou proměnnou a deklaruje se pomocí znaku & (ampersand). V programovacím jazyce C nemají reference obdobu. Referenci je nutné v deklaraci inicializovat nějakou l-hodnotou1 stejného typu jako je deklarovaná reference. Proměnná, na kterou reference odkazuje, se pak nazývá odkazovaná proměnná. Cokoli se pak provede s referencí, bude mít stejný dopad, jako by se to provedlo s odkazovanou proměnnou. Např.: int i; int& refi = i;
1
l-hodnota je výraz označující místo v paměti, kam lze zapisovat. Tedy například proměnná, naopak za l-hodnotu nelze považovat konstantu. Zjednodušeně řečeno lze za l-hodnotu považovat cokoli, co může být na levé straně přiřazení. Jazyk C++ 1
8
Provedeme-li nyní přiřazení: refi = 100;
Je to naprosto totéž, jako bychom provedli: i = 100;
Vezmeme-li v úvahu, že ukazatel ui je ukazatel na int, jsou pak následující dva příkazy naprosto ekvivalentní: ui = &ri; ui = &i;
V jazyce C++ nelze deklarovat ukazatel na referenci, pole referencí, referenci na referenci ani proměnnou typu void&. Příklad: int* ui = &i; int*& refui = ui; // OK – reference na ukazatel int&* urefi; // chyba – ukazatel na reference *ui = 100; *refui = 100; // dělá totéž co *ui = 100; void f(); void (&reff)() = f; // reference na fci void f(); reff(); // volání fce f();
Funkce může vracet referenci. Nejčastěji se reference používají k předávání funkcí odkazem.
Konstantní reference Konstantní reference neboli reference na konstantu vznikne, použije-li se v deklaraci reference modifikátor const. Konstantní referenci nelze po inicializaci přiřadit jinou hodnotu. Příklad: int i; const int& refi = i; refi = j; // Chyba, po inicializaci nelze přiřadit jinou hodnotu
Inicializátorem konstantní reference může být libovolný výraz. Pouze, pokud se nejedná o l-hodnotu, vytvoří překladač pomocnou proměnnou, do které uloží výsledek tohoto výrazu a reference potom ukazuje právě na tuto pomocnou proměnnou. Příklad: Jazyk C++ 1
9
int x = 100; const int& y = 2*x; // y odkazuje na pomocnou proměnnou s hodnotou 200
Implicitní int Pravidlo implicitního integeru, jakožto defaultního typu pro proměnnou či návratovou hodnotu funkce v případě, že v deklaraci není typ uveden, které je známé z programovacího jazyka C, není v normě jazyka C++ povoleno.
Funkce V programovacím jazyce C lze provést deklaraci: int f(void);
pro funkci bez parametrů a deklaraci: int f();
pro funkci s neznámým počtem a typem parametrů. V jazyce C++ znamenají oba dva výše uvedené zápisy funkci bez parametrů. Zatímco v programovacím jazyce C může volání funkce předcházet její deklaraci, v jazyce C++ tomu tak není. Před voláním funkce v programovacím jazyce C++ musí být uveden alespoň její prototyp (= informativní deklarace funkce).
Parametry funkce Stejně jako v jazyce C lze i v jazyce C++ předávat parametry funkce hodnotou: int f(double a); void swap(int* a, int* b);
V jazyce C++ lze navíc předávat parametry také odkazem. Parametry, jež jsou předávány odkazem, pak mají formální parametr typu reference. Pokud je formální parametr typu T&, skutečným parametrem musí být lhodnota typu T. Jestliže je formálním parametrem konstantní reference (const T&), skutečným parametrem pak může být libovolný výraz, který se dá použít k inicializaci proměnné typu T. Nejedná-li se o l-hodnotu typu T, vytvoří překladač pomocnou proměnnou, do níž uloží výsledek výrazu a formální parametr potom bude ukazovat na tuto pomocnou proměnnou.
Jazyk C++ 1
10
Shrnutí studijního bloku Tento studijní blok seznámil studenta s historickými souvislostmi, aktuálními normami jazyka C++ a s neobjektovými rozdíly mezi programovacími jazyky C a C++. Zejména pak se základními datovými typy, základními příkazy, ukazateli, referencemi a standardními vstupy a výstupy C++ programů.
Otázky k procvičení pasáže 1. 2. 3. 4. 5. 6.
Jaký je hlavní rozdíl (výhoda) jazyka C++ oproti jazyku C? Vysvětlete pojem deklarace. Vysvětlete pojem definice. Co je standardním vstupem C++ programu? Co je standardním výstupem C++ programu? Jakým způsobem lze přeskočit definici s inicializací v případě skoků goto a switch?
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
Jazyk C++ 1 Blok 2 Úvod do programovacího jazyka C++ Studijní cíl Ve druhém bloku budou rozebrány další neobjektové rozdíly programovacích jazyků C a C++ a budou představeny a zhodnoceny některé další základní technologie, které se této oblasti dotýkají. Po absolvování bloku bude student ovládat základní pojmy z oblasti programování v jazyce C++ a mít znalosti týkající se neobjektových rozdílů mezi jazyky C a 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. 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, …).
Neobjektové rozdíly mezi C a C++ (pokračování) Funkce V programovacím jazyce C lze provést deklaraci: int f(void);
pro funkci bez parametrů a deklaraci: int f();
pro funkci s neznámým počtem a typem parametrů. V jazyce C++ znamenají oba dva výše uvedené zápisy funkci bez parametrů.
Jazyk C++ 1
1
Zatímco v programovacím jazyce C může volání funkce předcházet její deklaraci, v jazyce C++ tomu tak není. Před voláním funkce v programovacím jazyce C++ musí být uveden alespoň její prototyp (= informativní deklarace funkce).
Parametry funkce Stejně jako v jazyce C lze i v jazyce C++ předávat parametry funkce hodnotou: int f(double a); void swap(int* a, int* b);
V jazyce C++ lze navíc předávat parametry také odkazem. Parametry, jež jsou předávány odkazem, pak mají formální parametr typu reference. Pokud je formální parametr typu T&, skutečným parametrem musí být lhodnota typu T. Jestliže je formálním parametrem konstantní reference (const T&), skutečným parametrem pak může být libovolný výraz, který se dá použít k inicializaci proměnné typu T. Nejedná-li se o l-hodnotu typu T, vytvoří překladač pomocnou proměnnou, do níž uloží výsledek výrazu a formální parametr potom bude ukazovat na tuto pomocnou proměnnou. Příklad definiční deklarace a volání funkce Swap se dvěma parametry volanými odkazem, která provede výměnu hodnot těchto dvou parametrů: void Swap(int& x, int& y) { int temp = x; x = y; y = temp; } int a = 100, b = 200; Swap(a, b);
Implicitní hodnoty parametrů funkce Programovací jazyk C++ umožňuje v první definici funkce (definiční deklarace nebo prototyp) definovat výchozí hodnoty jejích parametrů. Nejčastěji bývají implicitní hodnoty parametrů předepsány v prototypech v hlavičkových souborech. Při deklaraci implicitní hodnoty parametru lze vedle konstanty použít i výraz. Pokud se předepíše pro některý parametr výchozí hodnota, je nutné výchozí hodnotu předepsat i pro všechny následující parametry. A pokud se vynechá
Jazyk C++ 1
2
některý skutečný parametr, musí se též vynechat všechny parametry následující. Příklad: void f(int a, int b = 0, int c = getch()); //použití: x = f(1, 2, 3); //nepoužijí se implicitní hodnoty x = f(1, 2); //impl. hodnota se použije pro c x = f(1); // impl. hodnota se použije pro b a c
Referenční funkce Referenční funkcí nazýváme funkci, která vrací referenci. Za příkazem return musí následovat výraz představující l-hodnotu vráceného typu, jež bude následovat také po návratu z funkce (tedy např. globální proměnnou, lokální statickou proměnnou či formální parametr předávaný odkazem). Příklad: const int N = 5; int Pole[5] = {1, 2, 3, 4, 5}; int& PrvekPole(int idx) { if(idx < 0 || idx >= N) cout << “CHYBA!”; else return Pole[idx]; } PrvekPole(4) = 20; PrvekPole(5) = PrvekPole(4)*10;
Inline (vložené) funkce V programovacím jazyce C++ lze pomocí specifikátoru inline deklarovat tzv. vloženou funkci. Toho se využívá převážně u funkcí s malým tělem, např.: inline int DruhaMocnina(int x) { return x*x; }
A to hlavně z toho důvodu, že proces volání funkce, předávání parametrů, skoku do funkce a návrat z ní zabere více procesorového času než samotné vykonání těla funkce. Překladač potom na místo volání funkce (např. DruhaMocnina(5)) vloží samotné tělo funkce (např. 5*5). Specifikátor inline však je pro překladač pouze informativního charakteru, není pro něj nikterak závazný. Překladač může takto specifikovanou funkci přeložit také jako „obyčejnou“ a to zejména v těchto případech:
v programu je použit ukazatel na tuto funkci, funkce je rekurzivní, jsou v ní použity příkazy, které překladač vyhodnotí jako problémové.
Jazyk C++ 1
3
Přetěžování funkcí Přetěžování funkcí bývá někdy též označováno jako polymorfismus funkcí a umožňuje požívat více funkcí sdílejících jedno jméno. Prototypy těchto funkcí se vzájemně liší počtem nebo typem předávaných parametrů. Lze tedy např. deklarovat: void f(); int f(int); double f(double); void f(int, double);
//#1 //#2 //#3 //#4
Při volání potom překladač vybere tu funkci, jejíž formální parametry nejvíce odpovídají skutečným parametrům v zápisu funkce. Jestliže nastane situace, kdy skutečné parametry neodpovídají přesně formálním parametrům, snaží se překladač vybrat tu, pro kterou je nejsnazší jednoznačná konverze. Příklad: f(); // volá se #1 f(100); // volá se #2 f(100, 8.8); //volá se #4 f(‘a’); //volá se #2 f(100, 200); //volá se #4 f(0L); // Chyba, složitost převodu long na double nebo // int je stejná, není jednoznačná.
Nedostatečné rozdíly k rozlišení přetížených funkcí jsou: typ návratové hodnoty, parametr předávaný hodnotou vs. parametr předávaný odkazem, rozdíl mezi typem T a const T. Dostatečnými rozdíly ještě jsou typy const T&.
T* a const
T* a typy T& a
Výčtové typy Syntaxe: enum jmenovkanep { seznam enumerátorů } seznam_deklarátorůnep; Ke každému identifgikátoru enumerátoru lze přiřadit libovolný celočíselný konstantní výraz. Příklad: enum barvy { cerna = 10, bila = -33, modra = 2*cerna, cervena, ruzova = cervena + bila };
Zatímco v programovacím jazyce C se musí na výčtový typ odvolávat celým zápisem, tedy: enum barvy b = cerna;, v jazyce C++ postačí jmenovka: Jazyk C++ 1
4
barvy b = cerna;. V jazyce C by šlo klíčové slovo enum vynechat v případě, že by se výčtový typ deklaroval pomocí klíčového slova typedef. Zrovna tak by šlo v C++ použít taktéž úplný zápis nebo deklaraci pomocí typedef enum, ale je to zbytečné. Zatímco v jazyce C můžeme do proměnné výčtového typu přiřadit přímo celočíselnou hodnotu, v jazyce C++ tuto musíme přetypovat na příslušný výčtový typ: b = 10; //korektní zápis pouze v C, ne v C++ b = (barvy)10; //Korektní zápis jak v C, tak i v C++
Struktury a unie V programovacím jazyce C++ se řadí struktury i unie mezi objektové typy. V této kapitole budou však prozatím popsány z neobjektového pohledu.
Struktura Syntaxe: struct jmenovkanep {deklarace_složek} seznam_deklarátorůnep; Příklad: struct Osoba { char Jmeno[20]; int OsobniCislo; };
Zatímco v programovacím jazyce C se musí na strukturu odvolávat celým zápisem, tedy: struct Osoba clovek;, v jazyce C++ postačí jmenovka: Osoba clovek;.
Unie V programovacím jazyce C++ lze deklarovat anonymní unie, které nejsou v jazyce C k dispozici. Anonymní unie nemá ani deklaraci proměnných ani jmenovku. Ke složce anonymní unie se pak přistupuje pomocí samotného identifikátoru složky. Globální anonymní unie musí být statická; Příklad: int main() { union { int i; unsigned char c[4]; }; i = 0x11223344; cout << i << endl; for (int j = 0; j < 4; ++j) Jazyk C++ 1
5
cout << (unsigned) c[j] << ' '; system("pause"); return 0; }
Pole c bude obsahovat tyto hodnoty: c[0] c[1] c[2] c[3]
= = = =
0x44 0x33 0x22 0x11
Zarovnání dat v paměti Struktury (i třídy) a veškeré jejich složky jsou v operační paměti zarovnány podle nastavení překladače. Složka struktury začíná ve struktuře na x. bytu. X je spočítáno podle vztahu x = y + z, kde y je byte, na kterém končí předchozí složka struktury a z představuje nejmenší možné číslo takové, aby platil vztah x mod s = 0; s = min{st,da}. Ve výše uvedeném vztahu potom mod představuje zbytek po celočíselném dělení, st je velikost datového typu složky a da je zarovnání dat dle nastavení překladače. Pro výpočet x lze použít vzorec x = int(y/s)*s + w, kde w = 0 pokud y mod s = 0, nebo w = s, pokud y mod s > 0. int(x) představuje celou část čísla x. Celá struktura se tak zarovná na a bytů, kde a je definováno jako a = b + c. V tomto vztahu b představuje byte, na kterém končí poslední složka struktury a c je nejmenší číslo takové, aby platl vztah: a mod min{max{st1, st2, …stn}, da} = 0. Přičemž st1, st2, … je velikost datových typů složek struktury. Příklad: struct Struktura { char c; bool b1; __int64 i1; bool b2; __int16 i2; bool b3; };
K výše uvedenému příkladu znázorňuje následující tabulka přehled počátečních pozic jednotlivých složek struktury a celkovou velikost struktury pro jednotlivé typy zarovnání dat v paměti.
Jazyk C++ 1
6
Tabulka 1 - přehled pozic zarovnání dat v paměti
Byte, na kterém začíná složka pro da složka
b1 i1 b2 i2 b3 Velikost struktury
1
2
4
1
1
1
2
2
4
10
10
12
11
12
14
13
14
16
14
16
20
8 1 8 16 18 20 24
Bitová pole Na rozdíl od jazyka C, kde mohou být složky bitového pole pouze typu int nebo unsigned, mohou v jazyce C++ složky bitového pole být libovolného celočíselného typu, tedy včetně bool či char. Paměťová reprezentace bitových polí závisí na konkrétní implementaci jazyka C++. V implementaci C++ Builder je paměťová reprezentace následující. Složky bitového pole jsou rozděleny po skupinách. Skupina je vždy tvořena po sobě jdoucími složkami stejného datového typu bez ohledu na znaménko. Každá skupina je pak zarovnána podle typu dané skupiny a aktuálního nastavení zarovnání dat v paměti. Celková velikost bitového pole je nakonec zarovnána podle aktuálního nastavení zarovnání dat v paměti. Příklad: struct BitovePole { int s1 : 8; unsigned s2 : 16; unsigned long s3 : 8; long s4 : 16; long s5 : 16; char s6 : 4; };
Ve výše uvedeném příkladu jsou složky bitového pole rozděleny do tří skupin. První skupinu tvoří složky s1 a s2 a zabírají celkem 24 bitů. V případě zarovnání dat na 1 byte se za tuto skupinu nevloží žádný prázdný bit. V případě zarovnání na 4 byty se za tuto skupinu vloží 8 bitů. Jazyk C++ 1
7
Druhá skupina je tvořena složkami s3, s4 a s5. Dohromady tyto složky zabírají 40 bitů. Typ long ovšem zabírá 32 bitů, což vede k tomu, že překladač musí skupinu rozdělit na 2 podskupiny (1. podskupina je složena z s3 a s4 tj. má 24 bitů a 2. podskupina je s5 o velikosti 16 bitů). V případě zarovnání dat na 1 byte se před druhou podskupinu nevloží žádný bit, je-li však zarovnání na 4 byty, před druhou podskupinu se vloží 8 bitů. Třetí skupina je tvořena složkou s6 typu char, který se vždy zarovnává na byte. Nevloží se tedy před tuto složku žádný prázdný bit. Je-li zarovnání dat na 1 byte, vloží se za složku s6 4 prázdné byty. Je-li zarovnání dat na 4 byty, vloží se za s6 12 bitů. Celková velikost bitového pole bude buď 9, nebo na 12 bytů, podle způsobu zarovnání dat na 1 resp. 4 byty.
Ukazatele na funkce Ukazatel na funkci lze vytvořit příkazem: ukazatel = &nep identifikátor_fce Příklad: double (*f) (double); //do takto deklarovaného ukazatele lze přiřadit //funkci sin dvěma způsoby f = sin; f &= sin;
Rozlišovací operátor Rozlišovací operátor (angl. scope resolution operator) může být jak unární, tak binární.
Unární rozlišovací operátor umožňuje přístup k zastíněným globálním identifikátorům. Binární rozlišovací operátor vyznačuje, že identifikátor náleží do daného oboru (jméno objektového typu nebo jmenný prostor).
Syntaxe: ::identifikátor obor::identifikátor Příklad: int i = 100; int main(){ Jazyk C++ 1
8
int i = 0; cout << “lokalni i: ” << i << endl; cout << “globalni” << ::I <<endl; }
Operátory přetypování V jazyce C++ se mimo operátoru přetypování (typ) objevují ještě nové operátory sloužící k přetypování, jsou to:
const_cast, static_cast, dynamic_cast, reinterpret_cast.
Operátor (typ) V C++ má dva způsoby zápisu. V jazyce C lze použít pouze první způsob. Syntaktický zápis: (typ)výraz_cast název_typu(seznam_výrazůnep) typ – identifikátor typu deklarovaný pomocí typedef, nebo základní konstrukce (např. unsigned int, unsigned int*, …) název_typu – jednoslovný název základního datového či výčtového typu, typu deklarovaného pomocí typedef, třídy, struktury, unie. seznam_výrazů – jeden či více výrazů (v takovém případě se volá konstruktor objektového typu se jménem název_typu). Příklad: double x = 2.5; int y = (int)x*10; //y bude mít hodnotu 20 int z = int(x*10); //z bude mít hodnotu 25 void* v = &y; // korektní int* uy = (int*)v; //korektní int* uz = int*(v); //chyba, protože int* //není základní název typu
Jazyk C++ 1
9
Operátory const_cast, dynamic_cast, static_cast a reinterpret_cast Jedná se o operátory rozdělující si funkce operátoru přetypování (typ). Nyní bude uvedena jejich stručná charakteristika, podrobněji se jim věnuje některý z dalších bloků. Syntaxe je společná pro všechny operátory: název_operátoru(výraz) název_operátoru – jeden z operátorů const_cast, dynamic_cast, static_cast nebo reinterpret_cast. označení_typu – představuje typ, na který potřebujeme přetypovat. výraz – výraz, jehož výsledný typ potřebujeme přetypovat.
Přehled const_cast – přetypovává výraz přidáním/odebráním modifikátoru const nebo volatile (nestálá instance). Příklad: const int i = 500; int* ui; ui = &i; // Chyba ui = const_cast(&i); // OK
dynamic_cast – slouží k přetypování referencí a ukazatelů na objektové typy v rámci dědické hierarchie. Kontroluje se, zda má smysl. static_cast – běžné přetypování (např. mezi typy celých a reálných čísel), nekontroluje se, zda má smysl. reinterpret_cast – k nestandardnímu přetypování, např. celé číslo na ukazatel.
Operátor sizeof Na rozdíl od jazyka C, má tento operátor v jazyce C++ dva způsoby zápisu. V jazyce C je možný pouze první způsob zápisu. sizeof(označení_typu) sizeof unární_výraz Výsledkem je konstanta typu size_t. Při použití na výraz se tento nevyhodnotí, ale určuje se jen jeho výsledný typ, jehož velikost se vrací. Pokud Jazyk C++ 1
10
se operátor sizeof použije na referenci, vrací se velikost odkazovaného objektu. unární_výraz – např. proměnná, výraz s unárním operátorem, volání funkce. Každý výraz lze převést na unární za použití zápisu do závorek. Příklad: __int16 i=10; __int16& refi = i; size_t n; n = sizeof i; // n = 2 n = sizeof refi; // n = 2 n = sizeof &i; // n = 4 n = sizeof ++i; // n = 2, n = sizeof sin(1/2); // n n = sizeof (i*10); // n = n = sizeof (bool); // n = n = sizeof bool; // Error
i zůstane beze změny = 8, protože sizeof(double) 4, protože sizeof(int) 1
Dynamické proměnné Pro alokaci a uvolnění paměti lze použít v programovacím jazyce C++ stejně jako v programovacím jazyce C funkce malloc, calloc, realloc a free. Ovšem mnohem častěji se využívá operátorů new a delete.
New Alokuje prostor paměti pro proměnné. Způsoby syntakticky správného zápisu: new nothrownep označení_typu new_rozsahnep inicializátornep new umístěnínep označení_typu new_rozsahnep inicializátornep umístění – počáteční adresa alokace označení_typu – typ proměnné, která se má alokovat new_rozsah – počet prvků alok. pole inicializátor – v kulatých závorkách uvedená poč. hodnota proměnné Výsledkem je ukazatel na označení_typu. Příklad: int* a = new int; //*a nemá hodnotu int* b = new int(20); //*b má hodnotu 20 int* c = new int[10] //alokuje pole 10 prvků typu int, //vrací ukazatel na první prvek, //inicializaci prvků pole lze //provést až po alokaci //např. for cyklem Jazyk C++ 1
11
//alokace vícerozměrného pole typedef int tradek[10]; //dekl. typu řádek matice tradek* d = new int[5][10];//alokace matice o 5 řádcích //a 10 sloupcích, //inicializaci prvků opět //nutno provést až po alokaci
V případě neúspěchu operátoru new dojde k vyvolání výjimky typu bad_alloc.
Delete Uvolňuje paměť alokovanou proměnnými. Syntakticky korektní zápis: delete ukazatel delete [] ukazatel ukazatel – výraz, jež vrací adresu vrácenou operátorem new. Operátor delete sice uvolní alokovanou paměť, ale hodnotu ukazatele nemění. Operátor delete se nevolá pro proměnnou, která byla alokovaná s umístěním. Příklad: delete a; // dealokace jedoduché prom. a delete [] c; // dealokace pole c
Shrnutí studijního bloku Tento studijní blok seznámil studenta s neobjektovými rozdíly mezi programovacími jazyky C a C++. Zejména pak s rozdíly ve funkcích, operátorech a způsobu dynamického alokování a uvolňování paměti. Byly zavedeny termíny pro struktury a unie a s nimi související bitová pole, se kterým byl nastíněn způsob řazení dat v operační paměti za běhu programu.
Otázky k procvičení pasáže 1. 2. 3. 4. 5.
Jak mohou být předávány parametry funkcí? Co je to implicitní hodnota parametru funkce? Co jsou to inline funkce? Kdy a z jakého důvodu se inline funkce používají? Jakými operátory se dynamicky alokují a dealokují proměnné, jak lze dealokovat pole?
Jazyk C++ 1
12
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
13
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
Jazyk C++ 1 Blok 4 Objektové typy jazyka C++ (pokračování) Studijní cíl Ve čtvrtém bloku se pokračuje v probírání objektových typů jazyka C++. Po absolvování bloku bude student obeznámen se všemi základními způsoby vytváření instancí třídy v programovacím jazyce C++ pomocí různých typů konstruktorů.
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 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 (pokračování) Vnořené typy Programovací jazyk C++ umožňuje v těle jedné třídy deklarovat vnořený typ:
pomocí typedef, výčtový typ, objektový typ.
Na takto vytvořené vnořené typy se vztahují specifikátory přístupových práv. Pokud je vnořený typ užíván mimo obklopující třídu, je nutné jej klasifikovat buď názvem obklopující třídy a rozlišovacím operátorem nebo případně identifikátorem instance obklopující třídy a jedním z kvalifikačních operátorů. Jazyk C++ 1
1
Stejným způsobem je nutné identifikovat také výčtové konstanty vnořených výčtových typů. Příklad: class TOsoba { public: enum { MaxDelka = 20 }; private: char Jmeno[MaxDelka]; // zde se MaxDelka // nemusí kvalifikovat //... }; //mimo třídu je potřeba kvalifikovat char Pole[TOsoba::MaxDelka];
Metody vnořeného objektového typu nemají vzhledem ke složkám obklopující třídy žádné výjimky z pravidel o přístupnosti. A ani opačně nemá obklopující třída žádná privilegia při přístupu ke složkám vnitřní třídy. Pokud má mít třída přístupné chráněné a soukromé složky vnořené třídy, musela by být deklarována jako její přítel. Příklad: class TDList { public: class TUzel { // deklarace vno!eného typu TData Data; TUzel *Dalsi, *Predch; public: void SetData(TData& _Data); // ... }; private: TUzel *Prvni, *Posledni; public: // ... }; void TDList::TUzel::SetData(TData& _Data) { Data = _Data; }
V obklopující třídě lze vnořený typ deklarovat pouze jejím klíčem (class, struct, union) a jmenovkou. Definovat vnořený typ pak lze i mimo tělo obklopující třídy. V takovém případě je nutné jej potom kvalifikovat jmenovkou obklopující třídy. Příklad: class TDList { Jazyk C++ 1
2
public: class TUzel; // informativní deklarace vnořené třídy private: TUzel *Prvni, *Posledni; public: // ... }; class TDList::TUzel { // definiční deklarace vnořené // třídy TData Data; TUzel *Dalsi, *Predch; public: void SetData(TData& _Data); // ... }; void TDList::TUzel::SetData(TData& _Data) { Data = _Data; }
Inicializace bez konstruktoru Stejně jako v programovacím jazyce C, lze i v programovacím jazyce C++ inicializovat instance třídy stejně jako složky struktury a unie pomocí kulatých závorek. A to v případě, že tento objektový typ:
nemá konstruktor, všechny jeho atributy jsou veřejné a nekonstantní, ani jeden z jeho nestatických atributů není reference, nemá předka, nemá virtuální metodu.
Pomocí vnitřních složených závorek se deklarují atributy objektových typů a pole hodnot deklarované v rámci třídy. Příklad: class TA { public: int x[2]; int y; struct TB { int i, j; } b; } A = { { 1, 2 }, 3, { 4, 5 } }; // A.x[0] = 1, A.x[1] = 2, A.y = 3, A.b.i = 4, A.b.j = 5 // nemusí se inicializovat všechny hodnoty, v takovém // případě se zbytek inicializuje nulou. TA AA = { { 1, 2 }, 3 }; // AA.x[0] = 1, AA.x[1] = 2, AA.y = 3, AA.b.i = 0, // AA.b.j = 0 TA AAA = { }; // AAA.x[0] = 0, AAA.x[1] = 0, AAA.y = 0, // AAA.b.i = 0, A.b.j = 0 Jazyk C++ 1
3
Konstruktory Konstruktory třídy jsou metody (mající jistá specifika), které se volají při vytváření instance třídy, a to v rámci definiční deklarace, alokace pomocí operátoru new, při předávání parametrů objektových typů hodnotou, … Specifika konstruktorů:
Jmenovka konstruktoru je dána názvem třídy. Deklarace konstruktoru neobsahuje návratový typ (ani void). Nelze jej dědit. Konstruktor potomka používá ke své konstrukci konstruktory předků. Nejsou virtuální, statické, nestálé nebo konstantní. Mohou být volány pro instance deklarované s cv-modifikátory.
Deklarace konstruktoru Deklaraci konstruktoru lze zapsat následujícími způsoby, prototypem konstruktoru a definiční deklarací konstruktoru: modifikátorynep jméno_třídynep::nepjmenovka (spec_form_parnep); modifikátorynep jméno_třídynep::nepjmenovka (spec_form_parnep) inicializační_částnep tělo
jméno_třídy je název objektového typu, pro nějž se konstruktor deklaruje. Jméno třídy a rozlišovací operátor lze vynechat, pokud je deklarace součástí těla třídy. spec_form_par – specifikace formálních parametrů konstruktoru třídy. Vynechají-li se formální parametry, jedná se o bezparametrický konstruktor. Parametry konstruktoru mohou být libovolného typu kromě typu právě deklarované třídy (mimo reference či ukazatele na deklarovanou třídu). Příklad: class TA { public: TA(); // prototyp konstruktoru // ... }; TA::TA() // definice konstruktoru { //... } class TB { public: TB(int x) { /* ... */ } // vložený konsturktor // ... }; Jazyk C++ 1
4
Inicializační část konstruktoru Zapisuje se za dvojtečku mezi hlavičku a tělo konstruktoru. Slouží k nastavení hodnot nestatických atributů instancí a k předání parametrů konstruktorům předků. Korektní syntaktický zápis: :seznam_inicializací seznam_inicializací se zapisuje ve tvaru identifikátor (seznam_výrazůnep), kde identifikátor je název atributu nebo předka a seznam_výrazů představuje:
pro atribut neobjektového typu – výraz, jehož hodnota se přiřadí jako výchozí tomuto atributu, pro atribut objektového typu – seznam parametrů konstruktoru tohoto objektového typu, pro předka – seznam parametrů konstruktoru tohoto předka.
Atributy neobjektového typu, které nejsou uvedeny v inicializační části konstruktoru, jsou v případě globálních instancí inicializovány nulovou hodnotou, v případě lokálních nebo dynamických instancí nemají definovanou hodnotu. Atributy objektového typu jsou potom v případě, že nejsou uvedeny v inicializační části konstruktoru, inicializovány konstruktorem bez parametru. Výhodou je rychlost inicializace. Inicializační část se totiž provede ještě před vstupem do těla konstruktoru. Příklad: class TA { int x, y; public: TA(int _x, int _y) : x(_x), y(_y) {} }; class TB { int z; TA A; public: TB(int i, int j, int k); }; TB::TB(int i, int j, int k) : A(i, j) { z = k; } //Atribut A je inicializován voláním konstruktoru TA //s parametry i, j;
Jazyk C++ 1
5
Atributy deklarované třídy jsou inicializovány v pořadí, v jakém jsou deklarovány, ne v pořadí, v jakém jsou inicializovány. V inicializační části musí být uvedeny konstantní a referenční atributy. Konstantní atributy již později nelze měnit. Inicializační část konstruktoru nelze využít k inicializaci pole. Příklad: //V inicializační části musí být uveden atribut ri a y. //Nemůže v ní být uveden atribut z. class TC { int x; const int y; int& ri; static int z; public: TC(int& i) : ri(i), x(0), y(10) {} }; //Atributy jsou inicializovány v pořadí podle deklarace, //tedy x, y a ri
Definiční deklarace instance Pokud se definuje nová instance třídy, vždy to znamená volání konstruktoru. Jeho parametry se obdobně jako u „klasické“ funkce zapisují do kulatých závorek za jeho jmenovku. V případě, že se volá bezparametrický konstruktor, kulaté závorky se vynechávají. Příklad: class TA { int x, y; public: TA() : x(0), y(0) {} // konstruktor #1 // konstruktor #2 TA(int _x, int _y = 10) : x(_x), y(_y) {} }; //Definice instancí TA A1(10, 20); // volá se #2, x = 10, y = 20 TA A2(30); // volá se #2, x = 30, y = 10 TA A3; // volá se #1, x = 0, y = 0 TA A4(); // prototyp FUNKCE BEZ PARAMETRŮ, // která vrací typ TA // NEJEDNÁ se o definici instance A4 //Instanci je možné definovat též zápisem: TA A5 = TA(10, 20); // dtto TA A5(10, 20); TA A6 = TA(); // dtto TA A6;
Jazyk C++ 1
6
Implicitní konstruktor Angl. default constructor. Jedná se konstruktor, který může být volán bez parametrů. Pokud má parametry, ty musí mít předepsány implicitní hodnoty. Příklad: class TA { int x, y; // implicitní konstruktor TA(int _x = 0, int _y = 0) : x(_x), y(_y){} };
Implicitně deklarovaný implicitní konstruktor (angl. implicitly-declared default constructor) představuje konstruktor, který je pro danou třídu překladačem vytvořen automaticky. Tato třída nemá žádný uživatelem (explicitně) deklarovaný konstruktor. Implicitně deklarovaný implicitní konstruktor lze považovat za implicitně definovaný implicitní konstruktor (angl. implicitly-defined default constructor), pokud je použit k vytvoření instance třídy. Tento konstruktor má prázdné tělo a neobsahuje inicializační část. Tedy konstruktor, který kdyby byl pro třídu TA napsán uživatelem, by vypadal takto: TA() {}. V případě, že třída obsahuje alespoň jeden uživatelem definovaný konstruktor, překladač nevytváří žádný implicitní konstruktor. Příklad: class TA { int x, y; public: TA(int _x, int _y = 10) : x(_x), y(_y) {} }; class TB { TA A1, A2; public: TB(int i, int j) : A1(i, j) {} // Chyba //jelikož třída TA nemá implicitní konstruktor, //je třeba inicializovat v inicializační části i A2 };
Pole instancí Pokud se definuje pole instancí, překladač volá konstruktory jednotlivých prvků v pořadí, v jakém jsou uloženy v paměti.
Jazyk C++ 1
7
Příklad: class TA { int x, y; public: TA() : x(0), y(0) {} // konstruktor #1 // konstruktor #2 TA(int _x, int _y = 10) : x(_x), y(_y) {} }; TA A[10]; //pro prvky A[0] až A[9] se volá //implicitní konstruktor #1 TA B[10] = { TA(10, 20), TA() }; //pro prvek B[0] se //volá konstruktor TA(10, 20) //pro ostatní prvky se volá //TA().
Dynamické instance Dynamické instance se vytváří obdobně jako dynamické proměnné pomocí operátoru new. Syntakticky správný zápis je potom: ::nep new umístěnínep jméno_třídy inicializátornep inicializátor představuje seznam parametrů, který udává skutečné parametry konstruktoru. Rozlišovací operátor před klíčovým slovem new se používá v případě přetíženého operátoru new. Příklad: navazuje na předchozí příklad TA* A1 = new TA(10, 20); // volá se konstruktor #2 TA* A2 = new TA(); // volá se konstruktor #1 TA* A3 = new TA; // volá se konstruktor #1 TA* A = new TA[10]; // alokuje se 10 prvků typu TA, // pro každý prvek se volá // konstruktor #1
Kopírovací konstruktor Jedná se konstruktor, který lze volat s jedním parametrem typu reference (může být deklarován s cv-modifikátory const a valtile) na danou třídu. Např.: TA::TA(TA& a); TA::TA(TA& a, int i = 10);
Kopírovací konstruktor je překladačem volán, pokud se k inicializaci instance používá jiná instance téže třídy.
Jazyk C++ 1
8
Příklad: //A2 je instance třídy TA TA A1 = A2; //předávání parametru (objektového typu) hodnotou void funkce(TA A); TA A3; funkce(A3); //funkce vrací instanci objektového typu TA f();
Implicitně deklarovaný kopírovací konstruktor je vytvořen překladačem automaticky, pokud neexistuje kopírovací konstruktor vytvořený uživatelem. Tento konstruktor je public a inline. Pro třídu TA by mohl vypadat takto: TA::TA(const TA&) nebo TA::TA(TA&). Implicitně deklarovaný kopírovací konstruktor lze považovat za implicitně definovaný kopírovací konstruktor, pokud je použit k vytvoření instance třídy. Tento konstruktor kopíruje všechny nestatické atributy třídy. Pokud se jedná o atribut objektového typu, je volán kopírovací konstruktor. Uživatelem definovaný kopírovací konstruktor je potřeba například ve chvíli, kdy je součástí třídy dynamicky alokovaný atribut. Příklad: class TIntArr { int *a, n; public: TIntArr(int _n) : n(_n) { a = new int[n]; } }; TIntArr A(10); TIntArr B = A;
Jelikož ve výše uvedeném příkladu není explicitně definovaný kopírovací konstruktor, bude pro inicializaci instance B využit implicitně definovaný kopírovací konstruktor a dojde tedy pouze ke zkopírování atributů a a n. Výsledkem bude fakt, že atribut B.a bude ukazovat na stejné pole jako atribut A.a. Tento nežádoucí stav lze vyřešit definováním uživatelského kopírovacího konstruktoru: class TIntArr { int *a, n; public: TIntArr(int _n) : n(_n) { a = new int[n]; } TIntArr(const TIntArr& t); };
Jazyk C++ 1
9
//kopírovací konstruktor alokuje nové pole a překopíruje // do něho hodnoty z pole t.a TIntArr::TIntArr(const TIntArr& t) { n = t.n; a = new int[n]; for (int i = 0; i < n; ++i) a[i] = t.a[i]; }
Konverzní konstruktor Konverzním konstruktorem je konstruktor, který je deklarován bez modifikátoru explicit, lze jej volat s jedním parametrem a překladač jej dokáže využít k implicitním nebo explicitním konverzím typu prvního parametru na typ dané třídy. Takovým konstruktorem je například kopírovací konstruktor. Příklad: class TA { int x, y; public: TA(int _x, int _y = 0) : x(_x), y(_y) {} // ... }; void f(TA A); TA A = 10; // A.x = 10, A.y = 0 TA A2 = static_cast(10.5); // A2.x = 10, A2.y = 0 TA A3 = 10.5; // #1 //A, A2 a A3 jsou definovány implicitní konverzí typu //int (double) na typ TA. TA A4 = (TA)10; // explicitní konverze z celočís. typu TA A5 = static_cast(10); // explicitní konverze TA A6 = TA(10); // přímá definice instance bez konverze f(10); //volá se pouze konverzní konstruktor, //ne kopírovací
Explicitní konstruktor Konstruktor, který je deklarován s modifikátorem explicit (pouze v deklaraci v těle třídy, v definici mimo třídu se modifikátor explicit neuvádí). Lze jej použít pro explicitní konverze. Zabraňuje se tím implicitním konverzím. Zamezením implicitní konverze se zabraňuje nechtěným chybám. Pokud by se v předchozím příkladu deklaroval konstruktor s modifikátorem explicit, oznámil by překladač v případě volání f(a) chybu, jinak by toto volání považoval za správné. class TA { // ... Jazyk C++ 1
10
explicit TA(int _x, int _y = 0) : x(_x), y(_y) {} // ... };
Nyní je potřeba pro volání f(a) parametr a přetypovat na TA: //možné způsoby přetypování f((TA)a); f(static_cast(a)); f(TA(a)); //volá se pouze konverzní, nikoli kopírovací konstruktor //příklady TA A = 10; // Chyba – implicitní konverze TA A4 = (TA)10; // OK - explicitní konverze TA A5 = static_cast(10); // OK – explicitní konverze TA A6 = TA(10); // OK – přímá definice instance bez //konverze
Nepojmenovaná instance Instance beze jména. Používá se například v příkazech return zápisem konstruktoru. Příklad: class TA { int x, y; public: TA() {} TA(int _x, int _y) : x(_x), y(_y) {} friend TA Pricti(TA A, int n); // ... }; TA Pricti(TA t, int n) { return TA(t.x+n, t.y+n); // vytvoření nepojmenované // instance } TA A; A = Pricti(TA(10,20), 5); // vytvoření nepojmenované // instance v 1. parametru
Shrnutí studijního bloku Tento studijní blok seznámil studenta s vnořenými objektovými typy programovacího jazyka C++. Dále představil základní způsoby vytváření instancí a polí instancí pomocí konstruktorů objektových typů.
Otázky k procvičení pasáže 1. Jaké vnořené typy lze deklarovat v těle třídy? 2. Lze inicializovat instance objektového typu bez použití konstruktoru? 3. Objasněte výhody inicializační části konstruktoru. Jazyk C++ 1
11
4. Kdy překladač vytváří implicitní konstruktor? 5. Jaké jsou parametry implicitního konstruktoru?
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
12
Jazyk C++ 1 Blok 5 Objektové typy jazyka C++ (pokračování) a odvozené třídy Studijní cíl V pátém bloku se pokračuje v probírání objektových typů jazyka C++ a otevírá se kapitola odvozených třídy. Po absolvování bloku bude student obeznámen se všemi základními způsoby rušení instancí třídy v programovacím jazyce C++ pomocí různých typů destruktorů a získá přehled o dědičnosti v C++ programech.
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 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, …).
Destruktory Destruktor je metoda, která se implicitně volá v následujících případech:
zánik lokální nestatické instance, při volání operátoru delete pro zánik dynamické instance, při ukončení programu (funkce main).
Aby byla metoda považována za destruktor, musí splňovat:
Jméno metody je shodné se jménem objektového typu, jemuž předchází tilda (~).
Jazyk C++ 1
1
Je to bezparametrická metoda. Deklarace metody neobsahuje žádný návratový typ (ani void). Nedědí se, ovšem instance odvozené třídy volá ke svému zrušení destruktor předka. Nejsou deklarovány jako static, const ani volatile. Nelze zjistit jejich adresu v operační paměti.
Destruktor lze zavolat ve funkci f() explicitně, pokud je destruktor veřejný, nebo pokud je f() přítelem třídy. Syntakticky korektní způsoby zápisu deklarace destruktoru pro prototyp a definiční deklaraci: modifikátorynep jméno_třídynep::nep~jmenovka(); modifikátorynep jméno_třídynep::nep~jmenovka() tělo Kde modifikátory mohou být virtual, inline, virtual inline nebo inline virtual. Příklad: class TA { // ... public: ~TA(); // prototyp destruktoru }; TA::~TA() // definice destruktoru { // ... }
Pokud v dané třídě neexistuje uživatelem definovaný destruktor, překladač automaticky vytváří implicitně deklarovaný destruktor, který je inline a public. Implicitně deklarovaný destruktor lze považovat za implicitně definovaný destruktor v případě, že je použit ke zrušení nějaké instance třídy. Implicitně definovaný destruktor má prázdné tělo. Lze jej volat i explicitně z jiné metody, i když prakticky se tato možnost téměř nevyužívá. Explicitní volání destruktoru pro instanci A třídy TA by pak vypadalo: A.~TA(); Po provedení těla destruktoru se volají destruktory předků. Pokud dojde k ukončení programu voláním funkce exit(), nevolají se destruktory lokálních nestatických instancí.
Jazyk C++ 1
2
Jestliže dojde k ukončení programu voláním funkce abort(), nezavolají se žádné destruktor. Zánikem ukazatele v případě dynamických instancí nedochází k volání destruktoru instancí, musí se volat operátor delete nebo delete []. Jestliže je potřeba volat destruktory pro prvky pole, je potřeba je volat v opačném pořadí oproti pořadí jejich konstrukce. Příklad: class TA { char* s; int n; public: TA(int _n) : n(_n) { s = new char[n]; } ~TA() { delete[] s; } // ... }; void main() { TA* A1 = new TA(5); TA A2(5); // ... delete A1; // volá se destruktor třídy TA // pro instanci A1 } // po ukončení main se volá destruktor pro instanci A2
Odvozené třídy V programovacím jazyce C++ je umožněno používat takzvanou vícenásobnou dědičnost. To znamená, že jedna třída může mít několik bázových tříd (předků). Syntakticky správné zápisy deklarace objektového typu s předky: klíč jmenovka : seznam_předků { tělo_objektového_typu } seznam deklarátorůnep;
Kde klíč je class nebo struct. Seznam_předků je vždy složen nepovinně ze specifikátoru předka a jeho jmenovky. Specifikátor předka je pak tvořen nepovinně specifikací přístupových práv (public, protected, private) a nepovinně specifikátorem virtual. Omezením pro seznam předků je potom zákaz opakovaného výskytu stejné jmenovky nebo výskyt jmenovky právě deklarované třídy. Pokud má odvozená třída jen nevirtuální předky, zdědí všechny jejich atributy, metody a typy kromě konstruktorů, destruktorů a přetížených kopírovacích operátorů přiřazení =. Jazyk C++ 1
3
Příklad: class TA { int x, y; public: void f(); // ... }; class TB { double a, b; public: void g(); // ... }; //následující třída TC má dva nevirtuální předky // TA a TB a obsahuje atributy x, y, a, b, z // a metody f(), g() a h() class TC : public TA, public TB { int z; public: void h(); // ... };
Přístupová práva ke složkám předků Pokud je před jmenovkou předka uveden specifikátor přístupových práv, určuje nejširší možná oprávnění. Konkrétně, bude-li uvedeno:
public – zděděné složky budou mít stejná přístupová práva jako mají v předkovi protected – veřejné složky předka budou zděděny jako chráněné, ostatní zůstanou stejné jako v předkovi private – všechny zděděné složky budou soukromé
Pokud před jmenovkou předka není specifikátor přístupových práv uveden, bere překladač výchozí přístupová práva, tedy pro objektový typ deklarovaný pomocí class to bude specifikátor přístupu private a pro objektový typ deklarovaný pomocí klíčového slova struct to bude specifikátor přístupu public. Způsob, jak obnovit přístupová práva: class TA { protected: int x, y; public: int GetX() const { return x; } enum TBarva { modra, cervena }; // ... }; Jazyk C++ 1
4
class TB : private TA { protected: TA::x; public: TA::GetX; TA::cervena; // ... };
Ovšem zpřístupňovat takto výčtové konstanty nemá moc smyls, protože je lze zpřístupnit přes jmenovku třídy a rozlišovací operátor. Druhým způsobem by bylo použít klíčového slova using. Ve třídě TB by pak byl atribut x soukromý, y chráněný a metoda GetX() veřejná. class TB : public TA { private: using TA::x; // ... };
Virtuální a nevirtuální dědění Následující kód je příkladem nevirtuálního dědění //deklarace tříd struct TA { int a; static int sa; enum { ea = 10 };}; struct TB : TA { int b; }; struct TC : TA { int c; }; struct TD : TB, TC { int d; };
Následující kód je příkladem virtuálního dědění // Pokud mají být složky třídy TA ve třídě TD uvedeny //jen jednou, musí se třída TA specifikovat jako //virtuální předek tříd TB a TC: struct TA { int a; static int sa; enum { ea = 10 };}; struct TB2 : virtual TA { int b; }; struct TC2 : virtual TA { int c; }; struct TD : TB2, TC2 { int d; };
Jazyk C++ 1
5
Shrnutí studijního bloku Tento studijní blok seznámil studenta s principem, tvorbou a použitím destruktorů objektových typů programovacího jazyka C++. Dále byly probrány odvozené třídy, přístupová práva ke složkám předků a způsoby virtuálního a nevirtuálního dědění.
Otázky k procvičení pasáže 1. Kdy se implicitně volá destruktor? 2. Jaká specifika musí splňovat metoda, aby byla považována za destruktor? 3. Jaké destruktory se volají při ukončení programu funkcí abort()? 4. Jakým způsobem lze zpřístupnit složky předka, pokud byla třída odvozena se specifikátorem přístupových práv private? 5. Jaké parametry může mít destruktor?
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
6
Jazyk C++ 1 Blok 6 Polymorfismus Studijní cíl Šestý blok se věnuje polymorfismu v programovacím jazyce C++. Po absolvování bloku bude student obeznámen s principy časné a pozdní vazby, abstraktními třídami a paměťovou reprezentací polymorfismu.
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 v jazyce C++ a znalost základů a pojmů z 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, …).
Polymorfismus Jelikož se s instancemi pracuje často pomocí ukazatelů, vznikají situace, kdy není známý přesný typ instance, na kterou ukazatel ukazuje a je potřebné vyvolat metodu skutečné instance.
Časná a pozdní vazba V následujícím příkladu pracuje program s polem ukazatelů na objekty síťové infrastruktury, kterými mohou být jednak body reprezentované třídou TBod a jednak úseky reprezentovány třídou TUsek. Bod i úsek mají společného předka, a sice třídu TObjektSite, na který ukazují prvky pole os. Prvky pole jsou typu TObjektSite* a obsahují ukazatele na objekty různých potomků třídy TObjektSite. Prvky pole tak mají jednak statický typ (TObjektSite*) a jednak dynamický typ (TBod* či TUsek*). Pro všechny prvky pole je příkazem OS[i]->Vypis() volána metoda Vypis() třídy TObjektSite. Ta ovšem nic nevypíše.
Jazyk C++ 1
1
V tomto případě překladač použil takzvanou časnou vazbu – vyšel ze statického typu ukazatele TObjektSite* a podle toho zavolal metodu TObjektSite::Vypis(). Příklad: class TObjektSite { public: enum TTyp { osBod, osUsek }; protected: TTyp Typ; public: TObjektSite(TTyp _Typ) : Typ(_Typ) {} void Vypis() {} }; class TBod : public TObjektSite { int Cislo; public: TBod(int _Cislo) : TObjektSite(osBod), Cislo(_Cislo) {} void Vypis() { cout << "Bod: " << Cislo << endl; } }; class TUsek : public TObjektSite { int BodCislo[2]; public: TUsek(int Cislo1, int Cislo2); void Vypis() { cout << "Usek: " << BodCislo[0] << ' ' << BodCislo[1] << endl; } }; TUsek::TUsek(int Cislo1, int Cislo2):TObjektSite(osUsek) { BodCislo[0] = Cislo1; BodCislo[1] = Cislo2; } int i; enum { PocOS = 3 }; TObjektSite* OS[PocOS]; OS[0] = new TBod(100); OS[1] = new TBod(200); OS[2] = new TUsek(100, 200); for (i = 0; i < PocOS; i++) OS[i]->Vypis(); //... for (i = 0; i < PocOS; i++) delete OS[i];
V tomto případě by však bylo vhodnější docílit toho, aby se volala metoda Vypis() z konkrétního potomka, tedy aby byla použita tzv. pozdní vazba. Pozdní vazba metody se použije, pokud je metoda deklarována jako virtuální (je deklarována se specifikátorem virtual). V definici mimo třídu se už specifikátor virtual vyskytovat nesmí.
Jazyk C++ 1
2
Pokud třída obsahuje nějakou virtuální metodu, říká se jí polymorfní třída. V potomkovi takové třídy bude ona metoda opět virtuální, ta se může (ale nemusí) deklarovat se specifikátorem virtual. Virtuální metoda, která je v potomkovi nově definována se nazývá předefinovaná virtuální metoda. Výše uvedený příklad by se tedy upravil tak, aby měla třída TObjektSite metodu Vypis() definovanou jako virtuální. class TObjektSite { public: enum TTyp { osBod, osUsek }; protected: TTyp Typ; public: TObjektSite(TTyp _Typ) : Typ(_Typ) {} virtual void Vypis() {} };
Přestože destruktory mají v potomkovi i předkovi jiné jméno, mohou být tyto destruktory virtuální. Virtuální však nemohou být konstruktory, spřátelené funkce ani statické metody. Jestliže se volá destruktor pro ukazatel na předka, který ve skutečnosti ukazuje na instanci potomka, měl by být destruktor předka deklarován s modifikátorem virtual. To z toho důvodu, aby se volal destruktor potomka a z něho destruktor předka. V návaznosti na předchozí příklad, kdyby třída TBod obsahovala dynamický řetězec znaků, bylo by potřeba jej dealokovat v destruktoru. class TBod : public TObjektSite { int Cislo; char *Jmeno; public: TBod(int _Cislo, const char* _Jmeno); ~TBod() { delete[] Jmeno; }; // ... };
Ovšem, aby byl destruktor ~TBod() volán při dealokaci ukazatele na TObjektSite, musí se destruktor TObjektSite deklarovat jako virtuální. class TObjektSite { // ... public: virtual ~TObjektSite() {} };
I v případě, že uživatel nedefinoval destruktor v potomkovi, měl by být v předkovi destruktor deklarován jako virtuální. A to z toho důvodu, aby se Jazyk C++ 1
3
volal alespoň implicitně definovaný destruktor potomka, který zařídí dealokaci atributů objektových typů, které jsou složkami potomka. V následujícím příkladu se po provedení příkazu #1 zavolá implicitně definovaný destruktor třídy TC, který zavolá destruktor pro atribut b, a pak bude zavolán destruktor předka (třídy TA). Pokud by však měla třída TA destruktor nevirtuální, destruktor atributu b by se nezavolal. Příklad: class TA { int x; public: TA(int _x) : x(_x) {} virtual ~TA() { cout << "destruktor TA\n"; } }; class TB { int y; public: TB(int _y) : y(_y) {} ~TB() { cout << "destruktor TB\n"; } }; class TC : public TA { TB b; public: TC(int _x, int _y) : TA(_x), b(_y) {} }; void f() { TC* C = new TC(10, 20); TA* A = C; delete A; //#1 }
Virtuální metody by měly být deklarovány jak v potomkovi, tak v předkovi se stejným identifikátorem, počtem a typem parametrů. Pokud se liší v návratovém typu, musí splňovat tyto podmínky:
V potomkovi i předkovi je navrácen ukazatel nebo reference na třídu. Objektový typ metody předka musí být přímým nebo nepřímým předkem objektového typu metody potomka. Tento předek je ve třídě potomka veřejně přístupný. V obou metodách mají ukazatele či reference v návratovém typu stejné cv-modifikátory, nebo v metodě předka cv-modifikátory uvedeny jsou a v metodě potomka nejsou.
Vzhledem k tomu, že je pozdní vazba uplatňována při volání metod prostřednictvím ukazatelů a referencí, uplatňuje se též pro parametry funkcí Jazyk C++ 1
4
předávané odkazem a pro metody volané v těle jiných metod přes ukazatel this. Virtuální metodu lze v potomkovi předefinovat, i když v něm není viditelná. V následujícím příkladu zastiňuje metoda TB::f(int) virtuální metodu TA::f(), přičemž f(int) není virtuální. Metoda TC:f() je shodná s TA::f(), tudíž je virtuální a předefinovává ji. A to i přes to, že metoda TA::f() není ve třídě TC viditelná. struct TA { virtual void f(); }; struct TB : TA { void f(int); }; struct TC : TB { void f(); };
Jazyk C++ umožňuje volání virtuální metody jiného předka prostřednictvím virtuálního předka: struct struct struct struct
TA TB TC TE
{ : : :
virtual void f(); }; virtual TA { virtual void f(); }; virtual TA { }; TB, TC { };
TE E; TC* C = &E; C->f(); // volá se TB::f()
Když se však volání virtuální metody kvalifikuje jmenovkou třídy a rozlišovacím operátorem, potlačuje se tím pozdní vazba: class TA { public: virtual void f(); }; class TB : public TA { public: void f(); }; void TB::f() { // ... TA::f(); // volá TA::f() a ne TB::f() }
Abstraktní třídy Třída, která může být použita pouze a jen jako předek jiné třídy a nelze od ní vytvářet instance, se označuje jako abstraktní třída. Podmínkou abstraktní třídy je výskyt alespoň jedné čistě virtuální metody. Čistě virtuální metoda se vyznačuje tím, že nemá definici a její prototyp je: virtual prototyp = 0; Kde prototyp je vlastní prototyp nevirtuální metody. V návaznosti na předchozí příklad můžeme říci, že třída TObjektSite má metodu Vypis(), která „nic nedělá“ a nemá tak smysl vytvářet její instanci.
Jazyk C++ 1
5
Třída TObjektSite by tak mohla být abstraktní a metoda Vypis() čistě abstraktní: class TObjektSite { public: enum TTyp { osBod, osUsek }; protected: TTyp Typ; public: TObjektSite(TTyp _Typ) : Typ(_Typ) {} virtual ~TObjektSite() {} virtual void Vypis() = 0; // čistá virtuální metoda };
Abstraktní třídou je i potomek jiné abstraktní třídy, který nedefinuje zděděnou čistě virtuální metodu. Příklad: class TObjektSite2 : public TObjektSite { // ... // neobsahuje definici metody Vypis() } TObjektSite2 os2; // Chyba - TObjektSite2 je abstraktní
Abstraktní metodou může být i potomek třídy, která není abstraktní, protože jeho čistá virtuální metoda může předefinovat virtuální metodu předka. Čisté virtuální metody nelze v abstraktní třídě volat z konstruktoru či destruktoru.
Paměťová reprezentace Každá třída, která obsahuje virtuální metodu má svou tabulku virtuálních metod (vytvořenou překladačem) – VMT (angl. virtual method table), ve které se uchovávají adresy virtuálních metod dané třídy. Zároveň překladač do každé instance této třídy vloží skrytý atribut (při vzniku instance, volání konstruktoru), který obsahuje adresu VMT. V návaznosti na předchozí příklad. Když dojde k volání OS[i]->Vypis(); tak se program nejprve přemístí na adresu VMT, kde vyhledá adresu virtuální metody Vypis() a tu zavolá. Příklad: class TA { int a, b; public: virtual void f(); virtual void g(); Jazyk C++ 1
6
}; TA A1, A2; class TB : public TA { int c; public: virtual void f(); virtual int h(); }; TB B1, B2;
A1
B1 &TA::VMT
A2
a
&TA::VMT
TA::VMT
b
a
&TB::VMT
b
B2
a
&TB::VMT
b
a
c
b
c
TB::VMT
&TA::f()
&TB::f()
&TA::g()
&TA::g() &TB::h()
Obrázek 1 - paměťová reprezentace polymorfních tříd
Poznámka: Velikost instancí polymorfních tříd je o 4 byty větší než velikost instancí nepolymorfních tříd.
Třídní ukazatele V programovacím jazyce C++ jsou k dispozici klasické ukazatele k práci s atributy. Příklad: class TA { public: int x; int f(); // ... }; TA A; int *ux; ux = &A.x; // OK
Ovšem tímto způsobem nelze pracovat s nestatickými metodami: int (*uf)(); Jazyk C++ 1
7
uf = &A.f; // Chyba uf = A.f; // Chyba
Vedle obyčejných ukazatelů proto existují v jazyce C++ také takzvané třídní ukazatele neboli ukazatele do třídy, díky nimž lze pracovat s adresami atributů a metod. Do třídního ukazatele lze přiřadit výsledek získaný operátorem &. Třídní ukazatel obsahuje relativní adresu určité složky vzhledem k začátku instance. Syntakticky korektní zápis deklarace třídního ukazatele je: deklarační_specifikátory jméno_třídy::*deklarátor; Následující příklad ukazuje deklaraci třídy TA, jejího třídního ukazatele ui na atribut typu int a jejího třídního ukazatele uf na bezparametrickou metodu vracející typ int. Deklaraci třídního ukazatele ui2 s inicializací na atribut x. A přiřazení hodnoty do třídních ukazatelů mimo deklaraci. class TA { public: int x, y; int f(); // ... }; TA A1, A2; TA *UA2 = &A2; //deklarace tř. ukazatelů na atribut a na metodu int TA::*ui; int (TA::*uf)(); //deklarace tř. ukazatele s inicializací int TA::*ui2 = &TA::x; ui = &TA::y; // operátor & se musí uvést uf = TA::f; // dtto uf = &TA::f;
Dereference třídního ukazatele se vztahuje přímo na konkrétní instanci. Syntakticky korektní zápis dereferenčních operátorů: instance.*třídní_ukazatel ukazatel na instanci->*třídní ukazatel Příklad: A1.*ui = 10; // dtto A1.y = 10 A2.*ui = 15; // dtto A2.y = 15 UA2->*ui = 20; // dtto UA2->y = 20 resp. A2.y = 20
Jazyk C++ 1
8
Třídní ukazatele nelze využít k práci se statickými složkami třídy.
Unie Unie nelze považovat za plnohodnotné objektové typy a kvůli následujícím omezením:
Neumí dědičnost. Pokud nejsou specifikována přístupová práva pro složky unie, jsou tyto veřejně přístupné. Neumí virtuální metody. Sic může mít konstruktor, destruktor a přetížený operátor =, její atribut nemůže být typu s konstruktorem, destruktorem či přetíženým operátorem =. V anonymní unii neexistují metody ani statické složky.
Shrnutí studijního bloku Tento studijní blok seznámil studenta s principy polymorfismu v programovacím jazyku C++, včetně principů včasné a pozdní vazby. Dále představil abstraktní třídy, reprezentaci polymorfních tříd v operační paměti, třídní ukazatele a unie.
Otázky k procvičení pasáže 1. 2. 3. 4. 5.
Kterou třídu lze považovat za abstraktní? Jak vypadá prototyp čistě virtuální metody? Jaký je rozdíl mezi včasnou a pozdní vazbou? Co je třídní ukazatel? Jaká jsou objektová omezení unie?
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
9
Jazyk C++ 1 Blok 7 Přetěžování operátorů Studijní cíl Sedmý blok otevírá velkou kapitolu věnovanou přetěžování operátorů v programovacím jazyce C++. Po absolvování bloku bude student obeznámen s možnostmi a principy přetěžování operátorů. Bude schopen rozhodnout, kdy přetěžování operátorů a v jaké podobě používat.
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 v jazyce C++ a znalost základů a pojmů z 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, …).
Základní pravidla V programovacím jazyce C++ je umožněno u většiny operátorů rozšířit jejich definici na objektové (či výčtové) typy. Nelze vytvářet operátory nové. A nelze ani změnit význam operátorů pro vestavěné typy. Nelze změnit ani prioritu a asociativitu operátorů. S výjimkou operátorů new a delete nelze změnit ani počet operandů. Z hlediska přetěžování se operátory dělí do čtyř skupin: 1. Operátory, které nelze přetěžovat operátor podmíněného výrazu ?:, rozlišovací operátor ::, operátor přímé kvalifikace . (tečka), dereferencování třídních ukazatelů .*, sizeof, typeid, dynamic_cast, static_cast, const_cast, reinterpret_cast. 2. Operátory, které lze přetěžovat pouze jako nestatické metody objektových typů.
Jazyk C++ 1
1
operátor indexování [], volání funkce (), přiřazení =, nepřímé kvalifikace ->, přetypování (typ) 3. Operátory, které lze přetypovat jako obyčejné funkce nebo jako statické metody třídy. operátory new a delete 4. Operátory, které lze přetěžovat jako nestatické metody třídy nebo jako obyčejné funkce, které mají alespoň jeden parametr objektového typu či výčtového typu. ostatní operátory Ačkoli lze přetížit operátor tak, že lze změnit jeho význam, který má u vestavěných typů (např. operátor * na součet), je snaha význam dramaticky neměnit, aby byl jeho význam odhadnutelný. Každý přetížený operátor se deklaruje jako operátorová funkce se syntaxí: operator symbol_operatoru, kde symbol_operatoru je jeden operátor z: new
^
-=
<<=
--
delete
&
*=
>>=
,
new []
|
/=
==
->*
delete []
~
%=
!=
->
!
^=
<=
()
=
&=
>=
[]
<
|=
&&
>
<<
||
+=
>>
++
+ * / %
Parametry operátorové funkce nemohou mít předepsané implicitní hodnoty. Unární operátor se definuje jako obyčejná funkce s jedním parametrem nebo jako metoda objektového typu bez parametru. Podobně binární operátor se definuje jako obyčejná funkce se dvěma parametry nebo jako metoda objektového typu s jedním parametrem. Přetížený operátor lze volat dvěma způsoby:
Jazyk C++ 1
2
1. jako původní operátor 2. jako operátorovou funkci s parametrem v kulatých závorkách Příklad volání přetíženého operátoru: TKomplexCislo z = a + b; TKomplexCislo z = a.operator +(b);
V případě existence více přetížených verzí jednoho operátoru, uplatňují se stejná pravidla jako u přetížených funkcí.
Skupina 4 – ostatní operátory Unární operátory Unární operátory ze čtvrté skupiny lze přetížit definováním:
obyčejné funkce s jedním parametrem výčtového nebo objektového typu, nestatické metody třídy bez parametrů (jediným operandem je aktuální instance třídy)
Pokud by byl @ unárním operátorem a A instancí třídy, mohl by být zápis @A resp. A@ interpretován jako A.operator @() resp. operator @(A). Příklad: Je dána třída TBitKalendar, která představuje bitovou mapu kalendáře. Prvek Kal[i] představuje dny i. týdne, které jsou bitově zakódovány. První bit představuje pondělí, druhý představuje úterý, … Pro třídu TBitKalendar je definován unární operátor ~, vracející doplněk kalendáře. class TBitKalendar { enum { PocTydnu = 52 }; uint8_t Kal[PocTydnu]; public: TBitKalendar() { memset(Kal, 0, sizeof Kal); } TBitKalendar operator ~() const; // doplněk – může //být konstantní neboť nemění //instanci, na které je volán }; TBitKalendar TBitKalendar::operator ~() const { TBitKalendar t; for (int i = 0; i < PocTydnu; i++) t.Kal[i]= ~Kal[i]; return t; } TBitKalendar A, B;
Jazyk C++ 1
3
//dva způsoby, jak uložit doplněk kalendáře A do kal. B B = ~A; B = A.operator ~();
Operátory inkrementace ++ a dekrementace – Tyto dva operátory se od ostatních unárních operátorů čtvrté skupiny liší v tom, že lze přetížit jeji prefixovou i postfixovou variantu. Prefixová varianta se přetěžuje standardně jako obyčejná funkce s jedním parametrem nebo jako metoda třídy bez parametru. Postfixová varianta operátorů inkrementace a dekrementace se definuje jako obyčejná funkce se dvěma parametry, nebo jako metoda třídy s jedním parametrem. Tento jeden parametr navíc je typu int, slouží právě pro účel rozlišení postfixové a prefixové varianty a nelze jej v definici využít. Prefixová varianta se pak volá zápisem operátoru před operand a postfixová varianta se zapisuje za operand. Příklad Je dán výčtový typ TDen obsahující výčtové konstanty odpovídající jednotlivým dnům v týdnu. Pro tento výčtový typ je definován prefixový a postfixový operátor, který posouvá aktuální den na následující. enum TDen { pondeli, utery, streda, ctvrtek, patek, sobota, nedele }; TDen operator ++ (TDen& Den) // prefixový operátor { int d = Den + 1; return Den = (d == nedele+1) ? pondeli : static_cast(d); } TDen operator ++ (TDen& Den, int) // postfixový operátor { TDen DenPuv = Den; int d = Den + 1; Den = (d == nedele+1) ? pondeli : static_cast(d); return DenPuv; } //po provedení těchto příkazů bude jak v d, tak i v d2 //hodnota ctvrtek TDen d = streda; TDen d2 = ++d; // prefixový operátor //po provedení těcho příkazů bude v d hodnota neděle a //v d2 hodnota pondělí d2 = nedele; d = d2++; // postfixový operátor Jazyk C++ 1
4
d = operator ++(d2, 0); // volání operátorové fce // dtto d = d2++ //druhým parametrem může být libovolné číslo typu int
Binární operátory Binární operátory ze čtvrté skupiny lze přetížit definováním:
obyčejné funkce se dvěma parametry výčtového nebo objektového typu, nestatické metody třídy s jedním parametrem výčtového nebo objektového typu
U obyčejné funkce je jako levý operand předán první parametr a jako pravý operand je předán druhý parametr. U metody je jako levý operand použita aktuální instance a jako pravý operand parametr metody. Pokud by byl @ binárním operátorem a A instancí třídy, mohl by být zápis A @ B interpretován jako A.operator @(B) resp. operator @(A, B). Přetížení binárních operátorů +, -, *, /, % neznamená automatické přetížení jejich složených přiřazovacích variant +=, -=, *=, /=, %=. Tyto se musí přetížit samostatně. Složené přiřazovací operátory lze přetížit jako funkci i jako metodu, ale obyčejný přiřazovací operátor = pouze jako metodu. V následujícím příkladu je dána třída TMatice s atributy m – počet řádků, n – počet sloupců, a – dynamické pole ukazatelů na dynamická pole. Třída dále obsahuje operátorovou funkci pro násobení matice reálným číslem. Dále je definována instance A třídy TMatice, která je inicializována hodnotou 3. class TMatice { int m, n; double **a; public: TMatice(int _n = 0, int _m = 0, double c = 0); TMatice(const TMatice& t); ~TMatice(); // násobení matice číslem TMatice operator* (const double c) const; // nemění své operandy, proto může být const } TMatice TMatice::operator* (const double c) const { int i, j; TMatice t(*this); for (i = 0; i < m; i++) for (j = 0; j < n; j++) { t.a[i][j] *= c; Jazyk C++ 1
5
} return t; } TMatice A(5, 2, 3); //dva způsoby použití operátoru násobení //v obou případech se volá kopírovací konstruktor matice //prvky matice B budou mít hodnotu 6 TMatice B = A*2; TMatice B = A.operator * (2); //následující příkaz je chybný, protože neznáme //operátorovou funkci, kde by byl levý operand číslo //a pravý matice TMatice B = 2*A; //chyba //museli bychom dodefinovat funkci! (třeba vloženou) inline TMatice operator* (const double c, const TMatice& t) { return t*c; }
Skupina 2 – operátory přetěžované jen jako metody Operátor přiřazení = Tento operátor nelze dědit. Jedná se o nestatickou metodu ve tvaru: operator = (parametr), kde parametr představuje parametr libovolného typu. Pokud je parametr typu TA, TA&, const TA&, volatile TA& nebo const volatile TA&, hovoříme o uživatelem deklarovaném kopírovacím operátoru přiřazení. Pokud takovýto uživatelem deklarovaný kopírovací operátor přiřazení neexistuje, překladač automaticky vytvoří implicitně deklarovaný kopírovací operátor přiřazení, který by pro třídu TA odpovídal tvaru TA& TA::operator = (const TA&); nebo TA& TA::operator = (TA&); Jestliže je implicitně deklarovaný kopírovací operátor přiřazení použit k přiřazení do jedné instance dané třídy z instance stejné nebo odvozené třídy, je implicitně definovaný. Překladač může vytvořit implicitně definovaný kopírovací operátor přiřazení pro třídu, která:
má nestatický konstantní atribut,
Jazyk C++ 1
6
má nestatický atribut reference, má nestatický atribut objektového typu bez veřejně přístupného kopírovacího operátoru přiřazení, má předka bez veřejně přístupného kopírovacího operátoru přiřazení.
V dané třídě může být definováno více přetížených operátorů přiřazení lišících se typem formálního parametru. Přetížený operátor přiřazení nemění význam složených přiřazovacích operátorů +=, -=, … Ty se musí přetížit samostatně. V návaznosti na výše uvedený příklad, pokud by se nepřetížil operátor přiřazení, výraz B = A; by pro instance třídy TMatice provedl zkopírování atributu a příkazem B.a = A.a; V důsledku čehož by B.a ukazovalo na stejnou matici jako A.a. Tomu lze předejít definováním operátoru přiřazení: class TMatice { // ... TMatice& operator = (const TMatice& t); }; TMatice& TMatice::operator = (const TMatice& t) { if (this != &t) { this->~TMatice(); m = t.m; n = t.n; a = new double*[m]; for (int i = 0; i < m; i++) { a[i] = new double[n]; memcpy(a[i], t.a[i], n*sizeof(double)); } } return *this; }
Operátor indexování [] Přetížený operátor indexování je v jazyku C++ nestatická metoda mající tvar: operator [] (parametr), kde parametr je parametr libovolného typu. Pokud je A instancí třídy, jsou zápisy A[i] a A.operator[](i) ekvivalentní.
Jazyk C++ 1
7
V následujícím příkladu je definována třída TIntArr s atributem dynamického pole celých čísel. Její součástí je také přetížený operátor indexování sloužící ke zpřístupnění prvku pole na zadaném indexu. class TIntArr { int *a, n; public: TIntArr(int _n) : n(_n) { a = new int[n]; } ~TIntArr() { delete[] a; } //operátor vrací referenci, aby mohl být použit //i na levé straně přiřazení //tím se změní data instance třídy //z toho důvodu nemůže být konstantní int& operator [] (int Index); }; int& TIntArr::operator [] (int Index) { if (Index < 0 || Index >= n) Chyba(); return a[Index]; } //použití TIntArr A(10); A[0] = 1; //=========================================== //konstantní operátor by vracel výsledek hodnotou a //nezpůsobil by změnu dat instance //lze jej však použít jen na pravé straně přiřazení class TIntArr2 { // ... int operator [] (int Index) const; }; int TIntArr2::operator [] (int Index) const { if (Index < 0 || Index >= n) Chyba(); return a[Index]; } //použití const TIntArr2 B(5); A[1] = B[0]; // OK B[0] = 2; // Chyba
Operátor volání funkce () Přetížený operátor indexování je v jazyku C++ nestatická metoda mající tvar: operator () (seznam_parametrůnep), kde seznam_parametrůnep je seznam parametrů libovolného typu.
Jazyk C++ 1
8
Pokud je A instancí třídy, jsou zápisy A(x, y) a A.operator()(x, y) ekvivalentní.
Shrnutí studijního bloku Tento studijní blok seznámil studenta s principy přetěžování operátorů v programovacím jazyku C++. Podrobně se pak věnuje unárním a binárním operátorům dvou hlavních skupin, ve kterých lze operátory přetěžovat.
Otázky k procvičení pasáže 1. 2. 3. 4. 5.
Jaký je význam operátoru indexování? Kdy nelze přetížit operátor jako konstantní? Jakým způsobem lze přetížit většinu unárních operátorů? Jakým způsobem lze přetížit většinu binárních operátorů? Lze zavést v jazyce C++ nové operátory?
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
9
Jazyk C++ 1 Blok 8 Přetěžování operátorů (pokračování) Studijní cíl Osmý blok rozšiřuje kapitolu věnovanou přetěžování operátorů v programovacím jazyce C++. Po absolvování bloku bude student obeznámen s kompletními možnostmi a principy přetěžování operátorů. Bude schopen rozhodnout, kdy přetěžování operátorů a v jaké podobě používat.
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 v jazyce C++ a znalost základů a pojmů z 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, …).
Skupina 2 – operátory přetěžované jen jako metody (pokračování) Operátor nepřímé kvalifikace -> V jazyku C++ je operátor nepřímé kvalifikace pro účely přetěžování považován za unární operátor. Jeho operátorová funkce je tedy nestatická metoda bez parametru ve tvaru operator -> (). Jestliže je A instance třídy, bude zápis A->x interpretován jako (A.operator ()) -> x. Operátor nepřímé kvalifikace -> tedy musí vracet ukazatel na třídu nebo instanci třídy, ve které je také přetížen. Je-li například dána třída TAutoPtrMat, která obsahuje ukazatel na dynamicky alokovanou matici třídy TMatice a definováním destruktoru Jazyk C++ 1
1
zajišťuje její automatickou dealokaci, bude k ukazateli přistupovat operátorem nepřímé kvalifikace. class TAutoPtrMat { TMatice* Matice; public: TAutoPtrMat(TMatice* t) : Matice(t) {} ~TAutoPtrMat() { delete Matice; } TMatice* operator ->() { return Matice; } // ... }; void f() { TAutoPtrMat A(new TMatice(5, 3, 0)); A->Vypis(); } //matice se ukončením funkce f() automaticky dealokuje
Operátor přetypování Tvar operátorové funkce operátoru přetypování je v jazyce C++ složena z klíčového slova operator, názvu typu, na který se má objektový typ konvertovat (může obsahovat i znaky jako * nebo &) a kulatých závorek. Například: operator char* (); operator long int (); Pokud je deklarován operátor přetypování na typ TB ve třídě TA, bude tento operátor překladačem použit všude tam, kde se vyskytuje hodnota typu TA a překladač na tom místě očekává hodnotu typu TB. Tento operátor bude též použit při explicitním přetypování: (TB)A nebo static_cast(A), kde A je instance typu TA. class TB { long i; public: TB(long _i) : i(_i) {} // ... }; class TA { int i; public: TA(int _i) : i(_i) {} operator TB() { return TB(i); } operator int() { return i; } }; void f(TB B); //Pokud je A instance třídy TA //lze funkci f() volat následujícími způsoby Jazyk C++ 1
2
f(static_cast(A)); f((TB)A); f(TB(A)); f(A); //ve výše uvedených způsobech se nejprve volá //A.operator TB(), jejíž výsledek se předává do f()
Podobně lze zapsat příkaz cout << A;, který vypíše hodnotu A.i, jelikož se nejprve volá A.operator int().
Skupina 3 – operátory new a delete Ve třetí skupině se vyskytují operátory pro práci s pamětí. Jedná se o operátory:
new delete new[] delete[]
Tyto operátory lze jednak přetížit a jednak předefinovat, tedy lze nahradit jejich verzi verzí vlastní. Lze je definovat: 1. jako obyčejné funkce, 2. jako statické metody objektového typu.
Obyčejné operátorové funkce V programu se může operátor new zavolat pro alokaci proměnné buď bez parametru nothrow, potom překladač volá: void* operator new(size_t size) throw(bad_alloc); nebo s parametrem nothrow, pak překladač volá: void* operator new(size_t size, const nothrow_t&) throw();
Uvedené specifikace throw() a throw(bad_alloc) specifikaci výjimek, které se mohou rozšířit.
představují
Obdobným způsobem lze v programu zavolat operátor new[] pro alokaci pole buď bez nebo s parametrem nothrow. Potom překladač volá: void* operator new[](size_t size) throw(bad_alloc); void* operator new[](size_t size, const nothrow_t&) throw();
Ve všech výše uvedených funkcích parametr size představuje požadovanou velikost paměti v bajtech. Jazyk C++ 1
3
Oba operátory new i new[] lze zavolat také s parametrem umístění. Ten udává adresu, od které se má začít alokovat prostor v paměti. Překladač pak volá jednu z těchto funkcí (ty nelze předefinovat): void* operator new(size_t size, void* ptr) throw(); void* operator new[](size_t size, void* ptr) throw(); V programu se může operátor delete zavolat pro dealokaci proměnné. Překladač potom volá funkci: void operator delete(void* ptr) throw(); V programu se může operátor delete[] zavolat pro dealokaci pole. Překladač potom volá funkci: void operator delete[](void* ptr) throw(); Jestliže uživatel v programu předefinoval některý z výše uvedených prototypů funkcí, překladač použije už od začátku programu tuto. V předefinované funkci lze vynechat specifikace throw(bad_alloc) a throw(). V C++ programu lze operátory new a new[] přetížit, tedy lze u něho definovat různé parametry. Ty se uvádějí v kulatých závorkách, přičemž první parametr musí být typu size_t a návratový typ musí být void*. Pokud se v programu předefinuje operátor new[], měl by se předefinovat i operátor delete[]. V dále uvedením příkladu jsou tyto operátory předefinovány. #include <stdlib.h> #include <memory.h> void* operator new[](size_t size) { void *ptr = malloc(size); if (ptr) memset(ptr, 0, size); return ptr; } void operator delete[](void* ptr) { free(ptr); } void f() { int *a = new int[10]; // volá předefinovanou verzi // ... delete [] a; // volá předefinovanou verzi }
Jazyk C++ 1
4
Standardní chování operátoru new[] by v případě nulové hodnoty parametru size znamenalo vrácení adresy, která bude různá od adres ostatních objektů. A v případě neúspěšné alokace by došlo k zavolání funkce, jejíž adresu obsahuje ukazatel typu new_handler (ten se nastavuje pomocí funkce set_new_handler). Pro splnění takového chování by bylo potřeba pro nulovou hodnotu parametru size alokovat 1 bajt. A definovat globální proměnnou typu new_handler, která by se přenastavovala v předefinované verzi funkce set_new_handler. #include <stdlib.h> #include <memory.h> #include #include #include using namespace std; static new_handler new_hand; // proměnná new_hand je přístupná pouze v tomto souboru new_handler set_new_handler(new_handler my_handler) { new_handler predchozi = new_hand; new_hand = my_handler; return predchozi; } void* operator new[](size_t size) { void *ptr; if (!size) size = 1; while ((ptr = malloc(size)) == 0 && new_hand) new_hand(); if (ptr) memset(ptr, 0, size); return ptr; } void operator delete[](void* ptr) { free(ptr); } void my_handler() { cout << "Nedostatek pameti"; getch(); exit(1); } int f() { int *a = new int[1000000000]; // vrací 0 delete[] a; ::set_new_handler(my_handler);
Jazyk C++ 1
5
// pomocí operátoru :: se volá předefinovaná verze int *b = new int[1000000000]; // volá my_handler delete[] b; }
Jestliže by měl operátor new[] inicializovat alokovanou paměť nějakou zadanou hodnotou, musel by být ještě přetížen: void* operator new[](size_t size, unsigned char c) { void *ptr; if (!size) size = 1; while ((ptr = malloc(size)) == 0 && new_hand) new_hand(); if (ptr) memset(ptr, c, size); return ptr; }
Hodnotu 0 je potřeba přetypovat, jinak by překladač nevěděl, kterou variantu použít, zda s parametrem umístění typu void* nebo s parametrem unsigned char. void* operator new[](size_t size) { return operator new[](size, static_cast(0))); }
V programu můžeme provést alokaci pole s inicializací paměti hodnotou 255 příkazem: int *a = new (255) int[10];
Operátory new a delete jako metody Uvedené operátory new, new[], delete, delete[] lze též definovat jako metody objektových typů. Přestože není v jejich deklaraci uvedeno klíčové slovo static, jedná se o statické metody. Pro new a new[] musí platit:
návratová hodnota je typu void*, jejich první parametr je typu size_t, případné další parametry mohou být jakéhokoli typu.
Pro delete a delete[] musí platit:
návratová hodnota je typu void*, první parametr je typu void*, druhým parametrem může být parametr typu size_t.
Jazyk C++ 1
6
V následujícím příkladu je uvažována potřeba velkého počtu instancí třídy TA. Přičemž se předpokládá jen velmi krátká potřebná doba jejich existence. Proto jsou tyto instance alokovány do dynamicky za sebou do předem připraveného pole bazen. Ve chvíli, kdy se s alokací dojde na konec bazénu, začíná se alokovat opět od začátku. Nové instance tedy přepisují staré (předpokládá se, že již nejsou potřeba), které se nedealokují. class TA { int x; // data enum { n = 1000 }; // velikost bazénu public: void* operator new(size_t size); void operator delete(void* ptr) {} }; void* TA::operator new(size_t size) { static char bazen[n]; // oblast pro alokaci static int pos = 0; // ukazatel na aktuální pozici int i = (pos+size > n) ? 0 : pos; pos = i+size; return &bazen[i]; } TA* A = new TA; //volá metodu TA::operator new; TA* A = ::new TA; //volá obyč. operátorovou funkci TA* A = new TA[5]; //volá obyč. operátorovou fci new[] //aby se namísto toho volala metoda třídy TA //je potřeba dodefinovat new[] a delete[] v TA class TA { // ... void* operator new[](size_t size) { return operator new(size); } void operator delete[](void* ptr) {} };
Tím, že je proměnná bazen lokální statická se zabraňuje jejímu případnému zneužití. V uvedeném příkladu jsou operátory delete a delete[] definovány proto, aby se v případě omylem provedené dealokaci nevolaly standardní operátorové funkce delete resp. delete[]. V dané třídě by mohly být operátory delete a delete[] definovány také s druhým parametrem. void operator delete(void*, size_t) void operator delete[](void*, size_t)
Jazyk C++ 1
7
Volání operátorové funkce Pokud se operátor new zapíše klasicky, např: TB* B = new TB; provede se alokace a volání implicitního konstruktoru třídy TB. Operátorovou funkci new lze zavolat též explicitně: TB* B = (TB*)operator new(sizeof(TB)); Tím se provede alokace instance třídy TB bez volání konstruktoru. Výsledkem je void*, proto je nutné přetypování. Podobně to je s příkazem: delete B; který uvolní paměť a volá destruktor, kdežto příkaz: operator delete (B); uvolní paměť, ale destruktor nevolá.
Shrnutí studijního bloku Tento studijní blok seznámil studenta s dalšími principy přetěžování operátorů v programovacím jazyku C++. Podrobně se pak věnuje speciálním operátorům sloužícím k dynamické správě paměti new, new[], delete a delete[].
Otázky k procvičení pasáže 1. 2. 3. 4. 5.
Jaké požadavky platí pro operátory new a new[]? Jaké požadavky platí pro operátory delete a delete[]? Jaký je účel ukazatele new_handler? K zavolání čeho dojde příkazem TB* B = ::new TB;? K zavolání čeho dojde příkazem TB* B = new TB;?
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)
Jazyk C++ 1
8
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
9
Jazyk C++ 1 Blok 9 Šablony Studijní cíl Devátý blok se věnuje generickému programování v programovacím jazyce C++, a to zejména šablonám. Po absolvování bloku bude student obeznámen s principy tvorby šablon tříd a šablon metod.
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 v jazyce C++ a znalost základů a pojmů z 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, …).
Úvod Vedle všeobecně známých programovacích stylů, kterými jsou strukturované programovaní a objektově orientované programování lze v jazyce C++ uplatnit ještě programovací styl založený na vytváření abstraktních vzorů funkcí a tříd, takzvané generické programování. Jeho základem jsou šablony (angl. templates). Zjednodušeně by se dalo říci, že šablony v jazyce C++ mají podobný význam jako makra v jazyce C. Ovšem jsou zpracovávány překladačem a nabízejí více možností. Umožňují najednou popsat celou řadu funkcí lišících se například jen typem parametru, nebo umožňují popsat celou množinu tříd lišících se například jen datovým typem atributů. Šablona tedy představuje jakýsi vzor pro překladač, který podle ní vygeneruje konkrétní funkce nebo objektové typy. V tom případě se hovoří o instanci šablony.
Jazyk C++ 1
1
Deklarace šablony Šablonu nelze deklarovat na lokální úrovni uvnitř bloku. Lze ji ale deklarovat uvnitř objektového typu nebo uvnitř šablony objektového typu. Šablony se též často deklarují na úrovni souboru. Syntakticky korektní zápis deklarace šablony: template<seznam_parametrů> deklarace, kde seznam_parametrů znamená formální parametry ššablony oddělené čárkami. Ty mohou být hodnotovými typy, formálními typy nebo šablony objektových typů. Deklarace je pak deklarace nebo definice funkce či třídy, definice vnořené třídy, metody objektového typu, statického atributu šablony třídy nebo definice vnořené šablony. Pokud se nejedná o definiční deklaraci metody nebo funkce, musí být deklarace ukončena středníkem.
Parametry šablony Syntakticky korektní zápis parametrů šablony je: hodnotový parametr class identifikátor class identifikátor = označení_typu typename identifikátor typename identifikátor = označení_typu template<seznam_parametrů> class identifikátor template<seznam_parametrů> class identifikátor = jméno_šablony Pokud se nejedná o šablonu obyčejné funkce, lze pro všechny druhy parametrů šablony předepsat implicitní hodnoty. Jakmile je pro nějaký parametr předepsána implicitní hodnota, je nutné předepsat implicitní hodnoty také pro všechny následující parametry. Pokud je deklarováno více šablon stejného typu, lze implicitní hodnoty parametrů uvést jen v jedné z nich. Příklad:
Jazyk C++ 1
2
template class TA; template class TA { … }; // OK
template class TB; template class TB { … }; // OK
template class TC; template class TC { … }; // Chyba Parametr šablony je možné použít také v deklaraci následujících parametrů této šablony: template class TA { … };
typové parametry Následující dva zápisy jsou zcela ekvivalentní. A jak je patrno, typové parametry lze uvést jednak klíčovým slovem class a jednak klíčovým slovem typename. Od předepsání typového parametru klíčovým slovem class se upouští, neboť může být matoucí. I když je typový parametr uveden slovem class, skutečným parametrem může být jakýkoli, nejen objektový, typ. Skutečným parametrem je potom označení typu (nesmí jím však být lokální třída). //ekvivalentní zápisy template class TA; template class TA;
Hodnotové parametry U šablon se hodnotové parametry deklarují obdobným způsobem jako u funkcí formální parametry. Je však vyžadováno splnění podmínky, aby byl hodnotový parametr jedním z těchto typů:
Celočíselný o Skutečným parametrem je celočíselný konstantní výraz.
Jazyk C++ 1
template class TA { … }; TA<double, 10*5> A;
3
Výčtový o Skutečným parametrem je celočíselný konstantní výraz, který má za výsledek daný výčtový typ. enum TB { b1, b2, b3 }; template class TA { … }; TA A; Ukazatel na objekt o Skutečným parametrem je konstantní výraz, který představuje adresu pojmenovaného objektu s paměťovou třídou extern. Nelze použít adresu prvku pole. Reference na objekt o Skutečným parametrem je l-hodnota představující pojmenovaný objekt s paměťovou třídou extern. Ukazatel na funkci o Skutečným parametrem je funkce s externím linkováním. template class TA { ... }; int f(int x) { ... } TA A; Třídní ukazatel o Skutečným parametrem je konstanta představující adresový výraz představující adresu pojmenované složky třídy. struct TB { int x, y; } template class TA { ... }; TA A;
Veškeré hodnotové parametry šablony se chovají jako konstanty a lze tedy s nimi zacházet jako s konstantními výrazy.
Šablonové parametry Název existující šablony třídy může být parametrem pro jinou šablonu. Příklad: template class W> class TA { W a, b; public: TA(W _a, W _b) : a(_a), b(_b) {} }; template struct TB { T x, y; }; TB b1 = { 10, 20 }, b2 = { 30, 40 }; TA A(b1, b2);
Jazyk C++ 1
4
Třídy Šablona pro objektové typy, tj. pro třídy, struktury nebo unie, může obsahovat stejné druhy složek jako objektový typ. Tedy součástí šablony mohou být například vložené (inline), statické a virtuální metody, vnořené objektové typy apod. V následujícím příkladu je deklarována šablona dynamického pole TVektor. Prvky tohoto pole jsou potom typu T, což může být libovolný typ. template class TVektor { T *a; int n; static int PocInstanci; public: TVektor(int _n = 0) : n(_n) { a = new T[n]; PocInstanci++; } ~TVektor() { delete[] a; PocInstanci--; } T& operator[](int index) { return a[index]; } bool operator == (const TVektor& t); static int GetPocInstanci() { return PocInstanci; } };
Metody Pokud nejsou metody definovány přímo v těle šablony, musí být definovány jako šablony. Šablona této metody potom musí obsahovat stejné formální parametry jako šablona její třídy, a to ve stejném pořadí. Jména formálních parametrů se potom mohou lišit. Jména metod musí být kvalifikována jmenovkou třídy, za kterou jsou v lomených závorkách jména formálních parametrů ve stejném pořadí. Příklad template struct TA { void f1(); void f2(); void f3(); void f4(); // … }; template void TA::f1() { … } // OK template void TA::f2() { … } // OK template void TA::f3() { … } // Chyba template Jazyk C++ 1
5
void TA::f4() { … } // Chyba
V návaznosti na předchozí příklad s šablonou TVektor bude mít operátorová funkce == tuto definici: template bool TVektor::operator == (const TVektor& t) { if (n != t.n) return false; for (int i = 0; i < n; i++) { if (a[i] != t.a[i]) return false; } return true; }
Statické atributy Statické atributy je nutné definovat jako šablony. Šablony statického atributu se řídí stejnými pravidly jako šablony metod. V návaznosti na předchozí příklad by statický atribut šablony TVektor udávající počet instancí mohl být definován následovně: template int TVektor::PocInstanci = 0;
Vnořené třídy Zrovna tak i pro vnořené třídy, které jsou definované mimo obklopující šablonu třídy, se musí definovat jako šablony. Ty se opět řídí stejnými pravidly jako šablony metod. Příklad template struct TA { struct TB; // … }; template struct TA::TB { T z; T& Pricti(T _z); }; template T& TA::TB::Pricti(T _z) { z += _z; return z; } TA::TB B; // instance vnořené třídy B.z = 10;
Jazyk C++ 1
6
Instance Dosazením skutečných parametrů namísto formálních v šabloně třídy vzniká instance šablony třídy podle této šablony. Jméno se pak skládá ze jména šablony a ze skutečných parametrů v lomených závorkách. Příklad template class TA { … }; TA B; // OK TA A; // OK – TA TA<> C; // OK – TA TA D; // Chyba
V návaznosti na příklad šablony TVektor by se její instance dala deklarovat: TVektor A(10), B(20); // pole 10 a 20 celých čísel TVektor<double> C(10); // pole 10 čísel typu double TVektor< TVektor > D(10); // pole 10 prvků typu pole celých čísel //Výpis počtu vytvořených instancí pro jednotlivé typy // by mohl být následující: cout << TVektor::GetPocInstanci() << endl; cout << TVektor<double>::GetPocInstanci() << endl;
Při vytváření instance šablony se generují virtuální metody instance šablony třídy. Zároveň překladač vytvoří ty instance metod a statických atributů, které jsou v programu použity. Tedy pokud bychom měli deklaraci TVektor A(10), B(20); vytvořil by překladač instanci typu TVektor, instanci statického atributu TVektor::PocInstanci, instanci konstruktoru s jedním parametrem typu int a destruktoru. Pokud mají dvě instance šablony třídy skutečné parametry stejného typu, hodnotové parametry stejné hodnoty a šablonové typy se odkazují na stejnou šablonu, jsou tyto instance stejného typu. Například instance A, B a C v následujícím kódu jsou stejného typu: template struct TA { … }; typedef unsigned int uint; TA A; TA B; TA C; TA D;
Jazyk C++ 1
7
Příkaz typedef nedeklaruje nový typ, ale pouze synonymum pro typ již existující. Instanci šablony třídy lze použít jako předka jiné třídy a zrovna tak lze též deklarovat šablonu třídy jako potomka třídy jiné.
Shrnutí studijního bloku Tento studijní blok seznámil studenta s účelem a principy tvorby šablon jakožto základu pro princip generického programování v jazyku C++. Podrobně byla probrána deklarace šablon, typové, hodnotové a šablonové parametry, šablony tříd, jejich metod a statických atributů.
Otázky k procvičení pasáže 1. 2. 3. 4. 5.
Jak se deklarují typové parametry? Jakých typů mohou být hodnotové parametry? Lze použít šablonu třídy jako parametr jiné šablony? Čím je určen typ šablony? Čím je určena instance šablony?
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
8
Jazyk C++ 1 Blok 10 Šablony (pokračování) Studijní cíl Desátý blok se věnuje generickému programování v programovacím jazyce C++, rozšiřuje znalosti o šablonách. Po absolvování bloku bude student obeznámen s principy vnořených šablon, typových parametrů šablon a šablon funkcí.
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 v jazyce C++ a znalost základů a pojmů z 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řídy (pokračování) Vnořené šablony Vnořená šablona též nazývána jako členská šablona představuje složku třídy nebo složku šablony třídy, přičemž za složku je zde považována vnořená třída, v tom případě se hovoří o vnořené šabloně třídy nebo metoda, potom se hovoří o vnořené šabloně metody. Šablonou metody nemůže být virtuální metoda ani destruktor, ale může jí být konstruktor. V případě konstruktoru s jedním parametrem však nevytváří překladač kopírovací konstruktor. Název šablony metody se může shodovat s názvem obyčejné metody. Šablona metody se použije v případě, že si neodpovídají typy nebo počty formálních a skutečných parametrů. Vnořené šablony nemohou být součástí lokální třídy, tedy třídy deklarované v těle funkce.
Jazyk C++ 1
1
Ve vnořené šabloně lze použít formální parametr šablony obklopující třídy, ale ne naopak. V následujícím příkladu je deklarována šablona třídy TA (vnější), v níž je deklarována vnitřní šablona třídy TB. Šablona třídy TB má dva atributy a jednu metodu. Vnější šablona TA má pak atribut typu vnořené šablony TB. template struct TA { template struct TB { V z; U z2; V Pricti(V x); }; T x, y; TB b; }; template template V TA::TB::Pricti(V x) { z += x; return z; } TA A; A.x = 10; // x je typu int A.b.z = 5.4; // z je typu double //instance vnořené šablony TB //typ int v TA nemá na deklaraci //TB vliv, může být libovolného typu TA::TB B; B.z = 'c'; B.z2 = 5.4;
V následujícím příkladu je vidět šablona metody assign jako součást šablony třídy TVektor. Tato metoda slouží k přiřazení hodnot prvků jiné datové struktury do pole třídy TVektor. Tyto jsou reprezentovány iterátory first a last typu InputIterator. Pro typ InputIterator je potřeba definovat operátory ++ (posun na následující prvek), * (dereference ukazatele) a != (vrací true, pokud dva prvky neukazují na stejný prvek datové struktury). template class TVektor { //... template void assign(InputIterator first, InputIterator last); }; template template void TVektor::assign(InputIterator first, InputIterator last) { Jazyk C++ 1
2
InputIterator iter; T* iter2; delete[] a; n = 0; for (iter = first; iter != last; ++iter, n++); a = new T[n]; for (iter = first, iter2 = a; iter != last; ++iter, ++iter2) *iter2 = *iter; } //použití metody assign k přiřazení prvků pole //vektor A bude obsahovat první tři prvky pole b TVektor A; int b[] = { 10, 20, 30, 40 }; A.assign(b, b+3); //definování šablony konstruktoru template class TVektor { //… template TVektor(InputIterator first, InputIterator last) : a(0) { assign(first, last); PocInstanci++; } }; //Vektor C bude obsahovat hodnoty prvků b[1], //b[2] a b[3] TVektor C(b+1, b+4);
Vnořený typ typového parametru šablony Typovým parametrem šablony by mohla být třída obsahující deklaraci vnořeného typu. Ten lze použít v šabloně třídy (například k deklaraci atributu). V dále uvedeném příkladu se předpokládá, že parametrem T je objektový typ, v němž je deklarován vnořený typ TC. Vzhledem k normě jazyka C++ se předpokládá, že překladač bude kontrolovat existenci už v době, kdy narazí na deklaraci šablony. Proto je potřeba překladači prostřednictvím klíčového slova typename sdělit, že T::TC je typ. template class TA { typename T::TC b; // OK // … };
Pomocí klíčového slova typedef by bylo možné nadefinovat synonymum pro tento typ, např. template class TA { typedef typename T::TC TTC; TTC b; // … };
Jazyk C++ 1
3
Funkce Programovací jazyk C++ umožňuje pomocí šablon funkce popsat naráz celou řadu funkcí, které se liší například jen typem parametrů V následujícím příkladu je uvedena šablona vložené funkce Max, která vrací maximum ze dvou hodnot předaných parametrem. template inline T Max(T a, T b) { return a > b ? a : b; }
Instance Pokud se zavolá funkce, kterou překladač nezná, ale je schopen ji vytvořit ze šablony, vytvoří si její potřebnou instanci. V následujícím příkladu je ukázáno, jaké instanci by si překladač mohl vzhledem k předchozímu příkladu vytvořit. Nelze vytvořit poslední dvě šablony, protože překladač není schopen určit, jaký typ použít. cout cout cout cout
<< << << <<
Max(1, 2); // vytvoří se Max Max(1.4, 1.5); // vytvoří se Max<double> Max(1, 'b'); // Chyba Max(2.2, 1); // Chyba
Jazyk C++ však umožňuje překladači explicitně říci, jaký typ má použít. Tento typ se uvede v lomených závorkách za jménem funkce. cout << Max(1, 'b'); // bude se volat Max cout << Max<double>(2.2, 1); // bude se volat //Max<double>
Pokud je skutečný typ uveden jen jako návratový typ, překladač nedokáže v tomto případě vytvořit potřebnou instanci automaticky. template T f(int i) { return T(i*2); } double b = f(10); // Chyba double b = f<double>(10); // OK
Překladač se při vytváření instancí nepokouší o konverze typů, ale vždy vytvoří novou instanci. Tedy pokud už například vytvořil instanci int Max(int, int) a narazí na volání Max(2.3, 3.1), nebude provádět konverzi ale vytvoří novou instanci double Max(double, double).
Přetěžování V jednom oboru viditelnosti lze deklarovat více šablon funkcí se stejným názvem. Zrovna tak lze definovat šablonu funkce a obyčejnou funkci se stejným názvem. Pravidla pro určení, která varianta se bude volat, se řídí obdobnými pravidly jako u přetěžování obyčejných funkcí. Jazyk C++ 1
4
Pokud má překladač na vybranou, zda zavolat existující obyčejnou funkci nebo vytvořit instanci šablony funkce, upřednostní obyčejnou funkci, jestliže typy formálních parametrů přesně odpovídají typům skutečných parametrů. Pokud si typy přesně neodpovídají, ale lze je implicitně konvertovat, dá překladač taktéž přednost obyčejné funkci. V návaznosti na předchozí příklad šablony funkce Max, která nebude fungovat pro typ char, by se dala definovat obyčejná funkce: inline char* Max(char* a, char* b) { return strcmp(a, b) > 0 ? a : b; } //volání obyčejné funkce a ne šablony pro typ char* cout << Max("aaa", "bbb"); //Zrovna tak, pokud by existovala deklarace obyčejné //funkce int Max(int a, int b); //volala by se tato obyčejná funkce i v případě příkazu cout << Max(1, 'b');
Vazba jmen V těle šablony se dají použít také globální jména typů, proměnných, …, která jsou v místě deklarace šablony viditelná. V místě použití šablony se mohou vyskytovat stejná jména s jiným významem. Překladač ovšem vždy použije ta jména, která jsou známá v místě deklarace. Následující příklad vypíše na obrazovku hodnotu globální proměnné i, tedy vypíše hodnotu 10. int i = 10; template inline T GetI() { return i; } int main() { int i = 50; cout << GetI(); return 0; }
Jazyk C++ 1
5
Explicitní specializace Pokud je potřeba pro určité skutečné parametry deklarovat speciální verzi šablony, umožňuje to takzvaná explicitní specializace, kterou lze deklarovat pro:
šablonu obyčejné funkce šablonu třídy metody šablony třídy statický atribut šablony třídy třídu vnořenou do šablony třídy šablonu vnořenou do jiné šablony třídy šablonu metody vnořenou do šablony třídy
Syntakticky korektní zápis explicitní specializace šablony je: template <> deklarace, kde deklarace je definiční nebo informativní deklarace. Za jmenovkou se uvádí v lomených závorkách skutečné parametry.
Explicitní specializace šablony obyčejné funkce Explicitní specializace šablony obyčejné funkce je shodná s deklarací obyčejné funkce, které předchází specifikace template <>. Příklad template void Vypis(T t1, T t2) { cout << t1 << ", " << t2 << '\n'; } // explicitní specializace šablony funkce template<> void Vypis(double t1, double t2) { printf("%.1lf, %.1lf\n", t1, t2); } //příklady použití Vypis(10, 12); // OK – volá se primární šablona Vypis(10.0, 12.5); // OK - volá se explicitní //specializace Vypis(10, 12.5); // Chyba – neexistuje deklarace //Vypis(int, double) //V případě existence deklarace obyč. fce void Vypis(double, double); //by se příkazem Vypis(10, 12.5); //volala právě tato obyčejná funkce, protože u ní //lze provést automatickou konverzi 10 na typ double
Jazyk C++ 1
6
Explicitní specializace metody šablony třídy Součástí dříve uvedeného příkladu s deklarací šablony TVektor by mohla být metoda Vypis, vypisující na obrazovku hodnoty jednotlivých prvků pole. Pro prvky typu double by pak šlo specializovat metodu pro výpis hodnot prvků zaokrouhlených na jedno desetinné místo. template class TVektor { //… void Vypis(); }; template void TVektor::Vypis() { for (int i = 0; i < n; i++) cout << a[i] << ' '; cout << endl; } //specializace template < > void TVektor<double>::Vypis() { for (int i = 0; i < n; i++) printf("%.1lf ", a[i]); cout << '\n'; };
Explicitní specializace statického atributu šablony třídy V návaznosti na předchozí příklad s deklarací šablony TVektor by mohl mít například statický atribut PocInstanci jinou počáteční hodnotu pro typ TVektor<double>. template int TVektor::PocInstanci; // = 0 template<> int TVektor<double>::PocInstanci = 5; Specifikace template<> by mohla být u definice statického atributu vynechána.
Explicitní specializace šablony třídy Explicitní specializaci šablony třídy lze deklarovat například pro typ char šablony třídy TVektor. Může tak například obsahovat nulou zakončený řetězec znaků a jeden parametrický konstruktor const char*. template<> class TVektor { char* a; public: TVektor() { a = 0; } TVektor(const char* s); ~TVektor() { delete[] a; }
Jazyk C++ 1
7
//… }; TVektor::TVektor(const char* s) { a = new char[strlen(s)+1]; strcpy(a, s); }
Specifikátor template<> nesmí být u konstruktoru uveden. Zrovna tak nesmí být uveden u jiných metod definovaných mimo tělo šablony třídy. TVektor
C("test");
//využije konstruktor //TVektor(const char* s)
Explicitní specializace vnořené šablony Musí být uvedena mimo tělo obklopující šablony třídy. Pokud je pro obklopující šablonu třídy definována explicitní specializace, nemusí již být definována pro vnořenou šablonu. template struct TA { template struct TB { U z; }; T x; }; template <> template<> struct TA::TB { int z1, z2; // OK }; TA::TB<double> B; B.z = 2.3; TA::TB C; // instance explicitní // specializace TB C.z1 = 3; C.z2 = 4;
Následující deklarace však není přípustná, protože obklopující třída není explicitně specializovaná: template template<> struct TA::TB { char z3; };
Příklad template struct TA { T x; template void f(U u) { x = T(u); } }; template <> template <> void TA::f(const char* c) { x = c[0]; }
Jazyk C++ 1
8
TA A; A.f(10.5); // volá se primární vnořená šablona TA B, C; B.f('A'); // volá se primární vnořená šablona C.f("text"); // volá se explicitní specializace //vnořené šablony // C.x = 't'
Shrnutí studijního bloku Tento studijní blok seznámil studenta podrobněji se šablonami jakožto základem pro princip generického programování v jazyku C++. Podrobně byly probrány vnořené šablony, šablony funkcí (včetně instancí a přetěžování), vazby jmen a explicitní specializace šablon.
Otázky k procvičení pasáže 1. 2. 3. 4.
Jaká pravidla platí u přetěžování šablon funkcí? Jakým klíčovým slovem lze překladači říci, že se jedná o typ? Jak lze vytvořit synonymum pro typ? Lze použít uvnitř těla šablony globální jména, tedy názvy deklarované mimo tělo šablony? 5. V případě existence deklarace obyčejné funkce, jejíž formální paramterty se přesně shodují se skutečnými parametry a v případě existence šablony funkce, zavolá překladač obyčejnou funkci nebo bude vytvářet instanci šablony funkce?
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
9
Jazyk C++ 1 Blok 11 Šablony (pokračování) Studijní cíl Jedenáctý blok se věnuje generickému programování v programovacím jazyce C++, rozšiřuje znalosti o šablonách. Po absolvování bloku bude student obeznámen s principy parciální specializace šablon a explicitními instancemi.
Doba nutná k nastudování 1-2 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 v jazyce C++ a znalost základů a pojmů z 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, …).
Parciální specializace šablon Programovací jazyk C++ poskytuje způsob, kterým lze vytvořit alternativní definici k primární definici šablony. Před deklaracemi parciálních specializací šablony musí existovat alespoň informativní deklarace primární šablony. Každá parciální specializace je chápána jako samostatná šablona, proto musí obsahovat kompletní definici. Může ovšem obsahovat jiné složky, než které obsahuje primární šablona. Od primární deklarace šablony se deklarace parciální specializace šablony liší tím, že za názvem následují v lomených závorkách formální parametry určující způsob specializace.
Jazyk C++ 1
1
Příklady //#1 – primární nespecializovaná šablona třídy template
Při generování instancí šablony použije překladač tu verzi, která odpovídá skutečným parametrům a je nejvíce specializovaná. Příklad TA
10 10 50 10 20
a1; a2; a3; a4; a5;
// // // // //
použije použije použije použije chyba F
se #1 se #2 se #4 se #5 lze použít #3 i #5
Statické atributy, metody, vnořené třídy a šablony parciálně specializované šablony třídy definované mimo tělo šablony musí obsahovat stejný seznam formálních parametrů a stejný seznam specializovaných parametrů (za jmenovkou třídy) jako parciálně specializovaná šablona. Explicitní specializace složky parciálně specializované šablony se deklarují stejným způsobem, jako se deklaruje primární šablona. Příklad //#1 - definice metody primární šablony template void TA::f(){ … } //parciální specializace šablony template struct TA { void f(); }; //#2 - definice metody parciální specializované šablony template void TA::f() { … } // #3 - explicitní specializace metody parciální // specializované šablony template <> void TA::f() { … } Jazyk C++ 1
2
void g() { TA a1; TA a2; TA a3; a1.f(); // použije se #1 a2.f(); // použije se #2 a3.f(); // použije se #3 }
V následujícím příkladu je deklarovaná primární šablona třídy TKonstVektor, která obsahuje pole n prvků typu T. Šablona bude obsahovat pole dynamicky alokovaných prvků, jestliže T bude typu ukazatel. V tom případě bude deklarovaná parciální specializace. Pokud by navíc n bylo rovno 2, deklarace bude ještě jinak specializovaná. //#1 template class TKonstVektor { T a[n]; public: TKonstVektor(const T& t = T()) { for (int i = 0; i < n; i++) a[i] = t; } T& operator[](int i) { return a[i]; } }; //#2 template class TKonstVektor { T* a[n]; public: TKonstVektor(const T& t = T()) { for (int i = 0; i < n; i++) a[i] = new T(t); } T& operator[](int i) { return *a[i]; } }; //#3 template class TKonstVektor { T* a[2]; public: TKonstVektor(const T& t = T()) { a[0] = new T(t), a[1] = new T(t); } T& operator[](int i) { return *a[i]; } }; //příklad volání TKonstVektor A(0); // použije se #1 TKonstVektor B(1); // použije se #2 TKonstVektor<double*, 2> C; // použije se #3
Jazyk C++ 1
3
Explicitní vytvoření instance Pro šablony funkcí tříd, metod a statických atributů lze překladači přikázat vytvoření instance, aniž by došlo ihned k jejímu použití. Pro explicitně vytvořené instance šablony třídy překladač ihned vytvoří všechny její metody, statické atributy, a to i když nejsou v programu použity. Tyto se stanou součástí souboru *.obj a *.exe. U dalších typů šablon je to obdobné. Syntakticky korektní zápis explicitního vytvoření instance šablony je: template dekarace, kde deklarace je prototyp funkce nebo metody, informativní deklaraci třídy nebo deklaraci statického atributu. Za takovou deklarací se uvádí v lomených závorkách skutečné parametry šablony. Pokud se jedná o šablonu funkce, lze koncové parametry, které si umí překladač odvodit, vynechat. V návaznosti na předchozí příklad šablony třídy TVektor je uveden následující příkaz, který explicitně vytvoří instanci pro typ int. Vytvoří se tak instance všech metod a statických atributů pro typ int. Nevytváří se však instance vnořených šablon (tříd i metod). template class TVektor; To ovšem znamená, že se výše uvedeným příkazem nevytvoří instance konstruktoru TVektor(InputIterator, InputIterator) ani metody assign. Explicitní vytvoření jejich instancí je potřeba vtvořit samostatně, neboť je potřeba specifikovat typ pro parametr InputIterator. Pro typ int* by mohlo explicitní vytvoření instancí vypadat následovně. template void TVektor::assign::TVektor(int*, int*); Specifikaci lze za identifikátorem metody assign vypustit. Explicitně lze vytvořit i instance jednotlivých metod a statických atributů bez nutnosti tvořit explicitně instanci celé třídy. Například pro šablonu třídy TVektor by mohl být zápis následující:
Jazyk C++ 1
4
template bool TVektor<double>::operator TVektor& t) const;
==
(const
template int TVektor<double>::PocInstanci;
Shrnutí studijního bloku Tento studijní blok seznámil studenta podrobněji se šablonami jakožto základem pro princip generického programování v jazyku C++. Podrobně byly probrány parciální specializace šablon a explicitní vyváření instancí šablon.
Otázky k procvičení pasáže 1. Jak vypadá syntaxe korektního zápisu explicitního vytvoření instance šablony? 2. Co je to parciální specializace šablony? 3. Co musí předcházet deklaraci parciální specializace šablony? 4. V čem se liší deklarace parciální specializace od primární dekalrace? 5. Lze považovat parciální specializaci šablony za samostatnou šablonu?
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
5
Jazyk C++ 1 Blok 12 Šablony (pokračování) – přátelé Organizace programu Studijní cíl Dvanáctý blok se věnuje generickému programování v programovacím jazyce C++, rozšiřuje znalosti o šablonách. Po absolvování bloku bude student obeznámen s principy přátel šablon. Dále je probrána organizace programu vytvořeného v programovacím jazyce C++.
Doba nutná k nastudování 1-2 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 v jazyce C++ a znalost základů a pojmů z 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, …).
Přátelé V programovacím jazyce C++ lze vytvořit přítele třídy nebo i přítele šablony třídy. Tím může být:
šablona obyčejné funkce, šablona jiné třídy, specializace šablony obyčejné funkce, specializace šablony jiné třídy, obyčejná funkce, jiná třída.
Jazyk C++ 1
1
Jestliže je přítelem šablony třídy TA obyčejná funkce, zpravidla se jedná o funkci, která má jeden parametr typu reference nebo ukazatel na šablonu třídy TA. V takovém případě se pak jedná o šablonu obyčejné funkce s formálními šablonovými parametry shodnými s parametry šablony třídy TA. Příklad template class TA; template void f2(TA&); template class TB; template class TA { friend void f1(TA&); friend void f2(TA&); template friend void f3(TA&, U); friend class TB; template friend class TC; T x; public: TA(T _x) : x(_x) {} }; void f1(TA& A) { A.x += 2; } template void f2(TA& A) { A.x += A.x; } template void f3(TA& A, U u) { A.x += static_cast(u); } TA A(2); TA A2(10.5);
Obyčejná funkce f1 je považována za přítele instance šablony třídy TA. Lze ji použít pro instanci A, ale nelze ji použít pro instanci A2. f1(A); // OK f1(A2); // Chyba
Vedle obyčejné funkce f1 je přítelem šablony třídy TA také obyčejná funkce f2, a to včetně jejích explicitních specializací. Jelikož takto uvedená deklarace spřátelené šablony funkce vyžaduje existenci deklarace prototypu šablony funkce f2 před definicí šablony třídy TA a protože se v prototypu vyskytuje šablona TA, musí být před prototypem uvedena i informativní deklarace šablony třídy TA.
Jazyk C++ 1
2
Vzhledem k tomu, že šablona funkce f2 má formální šablonový parametr T, který je identický s formálním parametrem T šablony třídy TA, bude například přítelem instance typu TA instance šablony funkce f2, zapsané jako: void f2 class TA { template friend void f2(TA&); // … };
V tomto případě už není nezbytná informativní deklarace šablony funkce f2 před definicí šablony třídy TA. Přítelem šablony třídy TA je také šablona funkce f3. Šablona funkce f3 má dva typové parametry T a U, kde parametr T je identický s formálním parametrem T šablony třídy TA (mohl by však míti i jiné označení). Například funkce f3 s parametrem T rovným int a parametrem U libovolného typu, by taktéž byla přítelem instance TA. Dále lze za přítele šablony třídy TA považovat též specializaci šablony třídy TB pro typ long. Šablona třídy TB je definována tímto způsobem: template class TB { T y; public: TB(T _y) : y(_y) {} template & A) { y += A.x; } };
Metoda g může být volána pouze pro instanci typu TB: TB B1(3); TB B2(3.5); B1.g(A); // OK B2.g(A); // Chyba
Za přítele šablony třídy TA lze též považovat instanci šablony třídy TC, která je definována tímto způsobem:
Jazyk C++ 1
3
template class TC { T z; public: TC(T _z) : z(_z) {} template void h(TA& A) { z += A.x; } };
Pro libovolnou instanci šablony třídy TC lze volat metodu h. TC<double> C(3); C.h(A); // OK
Příklad: class TD; template void fd2(TD&, T); class TD { int x; template friend void fd1(TD&, T); friend void fd2(TD&, int); friend void fd2(TD&, char); public: TD(int _x) : x(_x) {} }; template void fd1(TD& f, T t) { f.x += static_cast(t); } template void fd2(TD& f, T t) { f.x g= static_cast(t); }
Pro libovolnou instanci třídy TD lze také volat funkci fd1, jejíž šablona je přítelem třídy TD. Tf f(2); fd1(f, 4.5); // OK
Za přítele třídy TD lze považovat také dvě specializace šablony funkce fd2 pro typy int a char. Funkci fd2 pak lze volat pouze v případě, že jejím druhým skutečným parametrem je typ int nebo char. fd2(f, 4); // OK, druhý parametr je typu int fd2(f, 'a'); // OK, druhý parametr je typu char fd2(f, 4.5); // Chyba, druhý parametr je typu double
Jazyk C++ 1
4
Organizace programu Používání šablon v programu jazyka C++ lze realizovat dvěma způsoby. 1) Informativní i definiční deklarace šablony se vloží do hlavičkového souboru, který se pomocí direktivy #include vkládá do těch modulů, kde se šablona využívá. 2) Do hlavičkového souboru se vloží pouze deklarace šablony (mimo případ šablony třídy, kdy se vkládá i její definice) a definice šablony se umístí do samostatného modulu. Do modulů, kde se šablona využívá, se pak bude pomocí direktivy #include zahrnovat pouze hlavičkový soubor. Příklad prvního způsobu: // soubor Vektor.h #ifndef VektorH #define VektorH template class TVektor { T a[n]; public: TVektor(const T& t = T()) { for (int i = 0; i < n; i++) a[i] = t; } T& operator[](int i) { return a[i]; } void Vypis() const; }; template void TVektor::Vypis() const { for (int i = 0; i < n; i++) cout << a[i] << ' '; cout << endl; } #endif
Do každého .cpp souboru (modulu) se pak uvede řádek: #include "Vektor.h" Příklad druhého způsobu: // -------- soubor Vektor.h -------#ifndef VektorH #define VektorH template
5
}; #endif // -------- soubor Vektor.cpp -------#include "Vektor.h" export template void TVektor::Vypis() const { for (int i = 0; i < n; i++) cout << a[i] << ' '; cout << endl; }
Shrnutí studijního bloku Tento studijní blok seznámil studenta podrobněji s použitím šablon z hlediska struktury programu v jazyce C++. Podrobně byly na začátku studijního bloku rozebrány možnosti spřátelených šablon.
Otázky k procvičení pasáže 1. Co může být přítelem šablony třídy? 2. K čemu slouží direktiva #include? 3. Jakým klíčovým slovem začíná každá definice složky šablony třídy v modulu .cpp, jestliže dekalrace byla v hlavičkovém souboru .h? 4. Kam je potřeba nalinkovat hlavičkový soubor s deklarací a definicí šablony, pokud chceme tuto šablonu používat? 5. Jestliže je přítelem šablony třídy obyčejná funkce, jaký parametr zpravidla obsahuje?
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
6
Jazyk C++ 1 Blok 13 Prostory jmen Studijní cíl Třináctý blok se věnuje řešení problému spojeným převážně s vývojem velkých aplikací, kde může snadno dojít ke konfliktu identifikátorů. Student bude seznámen s principem, vytvářením a užíváním jmenných prostorů v programovacím 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 v jazyce C++ a znalost základů a pojmů z 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, …).
Úvod Při programování větších aplikací, kde se kombinuje řada různých knihoven, nebo se programuje ve vývojovém týmu, může velice snadno nastat situace, že dojde ke konfliktu jmen globálních proměnných, tříd, funkcí, apod. V programovacím jazyce C++ se tyto problémy řeší zavedením takzvaných prostorů jmen (angl. namespace). Prostor jmen po formální stránce připomíná strukturu nebo třídu. Jména, která jsou deklarována uvnitř prostoru jmen lze používat vně tohoto prostoru. Musí se však kvalifikovat prostorem jmen, do kterého patří nebo zpřístupnit tento prostor jmen pomocí direktivy using.
Definice jmenného prostoru Syntakticky korektní zápis definice prostoru jmen je: namespace identifikátornep { posloupnost_deklarací }, kde identifikátor je název definovaného prostoru jmen. Pokud by byl identifikátor vynechán, definuje se takzvaný anonymní prostor jmen. Jazyk C++ 1
1
Posloupnost_deklarací pak představuje seznam jednotlivých deklarací proměnných, funkcí, tříd, typů apod. v rámci jednoho prostoru jmen. Uvnitř prostoru jmen může být definován další prostor jmen. Prostor jmen, v němž je definován jiný prostor jmen se pak nazývá nadřazený prostor jmen. Pokud existují uživatelem deklarované identifikátory mimo jím definovaný prostor jmen, jsou tyto identifikátory součástí takzvaného globálního prostoru jmen. Veškeré složky z prostoru jmen lze definovat i mimo tento prostor jmen. Musí však být specifikovány jmenovkou prostoru jmen a binárním rozlišovacím operátorem (::). Příklad namespace A { void f(); // ... //definice funkce uvnitř prostoru jmen void f() { /* ... */ } } namespace B { void g(); } //definice funkce mimo prostor jmen void B::g() { /* ... */ } // musí se kvalifikovat
Prostor jmen nelze definovat mimo oblast prostoru jmen, tedy nelze ho definovat například v těle funkcí či metod tříd. Kdykoli se identifikátor deklarovaný v prostoru jmen použije mimo něj, musí se kvalifikovat identifikátorem prostoru jmen pomocí binárního rozlišovacího operátoru. Jména deklarovaná v prostoru jmen zastiňují stejná jména z nadřazeného či globálního prostoru jmen. Pokud je potřeba zpřístupnit jména z nadřazeného prostoru jmen, musí být zpřístupněny jmenovkou prostoru jmen a rozlišovacím operátorem. Jména z globálního prostoru jmen se zpřístupní pomocí unárního rozlišovacího operátoru. Příklad //dekladrace a inicializace proměnných //v globálním prostoru jmen int a = 2; int b = 3;
Jazyk C++ 1
2
//definice uživatelského prostoru jmen namespace ProstorJmen { int a = 4; void f(); } //definice funkce z uživatelského prostoru jmen void ProstorJmen::f() { std::cout << "funkce f: a*b = " << a*b << endl; } //funkce main z globálního prostoru jmen int main() { //lokální proměnná int a = 5; std::cout << "lokalni a = " << a << endl; //výpis proměnné z uživatelského prostoru jmen std::cout << "ProstorJmen::a = " << ProstorJmen::a << endl; //výpis globální proměnné std::cout << "globalni a = " << ::a << endl; //volání funkce f() z uživatelského prostoru jmen ProstorJmen::f(); return 0; }
Výpis výše uvedeného programu: lokalni a = 5 ProstorJmen::a = 4 globalni a = 2 funkce f: a*b = 12
V následujícím příkladu je ukázána definice dvou prostorů jmen (nadřazeného a vnitřního). //definice vnějšího (nadřazeného) prostoru jmen namespace Vnejsi { int a = 1; //definice vnitřního (vnořeného) prostoru jmen namespace Vnitrni { void f() { std::cout << "Funkce f = " << a << endl; } //ještě není známá prom. a z vnitřního prostoru //jmen, tak se vypisuje prom. a z vnějšího //prostoru jmen int a = 2; void g() { std::cout << "Funkce g = " << a << endl; } //už je známá prom. a z vnitřního prostoru //jmen, tak se vypisuje tato
Jazyk C++ 1
3
} } int main() { std::cout << "Vnejsi a = " << Vnejsi::a << endl; std::cout << "Vnitrni a = " << Vnejsi::Vnitrni::a << endl; Vnejsi::Vnitrni::f(); Vnejsi::Vnitrni::g(); return 0; }
Výpis výše uvedeného programu Vnejsi a = 1 Vnitrni a = 2 Funkce f = 1 Funkce g = 2
Za přátele třídy jsou považovány pouze třídy ze stejného prostoru jmen. Příklad V následujícím příkladu je v prostoru jmen PA nadefinována třída TA. Uvnitř této třídy je deklarována spřátelená funkce f(). Jedná se však o funkci označenou #2 ne o globální f() označenou #1. Kdyby nebyla funkce f() (#2) v prostoru jmen PA deklarována, překladač by oznámil chybu. void f() { std::cout << "globalni funkce f\n"; } //#1 namespace class TA static friend };
PA { { int a; void f(); // přítelem je PA::f()
int TA::a = 10; void f() { std::cout << "a = " << TA::a << '\n'; } //#2 }
Pokud by se ve třídě TA (jmenného prostoru PA) měla deklarovat spřátelená funkce f z globálního prostoru jmen, muselo by se zapsat pomocí unárního rozlišovacího operátoru: namespace PA { class TA { friend void ::f(); }; }
Jazyk C++ 1
4
Definice po částech Definici jmenného prostoru lze rozdělit do několika částí. Jednotlivé části pak mohou být rozděleny id o několika zdrojových souborů. Příklad namespace A { int a; } namespace B { int f() { A::a + A::b; } // Chyba, A::b v tuto // chvíli neexistuje } namespace A { int b; }
Tento problém by šlo vyřešit například nejprve uvedením prototypu B::f() a definici provést až později, po deklaraci A::b. namespace A { int a; } namespace B { int f(); } namespace A { int b; } namespace B { int f() { return A::a + A::b; } // OK }
Shrnutí studijního bloku Tento studijní blok seznámil studenta s účelem, principy tvorby a používání jmenných prostorů v programovacím jazyku C++. Podrobně byla probrána definice globálního, anonymního, uživatelského, nadřazeného a vnořeného prostoru jmen.
Otázky k procvičení pasáže 1. 2. 3. 4. 5.
Jakým způsobem se definuje uživatelský prostor jmen? Co je to globální prostor jmen? Co je to nadřazený prostor jmen? Jaký problém řeší prostory jmen? Co je to anonymní prostor jmen?
Jazyk C++ 1
5
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
6