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:
- předání pole do funkce (to už jsme viděli)
- „výstupní“ parametry funkce pro vracení více hodnot z jedné funkce (např. pole)
- textové řetězce
- dynamická alokace paměti (viz později)
Probrané příklady
-
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élkyn
, pak následující příkaz spočítá součetc = 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);
-
Funkce pro rozdíl dvou vektorů.
-
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);
-
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]; } }