Hlavní strana -> Programování v C -> 13. Ukazatele - pointery

13. Ukazatele - pointery

Co to ty ukazatele můžou být? Na co jsou dobré a kde je použiju? Na tyhle a možná i další otázky se pokusím odpovědět v tomto díle s trefným názvem.

Tak hurá do toho a půl je hotovo, jenom ještě nevím, kdo udělá tu druhou půlku. Kdo hrál Horké léto, ten ví o čem mluvím. Tak dost keců, stejně vím, že tu druhou půlku budu dělat já.

Ukazatele jsou zvláštní proměnnou, která v sobě nedokáže udržet žádnou hodnotu, ale dokáže mít v sobě uloženou adresu. Adresu paměti. Díky tomu mohou ukazovat na libovolný typ dat, ale pouze na ten, na který byly nadefinovány. Když vytvoříte ukazatel nějakého typu, tak do něj nemůžete (tedy jde to, ale nesmíte to udělat) uložit žádnou hodnotu, můžete mu pouze přidělit adresu nějaké proměnné, která je stejného typu. A protože ukazatelové proměnné jsou pouze tak velké, aby se do nich vešla nějaká adresa paměti, dá se s nimi velice rychle pracovat a zabírají málo času CPU pokud se používají ve spojení s velkými datovými strukturami. Při použití s primitivními datovými typy dojde spíše ke zpomalení z jednoduchého důvodu. Pokud přistupujete k proměnné přímo, stačí přečíst její hodnotu. Při přístupu přes ukazatel je třeba nejdřív zjistit hodnotu ukazatele (tedy zjistit adresu kam ukazuje), přesunout se na tu adresu a tam teprve přečíst hodnotu proměnné, na kterou ukazuje. Je zde tedy jistý mezikrok.

Něco málo k zápisům. Při práci s ukazateli budem hlavně potřebovat získat adresu proměnné, uložit na tuto adresu hodnotu a umět tuto hodnotu z této adresy přečíst. Používají se k tomu 2 speciální znaky '&' a '*'. '&' se používá pro získání adresy a '*' se používá pro práci s hodnotou na adrese. '&' se nazývá referenční operátor ("vytvoří" z názvu proměnné před kterou stojí její adresu) a '*' se nazývá dereferenční operátor (z adresy "vyjme" hodnotu) Pomůže následující kousek kódu:

int a, *p;

p = &a;
*p = 20;

printf("Hodnota v a je: %d\n", a);
printf("Hodnota na kterou ukazuje p je: %d", *p);

Nevím do jaké míry jste schopni tohle pochopit, ale po malém vysvětlení by to neměl být problém. Na prvním řádku se vytváří 2 proměnné typu int, jedna je fyzická (a) a druhá (*p) je ukazatelová. Zatím ani v jedné není nic uloženo. Následující řádek přiřazuje do (p) adresu proměnné (a). Můžeme to přečíst asi takto "do ukazatelové proměnné (p) typu int ulož adresu proměnné (a)". Pak tady máme přiřazení hodnoty 20 do proměnné na kterou ukazuje (p). Dá se přečíst "hodnotu 20 zapiš do proměnné, na kterou ukazuje (p)". No a nakonec tady jsou dva výpisy na obrazovku pomocí funkce printf(). V prvním je použita jako argument přímo proměnná (a), ve druhém je v argumentu (*p), což znamená "hodnota na adrese, kam ukazuje p".

Myslím, že tady by neměl být problém a už teď si můžete zkusit sami vytvořit nějaký kratičký program, který bude podobným způsobem ukazatele používat a možná je lépe pochopíte, ale ještě předtím... Říkal jsem, že ukazatele se mohou odkazovat na jakýkoli datový typ, ale musí k tomu být předem určeny. V následujícím kousku kódu vytovřím proměnnou typu char, int a float a k nim příslušné ukazatelové proměnné, do kterých potom přiřadím adresy fyzických adres.

char ch, *chp;
int i, *ip;
float f, *fp;

/* priradim jednotlivym ukazatelovym prom. adresy fyz. prom. */

chp = &ch;
ip = &i;
fp = &f;

/* ted muzu pomoci "hodnota na adrese rovna se" priradit
   neprimo pres ukazatele jednotlivym promennym hodnoty */

*chp = 12;
*ip = 4096;
*fp = 3.14159265358979;

/* nelze priradit nespravny datovy typ */

ip = &f; /* toto je spatne */

!! Pamatujte si že: &prom dává adresu prom a *prom dává hodnotu prom !!!

Teď už by mělo být jasno. Pokud ne - víte co máte dělat, už to nebudu opakovat. (nápověda - napsat mi)

Můžu snad pokračovat dál a zmíním se o tom, že ukazatele se dají inkrementovat a dekrementovat (tedy zvyšovat o 1 nebo snižovat o 1). Není to ale (doprdele zasrané sluchátka, doufám, že pod pařezem budou nové... zlomil se kablík a pořád mi chce hrát jenom jedno ucho a škrčí to) až tak docela pravda, protože se bude inkrementovat adresa, kterou obsahují a nedají se klasickým způsobem používat pro zvyšování nebo snižování hodnoty čísla, na které ukazují. Místo toho abyste tedy zvětšili číslo na nějaké adrese se bude inkrementovat adresa. Co to v praxi znamená? Znamená to, že ukazatel bude ukazovat na následující prvek v paměti, a to s ohledem na typ daného ukazatele. Jak inkrementovat? Lehce:

int i, *ip;

ip = &i;
ip++; /* takto */

Ale použití v tomto příkladě není nejšťastnější, protože ukazatel nyní bude ukazovat do nám neznámé oblasti paměti a může to akorát způsobit havárii programu, takže tudy cesta nevede. Inkrementace a dekrementace ukazatelů má smysl pouze v případě, že je používáme ve spojení s polem nějakých prvků. Vytvoříme tedy celočíselné pole o 5ti prvcích, vytvoříme celočíselný ukazatel a do něj uložíme adresu počátečního prvku pole a potom tento ukazatel budeme inkrementovat, abychom se dostali k dalším prvkům. Tedy:

nt a[5], *p;

p = a; /* vsimnete si tohoto zapisu */
p++; /* ted ukazuje na 2. prvek */
p--; /* a tady zase na 1. prvek */

Proč jste si měli všimnout výše uvedeného zápisu p = a; ? Protože dosud bych vždy použil p = &a; ale tady je situace jiná právě proto, že se jedná o pole. Pokud použijete název pole bez indexu (bez čísla v hranatých závorkách), pak se automaticky vrací adresa prvního prvku pole.

p = a;
p = &a[0]; /* jsou v predchozim priklade ekvivalentni protoze 'a' je pole */

Ukazatelová aritmetika se chová vždy způsobem daným k tomu kterému typu dat. Pokud se použije na typ char, pak po inkrementaci ukazuje ukazatel o jeden byte v paměti dále. U short int je to o 2 byty a u float 4 byty. Ukazatelová aritmetika použitá na pole struktur, které by byly velké např. 9 bytů, by zvětšila adresu ukazatele o 9 bytů tak, aby inkrementovaný ukazatel ukazovat přesně na další objekt toho typu. Myslím, že k tomu budou vhodné nějaké příklady, a ty přidám zase příště, takže zkuste pouvažovat.

Jak tedy inkrementovat a dekrementovat hodnotu proměnné, na kterou ukazatel ukazuje? Stejně jako při přiřazení je třeba pužít *. A kvůli prioritám operátorů je nutné přidat i patřičné závorky.

int i, *p;

p = &i;
*p = 5;
(*p)++; /* inkrementace. v i je ted 6 */

*p++; /* co provede toto? */

Poslední řádek způsobí, že se inkrementuje samotný ukazatel (bude ukazovat na další adresu v paměti, která je mimochodem pro nás momentálně nedostupná a nebude to dělat nic dobrého, pokud se na tuto adresu pokusíme něco zapsat) místo toho, aby inkrementovat hodnotu, na kterou ukazatel ukazuje. Je to právě kvůli prioritám. Závorky jsou nutné.

Tohle by pro začátek mohlo stačit, ale ukazatele jsou velice široká oblast a takový krátký popis jistě nebude stačit. Myslím, že to přenechám dalšímu dílu o ukazatelích. Zatím. K.

Zpět

Programování v C | CZ 175/477 | Mapa stránek
Bc. Petr Klimánek, student Ostravské univerzity v Ostravě