Hodnotové a referenční datové typy

Proměnné a datové typy

SSŠVT


Hodnotové a referenční datové typy

Zásobník a halda (stack and heap)

Typický program v moderním běhovém prostředí má k dispozici (minimáně) tyto dva typy paměti:

Na zásobník se ukládají návratové adresy při volání podprogramů (procedur a funkcí, v OOP tzv. metod). Na zásobníku je rovněž alokováno místo pro všechny lokální proměnné v metodě, která se právě vykonává. Po spuštění programu je první vykonávanou metodou metoda Main ve třídě Program.

Na haldě vznikají většinou proměnné, či spíše data proměnných, jejichž instance vytváříme pomocí klíčového slova new. To platí pro ty proměnné, jejichž datový typ je tzv. referenční (reference type).


Hodnotové typy

Hodnotové typy (value types) jsou typy proměnných, jejichž hodnoty jsou uloženy přímo v místě, kde je proměnná alokována, tedy typicky na zásobníku.

Příkladem hodnotových typů jsou numerické typy (jako int, long, short, double nebo decimal), dále také typ char, typ bool, všechny datové typy uvozené klíčovým slovem enum a také "třídy" definované jako struct. Například datový typ pro ukládání datumu a času (System.DateTime) je rovněž hodnotový (je to struct).

Výhodou hodnotových typů je, že jsou "rychlé", protože jedním přístupem k určité proměnné získám ihned její hodnotu.


Referenční typy

Referenční typy (reference types) jsou typy proměnných, jejichž hodnoty nejsou uloženy přímo tam, kde je proměnná alokována. Typicky to vypadá tak, že vlastní proměnná je alokována na zásobníku, zatímco její hodnota je na haldě.

Obsah referenční proměnné (to, co je přímo na zásobníku) je pouze adresa (odkaz, anglicky reference) na místo v paměti (na haldě), kde jsou teprve užitečná data takové proměnné.

Příkladem referenčního datového typu je typ string pro práci s textovými řetězci. Dále jakákoliv uživatelem vytvořená třída uvozená klíčovým slovem class rovněž definuje referenční typ. Každé pole (např. int[] numbers = new int[5];) je referenční typ.

Referenční proměnné jsou "pomalejší". K získání hodnoty potřebuji 2 kroky:

  1. Napřed se podívám na zásobník a vyzvednu z proměnné její obsah, tj. ten odkaz (adresu do kusu paměti někde na haldě).
  2. Teprve ve druhém kroku se podívám (dle získané adresy) na haldu a přečtu si skutečnou hodnotu, kterou proměnná "uchovává".

Výhodou referenčních typů je to, že jejich hodnoty mohou být velmi variabilní, co do velikosti paměti potřebné pro jejich uchování. Např. u řetězců (string) nemusím předem vědět, zda budu mít textový řetězec o délce pár znaků (např. "Ahoj") nebo řetězec, jehož délka je několik tisíc nebo i set tisíc znaků (např. obsah celé knihy). U hodnot referenčních typů jsem limitován pouze velikostí paměti typu "heap" (velikostí té haldy).


Srovnání hodnotových a referenčních typů

Charakteristickým rysem referenčních datových typů je to, že při předávání parametrů z volající metody do metody volané se nekopírují jejich hodnoty (jako je tomu u hodnotových typů), ale pouze adresy, které odkazují na jejich data na haldě.

Totéž platí při přiřazení jedné referenční proměnné do druhé:

string a = "Ahoj";
string b = a;
        

Není to tak, že bychom nyní na haldě měli dvě místa, kde jsou uloženy řetězce "Ahoj" (jedno místo pro proměnnou a a druhé místo pro b). Předchozí dva řádky zařídí to, že na haldě je pouze jediný řetězec "Ahoj", zatímco v obou proměnných (v proměnné a i v proměnné b) je stejná adresa (stejný odkaz) na haldu, kde se nachází daný řetězec.

Dále je dobré vědět, že zatímco deklarováním proměnné hodnotového typu hned zařídím, že je v ní hodnota (nějaká defaultní, která se sice nedá použít, protože nám to nedovolí překladač, ale "něco" tam je), při deklaraci proměnné referenčního typu v proměnné nic není (neodkazuje na žádný "defaultní" objekt na haldě), má tzv. hodnotu null.

// Hodnotový typ.
int i;         // Deklarace proměnné (na zásobníku je např. 0).
i = 10;        // Inicializace proměnné na hodnotu 10 (na zásobníku je 10).
        
// Referenční typ.
string s;      // Deklarace proměnné (na zásobníku je null, na haldě není nic).
s = null;      // Inicializace proměnné na hodnotu null (na zásobníku je null, na haldě není nic).
s = "Ahoj";    // Inicializace proměnné na hodnotu "Ahoj" (na zásobníku je adresa odkazující někam na haldu, někde na haldě je řetězec "Ahoj").
// Předchozí řádek je třeba si představit jako:
// s = new string("Ahoj");    // Inicializace proměnné na hodnotu "Ahoj" (na zásobníku je adresa odkazující na haldu, na haldě je řetězec "Ahoj").