IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

4. diel - Referenčnej a hodnotové dátové typy

V predchádzajúcom cvičení, Riešené úlohy k 3. lekcii OOP v C # .NET, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.

V minulej lekcii, Riešené úlohy k 3. lekcii OOP v C # .NET , sme si vytvorili svoj prvý poriadny objekt, bola ním hracia kocka. Začíname pracovať s objektmi a objekty sú referenčnými dátovými typy, ktoré sa v niektorých ohľadoch správajú inak, než typy hodnotové (napr. int ). Je dôležité, aby sme presne vedeli, čo sa vo vnútri programu deje, inak by nás v budúcnosti mohlo všeličo prekvapiť.

Zopakujme si pre istotu ešte raz, čo sú to hodnotovej typy. Všeobecne sú to jednoduché štruktúry, napr. Jedno číslo, jeden znak. Väčšinou sa chce, aby sme s nimi pracovali čo najrýchlejšie, v programe sa ich vyskytuje veľmi veľa a zaberajú málo miesta. V anglickej literatúre sú často opisované slovami light-weight. Majú pevnú veľkosť. Príkladom sú napr. int , float , double , char , bool a ďalšie.

Aplikácia (resp. Jej vlákno) má operačným systémom pridelenú pamäť v podobe tzv. Zásobníka (stack). Jedná sa o veľmi rýchlu pamäť s priamym prístupom, jej veľkosť aplikácie nemôže ovplyvniť, prostriedky sú prideľované operačným systémom. Táto malá a rýchla pamäť je využívaná na ukladanie lokálnych premenných hodnotového typu (až na výnimky pri opakovaniach, ktorými sa nebudeme zaoberať). Premennou si v nej môžeme predstaviť asi takto:

Zásobník vb pamäte počítača - Objektovo orientované programovanie v C #

Na obrázku je znázornená pamäť, ktorú môže naše aplikácie využívať. V aplikácii sme si vytvorili premennú a typu int . Jej hodnota je 56 a uložila sa nám priamo do zásobníka. Kód by mohol vyzerať takto:

int a = 56;

Môžeme to chápať tak, že premenná a má pridelenú časť pamäte v zásobníku (veľkosti dátového typu int , teda 32 bitov), v ktorej je uložená hodnota 56 .

Vytvorme si novú konzolovú aplikáciu a pridajme si k nej jednoduchú triedu, ktorá bude reprezentovať užívateľa nejakého systému. Pre názornosť vypustím komentáre a nebudem riešiť viditeľnosti:

class Uzivatel
{
    public int vek;
    public string jmeno;

    public Uzivatel(string jmeno, int vek)
    {
        this.jmeno = jmeno;
        this.vek = vek;
    }

    public override string ToString()
    {
        return jmeno;
    }
}

Trieda má 2 jednoduché verejné atribúty, konštruktor a preťažený ToString() , aby sme používateľa mohli jednoducho vypisovať. Do nášho pôvodného programu pridajme vytvorení inštancie tejto triedy:

int a = 56;
Uzivatel u = new Uzivatel("Jan Novák", 28);

Premenná u je teraz referenčného typu. Pozrime sa na novú situáciu v pamäti:

Zásobník a halda v pamäti počítača - Objektovo orientované programovanie v C #

Vidíme, že objekt (premenná referenčného dátového typu) sa už neukladá do zásobníka, ale do pamäte zvanej halda. Je to z toho dôvodu, že objekt je spravidla zložitejšie ako hodnotový dátový typ (väčšinou obsahuje hneď niekoľko ďalších atribútov) a tiež zaberá viac miesta v pamäti.

Zásobník aj halda sa nachádzajú v pamäti RAM . Rozdiel je v prístupe a veľkosti. Halda je prakticky neobmedzená pamäť, ku ktorej je však prístup zložitejšia a tým pádom pomalší. Naopak zásobník je pamäť rýchla, ale veľkostne obmedzená.

Premenné referenčného typu sú v pamäti uložené vlastne na dvakrát, raz v zásobníku a raz v halde. V zásobníku je uložená iba tzv. Referencie, teda odkaz do haldy, kde sa potom nachádza naozajstný objekt.

Napr. v C ++ je veľký rozdiel medzi pojmom ukazovateľ a referencie. C # žiadne ukazovatele našťastie nemá a používa termín referencie, tie sa paradoxne princípom podobajú skôr ukazovateľom v C ++. Pojmy ukazovateľ a referencie tu spomenuté teda znamenajú referenciu v zmysle C # a nemajú s C ++ nič spoločné.

Môžete sa pýtať, prečo je to takto urobené. Dôvodov je hneď niekoľko, poďme si niektoré vymenovať:

  1. Miesto vo stacku je obmedzené.
  2. Keď budeme chcieť použiť objekt viackrát (napr. Ho odovzdať ako parameter do niekoľkých metód), nemusíme ho v programe odovzdávať ako kópiu. Odovzdáme iba malý hodnotový typ s referenciou na objekt namiesto toho, aby sme všeobecne pamäťovo náročný objekt kopírovali. Toto si vzápätí ukážeme.
  3. Pomocou referencií môžeme jednoducho vytvárať štruktúry s dynamickou veľkosťou, napr. Štruktúry podobné poli, do ktorých môžeme za behu vkladať nové prvky. Tie sú na seba navzájom odkazované referenciami, ako reťaz objektov.

    Založme si 2 premenné typu int a 2 premenné typu Uzivatel :

int a = 56;
int b = 28;
Uzivatel u = new Uzivatel("Jan Novák", 28);
Uzivatel v = new Uzivatel("Josef Nový", 32);

Situácia v pamäti bude nasledovné:

Referenčné hodnoty v C # v pamäti počítača - Objektovo orientované programovanie v C #

Teraz skúsme priradiť do premennej a premennú b . Rovnako tak priradíme aj premennú v do premennej u . Hodnotový typ sa v zásobníku len skopíruje, pri objekte sa skopíruje iba referencie (čo je vlastne tiež hodnotový typ), ale objekt máme stále len jeden. V kóde vykonáme teda toto:

int a = 56;
int b = 28;
Uzivatel u = new Uzivatel("Jan Novák", 28);
Uzivatel v = new Uzivatel("Josef Nový", 32);
a = b;
u = v;

V pamäti bude celá situácia vyzerať nasledovne:

Referenčné hodnoty v C # v pamäti počítača - Objektovo orientované programovanie v C #

Presvedčte sa o tom, aby ste videli, že to naozaj tak je :) Najprv si necháme všetky štyri premenné vypísať pred a po zmene. Pretože budeme výpis volať viackrát, napíšem ho trochu úspornejšie. Mohli by sme dať výpis do metódy, ale ešte nevieme, ako deklarovať metódy priamo v Program.cs a spravidla sa to ani veľmi nerobí, pre vážnejšie prácu by sme si mali urobiť triedu. Upravme teda kód na nasledujúce:

            // založenie premenných
            int a = 56;
            int b = 28;
            Uzivatel u = new Uzivatel("Jan Novák", 28);
            Uzivatel v = new Uzivatel("Josef Nový", 32);
            Console.WriteLine("a: {0}\nb: {1}\nu: {2}\nv: {3}\n", a, b, u, v);
            // priraďovanie
            a = b;
            u = v;
            Console.WriteLine("a: {0}\nb: {1}\nu: {2}\nv: {3}\n", a, b, u, v);
            Console.ReadKey();
    class Uzivatel
    {
        public int vek;
        public string jmeno;

        public Uzivatel(string jmeno, int vek)
        {
            this.jmeno = jmeno;
            this.vek = vek;
        }

        public override string ToString()
        {
            return jmeno;
        }
    }

Na výstupe programu zatiaľ rozdiel medzi hodnotovým a referenčným typom nespoznáme:

Konzolová aplikácia
a: 56
b: 28
u: Jan Novák
v: Josef Nový

a: 28
b: 28
u: Josef Nový
v: Josef Nový

Avšak vieme, že kým v a a b sú naozaj 2 rôzne čísla s rovnakou hodnotou, v u a v je ten istý objekt. Poďme zmeniť meno používateľa v a podľa našich predpokladov by sa mala zmena prejaviť aj v premennej u . K programu pripíšeme:

            // zmena
            v.jmeno = "John Doe";
            Console.WriteLine("u: {0}\nv: {1}\n", u, v);
                class Uzivatel
    {
        public int vek;
        public string jmeno;

        public Uzivatel(string jmeno, int vek)
        {
            this.jmeno = jmeno;
            this.vek = vek;
        }

        public override string ToString()
        {
            return jmeno;
        }
    }

Zmenili sme objekt v premennej v a znovu vypíšeme u a v :

Konzolová aplikácia
a: 56
b: 28
u: Jan Novák
v: Josef Nový

a: 28
b: 28
u: Josef Nový
v: Josef Nový

u: John Doe
v: John Doe

Spolu so zmenou v sa zmení aj u , pretože premenné ukazujú na ten istý objekt. Ak sa pýtate, ako vytvoriť naozajstnú kópiu objektu, tak najjednoduchšie je objekt znova vytvoriť pomocou konstruktoru a dať do neho rovnaké dáta. Ďalej môžeme použiť klonovanie, ale o tom zas až niekedy inokedy. Pripomeňme si situáciu v pamäti ešte raz a zamerajme sa na Jána Nováka.

Referenčné hodnoty v C # v pamäti počítača - Objektovo orientované programovanie v C #

Čo sa sním stane? "Zožerie" ho tzv. Garbage collector.

garbage collector - Objektovo orientované programovanie v C #

Garbage collector a dynamická správa pamäte

Pamäť môžeme v programoch alokovať staticky, to znamená, že v zdrojovom kóde vopred určíme, koľko jej budeme používať. Doteraz sme to tak vlastne robili a nemali sme s tým problém, pekne sme do zdrojového kódu napísali potrebné premenné. Čoskoro sa ale budeme stretávať s aplikáciami (a už sme sa vlastne i stretli), kedy nebudeme pred spustením presne vedieť, koľko pamäte budeme potrebovať. Spomeňte si na program, ktorý vytvoril priemer zadané hodnoty v poli. Na počet hodnôt sme sa užívateľa opýtali až za behu programu. CLR teda musel za behu programu poľa v pamäti založiť. V tomto prípade hovoríme o dynamickej správe pamäte.

V minulosti, hlavne v časoch jazykov C , Pascal a C ++, sa na tento účel používali tzv. Pointer, čiže priame ukazovatele do pamäte. Napospol to fungovalo tak, že sme si povedali operačnému systému o kus pamäti o určitej veľkosti. On ju pre nás vyhradil a dal nám jej adresu. Na toto miesto v pamäti sme mali pointer, cez ktorý sme s pamäťou pracovali. Problém bol, že nikto nestrážil, čo do pamäti dávame (ukazovateľ smeroval na začiatok vyhradeného priestoru). Keď sme tam dali niečo väčšieho, skrátka sa to rovnako uložilo a prepísala sa dáta za naším priestorom, ktorá patrila napríklad inému programu alebo operačnému systému (v tom prípade by našu aplikáciu OS asi zabil - zastavil). Často sme si však my v pamäti prepísali nejaká ďalšie dáta nášho programu a program sa začal správať chaoticky. Predstavte si, že si uložíte používateľa do poľa a v tej chvíli sa vám zrazu zmení farba užívateľského prostredia, teda niečo, čo s tým vôbec nesúvisí. Hodiny strávite tým, že kontrolujete kód pre zmenu farby, potom zistíte, že je chyba v založenia používateľa, kedy dôjde k pretečeniu pamäte a prepísanie hodnôt farby.

Keď naopak nejaký objekt prestaneme používať, musíme po ňom miesto sami uvoľniť, ak to neurobíme, pamäť zostane blokovaná. Pokiaľ toto robíme napr. V nejakej metóde a zabudneme pamäť uvoľňovať, naše aplikácie začne padať, prípadne zasekne celý operačný systém. Takáto chyba sa opäť zle hľadá, prečo program prestane po niekoľkých hodinách fungovať? Kde tú chybu v niekoľkých tisícoch riadkov kódu vôbec hľadať? Nemáme jedinú stopu, nemôžeme sa ničoho chytiť, musíme prejsť celý program riadok po riadku alebo začať preskúmavať pamäť počítača, ktorá je v binárce. Brrr. Podobný problém nastane, keď si niekde pamäť uvoľníme a následne pointer opäť použijeme (zabudneme, že je uvoľnený, to sa môže ľahko stať), povedie niekam, kde je už uloženého niečo iné a tieto dáta budú opäť prepísané. Povedie to k nekontrolovanému správanie našej aplikácie a môže to dopadnúť aj takto:

Blue Screen Of Death – BSOD vo Windows - Objektovo orientované programovanie v C #

Môj kolega raz povedal: "Ľudský mozog sa nedokáže starať ani o správu pamäte vlastné, nieto aby riešil memory management programu." Mal samozrejme pravdu, až na malú skupinu géniov ľudí prestalo baviť riešiť neustále a nezmyselné chyby. Za cenu mierneho zníženia výkonu vznikli riadené jazyky (managed) s tzv. Garbage collector, jedným z nich je aj C # a Java . C ++ sa samozrejme naďalej používa, ale len na špecifické programy, napr. Časti operačného systému alebo 3D enginy komerčných hier, kde je potreba z počítača dostať maximálny výkon. Na 99% všetkých ostatných aplikácií sa hodia C #, kvôli možnosti používať .NET a hlavne automatické správe pamäte. Používať .NET bolo umožnené aj v C ++, hovoríme o tzv. Managed C ++, kde výsledná aplikácia používala garbage collector. Projekt sa však neuchytil, pretože C ++ tak už nemalo žiadne výhody oproti C #, ktorý je modernejší.

garbage collector - Objektovo orientované programovanie v C #
Garbage collector je vlastne program, ktorý beží paralelne s našou aplikáciou, v samostatnom vlákne. Občas sa spustí a pozrie sa, na ktoré objekty už v pamäti nevedú žiadne referencie. Tie potom odstráni. Strata výkonu je minimálna a značne to zníži percento samovrážd programátorov, ladiacich po večeroch rozbité pointera. Zapnutie GC môžeme dokonca z kódu ovplyvniť, aj keď to nie je v 99% prípadov vôbec potrebné. Pretože je jazyk riadený a nepracujeme s priamymi Pointer, nie je vôbec možné pamäť nejako narušiť, nechať ju pretiecť a podobne, interpret sa o pamäť automaticky stará.

Hodnota null

Posledná vec, o ktorej sa zmienime, je tzv. Hodnota null . Referenčné typy môžu, na rozdiel od hodnotových, nadobúdať špeciálne hodnoty a to null . Kľúčové slovo null označuje, že referencie neukazuje na žiadne dáta. Keď nastavíme premennú v na null , zrušíme iba tú jednu referenciu. Pokiaľ na náš objekt existuje ešte nejaká referencie, bude aj naďalej existovať. Ak nie, bude uvoľnený GC. Zmeňme ešte posledná riadky nášho programu na:

            // zmena
            v.jmeno = "John Doe";
            v = null;
            Console.WriteLine("u: {0}\nv: {1}\n", u, v);

výstup:

Konzolová aplikácia
a: 56
b: 28
u: Jan Novák
v: Josef Nový

a: 28
b: 28
u: Josef Nový
v: Josef Nový

u: John Doe
v:

Vidíme, že objekt stále existuje a ukazuje na neho premenná u , v premennej v už nie je referencie. Hodnota null sa bohato využíva ako vo vnútri .NET, tak v databázach. K referenčným typom sa ešte raz vrátime. V budúcej lekcii, Riešené úlohy k 4. lekcii OOP v C # .NET , si zas niečo praktické naprogramujeme, nech si vedomosti zažijeme. Prezradím, že pôjde o objekt bojovníka do našej arény. To je zatiaľ všetko :)

V nasledujúcom cvičení, Riešené úlohy k 4. lekcii OOP v C # .NET, si precvičíme nadobudnuté skúsenosti z predchádzajúcich lekcií.


 

Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 850x (25.3 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C#

 

Predchádzajúci článok
Riešené úlohy k 3. lekcii OOP v C # .NET
Všetky články v sekcii
Objektovo orientované programovanie v C #
Preskočiť článok
(neodporúčame)
Riešené úlohy k 4. lekcii OOP v C # .NET
Článok pre vás napísal David Hartinger
Avatar
Užívateľské hodnotenie:
1 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