7. cvičení

Ukazatel (anglicky pointer) je proměnná speciálního typu, která nereprezentuje žádnou hodnotu, ale obsahuje adresu nějaké jiné proměnné v paměti počítače. Každý ukazatel má fixní velikost 64 bitů (resp. 32 bitů na 32-bitovém počítači). Ukazatele se tedy hodí pro správu dat o velkém objemu (např. polí), aby se zabránilo vytváření zbytečných kopií (to by vedlo ke spotřebování zbytečně velkého množství paměti a celkové neefektivitě programu).

Ukazatel lze deklarovat pomocí typu proměnné, na kterou se odkazuje, a hvězdičky. Např. ukazatel na proměnnou typu float lze deklarovat takto:

float* ukazatel;

Pokud dále vytvoříme proměnnou typu float, můžeme její adresu získat pomocí (unárního) operátoru & a zapsat do proměnné ukazatel:

float a = 1;
ukazatel = &a;

Pokud chceme pomocí ukazatele přistoupit k hodnotě, na kterou ukazuje, musíme ho tzv. dereferencovat pomocí (unárního) operátoru *:

*ukazatel = 2;

Nyní jsme do políčka v paměti, na kterou ukazuje ukazatel, zapsali hodnotu 2 typu float. Toto políčko je stejné, jako má proměnná a, takže pomocí ukazatele jsme změnili hodnotu proměnné a.

Pro ukazatele dále funguje aritmetika sčítání a odčítání, která je ale trošku jiná než pro klasické datové typy. Nejlépe je to vidět na příkladu s jednorozměrným polem:

int pole[20];

// vynulovani pole
for (int i = 0; i < 20; i++)
    pole[i] = 0;

// ukazatel na 0. prvek
int* ukazatel = pole;

// zapise 1 na 0. pozici
*ukazatel = 1;

// zapise 1 na 7. pozici
*(ukazatel + 7) = 1;

// ukazatel na 13. pozici
ukazatel = ukazatel + 13;

// zapise 1 na 15. a 11. pozici
*(ukazatel + 2) = 1;
*(ukazatel - 2) = 1;

// hranate zavorky lze pouzit i na ukazatel, nejenom na pole
ukazatel[1] = 1;
ukazatel[-1] = 1;

// vypis pole
for (int i = 0; i < 20; i++)
    printf("%d ", pole[i]);

Všimněte si, že hranaté závorky fungují stejně, jako přičtení celého čísla k ukazateli a následná dereference (výraz ukazatel[i] je ekvivalentní výrazu *(ukazatel + i)). Aritmetika ukazatelů tedy nefunguje po bytech, ale po políčkách o velikosti odpovídající datovému typu, na který ukazatel ukazuje.

Ukazatele se hodí např. v následujících případech:

Probrané příklady

  1. Funkce pro součet dvou vektorů. Funkci je třeba předat dva vstupní vektory (v1, v2), jeden výstupní vektor (v3) a délku vektorů (velikost, stejná pro všechny vektory). „Výstupní parametr“ je potřeba použít, protože pomocí návratového typu nelze vrátit pole vytvořené uvnitř dané funkce.

    void secti_vektory(int velikost, const float* v1, const float* v2, float* v3)
    {
        for (int i = 0; i < velikost; i++)
            v3[i] = v1[i] + v2[i];
    }
    

    Připomínám, že při volání funkce záleží na pořadí parametrů, nikoliv na pojmenování proměnných. Proto např. pokud máme deklarovány vektory a, b, c délky n, pak následující příkaz spočítá součet c = b + a:

    secti_vektory(n, b, a, c);
    

    A následující příkaz spočítá součet a = c + b:

    secti_vektory(n, c, b, a);
    
  2. Funkce pro rozdíl dvou vektorů.

  3. Funkce pro výpočet skalárního součinu dvou vektorů. Na rozdíl od předchozího příkladu má funkce návratový typ float namísto výstupního parametru.

    float skalarni_soucin(int velikost, const float* v1, const float* v2)
    {
        float s = 0;
        for (int i = 0; i < velikost; i++)
            s += v1[i] * v2[i];
        return s;
    }
    

    Funkci lze použít např. takto:

    float soucin = skalarni_soucin(n, a, b);
    
  4. Vektorový součin. Funkce provede výpočet pouze pro vektory délky 3, v ostatních případech se neprovede nic (výsledek není definován).

    void vektorovy_soucin(int n, const float* a, const float* b, float* c)
    {
        if (n == 3) {
            c[0] = a[1]*b[2] - a[2]*b[1];
            c[1] = a[2]*b[0] - a[0]*b[2];
            c[2] = a[0]*b[1] - a[1]*b[0];
        }
    }