Cesta k C++
Marek Libra
Verze ze dne: 2.9.1998 Díly: 1 - 6
Historie C++ Jazyk C vznikl roku 1972 u firmy AT&T pod taktovkou Dennise Ritchieho. Jazyk byl vyvinut puvodne pro Unix (je zajímavé, že vlastne Unix v nem byl i napsán). V roce 1978 prišla další verze, tentokrát s názvem Kernighan-Ritchie C. První norma vznikla v r. 1984 - ANSI C, další pak roku 1990. Jazyk C++ je jazyk vycházející z C, ale je doplnen o plno nových lepších prvku - tím je napríklad možnost používat naplno objektovou technologii programování (ta v C šla taky, ale bylo to hodne slabý). Nyní se již nové normy ANSI C neobjevují, zlepšují se totiž normy ANSI C++, který dnes již úplne nahradil starý C (vše co jde v C, jde i v C++ a leckdy daleko rychleji). Z hlediska prekladacu mám nejradeji Borlandské, protože mají nejsnáze ovladatelné prostredí, dobré "urychlovací" nástroje, nové verze splnují mezi prvními nové verze norem a firma má dobrou podporu. Pri výkladu budu vycházet tedy z Borlandského C++, ale v 99% prípadu bude jedno, který používáte. Program v jazyce C++ se ukládá do souboru *.C nebo *.CPP. Do techto základních souboru se mohou vkládat hlavickové soubory *.h. Ostatní soubory se tak casto nepoužívají, nebo jsou používány u složitejších programu. Pro jednoduché programy pro DOS stací otevrít nový soubor a zacít psát program. Programy pro Windows mívají již složitejší strukturu, takže jsou vytváreny pomocí tzv. Project souboru - prípona PRJ, u novejších prekladacu IDE (možno také vytváret Makesoubory, které, pokud vím, využívají Microsoftí prekladace). Tyto soubory jsou vytváreny prekladacem na základe vašich voleb. Obsahují jména souboru, které se mají slinkovat do EXE souboru a podobne. Ze zacátku budeme vycházet jenom z té jednodušší varianty, tedy ze samostatných CPP souboru.
Struktura programu Jazyk C++ je daleko volnejší než treba Pascal, co se týce struktury souboru. V Pascalu je soubor rozdelen do nekolika úseku (hlavicka programu, deklarace typu, konstant, promenných atd. ). V C++ tomu tak není. V podstate je jedno, kde deklarujete promennou nebo nový typ. Její platnost zacíná od místa deklarace a koncí koncem bloku, v nemž je deklarovaná. Naopak si musíte dávat pozor na psaní - C prekladac rozeznává velká a malá písmena, to znamená, že literály "Pepa" a "PEPA" nejsou stejné. V tomto delají Pascalisti rádi chyby. V C++ jsou rozeznávány následující základní termíny: • • • • • •
Klícová slova Konstanty Operátory Identifikátory Náveští Komentáre
Klícová slova jsou jednoduše receno príkazy jazyka, které jsou presne dány, nedají se predefinovat. Konstanty se delí na numerické, znakové a textové (literály), enumerické, ... Je to obdoba promenných, které mají ovšem po zadefinování nemennou hodnotu. Príkladem operátoru je napríklad operátor pro scítání (+).
Studna - Programátorské zdroje
1
Cesta k C++
Marek Libra
U identifikátoru musí být první písmeno, další znaky mohou být císla nebo písmena. Identifikují jména ruzných promenných, typu a funkcí. Náveští je urcitý text v programu, na který je "odskakováno" po zavolání príkazu skoku (goto). Do komentáru mužete vložit jakoukoli vaši poznámku k programu. Komentár je uvozen bud znaky // (dve lomítka) nebo /* (lomítko a hned za ním hvezdicka). Komentár uvozený // má platnost od uvedení lomítek až do konce rádku, komentár /* má platnost od tohoto zacátku až do místa, kde jsou znaky */ (hvezdicka a lomítko), které identifikují konec komentáre. Textu v komentárích si prekladac vubec nevšímá. Príkazy se sdružují do bloku { (zacátek - Pascalský begin) } (konec - Pascalský end) Príklad: {
}
// Zacátek nejakého bloku príkaz1; príkaz2; ... príkazn; // Konec nejakého bloku
Jak jste si mohli všimnout, každý príkaz je ukoncen stredníkem. Na jednom rádku muže být za sebou i nekolik príkazu, všechny však musí být oddeleny stredníkem (príkaz1; príkaz2; príkaz3).
Deklarace funkce V C++ mužete (spíše musíte, jinak byste se v programu leckdy nevyznali) používat funkce. Procedury (z Pascalu) v C++ neexistují. Máte zde pouze funkce. Obecná deklarace funkce je následující
<jméno funkce> ( [parametry funkce] ) Typ udává, jaký typ dat se bude vracet, uveden musí být vždy. Jméno funkce udává jméno funkce, kterým ji budeme dále v programu volat. Za jménem jsou vždy závorky. Do nich se mohou vložit parametry. Parametry mají predem stanovený typ (ten se stanový pri deklaraci), pri volání musí být do závorek uvedeny promenné nebo prímo hodnoty odpovídající jednotlivým typum. Tyto hodnoty za urcitých speciálních podmínek nemusí být pri volání uvádeny, ale k tomu se dostaneme pozdeji. Pokud chcete zadefinovat funkce, která nebude vracet žádnou hodnotu, jako typ použijte klícové slovo void. Taková deklarace vypadá treba takto: void Funkce1( int Parametr1, int Parametr2 ) { } Takto jsme si zadefinovali funkci Funkce1, která nevrací žádnou hodnotu a jako parametry má dve promenné typu int (tento typ je obdobou Pascalského integeru, tedy 2-bytového císla). Tato funkce ale nic nevykoná, pri prekladu by vás mel prekladac varovat, že promenné Parametr1 a Parametr2 nejsou využity. Tato funkce je klasickým príkladem na mrtví úsek programu. Stejne tak bychom si mohli zadeklarovat funkci Funkce2, která bude vracet hodnotu typu int a bude bez parametru: int Funkce2() { int VracenaHodnota;
Studna - Programátorské zdroje
2
Cesta k C++
Marek Libra
return VracenaHodnota; } Tato funkce také ješte nic neudelá. V tele funkce je deklarována promenná VracenaHodnota typu int a na konci funkce je klícové slovo (return), které ríká, že telo funkce koncí a co se má vrátit. V našem prípade se bude vracet hodnota v promenné VracenaHodnota. Klícové slovo return musí být uvedeno u každé funkce, která nemá návratový typ void. Void-funkce nesmí obsahovat return. Kvuli prehlednosti se nekdy zadeklaruje pouze na príslušném míste prototyp funkce a nekde jinde, kde se to hodí, se napíše celá deklarace. Tak napríklad kdybychom to chteli udelat u funkce Funkce2, vypadalo by to takto: int Funkce2(); /* Toto je prototyp, takže zde musí být stredník. Výsledne se chová, jakoby deklarace byla tady */ int Funkce2() /* Zde již stredník není, tato deklarace muže být uvedena, ke se vám to hodí */ { int VracenaHodnota; return VracenaHodnota; } Pokud máte funkci s parametry, stací do závorek napsat pouze jejich typ, názvy se psát nemusí (když je napíšete, nic se nestane).
Výpis na obrazovku Nyní je na case, abychom si ukázali, jak se dá vypsat jednoduchý text na obrazovku. Ješte malou poznámku - ze zacátku se budeme zabývat programováním pro DOS, pro Windows se výstup na obrazovku nebo základní programový blok deklaruje trochu jinak. Pokud chcete priložit k programu nejaký hlavickový soubor, udeláte to pomocí direktivy #include <jméno souboru> Znaménka < a > mohou být nahrazena uvozovkami. Pomocí techto souboru se napríklad rozširuje príkazová škála jazyka, avšak využití techto souboru je mnohem širší. Funkce, které budeme používat pro výstup na obrazovku jsou definovány v souboru stdio.h, napíšeme tedy na zacátek souboru *.CPP (ten si pojmenujte jak chcete, v príloze je tento program prepsán a má název cpp01.cpp): #include <stdio.h> Tento rádek lze uvést kdekoli v programu (vždy však pred prvním použití funkcí nebo typu definovaných v .H souboru), ale pro prehlednost je zvykem dávat direktivy #include na zacátek souboru. Základní programový blok, který se spustí hned po spuštení programu (samozrejme po provedení nezbytných úvodních úkolu) je funkce main, která má libovolný návratový typ a libovolné parametry. Je ovšem zvykem dávat návratový typ jako void. Parametry main funkce jsou parametry, které se zadávají pri spouštení programu z príkazové rádky za EXE soubor. Program pro výpis pozdravu by mohl vypadat treba takto: #include <stdio.h> void main() { printf( "Ahoj\n" ); printf( "Dnes máme ale skvelé pocasí" ); } Funkce printf je definována v souboru stdio.h . Umožnuje výpis formátovaného textu na obrazovku. Kdybychom chteli na monitor dostat hodnotu promenné x, mužeme to udelat takto: Studna - Programátorské zdroje
3
Cesta k C++
Marek Libra
printf( "\nHodnota promenné x je:
%d", x );
Znak uvedený za jedním zpetným lomítkem (\) udává nejakou cinnost, která se má provést. Napríklad písmeno n indikuje, že se má odrádkovat (enter). Pokud chcete k zobrazovanému textu pripojit nejakou promennou, zadejte na místo, kde má být, znak procenta a za nej písmeno indikující daný typ (pro typ int je to znak d). Jakmile ukoncíte literál k vytisknutí pomocí uvozovek, napište cárku a za ní v poradí, v jakém jste promenné v literálu uvádeli, jména promenný, jejichž hodnoty se mají za procento+znak dosadit (v našem príkladu se místo %d vypíše hodnota promenné x. Pro ty, kterí precházejí z Pascalu se to bude asi zdát strašne neprehledné a nepohodlné, ale verte mi, že si na to brzy zvyknete a naucíte se to rychle císt. Takováto syntaxe totiž šetrí hodne místa v programu. Nyní uvádím nejpoužívanejší hodnoty uvádené za "procento" a zpetné lomítko: Sekvence: \n \r \f \t \b \a \\ \´ \0 \" Sekvence: %c %d %d %u %lu %f %Lf %lf %x %X %o %s
Hex. hodnota: Význam: 0x0A nová rádka 0x0D návrat na zacátek rádky 0x0C nová stránka 0x09 tabulátor 0x08 posun doleva (backspace) 0x07 písknutí 0x5C 1 zpetné lomítko 0x2C apostrof 0x00 nulový znak uvozovky Typ: znak signed int signed long unsigned int unsigned long float - muže být i double long double double hexdec.císlo malými písmeny (2b3a) hexdec. císlo velkými písmeny (2B3A) osmickové císlo retezec
Základní typy promenných Zde si uvedeme nejzákladnejší používané typy v C
Typ unsigned char char enum unsigned int short int int unsigned long long float double long double
Studna - Programátorské zdroje
Délka 8 bitu 8 bitu 16 bitu 16 bitu 16 bitu 16 bitu 32 bitu 32 bitu 32 bitu 64 bitu 80 bitu
Rozmezí 0 až 255 -127 až 127 -32.768 až 32.767 0 až 65.535 -32.768 až 32.767 -32.768 až 32.767 0 až 4.294.967.295 -2.147.483.648 až 2.147.483.647 -38 +38 3,4 * 10 až 3,4 * 10 -308 1,7 * 10 až 1,7 * 10+308 -4932 3,4 * 10 až 3,4 * 10+4932
4
Cesta k C++
Marek Libra
Tyto typy platí pro 16-bitové prekladace. 32-bitové mají tyto a nekteré další, ale ty nejsou tak duležité. Pascalisti pozor! - v Pascalu je jeden standardní typ integer, který slucuje 3 Céckovské - int, long int, short int. Typ enum, nekdy nazývaný jako výctový, cástecne nahrazuje Pascalské set. Pomocí enum si mužete definovat nové typy, které budou vycházet standardne z int, ale mají omezenou množinu hodnot, jichž mohou nabývat. Takto si mužeme napríklad zadefinovat logický typ boolean, který není standardne Céckem podporován. Jeho deklarace je jednoduchá: enum boolean {false, true}; Za jméno nového typu (boolean) se uvádí složené závorky, do nichž se vpisují hodnoty, kterých muže typ nabívat. Ve své podstate je enum typem císelným, to znamená, že každá hodnota predstavuje urcité císlo (false je 0, true je 1). Zde jsem císelné hodnoty uvádet nemuseli, prekladac to za nás udelá sám, zacne nulou a pak pokracuje po jedné výše. Pokud byste chteli zacít jiným císlem, stací napsat treba false=5, true . Hodnote true již nemusíte zadávat hodnotu 6, ale když ji napíšete, nic se nestane, jen vám to usnadní orientaci.
Definice promenných Obecne se to dá napsat takto: typ jméno [=hodnota], ... ; U deklarace musí být typ uveden vždy a jméno také. Volitelne pri deklaraci mužete uvést i pocátecní hodnotu promenné. Po dokoncení deklarace mužete napsat cárku a za ni jméno další promenné, která bude téhož typu jako predchozí. Pokud máte nadeklarovány všechny promenné, které na jednom rádku chcete mít, udelejte stredník. Príklad: int Promenná1, Promenná2=5; char Znak; int Promenná3; Z tohoto príkladu je patrné, že deklaraci promenných téhož typu mužete rozložit do nekolika rádku. To by bylo pro dnešek asi vše. Príšte si povíme neco o operacích s promennými a jiných dalších základech.
Konstanty Konstanty v C mužete definovat dvema zpusoby: • •
pomocí klícového slova const pomocí direktivy #define
Pomocí const je pouze udáno, že nejaké "promenné" po jejím zadefinování se nesmí menit hodnota. Používání tohoto klícového slova není tak casté, jako #define. Príklady: const int i = 5; #define i 5 Použití const je myslím díky príkladu jasné. Za direktivou #define se uvádí konstanta, kterou chceme zadefinovat a za ni (oddeleno mezerami nebo tabulátorem) její hodnota. S typem se zde nepracuje, protože všude tam, kde takto definovanou konstantu použijete, se vloží její hodnota. Tyto konstanty jsou v C++ velmi casto používány. Pro zatvrzelé Pascalisty muže být užitecné toto: #define begin #define end }
{
Studna - Programátorské zdroje
5
Cesta k C++
Marek Libra
Když tuto definici uvedete na zacátku programu, budete moci potom používat všude v programu vaše oblíbené begin a end, aniž byste se museli bát, že si to spletete s poznámkou.
Operace promennými Základní operace jsou totožné jako u ostatních jazyku (scítání, odecítání, násobení, delení atd.). Oproti Pascalu zde jsou jen menší rozdíly. U prirazování k promenné p se místo := používá pouze = (jedno rovnítko). K porovnávání se nepoužívá jedno rovnítko, ale 2 uvedené hned za sebou (x==y). Príklad: p = 10; p = p +1; Pokud prirazujete znak (ne retezec), vkládá se do apostrofu (char c = ´x´). Prirazování muže být i nekolikanásobné: a = b = c = d = 3; V tomto prípade mají promenné a, b, c, d stejnou hodnotu (3). Pro prictení jednicky k promenné se používají dva plusy: p++; // tento výraz je totožný se zápisem p = p +1; To samé platí i pro odecítání jednicky: p--; // totožné s p = p -1; Tyto výrazy se dají používat jako prefix nebo suffix (p++ nebo ++p). Rozdíl je patrný, pokud tyto operátory zamícháte do složitejšího výrazu. p = 12 + b + c++; // do p se dosadí soucet 12 + b +c. Nakonec se c zvetší o jednicku p = 12 + b + ++c; // c se zmení o jednicku, do p se dosadí soucet 12 + b + (c+1) Pokud pricítáte k p nejakou hodnotu nebo výraz a výsledek ukládáte do p, dá se to napsat takto: p+=12; // totožné s p=p+12; p+=a+b*c-50; // totožné s p= p + (a+b*c-50). Závorky uvádím pro názornost Na tomto míste by bylo možná vhodné si zavést další pojem, je to l-hodnota. L-hodnota je adresa (napr. promenná p), ovšem konstanty (12) nebo výrazy (a+12) l-hodnotami nejsou. L-hodnota je tedy to, co mužeme dát na levou stranu prirazení. Krome výše uvedeného prirazovacích operátoru (+=), C zná i tyto: -=, *=, /=, %=, >>=, <<=, &=, ^=, |=. Význam binárních operátoru: Funkce scítání odecítání násobení reálné delení celocíselné delení delení modulo
Studna - Programátorské zdroje
Pascalský operátor + * / DIV MOD
6
C operátor + * / / %
Cesta k C++
Marek Libra
V C operátory >> a << slouží k bitovému posunu (doprava a doleva). Operace p << 2 posune bity v p o 2 místa doleva, puvodní první dva bity se vymažou a z druhého konce se nahradí 0. To samé platí i pro >> (ale obrácene, posouvá se doprava). Rozdelování celocíselného a reálného delení je provádeno automaticky (rozhodování o tom, jaký typ delení to má být je logické a není potreba ho nejak zvlášt definovat): int / int int / float float / int float / float
celocíselné reálné reálné reálné
Protože double a long double jsou desetinná (stejne jako float), uvádel jsem pouze float, ve skutecnosti platí to samé i pro ne.
Vstupy Nejprve se budu zabývat formátovaným vstupem - funkce scanf(). V podstate je to takový protiklad k funkci printf(). Je také definována v stdio.h. Platí pro ni podobné podmínky jako pro funkce printf(). Její syntaxe je: scanf( formát, parametry ); Za formát se dosazuje textový retezec, který specifikuje urcitou vkládanou hodnotu. Pro vstup císla int by to mohlo vypadat takto: scanf( "%d", &p); - p musí být promenná typu int Pred jméno promenné se dává znak &, který zapríciní, že je predávána adresa promenné, kde je v pameti uložena (ukazatel - viz muj clánek v nekterém predcházejícím císle, možná se k nim casem ješte vrátím). Príklad na tuto funkci najdete v souboru cpp02_01.cpp. Dalším príkazem pro vstup je príkaz getchar(), který precte pouze jeden znak. Je bez parametru, nactený znak dává pres návratovou hodnotu. Pozor - tato funkce nemá návratovou hodnotu typu char, nýbrž int! Ve své podstate je znak char numerický, tedy není textový. Veškeré operace s tímto typem se dejí na základe matematických operací s císly (numerickými hodnotami). Po skoncení operací je císlo (0-255) prevedeno podle práve aktivní ASCII tabulky na znakovou hodnotu. Typ char je tedy celocíselný jednobytový (=> rozmezí 0-255). Príkaz getchar() má návratovou hodnotu typu int kvuli tomu, že se také používá u operací se soubory (k tomu ale až potom). Pokud ovšem od uživatele cekáte znak (max. hodnota 255), stací když si zadefinujete promennou "znak" typu char. C++ se totiž vždy snaží zkonvertovat nejaký typ na jiný, když je to potreba. Nekdy, když to prekladac neudelá automaticky za nás, je potreba napsat pred promennou konvertovaného typu do závorek typ, na který budeme prevádet. Takže definice char znak; int cislo; cislo = 5; znak = cislo; /* zde se nemusí uvádet, na co budeme konvertovat, ale definice znak = (char) cislo by byla prehlednejší */ zapíše do promenné typu char hodnotu 5, kterou prebrala z promenné typu int. V 95% prípadu se konvertovat dá, vždy to nejak dopadne, nekdy úspešne, nekdy ne. Kdybychom do cislo vložili 300 hodnota je mimo rozsah typu char), do znak se uloží 44. Pokud je totiž konvertovaná hodnota mimo rozsah, "vymaže se" zacátek, a vloží se jen to, co se do promenné vejde (zde se pocítalo: 300 - pocet prvku v char = 44). Z toho platí, že když napíšete char znak;
Studna - Programátorské zdroje
7
Cesta k C++
Marek Libra
znak = getchar(); a vstupní zarízení bude klávesnice, bude vše Ok. Pokud by byl vstup ze souboru a hodnota by byla vetší než 255, provede se konverze. Jak tedy budete tuto funkci používat, nechám na Vás. Jako scanf() má svuj opak ve funkci printf(), funkce getchar() má opacnou funkci putchar(). Tato funkce vyšle jeden znak na výstupní zarízení, tento znak (hodnotu) dostává parametrem (int putchar(int znak) ). Pokud funkce probehne v porádku, vrátí se hodnota "znak", jinak se vrátí hodnota EOF (end of file, k tomu až nekdy pozdeji. EOF je konstanta definovaná v stdio.h). Pokud chcete na výstupní zarízení poslat znak (ne císlo), vložte ho do apostrofu ('p'). Pozor - literály (retezce) se uvádejí v uvozovkách, samostatné znaky v apostrofech.
Deklarace nového typu Nový typ se v C++ dá zadefinovat pomocí klícového slova typedef. Jeho syntaxe je: typedef
starý
nový;
Z této syntaxe plyne, že deklarace nového typu je vlastne pouze prejmenování starého. Má to význam napr. u typu s dlouhým jménem (unsigned long int a pod.). Deklarace tohoto typu by mohla vypadat treba takto: typedef unsigned long int NovyTyp; Tímto zpusobem zkrátíme dlouhý zápis na malý NovyTyp. V programu pak stací napsat NovyTyp NovaPromenna; a promenná NovaPromenna bude mít stejné vlastnosti jako jiná, která je typu unsigned long int. Možná si ríkáte, že je to k nicemu, ale obcas se to hodí (treba u struktur, ale tam je to spíše pro prehlednost).
Rízení behu programu Úplné základy jsou za námi. Ted se budeme venovat již zajímavejším záležitostem programování. Stejne jako ve vetšine jiných programovacích jazycích, tak i v C nemusí program jít "monotónne" za sebou, ale je možné ho urcitým zpusobem rídit. K tomu slouží plno druhu podmínek, smycek a skoku.
Podmínky Podmínky umožnují provést urcitý príkaz (množinu príkazu) pouze za urcitých podmínek. Asi nejvíce používanou je podmínka if. Stejne jako v Pascalu se její provádení delí na dve cásti - první, pokud podmínka je true a na druhou, uvozenou klícovým slovem else, pokud je podmínka rovna false. V C neexistuje neco jako then (z Pascalu ci Basicu), ale za podmínkou (uvedenou v závorkách) je rovnou uveden príkaz, poprípade blok príkazu. Po skoncení bloku se normálne uvede else a pokracuje se dalším príkazem nebo blokem. Pokud za if uvedete pouze 1 príkaz, je ukoncen stredníkem (neplést s Pascalem!). Slovo else se, pokud není potreba, uvádet nemusí. Príklad: int x=5; if (x==10) printf( "To je ale blbost"); else printf("To je ono!"); Už jsem se o tom zminoval, ale pro prirazení se používá jedno rovnítko (=) a pro porovnání dve za sebou (==). Pokud by jste chteli vyjádrit opak (jestliže se x nerovná 10), použijte operátor !=. Vykricník by se v C dal charakterizovat jako operátor negace. Nekolik podmínek mužete mísit v jednu, treba "jestliže x=10 a y=20" se napíše: if (x==10 && y==20)
Studna - Programátorské zdroje
8
Cesta k C++
Marek Libra
Jednotlivé "podpodmínky" se nemusí závorkovat pokud k tomu není logistický duvod. Operátor && je operátor odpovídající Pascalskému AND. Operátor || odpovídá OR. Krome podmíneného príkazu if existuje v C také tzv. podmínený výraz, který Pascal nemá. Je to obdoba príkazu if, ale jsou použity speciální znaky. Syntaxe je: podmínka ? výraz1 : výraz2; kde podmínka je podmínka, která, je-li rovna true, vyvolá provedení výrazu1. Pokud je nepravdivá, provede se výraz2. Odpovídá to: if (podmínka) výraz1; else výraz2; Použití je vhodné napríklad v prirazování: int a, b, c; b=2; c=5; a = (b==2) ? 10 : 20;
// v tomto prípade totéž co a=10
Jak vidíte, velmi se zkrátí zdrojový kód, ovšem pro méne zkušeného "ctenáre instrukcí" je to velmi neprehledné, proto se casteji používá if. Náš príklad by pak vypadal: int a,b,c; b=2; c=5; if (b==2) a = 10; else a = 20;
Cykly Co je to cyklus asi víte, takže se tím nebudu zabývat. K implementaci cyklu do C-programu mužete použít 3 základních zpusobu (záleží na tom, co považujete za príkaz skoku a co za cyklus). Nejjednodušším je while. Z jeho syntaxe while (podmínka) príkaz plyne, že podmínku, za které se príkaz (blok príkazu), který následuje za závorkou, provede, je v závorkách. Tato podmínka muže být i složitejší a pro dané podpodmínky platí to samé jako pro if. Podmínka se testuje pri vstupu do cyklu a následne po vykonání príkazu - pokud je true, jede se s provádením príkazu znovu. Z toho plyne, že se telo cyklu nemusí provést ani jednou (pokud podmínka je hned na zacátku pravdivá). Použití tohoto cyklu je v nepreberném množství príkazu, nejjednodušším je asi cekání na stisk požadované klávesy. Príklad je v cpp02_02.cpp. Dalším je cyklus do-while. Je to obdoba while, ale testování podmínky se provádí až na konci, takže príkaz (blok príkazu) v tele cyklu se vždy provede alespon jednou. Syntaxe je: do prikaz; // muže být blok príkazu ve složených závorkách while (podmínka) ; Cyklus je uvozen samostatným slovem do, za ním následuje príkaz a nakonec klícové slovo while s podmínkou v závorkách. Na konci rádku je stredník. Príklad je v cpp02_02.cpp. Zrejme nejpoužívanejším príkazem cyklu je for. Jeho syntaxe je: for (príkaz00;podmínka;príkaz01) príkaz1; // poprípade blok
Studna - Programátorské zdroje
9
Cesta k C++
Marek Libra
Za uvedením klícového slova for jsou v závorce uvedeny 3 "parametry" (mám to v uvozovkách, protože za parametry v plném slovasmyslu se to považovat nedá). Príkaz00 je vykonáván vždy, než se vstoupí do cyklu. Za celou dobu trvání (i když se opakuje 5x) je prováden jen jedenkrát. Podmínka indikuje, zda se má cyklus opakovat nebo ne. Príkaz01 je prováden vždy na zacátku dalšího provádení (pokud se príkaz1 provádí 5x, príkaz01 se provede také 5x). Príkaz1 muže být nahrazen blokem príkazu. Príkaz00 obvykle obsahuje pocátecní inicializaci cítace a príkaz01 operaci s ním. Príklad: int i; for (i=0;i<10;i++) printf( "Hodnota cítace:
%d", i );
V tomto príkladu se vypíše 10 zpráv. První bude "Hodnota cítace: 0". Hodnota bude stoupat, až se dostane na 9, kde cyklus skoncí. Hodnota 10 se již nevypíše, protože po skoncení 10. cyklu (i==9) se pricte do i jednicka (i==10). To již nesplnuje podmínku i<10, protože i==10. Kvuli tomu se cyklus ukoncí. Pokud byste chteli i zvetšovat po 5, místo i++ napište i = i +5. Pokud byste chteli používat promennou i pouze v tele cyklu a nikde jinde, mužete napsat místo i=0 int i=0 a nad smycku nepsat int i. Tímto se zadefinuje promenná i pouze pro tento cyklus. Po skoncení cyklu bude volání promenné i považováno za neplatné (samozrejme pokud není zadefinována ješte nekdy jinde). Tato syntaxe je podporována až novejšími verzemi ANSI C++ (pokud vím, v C to nešlo). V BC++ 5.0 a vyšších je platnost takto deklarované promenné také za telem cyklu až do skoncení aktuálního bloku (zda to prikazuje nejnovejší verze ANSI C++, nebo je to nepovinné vylepšení prekladace nevím, ted se mi to plete).
Skoky Mezi príkazy skoku patrí klícové slovo break. To umožnuje preskocení na konec aktuálního bloku a tím vynechání všech príkazu od aktuálního místa až do konce bloku. Príkaz goto. Tento príkaz umožnuje skok z nejakého místa na místo jiné. Je to ovšem omezeno pouze na blok jedné funkce (je zakázáno skákat z funkce A do funkce B pomocí tohoto príkazu). Za príkaz se uvádí název tzv. náveští. Náveští je deklarováno nekde jinde v programu. Ze syntaxe príkazu navesti: ... /* programový kód */ goto navesti; plyne, že se skocí na místo, které je uvozeno príslušným náveštím navesti. Na rozdíl od Pascalu se nemusí deklarovat na zacátku (pres label, pokud si dobre vzpomínám). Stací když na zvolené místo napíšete jméno náveští a za nej dvojtecku (:). Za príkaz goto pak napište jeho jméno a stredník. Používání techto skoku je proti logice jazyka C, proto by se meli používat jen v nevyhnutelném prípade. Castejší používání také zhoršuje optimalizaci kódu programu a zpomaluje beh aplikace. Dále používání tohoto príkazu velmi zneprehlednuje zdrojový kód. Když je ale budete používat, dávejte si pozor na správné urcení místa "doskoku". Napríklad, když budete skákat doprostred nejakého bloku, obycejne vynecháte místa pro inicializaci promenných a muže se stát, že nejaká promenná nebude mít požadovanou hodnotu (v lepším prípade) nebo nebude zadefinována vubec (to už bude horší). Tu poslední chybu dokáže odhalit jen málo prekladacu hned pri kompilaci. Pokud precházíte z Pascalu, kde používání tohoto príkazu bylo zcela bežné, pokuste se odnaucit ho používat, k C to moc nepatrí a v 99,5% prípadu se tomu dá vyhnout pomocí jiné C syntaxe. Príkaz switch umožnuje provedení urcité množiny príkazu pouze v prípade, že nejaká promenná obsahuje urcitou hodnotu. Syntaxe je: switch (promenná) { case hodnota1: Studna - Programátorské zdroje
10
Cesta k C++
Marek Libra case hodnota2: ... case hodnotaN: default:
} Promenná je promenná, která se bude porovnávat s urcitými hodnotami, které se uvádejí za klícová slova case. Za hodnotu se dává dvojtecka, za kterou se píše blok príkazu (který nemusí být oddelen složenými závorkami, takže blok se tomu vlastne ani ríkat nedá), který obvykle koncí príkazem break. Pokud byste break neuvedli, pokracovalo by se v provádení príkazu u dalšího case-bloku (popr. default), dokud se nenarazí na nejakém break. Za default se nedává žádná hodnota, tato cást se provádí, pokud promenná není shodná s žádnou hodnotou uvedenou za case. Príklad najdete v souboru cpp02_02.cpp.
Pole Obecná deklarace pro pole je asi tato: [pam. trída]
typ
jméno[pocet prvku]
V prvním díle seriálu jsem uvádel, že promenná se deklaruje zpusobem: typ
promenná
Ve skutecnosti je deklarace podobná s deklarací polí, tedy alespon na zacátku. Správná obecná deklarace je taková: [pam. trída]
typ
jméno
Stejne jako u polí se pamet ová trída udávat nemusí, automaticky je pak brána jako auto. Tato trída je používána u lokálních promenných(promenných, které jsou platné pouze v aktuálním bloku a jeho podblocích; mimo je neplatná). Další pamet ovou trídou je trída register. Tato trída má opet smysl pouze u lokálních promenných. Udává, že nejaká promenná má být uložena do registru procesoru místo do pameti. Presné umístení a to, zda ji vubec do registru uložit jde, si zvolí prekladac sám. Na takto deklarovanou promennou nelze vytvorit ukazatel. Výhoda je jasná - prístup k hodnote je v podstate v case s nulovou prodlevou. od zažádání procesoru. Trída static má urcité vlastnosti globálních promenných (promenných s platností ve všech blocích programu), tedy pri znovuvstoupení do urcitého bloku programu, kde byla deklarována, má stejnou hodnotu, jako když blok opouštela. Rozdíl mezi static a globální promennou je v tom, že se na ni mužeme odkazovat pouze v bloku, kde je deklarována, prestože je po deklaraci až do konce programu stále v pameti. Trída extern vytvárí odkaz na již existující globální promennou. Praktické využití je ve vícemodulovém programu (programu, který se skládá z více než 1 programového souboru - u BC++ je to nutné vytvorit pres projekt). Máte-li projekt skládající se ze 2 modulu (A.cpp a B.cpp) a v modulu A.cpp si zadeklarujete promennou i typu int, se kterou chvilku pracujete (zmení se její hodnota, aby tento príklad mel praktický význam), a potom se volá nejaká funkce deklarovaná v modulu B.cpp, která bude potrebovat hodnotu v i, ale z nejakého duvodu jí tuto hodnotu nebudete moci predat pres parametr, zadeklarujete si v modulu B.cpp promennou i typu int a s pamet ovou trídou extern. Potom s touto promennou již budete moci bez problému pracovat. Nyní zpet k polím. Pole má stejnou deklaraci jako promenná, ale za jménem se udává do hranatých závorek ( [ ] ) velikost jednoho rozmeru pole. Z toho plyne, že lze vytvorit i vícerozmerná pole. Velikost každého rozmeru se potom vkládá do vlastních hranatých závorek. Deklarace dvourozmerného pole s typy prvku int s velikostí 10x5 polí vypadá takto: int Pole[10][5];
Studna - Programátorské zdroje
11
Cesta k C++
Marek Libra
Prístup k jednotlivým prvkum je stejný jako v Pascalu - pres složené závorky (Pole[3][2]=15;). Pole se zacíná zaplnovat od nuly a poslední prvek v každém sloupci nebo rádku programátor nepoužívá. Velikost pole, stejne jako velikost promenné, nesmí presáhnout velikost jednoho segmentu (u 16bitových prekladacu to je 64kb u 32bitových to jsou 4Gb, což pro malé a strední programy je takrka neomezená hranice). Stejne jako promenné, i pole se mohou inicializovat hned pri deklaraci: int Pole1[5] = {12,0,5,4,3}; int Pole2[] = { 4, 3, 2 }; int Pole3[5] = { 5, 4, 3 }; U Pole1 je to asi jasné - do Pole1[0] se uloží 12 a tak to pujde dál až na konec pole. Pole2 se automaticky vytvorí na nejmenší možnou hodnotu - tedy na pole o trech využitelných prvcích (Pole[3]). Pole5 se inicializuje od nultého prvku (Pole3[0]) až po tolikátý, kolik máte zadáno hodnot. Zbytek prvku zustane nezmeneno (tedy v nich bude taková hodnota, jaká je v pamet ových bunkách již z drívejška od jiné promenné). Na to si dávejte pozor i u promenných, doporucuji nespoléhat na to, že po deklaraci je hodnota 0, je to ve velmi malém množství prípadu. Príklad na pole je v souboru cpp03_02.cpp.
Preprocessor Preprocessor je cást prekladace, která zpracovává zdrojový kód ješte pred vlastním prekladacem. Tento kód pripravuje pro prekladac. Preprocessor si z programu "vybírá" pouze príkazy uvozené znakem #. Tento znak uvozuje tzv. direktivu (príkaz preprocessoru). Príkaz pro preprocessor je ukoncen enterem, nikoli stredníkem. Bez techto direktiv se sice programátor obejde (program lze napsat i bez nich), ale velmi casto se používají, zejména pro urychlení prekladu, programového kódu, zamezení prekladu na nevyhovujícím prekladaci atd. Preprocessor také vkládá jiné soubory do základního programového souboru, definuje ruzná makra, která také zpracovává. Již dríve jsem uvedl, že direktiva #define je urcena pro definici konstant. V podstate slouží k zadefinování makra. Pokud byste chteli v urcitém míste programu zrušit platnost makra, použijte direktivu #undef. Príklad: #define Makro1 18+15 x=Makro1; // v porádku ... #undef Makro1 x=Makro1; // chyba, Makro1 je neznámý termín
Testování existence a obsahu makra Využití otestování, zda je nejaké makro definováno, je rozsáhlé. Nejcasteji se však používá pro testování, zda je nejaký soubor již vložen (pomocí direktivy #include). Slouží k zamezení prekladu nejaké cásti programu, pokud není potreba. Pokud chcete rozdelit prekládaný text jen na dva bloky, slouží k tomu 3 príkazy: #if, #else, #endif. #if podmínka .... #else .... #endif Pokud je podmínka splnena, preloží se blok mezi #if a #else, jinak se preloží blok mezi #else a #endif. Studna - Programátorské zdroje
12
Cesta k C++
Marek Libra
Direktiva #else se uvádet nemusí (záleží na tom, zda je k tomu logický duvod). Pokud je potreba text rozdelit na více než dve cásti, slouží k tomu príkazy #if, #elif, #endif. #if podmínka ... #elif podmínka ... #elif podmínka ... #endif V podmínce je možno použít všech operátoru z C++ (==, !=, <=, ...) . Navíc je možno použít operátoru defined, který otestuje existenci nejakého makra. Jeho syntaxe je: defined( makro ) Tento operátor se velmi casto používá spolu s operátorem !: !defined(makro)
Direktiva #include Pomocí této direktivy vložíte do základního programového textu text z jiného souboru (nejcasteji z hlavickového). Jméno vkládaného souboru se uvádí bud do uvozovek nebo do znamének vetší menší (< >). Do uvozovek se nejcasteji vkládají soubory vytvorené programátorem, protože takto definovaný soubor je hledán i v aktuálním adresári (soubor uvedený v < > se hledá pouze v zadefinovaných cestách - u BC++ 4,52 to je v Options|Projekt|Directories ). Na tomto míste by také bylo vhodné se zmínit o takzvaných Falešných makrech. To jsou makra, která jsou sice zadefinována, ale nemají žádnou hodnotu. Napr.: #define __math_h U tohoto makra není definována hodnota. Dá se použít napríklad pro otestování, zda nejaký hlavickový soubor již byl vkládán nebo ne. U vetšiny hlavickových souboru mužete nalézt toto: #if !defined(__math_h) #define __math_h .... #endif /* end of file */ Zde jsem použil príklad ze souboru math.h. Než se soubor vloží do programu, otestuje se, zda neexistuje makro __math_h. Pokud ne, zadefinuje se (bez udané hodnoty) a dále se zpracuje kód mezi #if a #endif. Za #endif je již uveden znak pro konec souboru, tedy konec hlavickového souboru. Toho se dá využít pri ochrane proti dvojnásobnému zavádení kódu, což je zbytecné. Když potom nekde použijete #include <MATH.H> #include <MATH.H> soubor math.h se zavede pouze jednou. Možná se Vám to jeví jako zbytecné, že byste se takto nespletli, ale hodne hlavickových souboru potrebuje pro provedení deklarace vnich uvedené nejaký jiný soubor. Když se náhodou takto sejdou dva hlavickové soubory, které budou požadovat 1 soubor (napr. matika.h a pocty.h požadují math.h), zavede se daný soubor pouze jednou.
Studna - Programátorské zdroje
13
Cesta k C++
Marek Libra
Makra s parametry Pokud si potrebujete vytvorit nejakou jednoduchou funkci, napr. umocnování, je vhodné k tomu použít makro, protože vykonání makra je daleko rychlejší než volání funkce. Pro umocnování by makro vypadalo takto: #define Umocni(n) ((n)*(n)) Pokud byste nekde použily x=Umocni(15), do x se dosadí druhá mocnina 15. Definice možná vypadá složite, ale není. Za jménem makra se uvede v závorkách jméno výrazu (bez typu, jedná se prece o makro). Potom následuje hodnota makra, která se bude vkládat všude tam, kde bude makro voláno (pochopitelne naše n se zmení na výraz). Hodnotu makra jsem hodne "zazávorkoval" kvuli tomu, kdyby nekdo chtel náhodou umocnovat záporné císlo. Parametru makra muže být více, záleží na tom, jaké makro zrovna chcete.
Direktiva #error Kdykoli se narazí na tuto direktivu, vypíše se zpráva (ve zprávách -Message- po kompilaci) uvedená za direktivou. Z logického duvodu je nejcasteji používána spolecne s direktivou #if. Príklad: #if defined(__soubor_h) #error Pokus o znovu zadefinovani souboru #else #define __soubor_h #endif Tento príklad by se málokdy použil, ale pro pochopení této direktivy je velmi dobrý. Casto se používá u 16-ti bitových prekladacu pri testování, pod jakým pamet ovým modelem je program prekládán. Príklad: #ifdef __SMALL__ #error Nelze prelozit pod pametovym modelem SMALL
Preddefinovaná makra Na tomto míste uvádím seznam maker, která jsou implicitne zadefinována (prekladacem): Makro: __BORLANDC__
__TURBOC__ __cplusplus __DATE__ __TIME__ __FILE__ __LINE__ __DLL__ __Windows __OVERLAY__
Význam: Císlo verze prekladace (pouze u Borlandských prekladacu). Verze 3,1 - hodnota 0x410, verze 4,0 a 4,02 - 0x452, verze 4,5 0x460 Císlo verze TC++ a BC++. Hodnoty jsou stejné jako u predchozího makra. Pokud se prekládá pod C++, má hodnotu 1, jinak nedefinováno. Aktuální datum. Aktuální cas. Práve prekládaný soubor - jméno. Práve prekládaná rádka. Pokud se generuje kód do DLL souboru, má hodnotu 1, jinak nedefinováno. Pokud se generuje kód pro Windows, má hodnotu 1, jinak nedefinováno. Pokud se generuje kód DOS aplikace s prekryvnými segmenty, má hodnotu 1, jinak nedefinováno.
Studna - Programátorské zdroje
14
Cesta k C++ __WIN32__ __TINY__
Marek Libra Pri 32bitovém prekladu (GUI i CONSOLE) má hodnotu 1, jinak nedefinováno. Pamet ový model, do kterého je prekládáno je Tiny (obdobne i níže).
__SMALL__ __MEDIUM__ __COMPACT__ __LARGE__ __HUGE__
Pretežování funkcí Preprocessor je za námi. Nyní bych se ješte rád vrátil k funkcím. V 1. Díle seriálu jsem se o nich trochu zminoval, nyní bych to chtel ješte rozvést. Pretežování funkcí je velice užitecná vec, podporuje ji však jenom pár jazyku. Je to v podstate možnost používat v programu nekolik funkcí stejného jména, ale s jinými typy parametru. Prekladac si potom automaticky zvolí tu správnou podle toho, jaký parametr zadáte. Príklad je v souboru cpp03_01.cpp.
Funkce definované v jiném modulu U vetších programu je nezbytné, aby se program skládal z více než jednoho souboru. Nevím, jak je to presne rešeno u Microsoftích nebo Watcomských prekladacu (zrejme nejak pres MAKesoubory). U Borlandských prekladacu jsou tyto soubory sdružovány do tzv. projektu. U verzí starších než 4,00 tyto projekty mohli obsahovat pouze jeden výsledný soubor (DLL, EXE, OVL, COM), u novejších již jeden projekt sdružuje libovolný pocet techto výsledku (target). Každý target muže obsahovat (i u starších verzí) více textových souboru (.cpp, .c, .rc, .def). V každém souboru si mužete definovat jenom urcitou množinu funkcí, objektu,... , které spolu nejak tématicky souvisí (to není podmínkou, ale je to hlavní duvod tohoto delení na více modulu). Pri programování pro Windows se nejcasteji používá každý soubor pro deklaraci nejaké trídy(struktury). Obecná definice trídy (uvedeny pouze prototypy funkcí, definice promenných bez pocátecní inicializace a pod.) se napíše do nejakého hlavickového souboru. Ten se potom uvede na zacátku modulu, ve kterém jsou potom jednotlivé funkce popsány. Když budete v kterémkoli jiném modulu (pouze v rozsahu targetu!) potrebovat tuto trídu (funkci), uvedete na zacátek modulu, že se má vložit hlavickový soubor s obecnou definicí a mužete pohodlne používat vše, co je definováno v jiném modulu. K objektove orientovanému programování se dostanu v príštím díle.
Retezce Retezcová promenná v C++ nemá vyhrazený speciální typ. V C++ retezec je úplne jiný než napr. v Pascalu. Protože retezec je pole znaku, stací zadefinovat pole typu char požadované délky. Príklad: char Retezec1[255]; char Autor[]="Marek Libra"; Za poslední znak retezce se dává znak 0. Funkce urcené k operacím s retezci tuto hodnotu zaplnují sami, pokud ale retezec zaplnujete vy (skládáním jednotlivých písmen), dejte si pozor, abyste na tuto hodnotu nezapomneli. Díky této hodnote je potreba vždy zadefinovat znakové pole o 1 znak vetší.. Díky tomu, že retezec je pole znaku, není správný zápis nebo jemu podobné: char String1[26]; char String2[26]="Nejaký text"; String1=String2; // Chyba!!! Studna - Programátorské zdroje
15
Cesta k C++
Marek Libra
Tento zápis je chybný. Kopírování retezce se provádí pres funkci char *strcpy( char *str1, *char str2 ), která zkopíruje retezec str2 do retezce str1 a vrátí ukazatel na str1. Tato funkce je definována v souboru string.h. Za parametr má 2 ukazatele na retezce. Protože jsem již o ukazatelích v nekterém predcházejícím císle psal, opakovat to nebudu, ale bylo by vhodné si ten clánek precíst, protože dále s tím budu pocítat. Pro formátovaný vstup a výstup má retezec kód %s (scanf("%s", &str ); ).
Další funkce pro práci s retezci Zde uvádím seznam nekterých dalších casto používaných funkcí: Funkce: int strlen( char *str ) char *strcat( char *str1, char *str2) char *strchr( char *str, char znak )
Význam: Vrácení délky retezce bez znaku 0 na konci. Pripojení str2 k str1. Vrací se ukazatel na str1. Vrátí ukazatel na první výskyt znaku "znak" v "str". Pokud se znak nevyskytuje, vrátí se NULL (konstanta pro 0 ). int strcmp( char *str1, char *str2 ) Porovnání retezcu. Záporné císlo, je-li lexikograficky str1 menší než str2, a kladné císlo, je-li str1 vetší než str2. Jsou-li oba retezce stejné, vrací se 0. char *strstr( char *str1, char *str2 ) Nalezení podretezce v retezci. Vrátí ukazatel na první písmeno podretezce str2 vyskytujícího se v str1. Pokud str2 není v str1, vrací se NULL. int atoi( char *str ) Konverze retezce na císlo (typ int). long atol( char *str ) Retezec str na císlo long. double atof( char *str ) Retezec str na císlo double. char *itoa( int, char *str, int Cislic ) Int císlo do retezce str. Cislic udává, kolika ciferné císlo se konvertuje. Vrací se ukazatel na první znak ve zkonvertovaném retezci. char *ltoa( long, char*str, int Cislic ) Jako predchozí, ale konvertuje se císlo typu long. char *ultoa( unsigned long, char *str, int Cislic )Jako predchozí, ale konvertuje se císlo typu unsigned long. Uvádel jsem pouze nepoužívanejší funkce. Pro kompletní výpis funkcí se podívejte do helpu.
Objektove orientované programování V dnešním díle našeho seriálu bych se chtel venovat objektove orientovanému programování (dále jen OOP). Zduraznovat praktický význam tohoto moderního zpusobu je zrejme v tomto casopise zbytecné, rovnež predpokládám, že znáte alespon obecnou terminologii kolem OOP. Pokud ne, rád se k tomu vrátím i treba ve speciálním clánku, protože pochopení technik OOP považuji (a jiste nejen já) za v dnešní dobe velmi duležitou vec. Vetšina z OOP je novinkou v C++, standardní C s tím pracovat neumí (až na drobnosti). Deklaraci objektu lze v C++ provést dvema zpusoby - bud pomocí klícového slova class nebo struct. Pro definici nového objektu se používá zadefinování nového typu k nemuž se pozdeji priradí promenná - požadovaný objekt. Obecne se deklarace dá napsat takto: struct|class typ { // složky } objekt; Tedy, na zacátek uvedete klícové slovo struct nebo class (k rozdílu mezi nimi se dostanu pozdeji), za ne název nového objektového typu typ, za nej do složených závorek definici Studna - Programátorské zdroje
16
Cesta k C++
Marek Libra
složek (metod nebo položek), za tento definicní blok složenou závorku a jméno nového objektu (objektové promenné). Objektových promenných muže být zadefinováno i více, vždy však oddeleny cárkou. Na konec "výctu" jmen promenných se dává stredník (musí se uvést, i když jména promenných neuvádíte). Pri definici mužete vynechat jméno typu nebo objektové promenné (nebo obou). Pokud vynecháte typ a uvedete promenné, zadefinují se promenné, ale pokud budete chtít deklarovat nekde na jiném míste v programu další objektovou promenno se stejnou strukturou, budete muset znovu opsat celou definici objektu. Vynecháte-li jméno promenné a uvedete-li jméno typu, mužete pozdeji pomocí definice typ promenná; /* kde typ je objektový typ shora deklarovaný a promenná je nový objekt */ zadefinovat novou promennou s platností (díky takto napsané definici) pouze v aktuálním bloku. Pro definování objektových promenných platí to samé jako pro definování normálních. Vynecháte-li jméno promenné, musíte (stejne jako by tam byla) uvést za koncovou složenou závorku (}) stredník. Pokud vynecháte typ i promennou, prekladac Vám chybu neohlásí (maximálne nejaké varování, že je to blbost), ale nezadefinujete vubec nic, takže takováto definice je absolutne nelogická. Rozdíl mezi objektem definovaným pomocí klícového slova struct a class je predevším v pocátecním nastavení prístupových práv pro složky (viz. dále). Struct má standardne nastavena práva na public, class na private. Dále je možné vytváret šablony pouze ze tríd (šablona - další vymoženost jazyka C++ - viz. nekdy príšte). Prístupová práva ke složkám objektu jsou tri: •
public: K takovýmto složkám lze pristupovat volne odkudkoli z programu, kde má objekt platnost
•
private: K private-složkám lze pristupovat pouze "zevnitr" objektu, položka je platná pouze v daném objektu, potomek k ní pristupovat nemuže
•
protected: Tyto složky jsou prístupné pouze "zevnitr" aktuálního (rodicovského) objektu nebo z potomka
Slova public, private, protected jsou klícová. Uvnitr objektu se muže vyskytovat nekolik "bloku" uvozených klícovými slovy. Príklad: struct typ { // složky nastavené na public - standardní nastavení na zacátku struct-objektu private: // private složky protected: // protected složky private: // private složky public: // public složky public: // zase public složky. Tato definice má spíše estetický význam (napr. si mužete rozdelit metody a položky) };
Studna - Programátorské zdroje
17
Cesta k C++
Marek Libra
Obcas, není to sice tak casté, ale vyskytne se to, je potreba zadefinovat typ, lépe receno, ríci, že neco takového se bude používat, ale vlastní definici složek uvést až dále v programu. C++ to umožnuje: struct|class typ; Takto definovaný typ se "na oko" jeví, jako by nemel žádné složky, ale ty se pozdeji dají dodeklarovat. Ovšem definice struct|class typ { }; neprovede to, co po ní chceme, tedy umožnit nám pozdeji dodelat další složky. Takto se zadefinuje pouze typ bez položek, což nemá praktický význam.
Klícové slovo friend Tento príkaz umožnuje prístup ke složkám nebo nekterým objektum s atributy private nebo protected. Tímto slovem mužete oznacit složku (objekt), která bude mít právo k tomu, aby mohla menit všechny složky objektu. Príklad: struct A { /* složky */ }; void HodnaFunkce(); class B { friend A; friend HodnaFunkce(); /* vlastní složky trídy B */ } Tímto byla zadefinována struktura A a funkce HodnaFunkce(). Dále bylo udáno, že všechny složky ze struktury A a funkce HodnaFunkce() mají volný prístup ke složkám ze trídy B, at mají nastavena jakákoli práva.
Definice složek objektu Nyní je na case, abychom si rekli neco o tom, jak definovat složky objektu. Nejprve položky (angl. data members), tedy promenné, pole a podobne. Definice položek s príslušností k nejakému objektu je skoro stejná jako definice položky v normální cásti programu. Uvedl jsem skoro. Malý rozdíl je - položka má platnost pouze v rámci práv vyhrazených klícovými slovy (public, protecetd, private). K jednotlivým public-položkám samozrejme mužete pristupovat "zvencí", stejne jako v Pascalu nebo kterémkoli jiném jazyku s objektovou podporou. Jak se definují promenné nebo pole zde uvádet nebudu, psal jsem o tom v nekterém predcházejícím císle, rovnou pristoupím k príkladu: struct B { int Pocitadlo; }; struct A { char Znak; int Cislo; long double Pole[255]; float *Ukazatel; B StruktB; };
Studna - Programátorské zdroje
18
Cesta k C++
Marek Libra
V tomto príkladu jsem vytvoril strukturový typ A, který má 5 položek - Znak (znaková promenná), Cislo (promenná typu int), Pole (pole s 255 prvky typu long double), Ukazatel (ukazatel na nejakou float hodnotu) a StruktB (objektová promenná typu B). Nakonec jsem nadefinoval vlastní strukturu struktury (zní to blbe, ale význam to dává) B. Ovšem objekt bez metod (angl. method) by ve vetšine prípadu nám byl na nic. Proto se krome položek do objektu pridávají i metody (funkce, konstruktory a destruktory). Jejich deklarace je opet skoro stejná jako deklarace funkcí jinde v programu (napsal jsem pouze funkcí, protože se samozrejme destruktory a konstruktory nedají ani jinde deklarovat, než v objektech). Rozdíl oproti "normálu" je stejný jako u položek - volání "zvenku" objektu je povoleno pouze pro public-metody. Potomek objektu muže volat pouze metody a položky nastavené na public nebo protected (naplatí pro prátele - friend). Deklarace konstruktoru a destruktoru je snadná. Provádí se stejne jako deklarace funkce, ale neuvádí se typ návratové hodnoty (jinými slovy, konstruktor, stejne jako destruktor, nevracejí žádnou hodnotu). Konstruktoru a destruktoru muže být v jednom objektu více, pri definování objektu se potom automaticky zvolí ten, který odpovídá zadaným parametrum (blíže vysvetlím pozdeji). Z toho plyne, že stejne jako u funkcí se v 1 objektu nemužou vyskytnout 2 konstr. a destr. se stejnými parametry. Pokud pri deklaraci objektu je (konstruktor a destruktor) neuvedete, prekladac je dodelá sám, automaticky. Oba budou mít telo bez príkazu. Jméno konstruktoru a destruktoru je vždy stejné se jménem objektového typu. Pro následující príklad budeme uvažovat strukturu A, jejíž implicitní nastavení public není zmeneno. Deklarace konstruktoru: struct A { A( /* parametry */ ) { /* telo konstruktoru */ } }; Kde A je jméno konstruktoru (shodné se jménem objektového typu), za /* parametry */ mužete (a nemusíte) dosadit parametry konstruktoru. Pokud žádné neuvedete, pozdeji nový objekt mužete nadeklarovat úplne normálne: A ObjektA; Uvedete-li napr. parametr int Cislo, deklarace by pak vypadala treba takto: A ObjektA( 25 ); Uvedete-li jeden konstruktor bez parametru a druhý téhož objektu s nejakým parametrem, uvádí se pri deklaraci objektu vždy závorka, tedy: A ObjektA1(); // Použije se konstruktor bez parametru A ObjektA2( 10 ); // Použije se konstruktor s parametrem int Cislo Deklarace destruktoru je stejná a platí pro ni stejná pravidla jako pro deklaraci konstruktoru, jenom se pred jméno dává znak "~" (vlnovka). Príklad se vztahuje ke stejnému objektu jako je uveden výše: ~A( /* parametry */ ) { /* telo destruktoru */ } V objektu muže pochopitelne být konstruktor a nemusí být destruktor (nebo obrácene). To ovšem není moc casté a casto se uvede pro prehlednost alespon jméno, i když za ním bude následovat prázdný programový blok (kousek za sebou uvedené složené závorky).
Studna - Programátorské zdroje
19
Cesta k C++
Marek Libra
Deklaraci funkcí rozebírat nebudu, jenom uvedu príklad: struct A { void FunkceA( int Cislo ); int B( int ); int C; }; V tomto príkladu jsem použil neúplnou definici, takže nekde dále budu muset uvést ješte telo jednotlivých funkcí. Neúplná definice se používá samozrejme i u konstruktoru a destruktoru.
Operátor :: (príslušnost k objektu) Tento operátor ríká, že nejaká složka má príslušnost k nejakému objektu. Toho se využívá napríklad pri "dodelávání" tela metody. Príklad (z výše uvedené definice): void A::FunkceA( int Cislo ) { printf("\n Zadáno císlo: %d", Cislo ); } Tedy, nejprve se uvede typ návratové hodnoty (u konstruktoru a destruktoru nic!), potom objektový typ, ke kterému má mít metoda príslušnost, za operátor :: se uvede jméno funkce s parametry (tyto parametry metod mohou být stejne jako u "normálních" funkcí preddefinované, tedy u nich lze udat implicitní hodnotu, ale o tom jsem se už asi zminoval).
Velikost objektu Velikost objektu závisí na celkovém souctu velikostí všech jeho položek. Ke zjištení velikosti slouží operátor sizeof. Ten je možno použít jak pro (objektovou) promennou nebo i pro zjištení velikosti typu. Syntaxe je: sizeof( /* parametr */ ); Za /* parametr */ se zadává bud jméno promenné nebo jméno typu. Vrací se celková zabíraná délka. Pomocí operátoru :: mužete také urcit i velikost jednotlivé položky: sizeof( A::C );
Používání složek objektu Na zacátku je samozrejme potreba vytvorit nejaký objekt pomocí objektového typu. Objektový typ: struct typ { public: typ(); ~typ(); int Cislo; char Znak; void Funk1(); double Funk2( char ); protected: int X; Studna - Programátorské zdroje
20
Cesta k C++
Marek Libra
int Funk3(); private: int Y; }; typ Objekt; Tak, a nyní máme vytvoren objekt Objekt, se kterým budeme nyní pracovat. Tento objekt je pouze demonstracní, tedy neuvádím tela jednotlivých metod, pokud to není soucást príkladu. Nejprve vytvorím telo konstruktoru: typ::typ() { Cislo=0; Znak='a'; X=Y=5; } Zde jsem udal, že hodnoty promenných Cislo, Znak, X, Y budou vždy po definici nejaké nové objektové promenné typu typ mít urcité hodnoty. A ted destruktor: typ::~typ() { }; Tento destruktor nekoná žádnou cinnost, má prázdné telo, pro náš príklad nepotrebuje nic delat. Stejným zpusobem by se dali vytvorit i další metody objektu, ale to již potrebovat nebudeme. V konkrétním programu byste ale museli definici uvést, linker by totiž ohlásil chybu. Jak jste si asi všimli u konstruktoru, volá-li se nejaká složka, která je uvnitr objektu, neuvádí se žádná príslušnost, prekladac si ji urcí sám (je mu to jasný). U takovéhoto volání by se však mel uvádet operátor ::, ale pro praktický význam nemá smysl, tak se neuvádí. Uvádí se pouze u potomku a to je z "estetického" duvodu nebo pokud se položka (metoda) v bázové tríde nazývá stejne jako v objektu potomka (ve kterém zrovna jsme) a my chceme pristupovat k rodicovské. Voláte-li nejakou složku "zvencí", slouží k tomu dva operátory: .
(tecka)
->
(pomlcka s znakem "je vetší")
Rozdíl mezi nimi je pouze v tom, zda objekt, od kterého se "odpichujeme" je ukazatel nebo promenná. Nejlépe to asi vysvetlí príklad: typ Obj1; typ *Obj2;
// Obj1 je objektová promenná // Obj2 je ukazatel na objekt
Obj2=&Obj1; // Obj2 nyní ukazuje na prostor rezervovaný Obj1, prístup k tomuto prostoru však bude jiný Chceme-li volat funkci Funk1(), dá se to provést takto: Obj1.Funk1(); // Zavolá se funkce Fuk1() z objektu Obj1 nebo Obj2->Funk1(); // Zavolá se funkce Funk1(), uložená v pametovém prostoru objektu Obj1 Rozdíl je nyní asi každému jasný - operátor . (tecka) se používá pro prístup ke složkám skalárních objektu a operátor -> pro prístup ke složkám objektu definovaných ukazatelem.
Studna - Programátorské zdroje
21
Cesta k C++
Marek Libra
Bitové položky objektu V objektu se casto vyskytují promenné, kterým stací pro správné urcení všech hodnot, kterých mohou nabývat (napr. bool - stací pouze 1 bit, hodnoty 0 nebo 1). Zbytek prostoru (dopocítáváno do velikosti daného typu - u int do 2 bajtu) je reservován, ale z logického hlediska se nikdy nevyužije. Nyní si možná ríkáte, no a co, tak jsou pamet ové nároky programu o 3/4 bajtu vetší, než je nezbytne potreba. Ale uvažujte, techto položek máte treba 50 - mužete napríklad pomocí objektu popisovat nejakou desku s prepínaci, u nichž platí: zapnuto (1) nebo vypnuto (0). A nic mezi. Pak už budou tyto ztráty pamet ového prostoru vetší. Ješte víc se to muže projevit, udeláte-li z tohoto objektu pole o nekolika prvcích. Jiste už nyní i každého programátorského zelenáce napadne, jak toto nevyužité místo minimalizovat. V C++ se to dá udelat velice snadno - pomocí bitových položek. Bitové položky jsou položky, u nichž uvedete, kolik bytu mají v pameti zabírat, címž pochopitelne omezíte i jejich hodnotový rozsah. Príklad definice bitové položky, které zabírá pouze 1 bit: unsigned char Logicka : 1; Tato promenná bude moci nabývat pouze hodnot, které je možno popsat ve dvojkové soustave 1 cifrou (pokud dobre pocítám, jsou to hodnoty 0 a 1). Dríve jsme si uvádeli definici typu bool pomocí výctového typu enum. Každý typ definovaný pomocí výctového typu enum, zabírá 2 bajty (vychází z typu int). A ted srovnejte velikost promenné Logicka s promennou definovanou pomocí bool. Logicka zabírá mnohem méne a výsledek je stejný. Tyto položky nejsou pouze vymožeností C++, i Pascal je zná (pres set). Prímé urcování velikosti bitové položky pomocí operátoru sizeof není možné, urcení velikosti objektu využívajícího bitové položky možné je. Je-li výsledná hodnota v necelých bajtech, zaokrouhluje se vždy nahoru (nejaký jiný objekt již nemuže využívat prostor v bajtu zbylý po bitových položkách z predcházejícího, ale stejne je to úspora). Nyní k organizaci techto položek v pameti. Jsou ukládány v pameti presne opacným zpusobem, než normální. Máte-li 5 položek, pricemž každá pro ulehcení zabírá 3 bity, bude uložení v pameti vypadat následovne (schematicky!):
Uložení normálních promenných objektu by probíhalo od prvního bitu prvního bajtu. U bitových položek se ukládá obrácene. V prípade, že k temto položkám budete pristupovat standardne pres operátory "." a "->", nemusí vás to zajímat. Pokud ale budete k nim pristupovat napr. pres ukazatele, je potreba to brát v úvahu (nelze použít "standardní" ukazatelovou aritmetiku).
Operátor this Tento operátor je velmi duležitý. Je to v podstate neco jako ukazatel, který vždy ukazuje na pocátecní adresu objektu. Využívá se toho treba když potrebujete predat nejaké externí funkci, pracující s daty vašeho objektu, adresu "aktuálního" objektu (programování pro Windows, bez toho se dnes už snad ani nehnete).
Studna - Programátorské zdroje
22
Cesta k C++
Marek Libra
V príštím díle ješte trochu rozvedu objekty. Pokud se k tomu dostanu, dám na ne už (konecne) nejaký praktický príklad. Sice se budu opakovat, ale napíši to radeji ješte jednou. Máte-li zájem o ucení jazyka C++ a s necím si nevíte rady (konkrétne treba dnes jste se nemuseli vyznat v terminologii kolem OOP nebo v necem jiném), napište mi. Pokud vznikne nejaký dotaz, zaradím ho rád do dalšího dílu. Neznáte-li základy OOP, je to na speciální clánek. Dále pokud máte pripomínky k mému výkladu (jdu moc rychle, nevyznám se v tom a pod.), budu rád, pokud se to ke mne také dostane.
Reference Reference nepatrí pouze k OOP, lze je využívat i u bežného programování (ted ale záleží na tom, co považujete za "bežné programování"). Jedná se v podstate o druh datového typu (nebo neco na ten zpusob). Mají velice blízko k ukazatelum. Asi nejcastejší uplatnení najdou u parametru volaných odkazem. Jejich definice je jednoduchá, stejná jako u ukazatelu, jenom se místo * (hvezdicka) dává &. Podmínkou je, že reference musí být ihned po deklaraci inicializována. int Prom, &Ref=Prom; Nyní máme zadefinovanou promennou Prom a referenci Ref na typ int odkazující na promennou Prom. Nyní, at provedeme jakoukoli operaci s Ref projeví se to na hodnote Prom. To platí i opacne. Príklad: Ref = 12; Prom = 0;
// Totéž jako: Prom = 12 // Totéž jako: Ref = 12
Referenci je samozrejme možné pri inicializaci dát urcitou konstantní hodnotu. V tomto prípade se v pameti vytvorí oblast o velikosti nutné k popsání hodnoty daného typu. Další práce s referencí je shodná, jako s konstantou, tudíž se to moc nepoužívá, lepší (spíš prehlednejší) je používat konstant (klícové slovo const). Príklad: int &ref = 10; Je možné odkázat referenci na jiný typ než je sama. V tomto prípade se v pameti vytvorí docasne prostor (po dobu platnosti reference) a do neho se zkonvertuje obsah promenné. Je zde ale hácek - zmena hodnoty reference již nemá vliv na hodnotu puvodní promenné (a naopak). Príklad: int Prom=50; char &Ref=Prom; Ref = 100;
// Prom je porád 50 !!
Až na to, když se referenci prímo priradí hodnota nebo je reference jiného typu než promenná (i když to má obcas smysl, napr. u funkcí, u kterých nevíte, co dostanete za parametr), je hlavní výhoda a smysl jejich používání v tom, že šetrí pamet a není s nimi tak složitá práce jako s ukazateli (které mají navíc i nekterá omezení). Kdo to ješte nepochopil - pomocí referencí se predává pouze odkaz (odtud název reference, reference=cesky odkaz) na již vytvorený pamet ový prostor. U jednoduchých typu, které jsem napríklad uvádel zde, by snad ani nevadilo, že se v pameti vytvorí další 2bytový prostor, ale vezmete si takovou velkou strukturu, zabírající kolem 10Kb v pameti popr. prímo seznamy nebo kontejnery (co to je, k tomu se dostanu pozdeji), to už je zase o necem úplne jiném.
Studna - Programátorské zdroje
23
Cesta k C++
Marek Libra
Dedicnost objektu Dedení objektu (-u) je jednoduché. Potrebujeme k tomu samozrejme predkovský objekt a název budoucího potomka. Potom se to provede nejak takto: <struct | class > potomek : <prístupové právo> predek { // Pridávané složky } obj_typ; Opet si musíte vybrat, jaký má být potomek - zda struktura nebo trída (platí pro to to samé, jako u "nededení", praktický rozdíl je tedy hlavne v pocátecním nastavení prístupových práv). Potom následuje název potomka, za ním dvojtecka (: ), za ní prístupové právo pro složky predka a za tím jméno predka nebo predku (oddeleno cárkou). Prístupová práva jsou zase 3 - public, protected, private. K složkám predka potom budete pristupovat takovým zpusobem, jakým vám povolí dané právo. Práva prístupu lze však pouze zúžit, niko-li rozšírit. Tedy je-li v potomku public-složka, a potomek dedí po predkovi s private právy, k dané složce se potom mužete z hlediska potomka chovat pouze jako k private-složce. Bude-li to však naopak (predek - private-složka, potomek dedí s public), ke složce mužete pristupovat pouze jako k private. Z toho plyne nejcasteji používané právo pri dedení - public. Za jménem predka následuje programový blok s výctem nových složek, které bude mít potomek oproti predkovi navíc. Za blokem je opet objektová promenné zakoncená stredníkem. Vyskytne-li se v potomkovi stejný název a typ složky jako má predek, bude se implicitne pracovat s potomkovou. Na predkovu se "sáhne" pouze v prípade, že to výslovne reknete (to zní pekne blbe, když se programy obycejne datlují na klávesnici). To mužete udelat následovne: struct A { int Prom; }; class B { int Prom; }; class C : public A, B { int Prom; void Funkce(); }; void C::Funkce() { Prom = 5; A::Prom = 15; B::Prom = 45; }
// Bere se promenná z C // Bere se promenná z A // Bere se promenná z B
Myslím, že je to jasné. Jenom ješte jednu poznámku - z príkladu plyne, že dedit muže i struktura od trídy a naopak.
Virtuální metody Praktický význam popisovat nebudu, pokud vím ve všech objektových jazycích je velmi (ne-li úplne) stejný. Virtuální metodu zadeklarujete pomocí klícového slova virtual: Studna - Programátorské zdroje
24
Cesta k C++
Marek Libra
struct A { virtual void Funkce(); }; void A::Funkce() { /* Telo */ } Zde je videt, že virtual se uvádí pouze jednou a to pri deklaraci v objektu (nezáleží na tom, zda napíšete telo hned nebo až potom). Dopisujete-li telo mimo blok objektu, virtual se již neuvádí (nesmí). Takto definované funkce lze volat i explicitne pomocí operátoru :: (2x dvojtecka). Pri používání virtuálních metod se vytvárí tabulka virtuálních funkcí, kde jsou zaznamenány odkazy na všechny virtuální funkce použité v programu. Pri behu (z pochopitelných duvodu pri prekladu nemuže prekladac presne vedet, kterou chcete) se pri zmínce o virtuální metode podívá do tabulky a podle daného odkazu zavolá príslušnou metodu. Tato tabulka se vytvárí v datovém nebo kódovém (programovém) segmentu, ale to vás víceméne nemusí tolik zajímat, pristupuje se k tomu stejne.
Objekt odkazující sám na sebe Jedná se o objekt A, který obsahuje ukazatel typ A. Príklad: struct A { A *predch, *nasl; // složky }; Této nebo podobné deklarace se používá napríklad u seznamu, kdy nevíte, kolik položek budete muset do pameti vložit. Pokud by se to rešilo pres pole o konstantní velikosti (stylem Typ Prom[50]) má to radu nevýhod a jen pár výhod. Výhody jsou, že je to snadné napsat, pro zacátecníka snadno citelné a prístup k položce (pokud presne znáte její polohu) je rychlý. Ovšem nevýhody prevažují nad výhodami, málokdy víte, jak bude seznam skutecne dlouhý, behem jeho existence se obycejne zkracuje nebo naopak prodlužuje a pod. Vytvorení takového seznamu je v C++ velice snadné. Do výše uvedených ukazatelu stací ukládat adresy následujícího nebo predcházejícího prvku daného seznamu. Jednotlivé prvky se vytvárejí dynamicky, tedy pomocí operátoru new a delete.
Dynamické alokování pameti Tento zpusob nemá uplatnení pouze s OOP, i když se ve spojení s OOP nejcasteji používá. Pri takovémto alokování se pamet "rozvrhne" až za behu programu a pritom se vetšina práce svalí na operacní systém. V podstate se alokuje v pameti oblast o urcité velikosti pro použití výhradne daným programem. K takto alokovanému pamet ovému bloku se potom pristupuje pres ukazatele (pokud jste plne nepochopily ukazatele a práci s nimi, nemužete pracovat s dynamicky alokovanou pametí). Nevýhodou, která zacátecníkum delá velké problémy, je, že po skoncení práce s tímto pamet ovým prostorem se musí dealokovat, aby byl použitelný pro jiné aplikace. Pokud byste to neudelali, brzy by se pamet zaplnila nevyužitelnými datovými bloky (i když Váš program již dávno nepobeží) a jediná možnost na jejich "dealokaci" je reset pocítace. Operátory new a delete jsou vymoženost C++, C pamet také dokázalo alokovat, ale provádelo se to jinak (a porád to samozrejme jde, kvuli kompatibilite a nekdy i z jiných duvodu). V C se alokovaná pamet specifikovala poctem potrebných bytu. New se používá pro vytvorení jednoho nebo nekolika objektu s konkrétním typem. Nejprve se budu venovat alokování v jazyku C, tedy pomocí funkcí. Slouží k tomu funkce malloc, free, calloc, realloc pro alokaci prostoru menšího než 1 16-bitový segment (64Kb) a funkce farmalloc, farfree, farcalloc, farrealloc pro alokaci vetšího prostoru. Jsou ale Studna - Programátorské zdroje
25
Cesta k C++
Marek Libra
použitelné pouze pod DOSem a 16-bitovými Windows. Jsou deklarovány v "alloc.h" a "stdlib.h". Syntaxe techto funkcí je: void void void void void void void void
*malloc(size_t size); free(void *block); *calloc(size_t nitems, size_t size); *realloc(void *block, size_t size); far *farmalloc(unsigned long nbytes); farfree(void far * block); far *farcalloc(unsigned long nunits, unsigned long unitsz); far *farrealloc(void far *oldblock, unsigned long nbytes);
Použitý typ size_t je stejný jako unsigned int (pouze predefinováno pomocí typedef). Pro zaalokování n-bytu se používá malloc (farmalloc), která alokuje size bytu a vrátí ukazatel na zacátek tohoto bloku. Tato funkce je "univerzální", návratová hodnota je typu void (možná si myslíte, že se to rozchází s tím, co jsem psal dríve, ale ono to tak horký nebude, viz. dále). Pri praktickém používání se typ void "pretypuje", jasnejší to bude asi z príkladu: int *pointer; pointer = (int *)malloc(20); Nejcasteji se takto provedená alokace používá proto, aby se do pamet ového prostoru ukládaly císla typu int (ovšem není to nutnost). Po skoncení práce s tímto prostorem ho odalokujeme: free( pointer ); Pro snazší práci existuje funkce calloc, které se zadává velikost 1 položky a pocet techto položek (ona to pak vynásobí a stejne zavolá malloc). Využívá se napríklad pro pole. Uvolnení prostoru je zase pomocí free. Funkce realloc slouží ke zmene velikosti alokované oblasti. Za parametr block se dává ukazatel na daný datový blok a size je nová velikost. Vracená hodnota je ukazatel na novou "polohu" alokované oblasti.
Operátory new a delete Dalším a mnohem snazším zpusobem alokace v C++ je použití techto operátoru. Operátor new slouží k provedení alokace a vrací ukazatel na alokovanou oblast. Typ nové oblasti se zadává za operátor, jedná-li se o objekt s konstruktorem (s parametry) tak za typ do závorek parametry konstruktoru. Príklad: int *i = new int; Prístup k takto alokované pameti (u 16-bitových prekladacu 2 byty, u 32-bitových to jsou 4 byty) se pristupuje snadno pres ukazatel i. Je-li i rovno NULL (makro s hodnotou 0), pamet nebyla alokována (obycejne z duvodu nedostatku pameti). Zadeklarovat mužete samozrejme i pole: int *i = new int[10]; Oproti normálne deklarovaným polí, lze za 10 uložit i promennou (new konstantu). Ovšem platí, že když se pozdeji zmení hodnota promenné, nezmení se již velikost pole (to je ostatne logické). Vícerozmerné pole se muže deklarovat napríklad takto: int **i=(int **) new int [10][50]; Krome prvního rozmeru pole, musí všechny rozmery být konstanty. Dealokace prostoru se provádí pomocí delete: delete i;
// Pro náš 1. príklad
nebo Studna - Programátorské zdroje
26
Cesta k C++
Marek Libra
delete [ ] i;
// Pro pole
nebo delete [ ] i;
// Pro matici
Seznamy S poznatky z tohoto císla seriálu byste už meli být schopni udelat zrejme nejzákladnejší datový útvar - seznam. Co tím mám na mysli - seznam si zkuste predstavit jako radu bloku alokované pameti, pricemž každý takovýto blok nese urcitou hodnotu, údaj a pod. .Každý prvek takovéto šnury mimo jiné obsahuje i adresu (ve forme ukazatele) na další prvek seznamu. Nekdy je výhodné vložit do prvku i ukazatel na predcházející prvek. Když je potreba pridat nový prvek, vytvorí se v pameti (samozrejme dynamicky) nová oblast, její adresa se uloží do ukazatele v predcházejícím prvku. Nove vytvorená oblast (tj. poslední prvek našeho seznamu) má daný ukazatel nastavený na NULL (neukazuje nikam). Díky tomu není problém tento konec urcit. K uvedené alokaci pameti se nejcasteji používá operátoru new a delete (je s nimi snazší práce).
Pretežování operátoru Další velmi užitecná vec. Zda se vyskytuje (nebo neco podobného) i v Pascalu nevím, tak daleko jsem v nem nedošel, ale myslím si, že alespon neco na ten zpusob tam urcite pujde. Podstata je podobná jako pri pretežování funkcí - vždy se zvolí ten, kterému odpovídají dané typu použitých hodnot. V podstate se dají pretežovat všechny operátory. Hlavní výhodou je, že matematické nebo logické operace lze provádet i u objektových promenných, nejen u primitivních typu (napr. int, long double). Standardne prekladac pretežuje pro každou trídu operátor = (melká kopie, viz. dále). Pretežování se pokusím vysvetlit na príkladu pretežování operátoru +: struct Data { int A; int B; int C; }; void main() { Data P1, P2, P3; P1.A=P1.B=2; // Pocátecní inicializace P1.C=5; P2=P1; // Správne, C++ má standardne tento operátor pretížen P3=P1+P2; // Chyba !!!!!, Prekladac neví, co má scítat } Tímto stylem napsaný kód je chybný - prekladac neví, co a jak má kopírovat. Proto se musí pretížit operátor +, címž se rekne, jak má scítání 2 objektu typu Data probíhat. Nekde pred funkci main() se musí uvést napríklad tato definice: Data operator + { Data Pomoc; Pomoc.A = A.A Pomoc.B = A.B Pomoc.C = A.C return Pomoc; };
(Data A, Data B) // Pomocná promenná + B.A; + B.B; + B.C;
Studna - Programátorské zdroje
27
Cesta k C++
Marek Libra
Nyní to již bude fungovat. Co uvedete v tele pretežovaného operátoru samozrejme záleží pouze na vás. Jaká bude návratová hodnota (tedy výsledek operaci pri použití takto pretíženého operátoru) je také Vaše vec. Provádíte-li tuto globální definici, tedy ne s príslušností pouze k 1 konkrétnímu objektu (a poprípade jeho potomkum), musí být dva parametry - 1. je levá strana pri použití operátoru a 2. je pravá strana. Pretežování s príslušností k objektu by mohlo vypadat nejak takto: struct Data { int A; int B; int C; Data operator + (Data B) { Data pomoc; pomoc = A+B.A; pomoc = A+B.B; pomoc = A+B.C; return pomoc; } }; Zde se uvádí již pouze pravá strana operátoru, na levou je implicitne dosazován daný objekt. Jediné operátory, které z logického duvodu nelze pretížit, jsou: . , .* , :: , ? : Príklad na dnešní clánek naleznete v souboru cpp05.cpp. I/O operace použité v príkladu jsou rešeny pres proudy. Co to je a jak se s nimi pracuje se pokusím vysvetlit v další kapitole. Pro tentokrát to zkuste brát tak, jak to je, nyní pro Vás bude zrejme zajímavé, podívat se na to, jak se pracuje s dynamickou alokací pameti a implementací seznamu (je pouze jednosmerný. Dvojsmerný by byl složitejší, i když nekdy rychlejší - to ale není náš prípad).
Knihovna pro I/O proudy C/C++ neobahuje operace pro I/O (není tomu tak napr. v Pascalu), aby byl jazyk snáze prenositelný mezi více platformami. Tyto funkce jsou však již nekterými standardy pridány, jsou ovšem pridány pomocí knihoven. Krome bežných funkcí printf() a scanf() a jim podobných lze I/O operace v rámci OOP rešit pomocí proudu. Jednotlivé I/O operace jsou provádeny pres objekty, pres které "prochází" proudy dat. Cást techto dat bývá obycejne uchovávány v pamet ovém prostoru (bufferu). K bufferu lze pristupovat pres metody objektu. Knihovna, která tímto zpusobem pracuje a kterou zde budu popisovat, je knihovna AT&T standardne implementována v prekladacích fy. Borland. Pro zápis do proudu se používá operátor << (inserter) a pro ctení >> (extraktor) - jsou to pretížené operátory bitového posunu. Obecná syntaxe je: proud << prvek; Do proudu proud se zapíší data z objektu prvek. Extraktor a inserter je standardne v knihovne pretížen pro primitivní typy a pro nekteré další objekty. Ve svých aplikacích jej pravdepodobne budete vždy také pretežovat pro vlastní typy dat. Knihovna pro práci s I/O proudy tvorí složitou (nikoli však nepochopitelnou) hiearchii dedených objektu. Základní trída streambuf obsahuje souhrn všech spolecných znaku pro Studna - Programátorské zdroje
28
Cesta k C++
Marek Libra
všechny typy proudu. Je to napr. vyrovnávací pamet (buffer). Buffer zvolené délky se vytvárí konstruktorem streambuf( char *kde, int delka ); Od této trídy jsou odvozeny další trídy pro diskové operace filebuf, pro znakové proudy v pameti strstreambuf a pro výstup na obrazovku conbuf. Hiearchie tríd pro ruzné typy proudu vypadá nejak takto:
Trídy uvedené níže jsou potomky príslušných tríd uvedených výše. Trída ios Jednotlivé objekty této trídy se bežne nepoužívají prímo, jsou spíše používány prostrednictvím objektu tríd potomku. Tato trída obsahuje implementaci spolecných znaku I/O operací. Formát dat v peoudu urcují bity x_flags, ke kterým se dá pristupovat pres funkce setf() a unsetf(). Dalším vyýznamným prvkem je pointer na pamet ovou oblast rezervovanou objektem trídy streambuf (buffer). Pro tuto trídu jsou standardne pretíženy nekteré operátory, takže výraz !stream je 1 vždy, když dojde k chybe uvnitr proudu. Významná metoda je také eof() - vrací 1, když bylo v proudu naraženo na speciální znak konce souboru. Trídy istream, ostream rozširují schopnosti trídy ios o funkce specifické pro I/O operace - istream pro I a obrácene. Až od této úrovne naší hiearchie jsou pretíženy operátory << a >> (viz víše). Pred vytvorením objektu techto tríd je treba vytvorit ješte buffer o urcité délce (trída streambuf nebo její podtrídy). Ukazatel na tuto oblast je parametrem konstruktoru tríd. Elementární prvky techto proudu jsou typu char. Z istream bych jmenoval napr. metodu get() vracící znak na aktuální pozici v proudu u ostream jí odpovídá put(). Tyto metody jsou hodne pretežovány na ruzné typy dat. Metoda flush() vyprázdní buffer a seek() nastaví aktuální pozici v proudu. Trída iostream implementuje proud pro obe I/O operace. Konstruktor má rovnež za parametr ukazatel na vyrovnávací pamet. Trídy istream, ofstream a fstream trídy specializované na diskové operace (souborové). V programech se casto používají objekty techto tríd (narozdíl od predchozích, až nyní je práce snažší, prestože se stále jedná spíše o elementární operace). Veluká výhoda je v tom, že obsahují i pretížený operátor bez parametru vytváreující vyrovnávací pamet - nemusíte ji vytváret sami. Pred
Studna - Programátorské zdroje
29
Cesta k C++
Marek Libra
prací s proudem se musí soubor otevrít metodou open() a po skoncení uzavrít metodou close(). Trídy istrstream, ostrstream, strstream jsou specializované na operace se znakovými retezci. Zapisujezte-li do proudu typu ostrstream hodnotu neznakového typu, provede se konverze na znakový retezec, který se uloží do do bufferu od aktuální pozice (napr. císlo typu int se zkonvertuje z binární podoby na znakovou: 123 => "123"). Pri opacné cinnosti - ctení z istrstream se konvertuje zpet ze znakové na binární (prípadne jinou). Trída istrstream má za parametr ukazatel char* ukazující na existující buffer v pameti (spravidla bývá již naplnen retezcem - potom se konvertuje na urcitý typ). Jedno z pretížení konstruktoru ostrstream() je bez parametru automaticky vytvárí buffer (dynamicky). Trída constream se spacializuje na výstup na obrazovku konstruktor je bez parametru. Jmenujme napr. metodu clrscr() s návratovou hodnotou typu void, která vycistí obrazovku a metodu window( int left, int top, int right, int bottom ) stejného typu umožnující definovat aktuální výrez obrazovky. Pro presný popis jmenovaných a ostatních metod doporucuji nahlédnout do helpu. Nemá smysl ho zde citovat, když jej máte pravdepodobne každý u své verze prekladace. Velice jednoduchý príklad je v príkladu z minulého císla (jedná se pouze o základní operace pro ctení a zápis spolu s pretížením inserteru a extraktoru).
Slovo na možný záver Nedávno jsem cetl v CW recenzi na DJGPP - prekladac C++. Jedná se o freeware program s ne moc vysokými hardwarovými nároky (na C++ prekladac). Podle recenze je výborný, daleko lepší než Borladský nebo od Watcomu, jediná nevýhoda je, že standardne nepodporuje Windows (tuto podporu si ale není problém doprogramovat ...).Preklaádá i pro 32-bit (v tom je jeho nejvetší síla), má výborné knihovny pro grafiku, zvuk, periferie a pod. . Pokud se mi podarí sehnat tento prekladac v rozumné dobe, skusím napsat recenzi. Možná si ríkáte, je to freeware, to bude stát za houby. Vyvracet Vám to zatím nemohu (doma to nemám), ale mužu ríct, že autori hry Quake si to urcite nemysleli. Na Internet prístup nemám, takže kdy recenze bude, je zatím ve hvezdách. Pro zájemce zde uvádím adresy, kde je možno prekladac najít (.zip soubor typuji na 10-15MB).: ftp://ftp.delorie.com/ 2 mirrory v CR: ftp://ftp.kolej.mff.cuni.cz/ ftp://ftp.vse.cz
Studna - Programátorské zdroje
30
Cesta k C++
Marek Libra
Obsah Historie C++_________________________________________________________________________1 Struktura programu __________________________________________________________________1 Deklarace funkce ____________________________________________________________________2 Výpis na obrazovku __________________________________________________________________3 Základní typy promenných ____________________________________________________________4 Definice promenných _________________________________________________________________5 Konstanty __________________________________________________________________________5 Operace promennými_________________________________________________________________6 Vstupy _____________________________________________________________________________7 Deklarace nového typu _______________________________________________________________8 Rízení behu programu ________________________________________________________________8 Podmínky___________________________________________________________________________8 Cykly ______________________________________________________________________________9 Skoky _____________________________________________________________________________10 Pole ______________________________________________________________________________11 Preprocessor_______________________________________________________________________12 Testování existence a obsahu makra ___________________________________________________12 Direktiva #include___________________________________________________________________13 Makra s parametry __________________________________________________________________14 Direktiva #error _____________________________________________________________________14 Preddefinovaná makra _______________________________________________________________14 Pretežování funkcí __________________________________________________________________15 Funkce definované v jiném modulu ____________________________________________________15 Retezce ___________________________________________________________________________15 Další funkce pro práci s retezci________________________________________________________16 Objektove orientované programování __________________________________________________16 Klícové slovo friend _________________________________________________________________18 Definice složek objektu ______________________________________________________________18 Operátor :: (príslušnost k objektu) _____________________________________________________20 Velikost objektu ____________________________________________________________________20 Používání složek objektu _____________________________________________________________20 Bitové položky objektu_______________________________________________________________22 Operátor this _______________________________________________________________________22 Reference__________________________________________________________________________23 Dedicnost objektu___________________________________________________________________24 Virtuální metody ____________________________________________________________________24 Objekt odkazující sám na sebe ________________________________________________________25 Dynamické alokování pameti _________________________________________________________25 Operátory new a delete ______________________________________________________________26 Seznamy __________________________________________________________________________27 Pretežování operátoru _______________________________________________________________27 Knihovna pro I/O proudy _____________________________________________________________28 Slovo na možný záver _______________________________________________________________30 Obsah_____________________________________________________________________________31 Cesta k C++ ________________________________________________________________________32
Studna - Programátorské zdroje
31
Cesta k C++
Marek Libra
Cesta k C++ Seriál pro WWW stránky Studna - Programátorské zdroje. Prílohy: cpp01.cpp cpp02_01.cpp cpp02_02.cpp cpp03_01.cpp cpp03_02.cpp cpp05.cpp
Copyright © Marek Libra, 1997, 1998 Autor: Marek Libra, [email protected] Redakce: Hynek Sládecek, [email protected]
http://www.czechia.cz/studna/
Studna - Programátorské zdroje
32