Dynamické premenné

Premenné

Globálne premenné:

* vzniknú pri spustení programu
* existujú počas behu programu
* zanikajú pri skočení programu
* počas kompilácie sa rozhodne, kde v pamäti budú takéto premenné umiestnené.

Lokálne premenné (a parametre):

* vznikajú pri zavolaní funkcie
* existujú počas vykonávania funkcie
* zaniknú pri skončení funkcie
* vznikajú v časti pamäte, ktorej hovoríme zásobník.

Veľkosť (počet bajtov) globálnej a lokálnej premennej je určená už počas kompilácie programu.

Niekedy je výhodné, ak môžeme riadiť vznikanie a zanikanie premenných a to hlavne takých, ktoré:

* nemajú v čase kompilácie programu presne stanovenú veľkosť
* počas behu programu menia svoju veľkosť (zoznamy)
* v pamäti zaberajú veľa miesta (veľké objekty, štruktúry, obrázky), a pritom potrebujeme iba na určitú dobu.

Dynamické premenné:

* ich vznik a zánik riadime pomocou príkazov
* vytvoríme ju v okamihu, keď premennú potrebujeme
* zrušíme ju, keď už premennú nepotrebujeme
* vznikajú v časti pamäte, ktorú nazývame halda (anglicky heap).


Dynamické premenné

Pri kompilácii sa nedá presne určiť, kde v pamäti dynamická premenná vznikne - aká bude jej adresa. Preto pri práci s dynamickými premennými používame smerníky, ktoré na dynamické premenné ukazujú:

float *p; // smerník na premennú typu float
p=new float; // vytvoríme premennú typu float a jej adresu uložíme do smerníka p
*p=3.14; // teraz môžeme pracovať s dynamickou premennou *p (priradíme hodnotu)
Caption=FloatToStr(*p);
delete p; // zrušíme dynamickú premennú

* Operátor new nájde v halde voľné miesto, na ktorom vytvorí dynamickú premennú uvedeného typu. Výsledkom operátora je smerník na novovzniknutú dynamickú premennú.
* S dynamickou premennou ďalej pracujeme pomocou smerníka.
* Operátor delete zruší dynamickú premennú - uvoľní v halde pamäťové miesto. Od tohto okamihu smerník ukazuje na neexistujúcu premennú (pozor, v smerníku nebude hodnota NULL).

Pozn.: z dôvodu kompatibility typov nasledujúce neskompilujeme:
p=new int; … chyba
Smerník na premennú typu float nie je kompatibilný so smerníkom na premennú typu int - výsledkom operácie new int je smerník na celočíselná premenná.


Pravidlá a odporúčania pri práci s dynamickou premennou

1. Po zrušení dynamickej premennej už s touto premennou nesmieme pracovať - čiže, nesmieme:

p=new float;
delete p;
Caption=FloatToStr(*p); ... premenná, ktorá bola na adrese p už neexistuje

Kompilátor príkazy preloží, ale chyba sa vyskytne až pri vykonávaní posledného príkazu, ktorý zisťuje obsah už neexistujúcej premennej (uvoľnené pamäťové miesto sa použije na niečo iné).

2. Nesmieme vykonať viacnásobné zrušenie jednej dynamickej premennej:

p=new float;
delete p; // zrušíme premennú *p
delete p; ... nesmieme zrušiť neexistujúcu premennú

Podobne, ako v predchádzajúcom príklade, aj tieto príkazy sa skompilujú, avšak chyba sa môže prejaviť až pri behu programu.

Treba si zapamätať pravidlo:

Každú premennú vytvorenú príkazom new musíme zrušiť príkazom delete.

3. Ak je šanca, že v programe budeme rušiť tú istú dynamickú premennú na viacerých miestach, potom používame nasledovnú možnosť:
p=new float;
...
delete p; // zrušíme premennú *p
p=NULL; // smerník neukazuje na žiadnu premennú
...
delete p; // ak p ukazuje na dynamickú premennú, týmto príkazom ju zrušíme, ak je p==NULL, nič sa nestane
p=NULL;

4. V programoch môžeme použiť smerník aj viackrát:

p=new float;
delete p;
p=new float;
delete p;

5. Dávajte si pozor na nasledujúce:

p=new float;
p=new float; // ... stratili sme adresu predchádzajúcej dynamickej premennej
delete p;
... teraz už nesmieme vykonať: delete p ... predchádzajúcim príkazom sme dynamickú premennú, na ktorú smerník p ukazoval, už zrušili.


Príklady s dynamickými premennými

Objekt ako dynamická premenná:

TKor *k;
k=new TKor; // vytvoríme objekt
k->dp(100);
delete k; // zrušíme objekt

Pole smerníkov:

int i,*a[100]; // a je pole smerníkov na celé čísla
for (i=0; i<100; i++) a[i]=new int; // vytvoríme 100 dynamických premenných
for (i=0; i<100; i++) *a[i]= ... ; // až teraz môžeme s jednotlivými dynamickými premennými pracovať
for (i=0; i<100; i++) delete a[i]; // zrušíme všetky dynamické premenné

Štruktúra, ktorá obsahuje smerníky:

struct TTest { // zadefinujeme štruktúru TTest
char znak; // znak
int *cislo; // smerník na číslo
};

Príklad 1.:
TTest X, Y;
X.znak='a';
X.cislo=new int;
*X.cislo=1234;
Y=X;
delete X.cislo;

Teraz už nesmieme:
delete Y.cislo
lebo smerník Y.cislo už ukazuje na neexistujúcu premennú.

Príklad 2.: rušenie viacerých dynamických premenných:
TTest *Z;
Z=new TTest; // Z ukazuje na dynamickú premennú typu TTest
Z->cislo=new int; // Z->cislo ukazuje na dynamickú premennú typu int
Z->znak='a';
*Z->cislo=0;
// tu niečo robíme ...
delete Z->cislo; // najskôr uvoľníme dynamickú premennú, na ktorú ukazuje Z->cislo
delete Z; // až potom uvoľnime dynamickú premennú, na ktorú ukazuje Z

Dynamické premenné nesmieme rušiť v takomto poradí:
delete Z;
delete Z->cislo; ... lebo Z už ukazuje na neexistujúcu dynamickú premennú


Dynamicky vytvorené pole

V C++ môžeme pomocou operátora new vytvoriť aj viacero dynamických premenných rovnakého typu, ktoré ležia v pamäti za sebou, a teda tvoria pole. Pole, ktoré takto vznikne, nazveme dynamicky vytvorené pole:
int i,*p;
p=new int[10];
// vznikne 10 celočíselných premenných, ktoré sú v pamäti uložené za sebou - to je pole
// premenná p ukazuje na 0. prvok poľa
for (i=0; i<10; i++) p[i] = ... ;
delete[] p; // zrušíme celé pole (všetkých 10 premenných)

Dynamické pole ako zoznam:
TKor *zoz=NULL; // smerník na zoznam - na začiatok poľa korytnačiek
int poc=0; // počet prvkov v zozname

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
for (int i=0; i<poc; i++) {
zoz[i].dp(1);
zoz[i].vl(random(30));
}
}

void __fastcall TForm1::FormDestroy(TObject *Sender)
{
delete[] zoz;
}

void __fastcall TForm1::Image1MouseDown(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y)
{
TKor *pom; // zadeklarujeme pomocný smerník
pom=new TKor[poc+1]; // vytvoríme nové pole
for (int i=0; i<poc; i++) // skopírujeme prvky pôvodného zoznamu
pom[i]=zoz[i];
pom[poc].Inic(Image1, X, Y); // inicializujeme novú korytnačku
pom[poc].Farba=TColor(random(0x1000000)) ;
delete[] zoz; // pôvodné pole zrušíme
zoz=pom; // zmeníme smerník tak, aby ukazoval na nové pole
poc++;
}


Zhrnutie

* s dynamickou premennou pracujeme pomocou smerníka
* dynamickú premennú vytvoríme: smerník = new typ
* dynamickú premennú zrušíme: delete smerník
* dynamické pole vytvoríme: smerník = new typ[dĺžka]
* dynamické pole zrušíme: delete[] smerník