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ů).

Základní datové typy jazyka C
NázevBitůVýznamPříklad
char8celé číslo, znak0, 255, 'a', 'A', 'e'
short1)16krátké celé číslo65535, -32767
int16/32celé číslo--||--
long1)32dlouhé celé číslo--||--
long long1)64ještě delší celé číslo9223372036854775807, -9223372036854775807
enum8/16/32výčtový typ
float32racionální číslo5.0, -5.0
double64racionální číslo s dvojitou přesností5.0l, 5l, -5.0L
long double80velmi dlouhé racionální číslo5.5L, 5l, -5.0l
pointer16/32/64ukazatel

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

#include <stdbool.h>
...
bool x = true;
x = false;
...

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.

unsigned int i,j,k;
signed int cislo1, cislo2 = cislo3 = 25;

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:

typ * jmeno;

Například:

float *uf; // Ukazatel na float. Má velikost typu "ukazatel".
int *ui;
void *uv;

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:

printf("uf = %p, *uf=%f\n", uf, *uf); /* vytiskneme si hodnotu uf ...

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ě.

Komentář Hlášení chyby
Created: 29.8.2003
Last updated: 11.3.2021
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..