Vianoce v ITnetwork sú tu! Dobí si teraz kredity a získaj až 80 % extra kreditov na e-learningové kurzy ZADARMO. Zisti viac.
Hľadáme nové posily do ITnetwork tímu. Pozri sa na voľné pozície a pridaj sa k najagilnejšej firme na trhu - Viac informácií.

1. diel - Úvod do ukazovateľov (pointer) v jazyku C

Vitajte u prvej lekcie pokročilého kurzu o programovaní v jazyku C. V tomto kurze sa naučíme pracovať s dynamicky alokovanú pamäťou v jazyku C a dostaneme sa aj k práci so súbormi. Asi vás neprekvapí, že predpokladom ku zdolanie seriálu je znalosť základných konštrukcií jazyka C.

Adresy v pamäti

Keď sme sa prvýkrát zmieňovali o premenných, hovorili sme si, že premenná je "miesto v pamäti", kam si môžeme uložiť nejakú hodnotu. Tiež vieme, že premenné majú rôzne dátové typy (napr. int) a tie zaberajú v pamäti rôzne miesta (napr. Int zaberá 32 bitov, teda 32 núl a jednotiek).

Pamäť počítača si môžeme predstaviť ako dlhú (takmer nekonečnú :) ) Rad núl a jednotiek. Niektoré časti pamäte sú obsadené inými aplikáciami a niektoré sú operačným systémom chápané ako voľné miesto. Aby sa dalo s pamäťou rozumne pracovať, je adresovaná, ako sú napr. Domy v ulici. Adresy sa väčšinou zapisujú v šestnástkovej sústave, ale stále sa jedná o obyčajná čísla. Adresy idú chronologicky za sebou a na každej adrese sa nachádza 1 bajt (teda 8 bitov, pretože adresovanie po drobných bitoch by bolo nepraktické).

Akonáhle v céčku deklarujeme nejakú premennú v zdrojovom kóde a aplikáciu spustíme, Céčko si povie operačnému systému o toľko pamäti, koľko je pre túto premennú treba. Od systému získa pridelenú adresu do pamäte, na ktorú môže hodnotu premennej uložiť (zjednodušene povedané). Tento proces nazývame alokácie pamäte.

Získanie adresy premenné

Jazyk C nás od adries zatiaľ plne odsťiňoval, pamäť alokovala za nás as našimi premennými sme pracovali jednoducho pomocou ich mien. Vytvorme si teraz jednoduchý program, ktorý založí premennú typu int a do nej uloží hodnotu 56. Adresu tejto premennej si získame pomocou tzv. Referenčného operátoru & (ampersand) a vypíšeme ju do konzoly. Vo formátovacom reťazci použijeme %p, čo ju vypíše v šestnástkovej sústave tak, ako sa na pamäťovú adresu sluší a patrí.

int main(int argc, char** argv) {
    int a;
    a = 56;
    printf("Proměnná a s hodnotou %d je v paměti uložená na adrese %p", a, &a);
    return (EXIT_SUCCESS);
}

výsledok:

c_pointery
Proměnná a s hodnotou 56 je v paměti uložená na adrese 0x23aadc

Vidíte, že na mojom počítači si systém vybral adresu 0x23aadc. Vy tam budete mať iné číslo. Situácia v pamäti počítača bude vyzerať takto:

pamäť počítača - Dynamická práca s pamäťou v jazyku C

(Dátový typ int má 32 bitov, preto teda zaberá 4 osmice bitov na 4 adresách. Udávame vždy adresu začiatku hodnoty.)

Ukazovatele (pointer)

Získať číslo adresy je síce pekné, ale ak by sme s pamäťou takto pracovali, bolo by to trochu nepraktické. Z toho dôvodu jazyk C podporuje tzv. Ukazovatele (anglicky Pointer). Ukazovateľ je premenná, ktorej hodnotou je adresa niekam do pamäte. Céčko ukazovateľ však neberie ako obyčajné číslo, ale vie, že ho má používať ako adresu. Keď do ukazovateľa teda niečo uložíme alebo naopak vypíšeme jeho hodnotu, nevypisuje sa adresa (hodnota ukazovateľa), ale používa sa hodnota, na ktorú ukazovateľ ukazuje.

Vráťme sa opäť k nášmu programu. Tentoraz si okrem premenné a definujeme aj ukazovateľ na premennú a. Ten bude tiež typu int, ale pred jeho názvom bude tzv. Dereferenční operátor * (hviezdička). Zvyknite si Pointer pomenovávať vždy tak, aby začínali na p_. Vyhnete sa tak v budúcnosti veľkým problémom, pretože Pointer sú pomerne nebezpečné, ako ďalej zistíme, a mali by sme si zrozumiteľne označiť, či je premenná pointer alebo nie.

int main(int argc, char** argv) {
    int a, *p_a;
    a = 56;
    p_a = &a; // Uloží do p_a adresu proměnné a
    *p_a = 15; // Uloží hodnotu 15 na adresu v p_a
    printf("Ukazatel p_a má hodnotu %d ukazuje na hodnotu %d", p_a, *p_a);
    return (EXIT_SUCCESS);
}

Aplikácia si vytvorí premennú typu int a ďalej ukazovateľ na int. Ukazovatele tiež majú vždy svoj dátový typ podľa toho, na hodnotu akého typu ukazujú. Do premennej a sa uloží hodnota 56.

Do ukazovateľa p_a (zatiaľ bez hviezdičky) sa uloží adresa premenné a, ktorú získame pomocou referenčného operátora &. Teraz chceme tam, kam ukazuje pointer p_a, uložiť číslo 15. Použijeme dereferenční operátor (*) a tým neuložíme hodnotu do ukazovateľa, ale tam, kam ukazovateľ ukazuje.

Následne vypíšeme hodnotu ukazovateľa (čo je nejaká adresa v pamäti, obvykle vysoké číslo, tu ho vypisujeme v desiatkovej sústave) a ďalej vypíšeme hodnotu, na ktorú ukazovateľ ukazuje. Kedykoľvek pracujeme s hodnotou ukazovateľa (nie adresou), používame operátor *.

výsledok:

c_pointery
Ukazatel p_a má hodnotu 23374500 ukazuje na hodnotu 15

Opäť si ukážme aj situáciu v pamäti:

pamäť počítača - Dynamická práca s pamäťou v jazyku C

Odovzdávanie referencií

Vieme teda na premennú vytvoriť ukazovateľ. K čomu je to ale dobré? Do premennej sme predsa vedeli ukladať aj predtým. Jednou z výhod pointer je tzv. Odovzdávanie referencií. Vytvorme si funkciu, ktoré prídu v parametri 2 čísla a my budeme chcieť, aby ich hodnoty prehodila (tejto funkcii sa anglicky hovorí swap). Naivne by sme mohli napísať nasledujúci kód:

// Tento kód nefunguje
void prohod(int a, int b)
{
    int pomocna = a;
    a = b;
    b = pomocna;
}

int main(int argc, char** argv) {
    int cislo1 = 15;
    int cislo2 = 8;
    prohod(cislo1, cislo2);
    printf("V cislo1 je číslo %d a v cislo2 je číslo %d.", cislo1, cislo2);
    return (EXIT_SUCCESS);
}

výsledok:

c_pointery
V cislo1 je číslo 15 a v cislo2 je číslo 8.

Prečo že aplikácia nefunguje? Pri volaní funkcie prohod() vo funkcii main() sa zoberú hodnoty premenných cislo1 a cislo2 a tie sa skopírujú do premenných a a b v definícii funkcie. Funkcia ďalej zmení tieto premenné a a b, avšak pôvodné premenné číslo1 a číslo2 zostanú nezmenené. Tomuto spôsobu, kedy sa hodnota premennej do parametra funkcie skopíruje, hovoríme odovzdávanie hodnodnou.

Všimnite si, že k prehodenie 2 čísel potrebujeme pomocnú premennú. Keby sme vo funkcii prohod() napísali len a = b; b = a;, Bola by v oboch premenných hodnota b, pretože hodnota a sa prvým príkazom prepísala.

Ľubovoľnú premennú môžeme odovzdať referencií a to tak, že funkciu upravíme aby prijímala v parametroch pointera. Pri volaní také funkcie potom použijeme referenčné operátor &:

void prohod(int *p_a, int *p_b)
{
    int pomocna = *p_a;
    *p_a = *p_b;
    *p_b = pomocna;
}

int main(int argc, char** argv) {
    int cislo1 = 15;
    int cislo2 = 8;
    prohod(&cislo1, &cislo2);
    printf("V a je číslo %d a v b je číslo %d.", cislo1, cislo2);
    return (EXIT_SUCCESS);
}

výsledok:

c_pointery
V cislo1 je číslo 8 a v cislo2 je číslo 15.

Keďže funkciu teraz odovzdávame adresu, je schopná zmeniť pôvodné premenné.

Niektorí programátori v jazyku C používajú často parametre funkcií v odovzdávaní hodnoty. To však nie je príliš prehľadné a ak nás netlačia výpočtovej čas a je to len trochu možné, mala by funkcie vždy vracať len jednu hodnotu pomocou príkazu return, prípadne môže vracať štruktúru alebo ukazovateľ na štruktúru / pole.

Možno vás napadlo, že konečne rozumiete funkciu scanf (), ktorá ukladá hodnoty do premenných odovzdaných parametre. Operátor & tu používame preto, aby sme funkciu odovzdali adresu, na ktorú má dáta uložiť:

int a;
scanf("%d", &a);

Odovzdávanie poľa

Polia a Pointer majú v céčku veľa spoločného. Preto keď odovzdáme poľa do parametra nejakej funkcie a polia v nej zmeníme, zmeny sa v pôvodnom poli prejaví. Pole je na rozdiel od ostatných typov vždy odovzdávané referencií bez toho aby sme sa o to museli snažiť.

void napln_pole(int pole[], int delka)
{
    int i;
    for (i = 0; i < delka; i++)
    {
        pole[i] = i + 1;
    }
}

int main(int argc, char** argv) {
    int cisla[10];
    napln_pole(cisla, 10);
    printf("%d", cisla[5]); // Vypíše číslo 6
    return (EXIT_SUCCESS);
}

Ako sme si povedali skôr, pole je vlastne spojité miesto v pamäti. Ale také miesto musíme vedieť nejako adresovať. Adresujeme ho práve pomocou ukazovateľa. Premenná typu pole totiž nie je nič iné ako ukazovateľ. To znamená, že nám bez problémov prejde nasledujúce operácie priradenie:

int pole[10];
int* p_pole = cisla;

V kapitole Aritmetika ukazovateľov si ukážeme, že je úplne jedno, či máme poľa alebo ukazovateľ.

Null

Všetkým pointerům ľubovoľného typu môžeme priradiť konštantu NULL. Tá udáva, že je pointer prázdny a že zrovna na nič neukazuje. Na väčšine platforiem sa NULL rovná hodnote 0 a tak sa v niektorých kódoch môžete stretnúť s priradením 0 miesto NULL. To sa všeobecne neodporúča kvôli kompatibilite medzi rôznymi platformami. Túto hodnotu budeme v budúcnosti hojne používať.

Čo si zapamätať: Pointer je premenná, v ktorej je uložená adresa do pamäti. Môžeme pracovať buď s touto adresou alebo s hodnotou na tejto adrese a to pomocou operátora *. Adresu ľubovoľnej premennej získame pomocou operátora &.

Hoci sme si Pointer pomerne slušne uviedli, ich pravým účelom je najmä dynamické prideľovanie pamäte. Na ktoré sa pozrieme hneď v nasledujúcej lekcii, Dynamická alokácia pamäte v jazyku C .


 

Všetky články v sekcii
Dynamická práca s pamäťou v jazyku C
Preskočiť článok
(neodporúčame)
Dynamická alokácia pamäte v jazyku C
Článok pre vás napísal David Hartinger
Avatar
Užívateľské hodnotenie:
2 hlasov
David je zakladatelem ITnetwork a programování se profesionálně věnuje 15 let. Má rád Nirvanu, nemovitosti a svobodu podnikání.
Unicorn university David sa informačné technológie naučil na Unicorn University - prestížnej súkromnej vysokej škole IT a ekonómie.
Aktivity