Ukazatel je proměnná, která místo hodnoty drží adresu jiné proměnné v paměti. Sám o sobě není k ničemu – užitečný je až tím, co se s ním dá dělat: dostat se na hodnotu, na kterou míří, posouvat ho po poli nebo ho předat funkci, která má něco změnit.
Operátor & vrátí adresu proměnné, operátor * naopak z adresy udělá hodnotu, která na ní leží (tomu se říká dereference). Přes dereferenci se dá hodnota nejen číst, ale i měnit.
int cislo = 42;
int *ukazatel = &cislo; // ukazatel míří na proměnnou cislo
printf("%d\n", *ukazatel); // 42 – hodnota na dané adrese
printf("%p\n", (void *) ukazatel); // samotná adresa
*ukazatel = 100; // změní se přímo proměnná cislo
printf("%d\n", cislo); // 100
S ukazatelem se dá počítat: přičtení jedničky ho neposune o jeden bajt, ale o velikost typu, na který ukazuje. U pole int tedy p + 1 míří na další prvek, ne na další bajt.
int pole[5] = {10, 20, 30, 40, 50};
int *p = pole; // jméno pole je adresa prvního prvku
printf("%d\n", *p); // 10
p++; // posun o jeden int
printf("%d\n", *p); // 20
printf("%d\n", *(p + 2)); // 40
Odečtením dvou ukazatelů do stejného pole dostaneš počet prvků mezi nimi:
int *zacatek = &pole[0];
int *konec = &pole[4];
printf("%td\n", konec - zacatek); // 4
Zápis pole[i] je jen pohodlnější způsob, jak napsat *(pole + i). Obojí se přeloží úplně stejně, takže si můžeš vybrat, co je v dané situaci čitelnější.
int pole[3] = {1, 2, 3};
printf("%d\n", pole[1]); // 2
printf("%d\n", *(pole + 1)); // 2 – totéž
Ukazatel může mířit i na jiný ukazatel. Píše se se dvěma hvězdičkami a používá se hlavně u dvourozměrných polí nebo tam, kde má funkce změnit samotný ukazatel volajícího.
int cislo = 5;
int *p = &cislo;
int **pp = &p;
printf("%d\n", **pp); // 5
Ukazatel, který nikam nemíří, se nastavuje na NULL. Dereference takového ukazatele program spolehlivě shodí, proto se před každým použitím ukazatele z nejistého zdroje kontroluje, jestli NULL není.
int *p = NULL;
if (p != NULL) {
printf("%d\n", *p);
} else {
printf("Ukazatel nikam nemíří\n");
}
U ukazatele se dá zamknout hodnota, adresa, nebo obojí – záleží, kam se const napíše. Nejčastější je první varianta: funkce data jen čte a nesmí je změnit.
const int *a; // nesmí se měnit hodnota, na kterou ukazatel míří
int *const b = &x; // nesmí se změnit adresa, hodnota ano
const int *const c = &x; // nesmí se měnit nic
Neinicializovaný ukazatel míří na náhodnou adresu a zápis přes něj poškodí paměť. Stejně nebezpečný je ukazatel na už uvolněnou paměť – po zavolání free se proto vyplatí nastavit ho na NULL.
int *p; // ŠPATNĚ: míří kdovíkam
int *q = NULL; // SPRÁVNĚ: jasně nikam
int *data = malloc(10 * sizeof(int));
free(data);
data = NULL; // ať se přes něj nedá omylem sáhnout znovu