Pojďte s námi programovat !

1. 9. 2008

Sdílet

c++ V zářijové lekci programátorského kurzu jazyka C++ si přiblížíme techniku zastiňovaní globálních prom...



c++

V zářijové lekci programátorského kurzu jazyka C++ si přiblížíme techniku zastiňovaní globálních proměnných, seznámíme se s konstantními proměnnými a vydáme se zkoumat typové konverze.



Zastínění globálních proměnných

Globální proměnné mohou být zastíněny stejnojmennými lokálními proměnnými. K této situaci dochází v následujícím fragmentu programového kódu jazyka C++:

Když si prostudujeme zdrojový kód, zjistíme, že pracujeme s globální proměnnou a typu int, které je v rámci definice přiřazena inicializační hodnota 10. Postoupíme-li dále, pak shledáme, že v těle hlavní funkce main se nachází definiční příkaz, jenž vytváří celočíselnou proměnnou se stejným jménem, jako má globální proměnná. V těle hlavní funkce nám tedy vzniká lokální proměnná, do které ukládáme celočíselnou konstantu 20. Nejzajímavější bod programu teprve přichází: v dalším kroku využíváme služeb objektu cout a snažíme se vypsat hodnotu proměnné a na obrazovku počítače. Ano, přesně tak to vypadá, ovšem dokážeme předpovědět, co vlastně bude na výstupu zobrazeno? Jako programátoři musíme vždy vědět, co děláme, a to i v těch nejzapeklitějších situacích. Konec konců psaní softwaru není žádná magie, i když se vás možná někteří vývojáři budou snažit přesvědčit o opaku. Na výstupu obdržíme hlášení, že v proměnné a je uložena hodnota 20. A nyní se pojďme podívat, proč tomu tak je.
Pokud je v jazyce C++ definována na lokální úrovni proměnná se stejným identifikátorem, jaký má globální proměnná, potom říkáme, že tato lokální proměnná zastiňuje globální proměnnou.

Dobrá, nyní již víme, že globální proměnná je v těle hlavní funkce main zastíněna lokální proměnnou. Znamená to ovšem, že s globální proměnnou nemůžeme ve funkci main vůbec manipulovat? Nikoliv, taková možnost tu samozřejmě je. Jenom musíme mít jistý prostředek, kterým vyjádříme náš záměr pracovat s globální proměnnou. Tímto prostředkem je rozlišovací operátor ::, s jehož přispěním lze zcela jednoznačně určit, že cílovým objektem je globální a ne lokální proměnná.

Všimněme si „čtyřtečky“ před proměnnou a – to znamená, že příkaz operuje s globální a nikoliv s lokální proměnnou. Obě proměnné můžeme používat současně, stačí, když si zapamatujeme, že před globální proměnnou stojí operátor ::.

Je možné, že zprvu se vám bude ona čtyřtečka před proměnnou zdát poněkud podivná, ovšem to je pouze půvab prvního seznámení. Příště to už bude zcela jistě lepší, věřte nám…

Konstantní proměnné

Proměnnou jsme definovali jako datový objekt, do něhož lze ukládat hodnoty určitého datového typu. Ukázali jsme si, jak se proměnné definují a jak se do nich přiřazují hodnoty. Přitom jsme se vší důkladností pravili, že jakmile je proměnná na světě, můžeme její hodnotu měnit tolikrát, kolikrát budeme chtít. Proměnná coby datový kontejner může měnit svou hodnotu, to je fakt, který plyne již ze samotného názvu „proměnná“. Nyní bychom vás měli seznámit s konstantní proměnnou, což je proměnná, která nesmí po prvotní inicializaci svou hodnotu měnit.
V programovací hantýrce jsou konstantní proměnné velice často označovány jako konstanty, ovšem takováto identifikace vede ke konfliktům se „skutečnými“ konstantami, o nichž jsme mluvili v jedné z předcházejících lekcí našeho programátorského kurzu. Abychom dodrželi jasnou a přehlednou terminologii, budeme proměnné, jež nelze reinicializovat, nazývat konstantními proměnnými.
Konstantní proměnné v jazyce C++ poznáte snadno, protože v jejich definičních příkazech se vyskytuje modifikátor const:

Když se v definici proměnné objeví modifikátor const, máme jasný důkaz o tom, že se jedná o konstantní proměnnou.

Praktickou aplikaci konstantní proměnné si předvedeme na programu pro výpočet obvodu kruhu. V tomto programu definujeme globální proměnnou s identifikátorem PI, která bude uchovávat aproximovanou hodnotu čísla ?. Vzhledem k tomu, že hodnota této konstanty se nebude nikdy měnit, návrh přetvořit globální proměnnou na globální konstantní proměnnou nám přijde zcela logický.

Modifikátor const ochraňuje globální konstantní proměnnou, poněvadž nikdy nedopustí, aby byla hodnota této konstantní proměnné modifikována. Ačkoliv je možné aplikovat modifikátor const také v souvislosti s lokálními proměnnými, konstantní proměnné (jako je PI v našem programu) jsou zpravidla umísťovány na globální úroveň, aby byly k dispozici všem funkcím programu.

Typové konverze

Při programování se docela často stává, že potřebujeme změnit datový typ jisté hodnoty (může se přitom jednat o diskrétní hodnotu, nebo o hodnotu, jež vzešla z výrazu). Změna datového typu hodnoty z původního na cílový se v programování označuje termínem typová konverze nebo též přetypování. Množina typových konverzí se dělí na dvě podmnožiny: implicitní a explicitní typové konverze. Při implicitních typových konverzích je změna datového typu hodnoty provedena kdykoliv je to nezbytné. Implicitní typové konverze realizuje kompilátor ve své vlastní režii, bez jakéhokoliv přičinění programátora. Překladač sleduje tok našeho zdrojového kódu, a pokud je to nutné, automaticky provádí požadovaná přetypování. Implicitní typové konverze se dostávají ke slovu tehdy, detekuje-li kompilátor nesoulad datových typů. Jestliže se objeví takovýto nesoulad, kompilátor se ho pokusí vlastními silami odstranit. To je pravý význam implicitního přetypování. Nicméně, za jistých okolností budeme chtít sami stanovit charakter konverzních operací. V těchto okamžicích uchopíme věci do vlastních rukou a prostřednictvím explicitní typové konverze nařídíme průběh konverzní operace.

Implicitní typové konverze

S implicitními typovými konverzemi jsme se již setkali, ovšem abychom zbytečně nepřerušovali výklad, ponechali jsme je bez povšimnutí. Nyní se k těmto doposud tajemným jevům vrátíme a uvedeme věci na pravou míru. Začněme jednoduchým přiřazovacím příkazem:

Toto přiřazení vyvolává nesoulad datových typů, poněvadž se snažíme uložit reálnou konstantu typu double do proměnné typu float. Na jedné straně tedy máme typ float, zatímco na druhé straně je to typ double. Nastane-li nesoulad zúčastněných datových typů v přiřazovacím příkazu, kompilátor převede datový typ reálné hodnoty na datový typ cílové proměnné. Implicitní typ double, který je všem reálným konstantám přiřazen, se tedy změní (neboli konvertuje) na typ float, čímž dospějeme k typové shodě. Do proměnné typu float smí být ukládány pouze hodnoty typu float a žádné jiné. Pokud bychom chtěli do proměnné typu float uložit hodnotu jiného datového typu, musí dojít k typové konverzi. Jak již víme, správným užitím sufixu f můžeme vytvořit reálnou hodnotu (čili konstantu) typu float. Tím sice ztrácíme polovinu přesnosti v pohyblivé řádové čárce, ovšem na druhou stranu neposkytujeme žádnou příležitost pro vznik nesouladu datových typů.
Zaměříme-li se výhradně na přiřazovací příkaz, pak můžeme prohlásit, že datový typ hodnoty nacházející se napravo od operátoru přiřazení (=) bude vždy implicitně konvertován na typ proměnné, která stojí nalevo od přiřazovacího operátoru. Setkáme se dejme tomu s takovýmto přiřazovacím příkazem:

Pokusme se určit datové typy všech entit, s nimiž při zpracování tohoto přiřazovacího příkazu přichází kompilátor do kontaktu. Nejsnadněji se vypořádáme s lokální proměnnou x, jejímž typem je int. Pohlédneme-li na aritmetický výraz, spatříme tři konstanty. První je desítka, jež představuje celočíselnou konstantu, které kompilátor implicitně přiřadí typ int.
Po operátoru násobení (*) následuje reálná konstanta 2.2f, které jsme pomocí přípony f přisoudili typ float. Na samém konci stojí opět reálná konstanta 0.125, ovšem bez přípony, což implikuje použití implicitního datového typu double. Aritmetický výraz tvoří tři konstanty, které můžeme zároveň chápat jako operandy. Dovedeme určit, jak bude tento aritmetický výraz vyhodnocen? Ale ovšemže ano, je to poměrně jednoduché cvičení.
Výpočet zahájíme komplexním pohledem na aritmetický výraz: vidíme tři operandy (konstanty) a dva operátory (* a +). Oba operátory patří k aritmetickým operátorům, o kterých budeme podrobněji hovořit v jedné z následujících lekcí tohoto kurzu. V této chvíli je důležité vědět, že operátor násobení (*) má vyšší prioritu než operátor sčítání (+). Jako první se tedy provede multiplikativní operace 10 * 2.2f. Ovšem pozor, operátor nemůže násobení okamžitě provést, poněvadž oba operandy, tedy konstanty, disponují odlišnými datovými typy! Připomeňme, že konstanta 10 je typu int a konstanta 2.2f je typu float. Aby mohl být součin proveden, operátor * si vyžádá uskutečnění implicitní typové konverze.
V procesu implicitní typové konverze bude konstanta s typem s „nižší“ prioritou přetypována na typ s „vyšší“ prioritou. V podvýrazu 10 * 2.2f je typem s vyšší prioritou typ float, což znamená, že konstanta 10 bude konvertována na typ float. Z celočíselné desítky se tak stane hodnota 10.0f. Po zpracování implicitní typové konverze již podvýraz vypadá jinak: 10.0f * 2.2f. Je zřejmé, že došlo k sjednocení typů, neboť po obou stranách operátoru * se nacházejí reálné konstanty. Operátor nyní může vypočítat součin, jehož hodnota je 22.0f – všimněme si, že součin je rovněž typu float. Avšak tím nekončíme, poněvadž aritmetický výraz pokračuje dále. K vypočtenému součinu musíme připočítat konstantu 0.125 typu double. Řešíme tedy podvýraz 22.0f + 0.125. Jestliže říkáte, že zde opět narážíme na neshodu datových typů, pak máte samozřejmě pravdu. Ovšem v tomto případě je typem s vyšší prioritou double, takže z konstanty 22.0f se stane 22.0 s typem double. Součet je tedy opět typu double a má hodnotu 22.125. A to je finální hodnota aritmetického výrazu umístěného na pravé straně přiřazovacího příkazu.
Jak budeme postupovat dál? V této chvíli musíme hodnotu 22.125 typu double přiřadit do proměnné typu int.

I když při implicitních typových konverzích realizovaných v rámci přiřazovacích příkazů může dojít k ztrátě datové informace, je to asi jediná výjimka, kdy je možné se s uvedenou ztrátou setkat. Jak jsme si ukázali při postupném vyhodnocování aritmetického výrazu, implicitní konverze ztrátu informací nezapříčiňují, neboť vždy jsou hodnoty převáděny z typů s menším rozsahem do typů s větším rozsahem. Níže uvádíme některé z takovýchto implicitních typových konverzí:

• char ? short,
• short ? int,
• int ? long,
• float ? double.
Podívejme se třeba na druhou možnost: short ? int. Proměnná typu short dovede uchovat 16bitovou celočíselnou hodnotu se znaménkem. Jelikož rozsah typu int je dvojnásobný (32 bitů), nečiní proměnné tohoto typu problém absorbovat všechny platné celočíselné hodnoty typu short.
Někteří začínající programátoři s půvabnou lehkostí vykouzlí něco podobného, jako je následující fragment zdrojového kódu:

Nuže, zdá se vám vše OK? Ale kdepak, kód v pořádku určitě není. Do očí bijící je druhý řádek v těle hlavní funkce main, v němž identifikujeme pokus o přiřazení hodnoty 1000 do proměnné typu char. Když kód přeložíme, kompilátor se nám odvděčí varováním C4244, které sděluje, že implicitní typová konverze z typu unsigned short na typ char může znamenat ztrátu dat. Ačkoliv se kompilátor dá umlčet explicitní typovou konverzí, přiřazovací příkaz neproběhne tak, jak nejspíše předpokládáme. Řešení je velice prosté: jelikož typ char má rozsah <–128, 127>, není jednoduše možné do proměnné tohoto typu uložit hodnotu 1000. Co s tím? Inu, situace je komplikovanější, než bychom si možná chtěli připustit. Kompilátor sice ohlásí varování, ovšem program nám spustit umožní. Úžas možná vyvolá zjištění, že proměnná n má hodnotu –24. Jak je to vůbec možné?
Vše souvisí s alokační kapacitou, kterou proměnné typu unsigned short a char disponují. Proměnná typu unsigned short zabírá v paměti 2 bajty, zatímco proměnná typu char si vystačí s bajtem jediným. Když hodnotu 1000, kterou ukládáme do proměnné typu unsigned short, převedeme do binární podoby, obdržíme následující posloupnost bitů: 1111101000. V tabulce vidíte důkaz předcházejícího tvrzení.
Co bezpečně víme je, že celočíselná konstanta 1000 je ve dvojkové poziční číselné soustavě vyjádřena pomocí deseti bitů. Každá proměnná typu unsigned short alokuje 2 bajty, z čehož plyne, že hodnoty uložené do této proměnné mohou být reprezentovány posloupností maximálně 16 bitů. Celočíselná konstanta 1000 vyžaduje pouze 10 bitů, takže její uložení do proměnné typu unsigned short s sebou nenese žádná rizika. Dobrá, ovšem jak 10 bitů konstanty 1000 vměstnáme do proměnné typu char, která smí operovat pouze s osmi bity? Přestože by možná bylo vzrušující přijít s nějakým kouzelnickým trikem a dospět k iluzi, že to jde, matematika nás nepustí. Stejně tak jako není možné do litrové lahve nalít dva litry limonády, nelze ani do 8bitové proměnné typu char uložit 10bitovou hodnotu. Jediné, co můžeme udělat, je to, že z původní 10bitové hodnoty „uřízneme“ úvodní 2 bity. Tímto „chirurgickým“ zákrokem se dopracujeme k následující binární hodnotě o délce osmi bitů: 11101000. Převodem získané binární hodnoty do desítkové soustavy obdržíme číslo –24.

(11101000)2 =
1 × 27 + 1 × 26 + 1 × 25 + 0 × 24+ +1 × 23+ 0 × 22+ 0 × 21+ 0 × 20
(11101000)2 = -128 + 64 + 32 + 8 = (-24)10

Výsledná hodnota je záporná, protože její nejvýznamnější bit (ten nejvíce vlevo) je 1. Všechny záporné hodnoty jsou ve dvojkovém doplňku kódovány s prvním „jednotkovým“ bitem.

Explicitní typové konverze

V okamžiku, kdy potřebujeme přetypovat hodnotu jistého datového typu na jiný, můžeme směle sáhnout po explicitních typových konverzích. Pro explicitní typové konverze je typické, že je provádí vždy programátor, kompilátor se tímto druhem typových konverzí automaticky nezabývá. Programovací jazyk C++ nabízí tři základní varianty, jak explicitně uskutečňovat konverze mezi datovými typy:

Explicitní typové konverze ve stylu jazyka C.

Explicitní typové konverze ve stylu jazyka C++.

Explicitní typové konverze realizované pomocí konverzních operátorů jazyka C++.

S prvními dvěma konverzními mechanizmy se seznámíme v následujících odstavcích.

Explicitní typové konverze
ve stylu jazyka C

Programátoři, kteří mají zkušenost s jazykem C, vědí, že v tomto prostředí lze explicitní přetypování provést podle tohoto vzoru:

(T)V

kde:
• T je cílový datový typ, do něhož bude konvertován typ hodnoty výrazu V.

Ačkoliv u konverzí budeme vždy pracovat s výrazy, tím, co bude konvertováno, je typ hodnoty, kterou daný výraz vyprodukuje. Hodnota výrazu je diskrétní konstanta, která je vyjádřena v jistém datovém typu. Typ hodnoty výrazu je považován za zdrojový (neboli originální) typ, zatímco typ, do něhož má být hodnota konvertována, se pokládá za cílový typ. Písmeno T ve zmíněném vzoru proto odpovídá specifikaci cílového datového typu. Originální datový typ není ve vzoru nikde zmiňován, poněvadž jej ani sami neznáme (tento typ bude určen v závislosti na typech operandů, jež formují výraz).

Použití explicitní typové konverze ve stylu jazyka C demonstruje následující výpis zdrojového kódu:

V kódu nejprve definujeme a inicializujeme proměnnou o typu int. Poté vytváříme proměnnou p typu short, do které bychom rádi přiřadili hodnotu proměnné o. Inicializační hodnota 30000 celočíselné proměnné o je typu int, proto ji explicitně převádíme na hodnotu typu short. Požadovaný cílový typ short uzavřeme do závorek a umístíme jej před proměnnou o. Takto vydáme příkaz pro přetypování hodnoty proměnné o z typu int na typ short.
Ne vždy je však tomu tak. Kupříkladu hodnotu proměnné o typu int je možné explicitně konvertovat na typ short rovněž v samostatném výrazu, který bude součástí výstupního příkazu:

Explicitní typová konverze nám umožňuje kontrolovat konverzní operace, ke kterým dochází při sjednocování typů v aritmetických výrazech.

Víte, jakou inicializační hodnotou bude naplněna proměnná p typu unsigned char? Správná odpověď zní 17. A zde je postup, jak jsme k ní dospěli: z reálné konstanty 18.77 typu double uděláme explicitní typovou konverzí celočíselnou konstantu 18 typu int (čímž ztrácíme desetinnou část). Další explicitní přetypování čeká rovněž reálnou konstantu 9.50 typu double, kterou měníme na celočíselnou hodnotu 9 typu char. Poslední konstanta 10 je celočíselná, přičemž jejím typem je implicitní int. Operátory + a – jsou aritmetické operátory se stejnou prioritou a vyhodnocováním ve směru zleva doprava. Výraz je tedy vyhodnocen jako (18 + 9) – 10, což je 17.

Explicitní typové konverze
ve stylu jazyka C++

Zatímco explicitní typové konverze podle norem jazyka C byly reprezentovány syntaktickým výrazem:

(T)V

tak u explicitních typových konverzí podle jazyka C++ se uplatňuje přesně opačný přístup, a sice:

T(V)

Do závorek se tedy nevkládá cílový datový typ, do něhož má být konvertován typ hodnoty výrazu, nýbrž samotný výraz, jenž je předmětem přetypování. Explicitní typová konverze v jazyce C++ se spíše podobá volání funkce, což zjistíme později, až se s funkcemi seznámíme blíže. Bude dobré, když si jako programátoři zapamatujete, že typová konverze smí být provedena i takovýmto syntaktickým zápisem.

V kódu explicitně přetypováváme typy reálných konstant 2.034 a 14.75 z double na int. Pokud bychom chtěli, mohli bychom přetypovat na int typ hodnoty celého aritmetického výrazu, nejenom typy prvních dvou konstant. Pak by ovšem kód vypadal následovně:

Co se změní? Tak především, změní se hodnota, kterou program zobrazí na výstupu. Zatímco v prvním fragmentu kódu je do proměnné vyraz uložena hodnota 50, v druhém programu je výslednou hodnotou 52. Rozdíl ve vyhodnocování spočívá v tom, že druhý program konvertuje až typ finální hodnoty (tedy hodnoty, kterou získáme, když zpracujeme celý aritmetický výraz). Podívejme se na obě konverze pod drobnohledem.

Tyto explicitní typové konverze se týkájí reálných konstant 2.034 a 14.75, jejichž implicitním datovým typem je double. Ano, to je jistě snadno pochopitelné: typ reálné konstanty se mění z double na int. Tato operace je spojena s likvidací všech číslic za desetinným oddělovačem, takže dále již pracujeme s celočíselnými a nikoliv s reálnými konstantami. Výraz je posléze vyhodnocován, jako by byl zapsán tímto způsobem:

Násobení má přednost před součtem, takže podvýraz 2 * 14 bude zpracován jako první. Jelikož oba operandy (2 a 14) jsou celočíselné, mohou být okamžitě vynásobeny, aniž by se musela uskutečnit implicitní typová konverze. K hodnotě součinu 28 je vzápětí přičteno celé číslo 22 s implicitním typem int. Tím pádem získáme „padesátku“, která je nejenom hodnotou celého aritmetického výrazu, nýbrž také inicializační hodnotou proměnné vyraz.
Když nám to tak báječně jde, pusťme se rovnou do druhého výrazu, jehož podoba je následující:

Vidíme, že zde je uzavřen do závorek celý výraz. To znamená, že na typ int bude konvertován až typ finální hodnoty tohoto výrazu. Jelikož se neobtěžujeme s žádnými explicitními typovými konverzemi podvýrazů, proběhnou standardně předepsané implicitní typové konverze. Součin 2.034 * 14.75 si nevyžaduje implicitní typovou konverzi, poněvadž obě reálné konstanty jsou shodného typu double. Součin činí 30.0015 a je opět typu double. Při zpracovávání součtu 30.0015 + 22 je hodnota 22 povýšena do role reálné konstanty typu double. Finální hodnotou aritmetického výrazu je tedy 52.0015, která bude posléže explicitně přetypována na hodnotu 52 typu int a takto uložena do připravené proměnné. 8 0462/CZ o

Je to podobné, jako kdybychom měli dvě krabice, jednu menší s nápisem short a druhou dvakrát tak velikou s nápisem int. Ať už naplníme menší krabici pouze z poloviny nebo zcela, vždy ji můžeme vzít a vložit ji do krabice s větším objemem. Při transportu předmětů z menší krabice do té větší neztrácíme vůbec nic, pouze získáváme větší úložný prostor. Stejně tak se chovají implicitní typové konverze, které jsme popsali. Napadá-li vás otázka, jak by to celé vypadalo naopak, pak vás poprosíme o chvíli strpení. Tuto otázku vyřešíme při ozřejmění podstaty explicitních typových konverzí.
Vývojové prostředí Microsoft Visual C++ 2008 Express zobrazuje obsahy proměnných datového typu char jako textové znaky. Proto bude na výstupu zobrazen textový znak, jehož interní číselný kód je –24.Nebudeme skrývat, že tato explicitní konverze je velice jednoduchá. Ve skutečnosti je natolik triviální, že by ani nemusela být ve zdrojovém kódu přítomna. Jak si jistě pamatujete, řekli jsme si, že kompilátor implicitně převádí typ hodnoty získané na pravé straně přiřazovacího příkazu na typ proměnné, která stojí nalevo. To je přesně ono, tudíž i kdybychom explicitní typovou konverzi v tomto případě neprovedli, kompilátor by stejného účinku dosáhl zpracováním implicitní konverze.