Práce s typy dat
Již od začátku výkladu se potýkáte 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.
O přetypování a ASCII tabulce
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íte dvě celá čísla, výsledkem je zase celé číslo. To se vám nemusí vždy hodit. Také víte, že znaky jsou jenom bity a bajty, tudíž je lze reprezentovat jako číslo a stejně tak lze (celé) číslo reprezentovat jako znak. Jakému číslu je přiřazen jaký znak, to určuje tzv. ASCII tabulka. Někdy zase můžete chtít reprezentovat číslo typu signed int (celé číslo se znaménkem) jako unsigned int (celé číslo bez znaménka).
ASCII tabulka
(ASCII = American Standard Code for Information Interchange)
Vnitřní interpretace znaku je v jazyku C typu int a v jazyku C++ typu char. Velikost char 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 (v 8-bitovém kódování), tedy soubor obsahující jen znaky, pak co jeden znak, to jeden bajt. Bez ohledu na to, že v jazyce C je znak typu int – to je pouze „vnitřní interpretace znaku“, tj. hodnota, se kterou pracuje program. Při vstupu/výstupu do/z programu proudí vždy 8 bitů.
Výjimkou může být znak konec řádku, který se v DOSu a Windows reprezentuje dvojicí znaků s kódem (číselnou hodnotou) 10 a 13. Naproti tomu v Linuxu se zapisuje jen jeden znak – 10. (Takže to vlastně žádná vyjímka není, prostě jsou to někdy dva znaky :-))
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). Pod těmito čísly jsou uloženy například znaky s háčky a čárky, které se bohužel kódování od kódování liší. Např. pro češtinu se použají kódování windows-1250 (na Windows), iso-8852-2 (ve starších verzích Linuxu) atp. Většina znaků má v obou kódování stejné číslo, ale některé (s interpunkcí) mají jiná čísla. Proč tomu tak je, na to se zeptejte Billa Gatese.
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 stejný počet bitů (obvykle 8).
Pokud pracujete s řetězcem jako celkem, je to vcelku jedno,
ale pokud chcete pracovat s jednotlivými znaky, už nastává problém – je byt celý znak,
nebo jen jeho část? Kolik znaků je v řetězci? (v UTF-8 neplatí, že počet znaků = počet bytů)
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í.
UTF-8 je dnes nejpoužívanější kódování na webu a v Linuxu.
Kvůli problémům s UTF-8 používám v příkladech text bez diakritiky. Nevím taky, jestli budete používat zdrojáky
na Windows (kde se používá kódování windows-1250) nebo Linuxu (UTF-8),
ačkoliv existuje spousta programů, které dokáží převádět z jednoho kódování do druhého.
V Linuxu například máte program recode
.
Přetypová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ý chcete 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ž byste 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 ukáži, jak se přetypování může využít.
- /*------------------------------------------------*/
- /* c08/pretyp.c */
- #define _CRT_SECURE_NO_WARNINGS
- #include <stdio.h>
- {
- c = a / b;
- }
- /*------------------------------------------------*/
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, teda psal, 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č vyhodnocuje jako int (tak je to definováno standardem). 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 u typu long použít modifikátor l
:
long x = 10l;
, u typu float zapsat číslo s desetinnou čárkou: float x = 10.0;
,
nebo můžete použít přetypování: long x= (long) 10;
.
První a druhý způsob se používá právě u konstant, třetí způsob zvláště u
proměnných, protože u nich první způsob použít nelze.
Přetypování návratové hodnoty funkce
Přetypovávat můžete i návratové hodnoty funkcí. Například funkce
printf()
vrací typ int. Návratovou hodnotu lze
získat takto:
Pokud návratovou hodnotu funkce chcete ignorovat, můžeme to
překladači zdělit přetypováním návratové hodnoty na typ void
takto:
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áte
jasně najevo, že návratovou hodnotu opravdu, ale opravdu nechcete
a on už pak dá pokoj.
Chyby přetypování
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áte nějakou virtuální obrazovku, která obsahuje nejméně 80 sloupců, ale může jich mít i více. Počet sloupců máte uložen v proměnné COLS. Obrazovku chcete 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žijete k tomu následující výpočet:
Tedy:
okno2 = COLS - (12 + (COLS-80) * 0.25));
Na první pohled se může zdát, že okno1 + okno2 = COLS
.
Podívejte se, jak může takový výpočet interpretovat překladač pro
COLS = 84 a COLS = 85:
Pro COLS = 84:
okno1 = 13;
okno2 = (84 - (12 + (84-80) * 0.25)) = 84 - 13.0 = 71.0
okno2 = 71;
To je OK. Ale pro COLS = 85:
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;
Chybami se člověk učí, stroj blbne :-).
Přetypování vám samozřejmě nepomůže vždy a ve všem. Celočíselnou polovinu z lichého čísla prostě nevypočítáte ;-).
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 nakonec přetypovává vždy
na typ proměnné, do které se hodnota ukládá.
Pokud se pokusíte uložit do proměnné hodnotu větší, než kterou je schopna pojmout
(například char ch = (char) 256;
1)),
výsledkem bude nedefinovaná hodnota, tj nikdo vám nijak nedefinuje,
jaká nakonec bude skutečná hodnota proměnné ch. Rozhodně to bude něco mezi 0 a 255,
ale to je asi tak všechno, co se o tom dá říct.
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.