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:

ASCII tabulka
kód znak pozn.
1 CTRL+a
2 CTRL+b
3 CTRL+c
4 CTRL+d
5 CTRL+e
6 CTRL+f
7 CTRL+g
8 CTRL+h, BackSpace
9   CTRL+i, Tab
10   CTRL+j, stránka
11 CTRL+k
12 CTRL+l
13   CTRL+m
14 CTRL+n
15 CTRL+o
16 CTRL+p
17 CTRL+q
18 CTRL+r
19 CTRL+s
20 CTRL+t
21 CTRL+u
22 CTRL+v
23 CTRL+w
24 CTRL+x
25 CTRL+y
26 CTRL+z
27 CTRL+[,Esc,Ctrl+Esc
28 CTRL+\
29 CTRL+]
30 CTRL+^
31 CTRL+_
32   mezera
33 !  
34 "  
35 # sharp
36 $  
37 %  
38 & ampersand
39 ' apostrof
40 (  
41 )  
42 *  
43 +  
44 , čárka
45 - mínus
46 . tečka
47 /  
48 0  
49 1  
50 2  
51 3  
52 4  
53 5  
54 6  
55 7  
56 8  
57 9  
58 :  
59 ;  
60 <  
61 =  
62 >  
63 ?  
64 @  
kód znak pozn.
65 A  
66 B  
67 C  
68 D  
69 E  
70 F  
71 G  
72 H  
73 I  
74 J  
75 K  
76 L  
77 M  
78 N  
79 O  
80 P  
81 Q  
82 R  
83 S  
84 T  
85 U  
86 V  
87 W  
88 X  
89 Y  
90 Z  
91 [  
92 \  
93 ]  
94 ^  
95 _ podtržítko
96 ` akcent
97 a  
98 b  
99 c  
100 d  
101 e  
102 f  
103 g  
104 h  
105 i  
106 j  
107 k  
108 l  
109 m  
110 n  
111 o  
112 p  
113 q  
114 r  
115 s  
116 t  
117 u  
118 v  
119 w  
120 x  
121 y  
122 z  
123 {  
124 | svislá čára
125 }  
126 ~ tilda (vlnovka)
127  CTRL+BackSpace

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.

  1. /*------------------------------------------------*/
  2. /* c08/pretyp.c                                   */
  3. #define _CRT_SECURE_NO_WARNINGS
  4. #include <stdio.h>
  5.  
  6. int main(void)
  7. {
  8.     int a, b;
  9.     float c, d;
  10.     char ch = 'x';
  11.  
  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);
  20.  
  21.     printf("ASCII kod znaku %c je %i\n", ch, (int) ch);
  22.     return 0;
  23. }
  24.  
  25. /*------------------------------------------------*/
Visual Studio

Makro _CRT_SECURE_NO_WARNINGS umožňuje použít funkci scanf() ve Visual Studiu.
Více informací viz první příklad u scanf().

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:

int navrat;
navrat = printf("Hello World");

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:

(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á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:

(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ívejte 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 = 71.0
okno2 = 71;

To je OK. 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 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.

Komentář Hlášení chyby
Created: 29.8.2003
Last updated: 16.6.2014
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..