| ← vstup a výstup | C/C++ | pole a ukazatele → |
Již od začátku výkladu se potýkáme s bity a bajty, s adresami a ukazateli a datovými typy. Neustále se snažím vysvětlit kdy se jakým způsobem bity reprezentují, kdy jako číslo, kdy jako znak, kdy jako číslo se znaménkem a kdy bez atd. V této kapitole si důležité věci zopakujete a dozvíte se poslední informace, které vám chybí. Po prostudování této kapitoly by vám již mělo být naprosto jasné, jak a proč se bajty interpretují tím či oním způsobem.
Přetypování je způsob, jak změnit interpretaci nějakého datového typu, ať již proměnné či konstanty. Například když dělíme dvě celá čísla, výsledkem je zase celé číslo. To se nám nemusí vždy hodit. Také víme, že znaky jsou jenom bity a bajty, tudíž je lze reprezentovat jako číslo a stejně tak lze číslo reprezentovat jako znak. Jakému číslu je přiřazen jaký znak, to určuje právě ASCII tabulka. Někdy zase můžeme chtít reprezentovat číslo typu signed int (celé číslo se znaménkem) jako unsigned int (celé číslo bez znaménka).
Znak je typ char. Jeho velikost je 8 bitů, což je rozsah čísel od 0 do 255. Jakému číslu je přiřazen jaký znak, to určuje právě ASCII tabulka. Pokud vytvoříte nějaký čistě textový soubor (tedy soubor obsahující jen znaky), pak co jeden znak, to jeden bajt. (Výjimkou může být znak konec řádku, který se v DOSu a Windows reprezentuje dvojicí znaků 10 a 13 (v Linuxu jen 10). Některým číslům není přidělen viditelný znak. Například kód 65 odpovídá znaku velké A, ale kód 1 odpovídá stisku kláves CTRL+a a nemá žádný zobrazitelný ekvivalent. Interpretace číselného kódu se může měnit, v závislosti na použitém kódování (kódy 128-255).
Zde vidíte tu zajímavější část ASCII tabulky:
|
|
Jiným typem kódování než je ASCII, je například UTF-8. To využívá k interpretaci znaku proměnlivý počet bytů. To je pro jazyk C trochu problém, protože pro jeden znak vyhrazuje vždy jeden byte. Drobnou útěchou může být, že prvních 127 znaků v UTF-8 zabírá jeden bajt a jsou stejné jako v ASCII kódování.
Přetypování slouží ke změně datového typu objektu (proměnné, konstanty,...). Přetypování datového typu se provádí tak, že se před výraz napíše do závorky typ, na který chceme přetypovat. Nelze samozřejmě přetypovat cokoliv na cokoli, například řetězec na číslo. Přetypování je jeden z mocných nástrojů jazyka C. K přetypování výrazů dochází mnohdy automaticky, aniž by jste si to uvědomovali. Například když do proměnné typu float přiřadíte celočíselnou konstantu, ta se nejprve (automaticky) převede na typ float a pak se teprve uloží do proměnné. V příkladu si ukážeme, jak se přetypování může využít.
1: /*------------------------------------------------*/ 2: /* pretyp.c */ 4: #include <stdio.h> 6: int main(void) 7: { 8: int a, b; 9: float c, d; 10: char ch = 'x'; 12: printf("Zadej delenec: "); 13: scanf("%d", &a); 14: printf("Zadej delitel: "); 15: scanf("%d", &b); 16: c = a / b; 17: d = (float) a / b; 18: printf("Celociselne deleni: %+5.2f\n", c); 19: printf("Racionalni deleni: %+5.2f\n", d); 21: printf("ASCII kod znaku %c je %i\n", ch, (int) ch); 22: return 0; 23: } 25: /*------------------------------------------------*/
Výstup z programu:
Zadej delenec: 50 Zadej delitel: 3 Celociselne deleni: +16.00 Racionalni deleni: +16.67 ASCII kod znaku x je 120
Jak jsem již říkal, k přetypování dochází mnohdy automaticky. Například když do proměnné long uložíte číslo 10. 10 je konstanta, kterou překladač vyhodnotí jako int. Proto se nejdříve musí číslo 10 přetypovat na typ long. Překladač vás v takovém případě může varovat, že zde dochází k přetypování a to není hezké. Řešení je použít buďto modifikátor L ( long x = 10l; nebo float x = 10.0; místo x = 10;), nebo přetypování (long x= (long) 10;). První způsob se používá právě u konstant, druhý způsob zvláště u proměnných, protože u nich první způsob použít nelze. ( x= (long) a;).
Přetypovávat můžete i návratové hodnoty funkcí. Například funkce printf() vrací typ int. Návratovou hodnotu lze získat takto:
navrat = printf("Hello World");
Pokud návratovou hodnotu funkce nikam neukládáme, můžeme to překladači zdělit přetypováním návratové hodnoty na typ void takto:
(void) printf("Hello World");
Překladač by totiž mohl u některých funkcí (u printf() to standardně nedělá) hlásit varování, že funkce vrací návratovou hodnotu a že se nikam neukládá. Tímto přetypováním překladači dáme jasně najevo, že návratovou hodnotu opravdu, ale opravdu nechceme a on už pak varovat nebude.
Někdy hraje přetypování docela zásadní roli. Přetypování totiž neprobíhá jako zaokrouhlování, desetinná část se usekne. Může tak docházet k problémům, které se těžko odhalují. Podívejte se na následující příklad. Máme nějakou virtuální obrazovku, která obsahuje nejméně 80 sloupců, ale může jich mít i více. Počet sloupců máme uložen v proměnné COLS. Obrazovku chceme rozdělit na dvě části tak, aby jedna část obsahovala 12 sloupců plus cca 25% z toho, co je nad 80 sloupců a druhá část zbytek. Využijeme k tomu následující výpočet:
(12 + ((COLS - 80) * 0.25));
Tedy:
okno1 = (12 + ((COLS-80) * 0.25)); okno2 = COLS - (12 + (COLS-80) * 0.25));
Na první pohled se může zdát, že okno1 + okno2 = COLS. Podívejme se, jak může takový výpočet interpretovat překladač pro COLS = 84 a COLS = 85:
Pro COLS = 84:
okno1 = (12 + ((84-80) *0.25)) = (12 + (4 *0.25)) = (12 + 1.0) = 13.0 okno1 = 13; okno2 = (84 - (12 + (84-80) * 0.25)) = 84 - 13.0 = 72.0 okno2 = 72;
Ale pro COLS = 85:
okno1 = (12 + ((85-80) *0.25)) = (12 + (5 *0.25)) = (12 + 1.25) = 13.25
okno1 = 13;
okno2 = (85 - (12 + (85-80) * 0.25)) = 85 - 13.25 = 71.75
okno2 = 71; /* a chyba je na světě */
Řešení je jednoduché. Stačí výraz správně a včas přetypovat. Podívejte se na výpočet s přetypováním pro COLS = 85;
okno1 = (12 + (int) ((85-80) *0.25)) = (12 + (int) (5 *0.25)) =
= (12 + (int) 1.25) = 12 + 1 = 13;
okno2 = (85 - (12 + (int) (85-80) * 0.25)) = 85 - (12 + (int) 1.25) =
= 85 - (12 + 1) = 85 - 13 = 72;
Chybami se člověk učí, stroj jenom blbne :-).
Pokud překladač narazí při výpočtu na dva rozdílné typy,
interpretuje oba jako ten větší (přesnější) z nich. Proto se výpočet
5 * 0.25
automaticky interpretuje jako ((float) 5) * 0.25 a ne
5 * (int) 0.25. Výsledek, který se ukládá
do proměnné se do této proměnné musí vejít, proto se přetypovává vždy
na typ proměnné.
Pokud se pokusíte uložit do proměnné hodnotu větší, než kterou je schopna pojmout
(například char ch = 256;
1)),
výsledkem bude nedefinovaná hodnota, tj nikdo vám nijak nedefinuje, jaká nakonec bude
skutečná hodnota proměnné.
1) Tento výraz může jeden překladač interpretovat jako jedničku, jiný jako nulu, jiný ponechá v proměnné ch to, co v ní bylo před přiřazením ... zkrátka nikde není definováno, co se má stát. Nemá smysl zkoušet jak váš překladač takový výraz vyhodnotí, protože příští verze překladače (nebo nějaký jiný překladač) to může vyhodnotit jinak. Určitě nechcete psát zdrojový kód, který bude spolehlivě fungovat jen s jedním překladačem jedné verze.
| ← vstup a výstup | C/C++ | pole a ukazatele → |
