Pouze tento týden sleva až 80 % na e-learning týkající se C# .NET. Zároveň využij akci až 30 % zdarma při nákupu e-learningu - Více informací.
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í.
C# week + discount 30

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

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

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

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

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

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

garbage collector

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

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
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é 761x (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 Čápka
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
David je zakladatelem ITnetwork a programování se profesionálně věnuje 13 let. Má rád Nirvanu, sushi 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

 

 

Komentáre

Avatar
Petr Žákavec:2.5.2020 23:50

také by mě to zajímalo (string - referenční typ)

 
Odpovedať
2.5.2020 23:50
Avatar
Savi
Člen
Avatar
Savi:20.7.2020 9:55

Ahoj, tak jsem u další lekce a mám zase dotaz :-/.

int a = 56;
            int b = 28;
            Uzivatel u = new Uzivatel("Karel Havlíček", 50);
            Uzivatel v = new Uzivatel("Vlastimil Hebr", 30);

            Console.WriteLine("{0}  {1}  {2}  {3}", a, b, u, v);

            u = v;
            u.jmeno = "Václav Havel";


            Console.WriteLine("{0}  {1}  {2}  {3}", a, b, u, v);

            Console.ReadKey();

Z textu jsem pochopil, že u=v zařídí to, že u ukazuje na Vlastimila. Proč když změním jméno v u až "po u=v", tak se mi vypíše 2x Havel? Představil jsem si ten obrázek v článku, kdy mi obě reference ukazují na Vlastimila a já měním jméno nevyužitého objektu. Může za to ten GC? :-O Nebo to chápu úplně blbě ? Děkuji !!

 
Odpovedať
20.7.2020 9:55
Avatar
Radek Veverka
Tvůrce
Avatar
Odpovedá na Savi
Radek Veverka:20.7.2020 10:25

Představil jsem si ten obrázek v článku, kdy mi obě reference ukazují na Vlastimila a já měním jméno nevyužitého objektu.

Když ti obě reference ukazují na Vlastimila, tak jak můžeš měnit jméno nevyužitého objektu? Měníš jméno Vlastimila a je jedno jestli přes u nebo v.

 
Odpovedať
20.7.2020 10:25
Avatar
Savi
Člen
Avatar
Odpovedá na Radek Veverka
Savi:20.7.2020 10:50

Perfektní. Už to asi chápu. Koukal jsem na to trochu jinak ;) Děkuji !

 
Odpovedať
20.7.2020 10:50
Avatar
Bohumír Bednařík:22.7.2020 11:08

Řekl bych, že string je tak trošku jiný typ. Viz např. https://docs.microsoft.com/…ystem.string?…

Důležitá bude nejspíš ta pasáž:
The value of the String object is the content of the sequential collection of System.Char objects, and that value is immutable (that is, it is read-only).

Když do proměnné typu string zapíšeš nový řetzec, vlastně ji znovu vytváříš, bez ohledu na to, že jsi si ji před tím "zkopíroval" z jiné proměnné typu string.

Pokud se pletu, nechť mě někdo opraví (nejsem programátor).

 
Odpovedať
22.7.2020 11:08
Avatar
Poledno Martin:8.9.2021 12:40

Super článek! díky

 
Odpovedať
8.9.2021 12:40
Avatar
Petr Mašek
Člen
Avatar
Petr Mašek:10. júla 18:24

Ahoj, mám dotaz.. řídil jsem se návodem a dokonce si i stáhnul soubor v lekci, a přesto mi VS vyhazuje chybu a nelze jej spustit. Věděl by někdo co s tím? Kódy mám identické a stejně nejede. Děkuji za odezvu

 
Odpovedať
10. júla 18:24
Avatar
Martin McDermot:25. júla 10:21

Jen si odložím, že klasické proměnné (int, float. atd) můžou také mít hodnotu "null".

Tato hodnota nepůjde

public int hodnota = null;

Ale tato ano

public int ?hodnota = null;
 
Odpovedať
25. júla 10:21
Avatar
Odpovedá na Petr Mašek
Jaroslav Drobek:2. augusta 6:18

Máš tam namespace Ref_a_hodnot - pokud zařídíš stejný jmenný prostor i u třídy Uzivatel.cs, mělo by to fungovat. V poskytnutých zdrojácích (Program.cs, Uzivatel.cs) bylo namespace referencni_typy.

 
Odpovedať
2. augusta 6:18
Avatar
Petr Mašek
Člen
Avatar
Odpovedá na Jaroslav Drobek
Petr Mašek:2. augusta 8:24

Já jsem trubka.. díky už to funguje

 
Odpovedať
2. augusta 8:24
Robíme čo je v našich silách, aby bola tunajšia diskusia čo najkvalitnejšia. Preto do nej tiež môžu prispievať len registrovaní členovia. Pre zapojenie sa do diskusie sa zaloguj. Ak ešte nemáš účet, zaregistruj sa, je to zadarmo.

Zatiaľ nikto nevložil komentár - buď prvý!