Pojďte s námi programovat !

1. 6. 2008

Sdílet

Plni energie pokračujeme ve svižném tempu našeho programátorského kurzu jazyka C++. V červnovém pokračování js...



Plni energie pokračujeme ve svižném tempu našeho programátorského kurzu jazyka C++. V červnovém pokračování jsou na pořadu dne další manipulační operace s proměnnými, užití konstant a docela hezká sbírka doprovodných praktických programů.


Programovací jazyk C++ umožňuje i takovou fintu, jakou je vícenásobné přiřazení. Technika vícenásobného přiřazení nám umožňuje přiřadit jednu hodnotu do více proměnných v rámci jednoho příkazu. Postupuje se přitom podle této směrnice:

Přiřazení probíhá zprava doleva: nejdřív je Hodnota přiřazena do Proměnné1, čímž je tato proměnná inicializována. Posléze je hodnota Proměnné1 přiřazena do Proměnné2. Stejný proces se opakuje rovněž pro Proměnnou3. V okamžiku, kdy se uskuteční inicializace Proměnné3, je proces vícenásobného přiřazení u konce. Po prozkoumání podstaty vícenásobného přiřazení už jenom dodáme, že všechny proměnné, které se v příkazu nacházejí, budou obsahovat stejnou hodnotu.

Vícenásobné přiřazení se nesmí odehrávat v definičním příkazu, v němž dochází k vytvoření většího počtu proměnných. Vícenásobné přiřazení je zcela samostatným příkazem, který nemá s definicí proměnných nic společného (samozřejmě, aby bylo možné jednu hodnotu uložit do více proměnných, musejí tyto proměnné v době vícenásobného přiřazení již existovat). Pozor tedy na takovýto zdrojový kód:

Ve fragmentu zdrojového kódu jsou sice definovány tři celočíselné proměnné, ovšem v rámci definice je inicializována pouze jedna z nich: jde o poslední proměnnou s identifikátorem prom_c. A přestože kompilátor přeloží kód bez chybových hlášení, neodpustí si dvě varování. První z nich říká, že proměnná s názvem prom_b není nikde v kódu použita. Problematičtější je bezesporu druhé varování, pojednávající o užití neinicializované proměnné prom_a. Jsme zastánci tvrzení, že zejména druhé varování by měl kompilátor okamžitě označit za chybu (kdyby tomu tak bylo, programátor by nedovedl přeložit, sestavit a tedy ani spustit chybný program). Když si uvědomíme, že proměnné prom_a a prom_b jsou lokálními proměnnými, tak ihned víme, že nebudou implicitně inicializovány.
Jestliže bychom měli dostatek odvahy a program spustili, došlo by ke generování chybové výnimky za běhu programu. K problematice chybových výjimek se dostaneme, i když si už nyní můžeme povědět, že chybové výjimky jsou jakýmsi obranným mechanizmem, díky kterému se program brání vůči mimořádným a zpravidla chybovým stavům. Když tedy vznikne chyba, bude sestavena chybová výjimka, kterou programátoři dovedou zachytit a ošetřit. Tomuto procesu se říká zpracování chybové výjimky.

Jazyk C++ dovoluje vývojářům agregovat definici a inicializaci většího počtu proměnných do jednoho příkazu. Přitom ovšem musí být splněna podmínka, která říká, že všechny takto definované a inicializované proměnné budou disponovat identickým datovým typem.

Příkaz provádějící vytvoření a inicializaci více proměnných najednou má tuto generickou podobu:

Datový typ je určen pouze jednou a vztahuje se na všechny proměnné, které v tomto příkazu vzniknou. Ačkoliv v popsaném modelu znázorňujeme vytvoření dvou proměnných, celkový počet založených proměnných není prakticky omezen (na druhou stranu, v běžném kódu se nesetkáme s více než čtyřmi až pěti paralelně vytvářenými proměnnými). Pozornost si zaslouží rovněž operátor čárka (,), který jednotlivé proměnné odděluje od sebe.

Tento program vypočítává součet hodnot, jež jsou uskladněny v proměnných cislo1 a cislo2 (obě typu int). V rámci prvního příkazu obě proměnné zakládáme a ihned jim přiřazujeme hodnoty 10 a 3. Na dalším řádku definujeme celočíselnou proměnnou soucet, kterou inicializujeme součtem hodnot proměnných cislo1 a cislo2.

Celočíselné konstanty

To, že každá proměnná musí mít svůj datový typ, již víme. Nyní rozšíříme spektrum našich znalostí a povíme si, že každá hodnota, s kterou budeme při programování manipulovat, musí rovněž disponovat svým datovým typem. Diskrétní hodnoty stojící ve zdrojovém kódu samostatně se nazývají konstanty. Třeba hodnota 1000, kterou jsme v jednom z předcházejících fragmentů zdrojového kódu postupně přiřadili do třech proměnných, je celočíselná konstanta.
Při překladu zdrojového kódu si kompilátor všímá takovýchto konstant a automaticky jim přiděluje datový typ. Kompilátor přiděluje konstantám co nejefektivnější datový typ, který lze vzhledem k hodnotě konstanty použít. Naše „tisícovka“ je celočíselnou konstantou, kompilátor jí tudíž přisoudí typ int. Je to proto, že s 32bitovými celočíselnými hodnotami typu int jsou na 32bitových platformách (32bitový procesor a 32bitový operační systém) výpočetní operace prováděny nejrychleji. Přestože je datový typ int nejrozumnější volbou, nelze jej aplikovat při každé příležitosti (kupříkladu při práci s reálnými číselnými konstantami). Zkusme například přezkoumat, zda se změní zacházení s celočíselnou konstantou, když výpis kódu upravíme následovně:
V kódu jsme nahradili datový typ int typem short. Všechny tři proměnné jsou nyní proměnnými typu short, což znamená, že jsme omezili rozsah hodnot, jež do nich mohou být uloženy. Kompilátor však bude navzdory tomu s konstantou 1000 nakládat jako s celočíselnou hodnotou typu int. To je velice zajímavé, poněvadž pro uchování této konstanty by bohatě stačil i typ short. Pro úplnost však musíme dodat, že původně asociovaný typ int celočíselné konstanty bude před přiřazením převeden na typ short. Tomuto procesu se říká implicitní typová konverze a ještě se s ní setkáme.

Celočíselné konstanty mohou být vyjádřeny v různých pozičních číselných soustavách. Při programování v jazyce C++ jsou důležité tři soustavy, a to decimální (se základem 10), oktálová (se základem 8) a hexadecimální (se základem 16). K zmíněnému trojlístku pozičních číselných soustav se řadí ještě binární soustava (se základem 2), která je důležitá hlavně z hlediska nízkoúrovňového zpracování dat a strojových instrukcí.

V naprosté většině případů budeme celočíselné konstanty zapisovat v decimální čili desítkové číselné soustavě. Je to zcela přirozené, vždyť lidé se s reprezentací čísel v desítkové soustavě setkávají již od školních let. Někdy se však může hodit pracovat s osmičkovou (oktálovou) nebo šestnáctkovou (hexadecimální) číselnou soustavou. Abychom ozřejmili význam jednotlivých číselných soustav a převodů mezi nimi, podívejme se na Číselné soustavy v matematice a programování.

Číselné soustavy v matematice a programování

Jakoukoliv hodnotu v desítkové soustavě můžeme zapsat v podobě součtu mocnin čísla 10. Uvažujme například o čísle 1253:

(1253)10 = 1 × 1000 + 2 × 100 + 5 × 10 + 3 × 1
(1253)10 = 1 × 103 + 2 × 102 + 5 × 101 + 3 × 100

Desítková soustava používá pro vyjádření číselných hodnot deset číslic: 0, 1, 2, 3, 4, 5, 6, 7, 8 a 9.

Oktálová číselná soustava

Oktálová (osmičková) soustava pracuje s mocninami čísla 8, přičemž libovolná hodnota je v této číselné soustavě vyjádřena posloupností cifer z intervalu <0, 7>.

(6203)8 = 6 × 83 + 2 × 82 + 0 × 81 + 3 × 80

Pro převedení oktálové konstanty 6203 do desítkové soustavy rozvineme mocniny čísla 8 a vypočteme celý součet, tedy obdržíme ekvivalent čísla 6203 v desítkové soustavě.

(6203)8 = 6 × 512 + 2 × 64 + 0 × 8 + 3 × 1
(6203)8 = 3072 + 128 + 0 + 3
(6203)8 = 3072 + 128 + 0 + 3
(6203)8 = (3203)10

Jak je vidět, dospěli jsme k závěru, že osmičkové hodnotě 6203 odpovídá číslo 3203 v desítkové číselné soustavě. Čtenáři s detektivní povahou mohou v této chvíli namítnout, zda můžeme nabídnout důkaz o zpětné konverzi. Jednoduše řečeno, jak dokážeme, že číslo 3203 v desítkové soustavě je doopravdy protějškem čísla 6203 v osmičkové soustavě? Nuže, důkaz o této skutečnosti podáme tak, že desítkovou číselnou hodnotu budeme neustále dělit osmi, přičemž si vždy poznačíme zbytek po dělení. Matematický algoritmus můžete vidět v tabulce 1.

Úspěšný rozklad desítkové konstanty si vyžádal čtyři kroky našeho matematického algoritmu. V okamžiku, kdy je podíl nulový, algoritmus končí, přičemž za sebou zanechává čtyři hodnoty, které můžeme nalézt v sloupci Zbytek po dělení. A právě tyto zbytky jsou pro nás cenné, protože když je zapíšeme inverzně v posloupnosti 6203, získáváme osmičkový ekvivalent čísla 3203. Hezké a elegantní, co říkáte?

Aby bylo ve zdrojovém kódu jazyka C++ možné již od prvního kontaktu odlišit konstantu zadanou v oktálové soustavě od decimální hodnoty, před osmičkovou hodnotou se nachází prefix 0.

V proměnné cislo_v_8 typu int je uložena číselná hodnota zapsaná v osmičkové soustavě. To zjistíme velice snadno podle úvodní nuly, která říká, že následující posloupnost čísel vyjadřuje „osmičkové číslo“. Z hlediska výpočetního aparátu oktálové soustavy je zřejmé, že hodnota smí být složena pouze z čísel, jež patří do intervalu <0, 7>. Pokud bychom se spletli a nechtěně uvedli neplatnou číslici (třeba devítku), kompilátor by nás zastavil s chybovým hlášením C2041: illegal digit ‚9‘ for base ‚8‘ (Neplatná číslice pro základ 8).

V proměnné cislo_v_8 je uložena celočíselná konstanta. Co se stane, když stávající podobu zdrojového kódu rozšíříme tímto způsobem?

Na tomto místě bychom mohli mezi programátory uspořádat anketu s jedinou otázkou: „Co myslíte, že se zobrazí na výstupu v konzolovém okně?“ Nuže, jak byste odpověděli? Logika by nám mohla říct, že vzhledem k tomu, že pracujeme s celočíselnou konstantou zapsanou v oktálové soustavě, v okně příkazového řádku se objeví tatáž hodnota, ovšem bez úvodní nuly (tedy 637). Ve skutečnosti se objekt cout chová trošičku jinak. Chování objektu cout ovšem není svévolné, pouze odráží skutečnost, že osmičkové číslo je převedeno na svůj desítkový protějšek ještě předtím, než jej uložíme do přichystané celočíselné proměnné. V proměnné cislo_v_8 se proto bude nacházet hodnota 415, což je číslo, které dostaneme, když původní osmičkovou hodnotu převedeme na desítkový ekvivalent.
Objekt cout jenom přebírá hodnotu proměnné cislo_v_8 a zobrazuje ji tak, jak to běžné dělá, tedy v decimální číselné soustavě. Po pravdě řečeno, celočíselná konstanta 415 je nejprve konvertována do podoby textového řetězce (posloupnosti textových znaků), až poté je odeslána do výstupního datového proudu.

Hexadecimální číselná soustava

V informatice se poměrně často číselné hodnoty „kódují“ v hexadecimální (šestnáctkové) číselné soustavě. Základem této soustavy je číslo 16 a jakákoliv hodnota musí být proto jednoznačně reprezentovatelná posloupností číslic 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 a abecedními znaky A, B, C, D, E a F. Uvedené znaky přitom náleží následujícím hodnotám: A = 10, B = 11,
C = 12, D = 13, E = 14, F = 15. Celkem máme k dispozici 9 čísel a 6 znaků, které formují zápis požadované celočíselné hodnoty v hexadecimální číselné soustavě.

Z ničeho nic se před námi objeví číslo 2B3C. Jak bychom jej – za účelem lepší srozumitelnosti pro široké publikum – převedli do desítkové soustavy?

(2B3C)16 = 2 × 163 + 11 × 162 + 3 × 161 + 12 × 160

Postupnou úpravou aritmetického výrazu získáváme:

(2B3C)16 = 2 × 4096 + 11 × 256 + 3 × 16 + 12 × 1
(2B3C)16 = 8192 + 2816 + 48 + 12
(2B3C)16 = (11068)10

Zkoušku správnosti provedeme postupným dělením desítkové hodnoty šestnáctkou a zaznamenáním zbytků po dělení (tabulka 2).
Při převodu čísla z desítkové do šestnáctkové soustavy nás vede stejný algoritmus jako při konverzi hodnot z desítkové do osmičkové soustavy. Rozdíly, s nimiž se setkáváme, jsou dva. Za prvé, dělíme šestnáctkou a ne osmičkou. A za druhé, pokud hodnota zbytků po dělení překročí hranici danou číslem 9, zapojujeme do hry jejich substituci abecedními znaky. To se děje také ve výše znázorněném algoritmu na dvou místech: v prvním kroku nahrazujeme zbytek po dělení znakem C, zatímco ve třetím kroku je zbytek substituován znakem B. Inverzně přečtená hodnota zní 2B3C, čímž jsme nepopiratelně prokázali správnost našeho postupu.
Přeneseme-li se do prostředí programovacího jazyka C++, pak můžeme prohlásit, že šestnáctkové konstanty jsou zde uváděny s prefixem (předponou) 0x nebo také 0X.

Binární číselná soustava

Přestože je pravda, že s binárními celočíselnými konstantami nebudeme ve zdrojovém kódu jazyka C++ přímo pracovat, binární číselná soustava zaujímá v oblasti počítačů nejvýznamnější postavení ze všech doposud zmíněných číselných soustav. Proto bychom si ji nikdy nedovolili z výkladu vyřadit.

Počítače jsou stroje, které bleskovou rychlostí provádějí operace, jež jsou fyzikálně reprezentované dvěma základními stavy („zapnuto/vypnuto“, „protéká proud/neprotéká proud“). Aparátem pro logické vyjádření těchto dvou stavů disponuje binární aritmetika, která ke svým výpočetním úkonům používá dvojkovou neboli binární číselnou soustavu. Binární soustava pracuje pouze s jedničkami a nulami, takže jakákoliv číselná konstanta musí být konvertovatelná do kratší nebo delší posloupnosti nul a jedniček. Základem dvojkové soustavy je pochopitelně číslo 2, z čehož plyne, že číselné hodnoty budou vyjadřovány jako součty mocnin dvojky.

Máme-li dvojkovou konstantu, pak ji do desítkové převedeme přímočarým rozkladem na příslušné mocniny se základem 2. Dejme tomu, že bychom měli zjistit decimální reprezentaci binárního čísla 101110. Jak budeme postupovat?

(101110)2 =
1 × 25 + 0 × 24 + 1 × 23 + 1 × 22 +
+ 1 × 21+ 0 × 20
(101110)2 =
1 × 32 + 0 × 16 + 1 × 8 + 1 × 4 +
+ 1 × 2 + 0 × 1
(101110)2 = 32 + 8 + 4 + 2
(101110)2 = (46)10

Inu, v matematice už musíte být tak zběhlí, že konverze decimální celočíselné hodnoty do formy dvojkového čísla bude pro vás pouhou příjemnou hrou s čísly (tabulka 3).

Přečteme-li zbytky po dělení vzestupně, získáváme důkaz dokládající, že (46)10 = (101110)2. Pro interpretaci čísla 46 v binární soustavě stačí, když vyhradíme pouhých 6 bitů. Běžně se však binární hodnoty zapisují v posloupnosti osmi nebo šestnácti bitů. Kdybychom chtěli 6bitovou hodnotu rozšířit na 8 bitů, jednoduše bychom zleva doplnili dva nulové bity. Analogicky, při rozšíření na 16 bitů přidáváme 10 nulových bitů. Proto při osmibitové notaci (46)10 = (00101110)2 a (46)10 = (0000000000101110)2 při šestnáctibitové notaci.

V jazyce C++ můžeme celočíselné konstanty opatřovat sufixy (příponami) u nebo U a l nebo L. Přípony u/U jsou použitelné u nezáporných konstant, jejichž datovým typem bude unsigned int. Jsou-li konstanty tak veliké, že přesahují rozsah typu int, můžeme jim přiřadit typ long – toho docílíme aplikací přípony l nebo L. Následující výpis zdrojového kódu demonstruje význam sufixů:

Reálné konstanty

V programovacím jazyce C++ nejsou celočíselné konstanty osamoceny. Ve skutečnosti se můžeme setkat rovněž s reálnými konstantami, znakovými a řetězcovými konstantami. Reálné konstanty jsou konstanty v podobě čísel s pohyblivou řádovou čárkou. Reálná konstanta je každá hodnota, která disponuje desetinným oddělovačem. Ve zdrojovém kódu jazyka C++ je tímto oddělovačem tečka, jak jsme si již několikrát předvedli. Stejně tak víme, že kompilátor přiřadí každé reálné konstantě implicitní datový typ double – tuto skutečnost jsme rozebírali při stavbě programu pro výpočet hmotnostního indexu BMI. Typ double je schopen uchovat hodnotu v pohyblivé řádové čárce s dvojitou přesností, což ne vždy potřebujeme. Vystačíme-li si s méně náročným typem float, přidáme k desetinné konstantě příponu f (nebo F), čímž změníme datový typ, jenž byl konstantě implicitně přisouzen.

Užití reálné konstanty se sufixem f přibližuje program pro výpočet obsahu kruhu.

Přípona f je spojena s reálnou konstantou 3.1415, která aproximuje konstantu Pí (p). To znamená, že datovým typem této reálné konstanty již nebude implicitní double, nýbrž explicitní float. Jakmile programu sdělíme celočíselnou hodnotu reprezentující poloměr kruhu, za malý okamžik obdržíme informaci o jeho obsahu. Tento fragment kódu připomíná další vymoženost jazyka C++: proměnnou smíme definovat všude tam, kde se nám zlíbí. Zpravidla se programátoři drží pravidla, které říká, že proměnné by měly být definovány těsně před svým použitím, resp. inicializací. Přesně v tomto duchu pracujeme s proměnnou obsahKruhu, která je definována a současně inicializována až tehdy, když máme po ruce všechna data nutná pro výpočet obsahu imaginárního kruhu.

Studenti zkoumající taje analytické geometrie jsou často postaveni před úkol určit vzdálenost mezi dvěma body. Tyto body leží v dvourozměrném (2D) euklidovském prostoru a jejich pozice jsou jednoznačně určeny prostřednictvím jejich x-ových a y-ových souřadnic. Mějme body A a B, jejichž souřadnice jsou A[Ax, Ay] a B[Bx, By]. Vzdálenost mezi body A a B je v rovině určena tímto matematickým vztahem:
|AB| = ? (Bx ? Ax)2 + (By ? Ay)2
Zmíněný vzoreček využívá Pythagorovu větu, jeho grafický důkaz uvádíme na obrázku.

A nyní se podívejme na program, jenž implementuje algoritmus pro určení vzdálenosti mezi dvěma body v 2D euklidovském prostoru.

Komentář k programu: Soustředíme se pouze na tzv. kritická místa programu. Jelikož v zájmu vypočtení vzdálenosti mezi dvěma body musíme vypočítat druhou odmocninu ze součtu druhých mocnin rozdílů jednotlivých souřadnic bodů, budeme potřebovat funkci sqrt. Tato funkce se nachází v hlavičkovém souboru math.h, který musíme importovat pomocí direktivy #include preprocesoru (všimněte si, že používáme nový název „cmath“, přesně podle konvencí ISO standardu pro jazyk C++). Patrně nejsložitější je definiční inicializace proměnné vzdalenost. Nás bude zajímat vyhodnocení aritmetického výrazu, jenž je přítomen na pravé straně definiční inicializace. Zmíněná funkce sqrt zjistí druhou odmocninu vstupní hodnoty, kterou ji předáme (vstupní hodnota se nazývá též argument funkce). Vstupní hodnotou je součet druhých mocnin, který vypočteme takto:
(B_x - A_x) * (B_x - A_x) + (B_y - A_y) * (B_y - A_y).

Když uvážíme, že všechny zapsané proměnné jsou celočíselné, pak i výsledná hodnota aritmetického výrazu je celé číslo. Potíž je v tom, že formální parametr funkce sqrt očekává reálný argument. Musíme proto původně celočíselnou hodnotu explicitně převést na hodnotu typu double. To se děje v průběhu explicitní typové konverze, která obecně vypadá takto: T(V), kde T je cílový typ a V je výraz, který je nejprve vyhodnocen, a poté je typ hodnoty výrazu převeden na cílový typ T. Prozatím zkusme brát explicitní přetypování jako fakt, v dalších lekcích našeho kurzu budeme mít ještě dostatek času na experimentování s typovými konverzemi. Jakmile funkce sqrt obdrží vstupní data v požadovaném formátu, vypočte pro nás jejich druhou odmocninu, kterou je opět reálné číslo. To posléze ukládáme do proměnné vzdalenost, jejíž hodnotu sdělujeme uživateli. 8 0286/CZ oPokud modifikátor unsigned spojíme s jakýmkoliv z integrálních datových typů (char, short, int a long), získáme nový, tzv. neznaménkový integrální datový typ. Pro neznaménkové typy je příznačné, že jsou schopny interpretovat pouze hodnoty z intervalu <0, max>, kde hodnota max závisí na použitém datovém typu. Proměnná neznaménkového typu smí uchovávat pouze přirozená čísla, nikdy záporné hodnoty.
Při pojednání o reprezentaci čísel v různých pozičních číselných soustavách platí konvence, že samotná číselná hodnota je zapsána v závorkách a základ číselné soustavy, v níž je hodnota interpretována, je umístěn v dolním indexu. Tuto konvenci budeme dodržovat, neboť je nejenom efektivní, nýbrž také dobře čitelná. Pokaždé, když se podíváte na dolní index dotyčné číselné hodnoty, budete okamžitě vědět, v jaké soustavě je tato hodnota vyjádřena.

Autor článku