9. Ukazovatele a polia
9.3 Statické a dynamické pole
Ak deklarujeme pole pomocou [ ], čiže jeho veľkosť je známa už v čase prekladu, ide vždy o statické pole
bezohľadu na to, či sa táto deklarácia nachádza vo vnútri funkcie alebo
nie. Pole, pre ktoré sa pamäť alokuje v priebehu vykonávania programu
sa nazýva dynamické. Ak už raz máme pre pole pamäť k
dispozícii, nezáleží na tom, či sa alokovala staticky alebo dynamicky,
s poľom sa pracuje rovnako - môžeme používať klasický zápis
(pomocou indexovanej premennej) alebo využiť možnosti, ktoré nám dávajú
pointery a pointerová aritmetika.
Ten
istý príklad ešte raz, tento raz však použijeme dynamické pole
a pointerovú aritmetiku. Postup riešenia je však (až na alokáciu
pamäte) totožný:
void main(void)
{
int *p; /* deklarujeme p ako pointer na int */
int n, i, max;
printf("pocet prvkov n = ");
scanf("%d\n", &n);
/* dynamicky alokujeme pamäť potrebnú pre pole */
p = (int*)malloc(n*sizeof(int));
for (i = 0; i < n ; i++) {
printf("%d. prvok: ", i + 1);
scanf("%d", p + i); /* p + i je adresa! */
}
max = *p; /* alebo tiež max = *(p+0) */
for (i = 0; i < n ; i++) {
if (*(p + i) > max) max = *(p + i);
}
printf("Maximum je %d \n", max);
}
Deklarovali sme p ako pointer na int. Týmto sme ale vyhradili iba 4 bajty pre pointer p, žiadna pamäť pre pole sa ešte nealokovala. To musí programátor zabezpečiť sám. Použili sme preto funkciu malloc, po jej volaní (kvôli úspore miesta bez testovania úspešnosti pridelenia pamäti :-) pointer p ukazuje na začiatok bloku pamäti vyhradeného pre pole - na prvý prvok tohto poľa.
Pointer p môžeme považovať za dynamické pole!
Výhodou je, že pre pole takto rezervujeme len toľko pamäte, koľko je skutočne potreba.
Pristupovať k prvkom poľa môžeme dvoma spôsobmi, oba zápisy sú úplne rovnocenné:
| Prvok poľa s indexom 0: | *(p + 0) == p[0] == *p | | Prvok poľa s indexom 1: | *(p + 1) == p[1] | | Prvok poľa s indexom i: | *(p + i) == p[i] |
p + i je adresa prvku poľa s indexom i, *(p + i) je jeho hodnota
Aj keď používame pole statické, môžeme pokojne použiť "pointerový" zápis. Majme takúto deklaráciu:
int a[6], *b;
a je statické pole šiestich prvkov, b je pointer na int, zatiaľ neukazuje na nič zmyslupné
b = (int*) malloc(6*sizeof(int));
b je dynamické pole, má tiež 6 prvkov, pointer b ukazuje na prvý prvok poľa
| adresa prvku s indexom i: | &a[i] == a + i &b[i] == b + i | | prvok s indexom i
| a[i] == *(a + i) *a == a + 0 == a[0] b[i] == *(b + i) *b == b + 0 == b[0] |
Do
statického poľa sa zvykne pristupovať pomocou indexov a do
dynamického poľa pomocou pointerovej aritmetiky, ale záleží len na
programátorovi, aký spôsob si vyberie (pokojne si môže vybrať aj jeden
so spôsobov a používať vždy len ten). Prístup k prvkom poľa
pomocou pointerov býva obyčajne efektívnejší. Pri indexácií sa totiž
tiež musí vypočítať najprv adresa príslušného prvku poľa podľa vzťahu
&a[i] == bázová adresa a + i*sizeof(int),
čo je časovo náročnejšie ako pripočítavať k pointeru len nejakú konštantu: a + i.
Väčšina kompilátorov však zvyčajne prístup pomocou indexov aj tak
prevedie na prístup pomocou pointerov, takže vnútorne statické
i dynamické pole vlastne "funguje" rovnako.
Dôležitý rozdiel!
a je tiež pointer ale konštantný, čo znamená, že nemôže byť zmenený!
Napr. priradenie
a = b
je vylúčené!
Avšak naopak t.j. priradenie
b = a;
je v poriadku. Premenná b
je predsa len "obyčajný" pointer na int, nemusí ukazovať len na prvky
pôvodného poľa. Pozor ale na to, aby ste zmenou pointeru nechtiac
nestratili prístup k poľu!
Ak potrebujeme alokovať pamäť pre n prvkov, z ktorých každý má rovnakú veľkosť, môžeme použiť aj funkciu
calloc(n, velkost),
ktorá alokuje toto pole prvkov rovnako ako príkaz
malloc(n * velkost)
a naviac
ho vynuluje! Tiež vráti pointer na začiatok prideleného bloku pamäti
alebo NULL, ak sa požadovanú pamäť nepodarilo prideliť. V niektorých
systémoch je však nutné takto alokovanú pamäť uvoľniť pomocou funkcie cfree!
|