Datové typy, bloky, deklarace a definice
Základní datové typy
V minulé kapitole jste se setkali s datovými typy int
a void
.
void
je takový zvláštní datový typ, který vlastně říká, že žádná data nebudou. int
je jméno pro celé číslo (nelze jej použít pro číslo s desetinnou částí), které má nějakou velikost
(viz tabulka datových typů).
Název | Bitů | Význam | Příklad |
---|---|---|---|
char | 8 | celé číslo, znak | 0, 255, 'a', 'A', 'e' |
short1) | 16 | krátké celé číslo | 65535, -32767 |
int | 16/32 | celé číslo | --||-- |
long1) | 32 | dlouhé celé číslo | --||-- |
long long1) | 64 | ještě delší celé číslo | 9223372036854775807, -9223372036854775807 |
enum | 8/16/32 | výčtový typ | |
float | 32 | racionální číslo | 5.0, -5.0 |
double | 64 | racionální číslo s dvojitou přesností | 5.0l, 5l, -5.0L |
long double | 80 | velmi dlouhé racionální číslo | 5.5L, 5l, -5.0l |
pointer | 16/32/64 | ukazatel |
Kompletní tabulku můžete najít např. na Wikipedii
V prvním sloupci vidíte základní datové typy jazyka C. Ve druhém vidíte to, kolik zabírají (orientačně) bitů v paměti. Čím více bitů, tím větší číslo je možné zapsat. Na druhou stranu, zabírají více paměti a práce s nimi je pomalejší. Ve třetím sloupci jsou významy čísel a ve čtvrtém příklady.
Co je na jazyku C zajímavé (a za co ho někteří nemají rádi) je to,
že velikost všech datových typů není standardem pevně dána.
Můžete se spolehnout je na to, že
sizeof(char) = 1
(1 byte) 2) a že:
Proč? Závisí to především na procesoru. Na 16-bitovém procesoru bude mít int
velikosti 16 bitů
(16bitovému procesoru se s takto velkým číslem "nejlépe"
pracuje), na 64-bitovém procesoru zase 64-bitů. Ovšem kvůli zpětné kompatiblitě se klidně může stát, že 64-bitový
překladač bude používat pro int jen 32 bitů. Pro ukazatel (pointer) by ale měl používat 64-bitový systém 64-bitů,
protože 32 bitů není dostatečně velký typ na adresování celého rozsahu paměti.
Mimochodem, možná to ještě nevíte, ale už víte, proč 32-bitové počítače mohou mít max. 4GiB paměti. Hádejte, jak velké je maximální číslo, které se vejde do 32 bitů?
Při výběru vhodného datového typu byste měli počítat s tím, že rozsah datového typu (tj. nejmenší a největší číslo, které do něj můžete nacpat) se bude
lišit „počítač od počítače“.
Pokud budete mít při návrhu pocit, že by mohlo dojít k „přetečení“ rozsahu (takhle se říká tomu, když se snažíte do nějaké proměnné uložit větší čislo, než se tam vejde),
používejte pro ověření velikosti čísla konstanty z knihovny <limits.h>.
Konkrétní rozsahy datových typů můžete najít ve standardní knihovně <limits.h>.
Zjistit velikost datového typu můžete pomocí operátoru sizeof()
,
který vrací počet bytů (nikoliv bitů) datového typu (nebo proměnné). Například sizeof(char)
by se vždy mělo rovnat 1.
Další možnosti ukáži o několik kapitol dále,
a to v příkladu s binárními operátory (to teď ale pusťte klidně z hlavy).
Typy char, short, int, long a long long mohou být se znaménkem nebo bez znaménka. Určí se to pomocí klíčových slov signed (se znaménkem) nebo unsigned (beze znaménka). „signed“ je defaultní hodnota, proto ji není třeba psát. Vzpomeňte si na kapitolu o bitech a bajtech, kde jsem psal o reprezentaci záporných čísel.
K čemu jsou datové typy? Když počítač počítá s nějakými čísly, musí je mít uložené někde v paměti. Toto místo v paměti získáte tak, že vytvoříte proměnnou. A aby počítač věděl, kolik místa takové proměnné má v paměti dát, musíte proměnné určit datový typ. To dává smysl, ne? :-).
Proměnná je tedy nějaké vyhrazené místo v paměti o nějaké velikosti (dané datovým typem), kam si můžete ukládat nějaké čísla (resp. nejen čísla, ale i testové řetězce a další složitější struktury, o kterých bude řeč později).
Následující příklad snad osvětlí použití datových typů a proměnných:
/* c05/typy.c */
#include <stdio.h>
int a;
int main(void)
{
/* deklarace promennych */
unsigned int b;
float c;
a = 5;
b = 11;
a = a / 2;
c = 5.0;
printf("a=%i\nb=%u\nc=%f\n", a, b, c / 2);
return 0;
}
/*------------------------------------------------*/
Výstup z tohoto programu bude následující:
a=2 b=11 c=2.500000
Všimněte si, že do proměnné a lze uložit pouze
celé číslo. Takže a/2, tedy 5/2 je 2 a ne 2.5. Celé číslo (int) prostě neukládá desetinnou část.
Výsledek se ani nezaokrouhlil, prostě se „useknul“.
Vyzkoušejte si do proměnné b uložit záporné číslo (třeba -11). Schválně, uhodnete, co se stane?
Boolovské datové typy
V některých programovacích jazycích se používá datový typ bool, který může nabývat pouze hodnot true (pravda) nebo false (nepravda). Tyto hodnoty se používají ve vyhodnocování podmínek při řízení běhu programu a podmíněných operátorů. Oboje budu probírat později. Na tomto místě jen řeknu, že v jazyce C takovýto typ neexistuje.
V jazyce C se jako nepravda vrací 0 a jako pravda 1. A dále platí, že cokoliv nenulového se vyhodnocuje jako pravda (tj. vše kromě 0 a NULL). Čísla 2, 0.1, -1, všechny jsou pravda.
Nic není ztraceno. V jazyce C si můžete vytvářet vlastní datové typy pomocí operátoru
typedef
, nebo si taky můžete hodnoty true a false
nadefinovat pomocí preprocesoru. Ale na to zapomeňte :-).
Standard C99 (stejně jako C++) totiž přináší knihovnu <stdbool.h>, která to už za vás udělala.
Takže odvolávám co jsem odvolal :-). Datový typ bool můžete používat, pokud zahrnete do svého zdrojáku <stdbool.h>:
Příklad vám zatím neukážu, protože to nemá smysl, dokud se nedostanete k vyhodnocování podmínek.
K čemu že je to vlastně dobré si definovat bool
? Kvůli čitelnosti. Když si chcete udržovat
informaci o tom, zda je něco pravda nebo ne, je čitelnější to ukládat do
bool
jako true nebo false, než třeba do char
jako 0 nebo 1 (nebo 0.1 …).
Zdrojové kódy by se měli v první řadě psát tak, aby byli dobře čitelné pro programátory.
Bloky, deklarace a definice
Blokem se rozumí vše, co je mezi složenými závorkami
{
a }
.
V příkladě nahoře je jediný blok, a to tělo funkce
main()
.
Všechny proměnné, které deklarujete v bloku, jsou tzv. lokální proměnné
a proměnné mimo blok jsou globální. Lokální proměnné platí jen v
těle bloku a v blocích vnořených (jinde je překladač neuvidí, tj. jinde, než v daném bloku,
s nimi nemůžete pracovat).
Proměnné jsou místa kdesi v paměti počítače, které
definujete v programu. Velikost místa v paměti závisí na datovém
typu proměnné. Do proměnných lze vkládat hodnoty, měnit je a
číst. V příkladě typy.c jsem vytvořil globální proměnnou a
(je mimo blok, tj. mimo tělo funkce main()
)
a dvě lokální proměnné b a c.
Deklarací proměnných se určuje, jaké proměnné se v programu objeví. Nejdříve se určí datový typ a za ním názvy proměnných oddělené čárkou. Deklarace se ukončí středníkem. Při deklaraci je možné do proměnné rovnou vložit hodnotu. Názvy proměnných nesmí obsahovat mezeru, národní znaky (č, ř, š atp.) a nesmí začínat číslem. Jejich maximální délka je závislá na překladači. Jazyk C rozlišuje malá a velká písmena, takže můžete vytvořit proměnné se jmény Ahoj, ahoj, aHoj a program je bude chápat jako různé identifikátory. Dobrým zvykem je pro proměnné používat vždy jen malá písmena.
Dokud neurčíte jakou má proměnná hodnotu, může v ní být jakékoliv číslo. Např. proměnné cislo2 a cislo3 mají hodnotu 25, všechny ostatní obsahují náhodnou hodnotu!
Překladač jazyka Java vám nedovolí použít proměnnou před přiřazením hodnoty, překladač jazyka C/C++ ano, ale pořád je to chyba. Nenechte se zmást tím, že v proměnné, které nepřiřadíte hodnotu, je většinou nula. Tak to může být v 999 případech z 1000, ale pak to tak jednou nebude a to může vést k nedpředvídatelným chybám …
Proměnné mohou být v jazyce C deklarovány pouze na začátku bloku funkce
– ihned za úvodní závorkou ({
)
(tzv. lokální proměnné), nebo na začátku programu (globální
proměnné). Lokální proměnné „existují“ jen v bloku, ve kterém jsou
definovány (případně v blocích v něm vnořených) a nelze je tudíž
používat mimo něj. V jazyce C++ je možné deklarovat proměnné téměř
kdekoliv, ale stále platí, že proměnná má platnost pouze v bloku,
ve kterém je deklarována.
Definice říká, jak bude nějaký objekt (funkce, datový typ) vypadat. V programu
například definuji funkci main()
. Základní datové typy jsou
definovány normou ANSI C. Můžete definovat i vlastní typy dat,
další funkce atd., ale k tomu se dostanu později. Jen si prosím nepleťte
pojmy definice a deklarace. Definovat objekt můžete jen jednou (velikost typu int musí být pro všechny
zdrojové soubory stejná), deklarovat vícekrát (můžete deklarovat mnoho proměnných typu int).
Textové řetězce
Už víte, jak si vytvořit proměnnou pro celé čísla různých velikostí, čílsa proměnné velikosti nebo 8-bitový znak3). Jak ale zapsat nějaký textový řetězec?
Text jsou vlastně jenom znaky poskládané zasebou. Jinak řečeno, je to pole znaků. Textové pole je souvislá řada znaků, která je v paměti, někde začíná a někde končí. Pro textové řetězce neexistuje žádný datový typ. Místo toho si pouze uložíte ukazatel na začátek pole a pak pracujete s tímto ukazatelem. Takže než ukážu jak pracovat s textem, nejdřív musím vysvětlit, co jsou ukazatele.
Ukazatele
Ukazatele (též zvané pointry) jsou jednou z nejtěžších věcí na pochopení v jazyku C. Ukazatele jsou proměnné, které uchovávají adresu ukazující do paměti počítače. Při jejich deklaraci je třeba uvést, na jaký datový typ bude ukazatel ukazovat. Paměť počítače je adresována po bytech. Číslo 0 ukazuje na první bajt v paměti, číslo 1 na druhý bajt atd. Pokud máte šestnáctibitový překladač/processor, bude velikost ukazatele 16 bitů, u 32 bitového překladače 32 bitů. (Přesněji řečeno, závisí to na šířce adresové sběrnice. Ještě přesněji řečeno, program většinou pracuje s adresou "virtuální paměti" (kterou mu „podstrčí“ operační systém), takže například na x86-64 architektuře má (virtuální) paměť 48 bitů. Morální poučení z toho zní: ukládejte adresy do typů "ukazatel" a nechte na překladači, ať vybere správnou velikost).
Deklarace ukazatele na nějaký datový typ vypadá takto:
Například:
Proměnné uf, ui a uv jsou ukazatele.
Proměnná uf je ukazatel na typ float, ui je ukazatel na typ int a uv je tzv. prázdný ukazatel u kterého není určeno, na jaký datový typ se bude ukazovat. To překladači neumožňuje typovou kontrolu, proto je lepší jej nepoužívat, pokud to není nutné. Do všech třech proměnných lze vložit libovolné (celé) číslo, které pak bude překladačem chápáno jako adresa do paměti. Všichni tři ukazatelé mají stejnou velikost (16, 32 nebo 64 bitů - podle překladače).
K získání adresy nějaké proměnné slouží operátor &
(viz příklad níže).
K získání dat z adresy, která je uložena v ukazateli, slouží operátor
*
(viz příklad).
/* c05/ukazatel.c */
#include <stdio.h>
int main(void)
{
int i; /* promenna int */
float f, f2;
int *ui; /* ukazatel (na typ int) */
float *uf;
f = 5.5;
uf = 50; /* v ukazateli uf je hodnota 50. Co je v pameti na 50 bajtu
nikdo nemuze vedet, proto se takto do ukazatele adresa
nikdy (!) neprirazuje */
uf = &f; /* v ukazateli uf je hodnota adresy promenne f (tak je to
spravne :-)) */
ui = &i;
f2 = *uf; /* do f2 se priradi hodnota z promenne f, tedy 5.5 */
*ui = 10; /* tam, kam ukazuje adresa v ui (tedy do i) se ulozi 10.
Hodnota promenne ui se nezmeni (je to stale adresa
promenne i) */
printf("f = %f\n", f); /* hodnota v f se od prirazeni 5.5 nezmenila */
printf("f2 = %f\n", f2); /* do f2 se priradila hodnota z promenne f, na
kterou jsem se odkazoval pomoci ukazatele uf */
printf("i = %i\n", i); /* do i jsem pres ukazatel ui priradil 10 */
printf("uf = %u, *uf=%f\n", uf, *uf); /* vytiskne hodnotu uf a hodnotu
na teto adrese (tj. adresu f2 a
hodnotu f2) */
return 0;
}
/*------------------------------------------------*/
Výstup z programu bude následující:
f = 5.500000 f2 = 5.500000 i = 10 uf = 3221224276, *uf=5.500000
Proměnná uf obsahuje adresu paměti, na které byla proměnná f uložena. Ta se bude při každém spuštění programu lišit, neboť paměť přiřazuje programu operační systém. Do které části paměti program nahraje nelze předem určit (alespoň my, obyčejní smrtelníci, to nedokážeme).
V programu jsem se dopustil dvou chyb. Jednak to bylo přímé
přiřazení hodnoty typu integer do proměnné uf (uf = 50;
) – měl bych přiřazovat
hodnotu typu ukazatel. A pak v poslední funkci
printf()
, kde se tiskne hodnota ukazatele uf
jako celé číslo bez
znaménka (unsigned int, spec. znak %u
).
Je možné, že vám tyto konstrukce překladač ani nedovolí přeložit!
Tisknutím ukazatele (pointeru) jako unsigned int jsem chtěl zdůraznit, že je to číslo (které uchovává
nějakou adresu paměti). Ukazatele (pointery) by se správně měli tisknout přes %p
(pak se vytisknou v 16tkové soustavě). Nahraďte řádek 31 tímto:
Za zmínku ještě stojí přiřazení f2 = *uf;
. Jak to
funguje? Nejdříve se vezme hodnota z uf
– to je adresa – a z této adresy se přečte
číslo dlouhé tolik bitů, kolik bitů má typ, pro který je ukazatel
určen. Proměnnou uf jsem deklaroval
jako ukazatel na typ float, který má 32 bitů. Proto se vezme 32 bitů začínajících na adrese uložené
v uf a tyto bity se pak chápou jako
racionální číslo a jako takové se uloží do f2.
Kdybych deklaroval uf jako
ukazatel na typ char (char *uf;), pak by se sice začalo číst
ze stejné adresy, ale z ní by se přečetlo jenom 8 bitů!
Asi vám v tuto chvíli není moc jasné, k čemu jsou ukazatele
dobré. K tomu se však velice rychle dostanu. Zatím je třeba, aby
jste pochopili, k čemu slouží operátor &
a *
a také pochopili, že ukazatel, ať už ukazuje na jakýkoliv typ,
je stále jen ukazatel. Z toho také vyplývá, že velikost ukazatele je stejná,
ať už ukazujete na char, nebo long double:
sizeof(char*) == sizeof(long double*)
.
Všiměte si taky, že se hvězdička používá ke dvoum různým účelům. V deklaraci proměnné říká „toto je ukazatel“. Jako operátor říká „nepracuj s ukazatelem, ale s proměnnou, na kterou ukazatel ukazuje“.
NULL
NULL je speciální hodnota, která označuje velké nic. Používá se
v souvislosti s ukazateli. NULL
můžete vložit do
ukazatele a pak můžete testovat, zda ukazatel obsahuje NULL
, nebo
adresu někam. NULL
se také často používá jako argument funkcí, když
tam, kde je vyžadován jako argument ukazatel, žádný ukazatel vložit nechcete.
Praktické využití této hodnoty ukáži později, například v ukázce použití NULL
(zatím na to nekoukejte, nebo vám to vypálí oči).
Přesná definice toho, co je NULL se moc nepovedla, takže někdy se setkáte s tím,
že je to (int) 0
, jindy (int *) 0
atp., což je trochu
zmatek pro překladače kontrolující správné použití typů (int versus pointer na int). Proto jazyk C++ později zavedl klíčové
slovo nullptr (ano, malými písmeny).
1)
long je ve skutečnosti zkratka pro long int, short pro short int a long long pro long long int.
2)
sizeof()
je operátor, který vrací velikost datového typu v bytech. Tj. pro char vrací 1.
Jazyk C dokonce ani nezaručuje, že 1 bajt = 8 bitů!
3)
Moderní kódování, např. UTF-8, využívá pro některé znaky (hlavně ty s čárkami a háčky) více jak 8 bitů. O tom ale zase někdy příště.