Učíme se C (15.díl) - Ukazatele - Builder.cz - Informacni server o programovani

Odběr fotomagazínu

Fotografický magazín "iZIN IDIF" každý týden ve Vašem e-mailu.
Co nového ve světě fotografie!

 

Zadejte Vaši e-mailovou adresu:

Kamarád fotí rád?

Přihlas ho k odběru fotomagazínu!

 

Zadejte e-mailovou adresu kamaráda:



C/C++

Učíme se C (15.díl) - Ukazatele

9. ledna 2001, 00.00 | Ukazatele v jazyce C, jejich definice, inicializace, operátory reference a dereference,
generické ukazatele a přetypování ukazatelů.

Ukazatele

Vedle obvyklého přístupu k datům v paměti pomocí proměnných, nabízí jazyk C i možnost pracovat s těmito daty nepřímo, pomocí ukazatelů. Ukazatele (pointery) jsou velmi důležitou součástí jazyka a vzhledem k značně širšímu okruhu jejich použití, než je tomu v jiných jazycích, je takřka nezbytné dobře se s problematikou ukazatelů seznámit.

Ukazatel je v podstatě proměnná (nebo konstanta) jako každá jiná. Liší se jen způsob, jak se s ní pracuje. Klasická proměnná představuje určitý kus paměti, ve které je uložena hodnota této proměnné. To platí i pro pointery, jenže v jejich případě je touto hodnotou adresa nějakého místa v paměti. Říkáme pak, že pointer na místo určené touto adresou ukazuje. Samotná adresa, kterou pointer uchovává je pro nás většinou nezajímavá, důležitější je to, co se na této adrese nachází.

Bázový typ ukazatele

Bázový typ ukazatele určuje, jak se bude interpretovat místo v paměti, kam tento pointer ukazuje. Například, je-li bázovým typem ukazatele typ int, znamená to, že obsah paměti, kam pointer ukazuje, se bude interpretovat jako datový objekt typu int. Bázový typ ukazatele určujeme při jeho definici, je ale možné určit ho až za běhu programu.

Definice proměnné typu ukazatel

bázový_typ *identifikátor;

Ukazatele definujeme podobně jako jiné proměnné. Rozdílem je, že před identifikátorem musíme uvést ještě znak ‘*‘, kterým dáme překladači vědět, že definujeme pointer. Není potřeba navzájem od sebe oddělovat definice obyčejné proměnné a pointeru, jejichž typy (typ a bázový typ) jsou stejné.

char    *p1;
int  i, *p2;

V případě, že jsou proměnné p1 a p2 statické, budou mít nulovou hodnotu. Jsou-li automatické, bude jejich hodnotou nějaká náhodná adresa. I přesto ale lze s pamětí, kam tyto pointery ukazují, pracovat, tedy číst a zapisovat do ní. V takovém případě můžeme lehce přepsat paměť používanou jinými programy nebo systémem samotným. To má většinou za následek zhroucení programu nebo celého systému. Proto musíme ukazatel inicializovat nějakou smysluplnou hodnotou ještě před jeho prvním použitím.

Referenční operátor &

Abychom mohli do ukazatelové proměnné přiřadit hodnotu (nějakou adresu), musíme ji nejprve někde získat. K tomu nám může posloužit referenční operátor '&'. Jeho zapsáním před identifikátor proměnné získáme adresu paměti, kde je tato proměnná uložena. Tuto adresu pak můžeme přiřadit nějaké ukazatelové proměnné.

char *p, c;

c = 12;
p = &c;
// p nyní ukazuje na proměnnou c

Operátor dereference *

Jak už bylo zmíněno, pomocí pointeru můžeme přistupovat k datovému objektu, na který ukazuje. K tomu nám poslouží operátor dereference '*'. Použitím tohoto operátoru na ukazatel získáme objekt, na který ukazatel odkazuje, přičemž typ takto získaného objektu bude shodný s bázovým typem dereferovaného ukazatele. Navíc je tento objekt l-hodnotou, což znamená, že může stát na levé straně přiřazovacího příkazu. Ukažme si to na příkladu:

int i=10, *p;

p = &i;
*p = 20; //zápis ekvivalentní i=20;
printf("%d", i);

Po nadefinování proměnných i a p jsme, za pomoci operátoru reference, přiřadili ukazateli p adresu paměti, na které je uložena proměnná i. Pointer p teď tedy ukazuje na proměnnou i. Použitím operátoru ‘*’ na tento pointer jsme nepřímo získali samotnou proměnnou i, do které jsme přiřadili hodnotu 20. Kontrolní výpis hodnoty i nám potvrdí, že její hodnota byla skutečně změněna.

Adresy objektů různých datových typů jsou navzájem kompatibilní, a tak je například možné uložit do ukazatele s bázovým typem char adresu proměnné typu int. Když pak ale budeme přistupovat k této proměnné pomocí ukazatele, bude se obsah odkazované paměti interpretovat jako objekt typu char. Tím pádem budeme pracovat jen s jedním bytem odkazované proměnné. Protože takové přiřazení většinou znamená chybu programátora, většina překladačů vypíše varování. Použití adresy neshodného bázového typu k inicializaci pointeru ukazuje následující příklad:

int   i=353;
char  *p;

p = &i;
*p = 120;
printf ("%d", i);

Kontrolní vypsání hodnoty proměnné i nám potvrdí, že přiřazení hodnoty 120 pomocí dereferovaného ukazatele nemělo kýžený efekt. Přesto, že většinou se jedná o chybu, může nastat situace, kdy tento postup zvolíme záměrně. Varování překladače se pak zbavíme tak, že přiřazovanou hodnotu přetypujeme na odpovídající typ.

int      i;
char   *pc;
double *pd;

pc = (char*)&i;
pd = (double*)pc;

Nyní máme dva ukazatele různých bázových typů ukazující na stejné místo v paměti, na proměnnou i. Budeme-li chtít některý z těchto ukazatelů použít pro práci s proměnnou i jako s objektem typu int, musíme dereferovat správně přetypované ukazatele (Je jasné, že nyní už nepřetypováváme kvůli zamezení varovného hlášení, ale přímo kvůli správné funkci programu).

*(int*)pd=50;
printf ("%d", i);

Ukazatele NULL

Jedním z případů, kdy ukazatele inicializujeme konstantní hodnotou je přiřazení symbolické konstanty NULL (definované ve stdio.h). Má-li pointer přiřazenu hodnotu NULL, znamená to, že nikam neukazuje. Pro pointery všech bázových typů platí, že jim lze hodnotu NULL přiřadit bez přetypování.

Inicializace při definici

Pointery lze, stejně jako jiné proměnné, inicializovat již při definici, ale zatímco u obyčejné proměnné jsme často inicializovali nějakou konstantní hodnotou, u ukazatelů většinou žádnou konkrétní hodnotu neznáme. Proto budeme pointery inicializovat nejčastěji adresou některé dříve nadefinované proměnné, nebo hodnotou jiného pointeru. Přesto existuje jeden častý případ, kdy inicializujeme absolutní adresou, a to konstantou NULL. Méně častým případem je pak například přímá adresace videopaměti apod.

double x;
double *p1=&x;         //inicializace adresou proměnné
double *p2=p1;         //hodnotou jiného ukazatele
int    *p3=NULL;       //konstantou NULL
int    *p4=(int*)0x80; //jinou absolutní adresou

Ukazatel typu void (generický ukazatel)

Typ void není klasickým typem, na jaký jsme zvyklí. Vlastně je to spíš prostředek jak vyjádřit, že někde něco chybí nebo není specifikováno. Abych byl přesnější, tak, vytvoříme-li funkci vracející typ void, znamená to, že žádnou hodnotu vracet nebude. Pokud nadefinujeme ukazatel, jehož bázovým typem bude void (tzv. generický ukazatel), dáváme tím vlastně najevo, že bázový typ pointeru není předem určen. Do takového pointeru lze bez přetypování přiřadit hodnotu ukazatele libovolného bázového typu. Pro zvýšení čitelnosti programu je však dobré i v tomto případě přetypování použít. I v opačném směru, tedy přiřazení void ukazatele ukazateli jiného bázového typu, lze přetypování zcela vynechat, ale ani v tomto případě to nelze doporučit, tím spíš ne, že v C++ je už přetypování (v tomto směru) povinné. Chceme-li dereferovat ukazatel typu void, musíme ho vždy nejdřív přetypovat!

int  i;
void *p;

(int*)p=&i;
*(int*)p=36;
printf ("%d", i);

Obsah seriálu (více o seriálu):

Tématické zařazení:

 » Rubriky  » C/C++  

 

 

 

Nejčtenější články
Nejlépe hodnocené články

 

Přihlášení k mému účtu

Uživatelské jméno:

Heslo: