2. Hodnotové typy

2.7. Struktura

V C# (.NET Framework) jsou dvě implementace konceptu třídy postavené na základě objektově orientovaného programování - třída a struktura. Struktura je dalším hodnotovým typem oproti třídě (ta je referenčního typu - rozdílnost je zejména ve způsobu ukládání. Více o způsobu ukládání se dozvíme později). Výhodou typu struktura je přímé uložení v paměti (v zásobníku) a tedy menší zatěžování správce paměti. Strukturu je tedy vhodné použít v případech, kdy nepotřebujeme ukládat, nebo pracovat s velkým množství dat.  Struktura může mít vlastní atributy – vlastní datové složky (anglicky "Fields") a metody (anglicky "Methods"). Metody můžeme vnímat jako podprogramy, které jsou implementovány ve struktuře.

Struktury jsme již využívali dříve, aniž bychom si to uvědomili. Primitivní datové typy jako např. int, long, float apod., jsou totiž ve skutečnosti strukturami. Mají totiž vlastní metody např. metoda ToString, která konvertuje daný datový typ do textu (string). K těmto metodám se dostaneme pomocí tečkové notace – viz Kód 1. Metoda ToString je definována pro každý typ, včetně struktur a tříd. Pokud potřebujeme, můžeme definovat novou verzi této metody, která potlačí její výchozí chování.

//Deklarace celočíselné proměnné včetně inicializace
int intPromenna = 7;
//Výpis s použitím metody ToString pro datový typ int
Console.WriteLine(intPromenna.ToString());

Kód 1: Příklad deklarace proměnné datového typu int včetně použití metody WriteLine

Tyto metody jsme zatím nemuseli používat, protože Console.WriteLine tuto metodu volá automaticky, když je zapotřebí. 

Měli bychom také uvést zásadní metodu Parse, kterou jsme využívali pro konverzi načteného textu a jeho konverze do příslušného datového typu – viz Kód 2. Tato metoda přečte jednotlivá písmena v textu a převede tento text v našem případu do celočíselné podoby. Pozor, datový typ string není struktura ale třída (o třídách se dozvíme později).

//Deklarace textové proměnné včetně inicialiazce
string stringPromenna = "42";
//Konverze na příslušný datový typ
int intPromenna = int.Parse(stringPromenna);

Kód 2: Příklad deklarace proměnné datového typu int a použití metody Parse

Pokud si vzpomínáte, využili jsme v kapitole o přetečení celého čísla metodu MaxValue, která nám vracela horní mez celého čísla.


Pro deklaraci se využívá klíčové slovo struct, za kterým se uvádí název struktura a tělo ve složených závorkách. V těchto složených závorkách může být uveden konstruktor, konstanty, datové složky, metody, vlastnosti, atd. 

Pojďme si na příkladu vysvětlit implementaci struktury.


Ilustrativní příklad StrukturaCas
Program StrukturaCas

  • Popis: Příklad deklarace struktury, která obsahuje datové složky, konstruktor a metody 
  • Zadání: Vytvořte strukturu casovyOkamzik, která pomocí vlastní metody převede zadaný celkový počet sekund na hodiny, minuty a sekundy a vypočtené hodnoty vypíše v hlavní metodě Main (pomocí metod VratHodiny, VratMinuty, VratSekundy implementovaných ve struktuře). To znamená, v třídě Program deklarujte strukturu s názvem Cas. Tato struktura bude obsahovat následující celočíselné atributy: hodiny - počet hodin; minuty - počet minut; sekundy - počet sekund. Tyto atributy budou přístupné pouze v rámci struktury. V rámci struktury vytvořte metodu, se shodným názvem jako struktura, pro inicializaci datových složek.  Tato metoda přijímá celočíselný vstupní argument sek představující celkový počet sekund. V konstruktoru převeďte celkový počet sekund na hodiny, minuty, sekundy
  • Řešení: StrukturaCas_Main

Pojďme si vyzkoušet deklaraci struktury, kterou jsme uvedli ve třídě Program mimo metodu Main – viz Kód 3. Pro deklaraci struktury se využívá klíčové slovo struct, za kterým je uveden název struktury a za ním její tělo ve složených závorkách. V těchto složených závorkách jsme uvedli deklaraci celočíselných proměnných – jedná se o tzv. datové složky. Princip našeho příkladu spočívá v deklaraci struktury vyjadřující čas, kde jednotlivé datové složky vyjadřují počet hodin, minut a sekund. Před datovými složkami je uvedeno řízení přístupnosti k těmto složkám - tzv. modifikátor přístupu. Pokud je u datové složky uvedeno private, je k těmto složkám přístup pouze uvnitř definované struktury. Příkladem uveďme, že někdo by se mohl počet hodin nastavit na hodnotu 25, což by nebylo příliš vhodné pro další výpočty např. celočíselné dělení potřebné pro převod na dny (vycházíme z předpokladu, že den má 24 hodin :-) ). Protějškem k private je public.

//Deklarace datového typu struktura
struct Cas
{
    //Deklarace celočíselných datových složek
    private int hodiny, minuty, sekundy;
}

Kód 3: Deklarace datového typu struktura včetně deklarace celočíselných datových složek

U struktury nelze inicializovat hodnotu datové složky inicializovat přímo při její deklaraci – viz Kód 4!

//Při deklaraci nelze ve struktuře inicializovat hodnotu složky -> K.O.
private int hodiny = 0;
private int minuty, sekundy;

Kód 4: Příklad špatné deklarace datových složek s využitím inicializace

Asi Vás napadne, jak se k těmto složkám ale dostaneme z venku. Využijeme k tomu metodu implementovanou ve struktuře sloužící k inicializaci hodnot vlastních datových složek. Tato metoda se obecně nazývá konstruktor (anglicky „Constructor“). Kompilátor v C# umí vygenerovat vlastní (tzv. implicitní) konstruktor - viz Kód 5. 

//Deklarace proměnné typu struktura, včetně zavolání implicitního konstruktoru
Cas casovyOkamzik = new Cas();

Kód 5: Deklarace proměnné casovyOkamzik datového typu struktura včetně zavolání implicitního konstruktoru struktury Cas

Tento implicitní konstruktor nastaví všechny celočíselné datové složky struktury Cas na hodnotu 0 - viz Obrázek 1. Proměnná typu struktura je uložena v paměti typu zásobník (je to hodnotový typ).


Obrázek 1: Proměnná typu struktura Cas vygenerována implicitním konstruktorem

Struktura je  hodnotového typu, proto ji lze vytvořit (deklarovat) i bez volání konstruktoru. Pak ale její datové složky jsou neinicializovány - viz Obrázek 2. Pokud se tedy pokusíte vypsat hodnotu nějaké nějaké datové složky, kompilátor zahlásí chybu.


Obrázek 2: Deklarace proměnné typu struktura Cas (bez použití konstruktoru)

My však využijeme námi navržený (neimplicitní, tj. explicitní) konstruktor má stejný název jako je název struktury, tedy Cas. V tomto konstruktoru musí být nastaveny všechny datové složky struktury! Do konstruktoru bude vstupovat formální parametr sek, který vyjadřuje celkový počet sekund. Tento celkový počet sekund chceme převést na hodiny, minuty a sekundy. Tento parametr je předáván hodnotou, tzn., že do tohoto parametru se zkopíruje hodnota. S tímto parametrem budou dále prováděny v konstruktoru další aritmetické operace v našem případě celočíselné dělení a zbytek po celočíselném dělení. Až konstruktor skončí, tento parametr zanikne. Jedná se tedy pouze o vstupní parametr. 

Jak si můžete všimnout v dalším řádku kódu je proveden přepočet na hodiny pomocí celočíselného dělení. Tento výsledek je zapsán do datové složky náležící počtu hodin, tj. datová složka hodiny. Pro přístup do této složky využijeme klíčové slovo this s tečkou. Použitím tohoto slova přistoupíme k seznamu datových složek definovaných uvnitř struktury. Hodnotu této složce přiřadíme pomocí operátoru přiřazení. Následuje odebrání celých hodin z tohoto parametru pomocí zbytku po celočíselném dělení. Zbytek tedy reprezentuje zbývající minuty a sekundy. Princip výpočtu je znovu stejný – viz Kód 6.

//Deklarace vlastního konstruktoru
public Cas(int sek)
{
    //Převod sekund na celé hodiny -> nastavení datové složky
    this.hodiny = sek / (60 * 60);
    //Zbytek sekund po odebrání celých hodin
    sek = sek % (60 * 60);
    //Převod sekund na celé minuty -> nastavení dat. složky
    this.minuty = sek / 60;
    //Zbytek sekund po odebrání celých minut -> nastavení dat. složky
    this.sekundy = sek % 60;
}

Kód 6: Deklarace vlastního konstruktoru Cas datového typu struktura

Po vypočtení a přiřazení hodnoty do poslední datové složky již proměnná obsahuje hodnoty - proběhla jejich inicializace pomocí vlastního konstruktoru - viz Obrázek 3.


Obrázek 3: Proměnná typu struktura Cas vygenerována vlastním konstruktorem

Pro obdržení hodnoty uložené v datové složce např. hodiny, vytvoříme metodu VratHodiny (funkci), která nám navrací celočíselnou hodnotu počtu hodin (hodiny jsou datového typu int). Tato metoda je volně přístupná i mimo strukturu - přístupový modifikátor public. Tato metoda nemá žádný vstupní parametr, protože pouze potřebujeme získat výstup, tj. hodnotu příslušné datové složky. K té přistoupíme pomocí tečkové notace s využitím this. Pro přiřazení výstupu této metody využijeme příkaz return - viz Kód 7. 

//Deklarace metody ve struktuře - navrací hodnotu datové složky 'hodiny'
public int VratHodiny()
{
    //Navrácení hodnoty datové složky
    return this.hodiny;
}

Kód 7 Deklarace metody VratHodiny - navrácení hodnoty datové složky

Obdobně budeme postupovat u dalších datových složek minuty a sekundy - viz Kód 8. 

//Deklarace metody navracející počet minut uložených v datové složce
public int VratMinuty()
{
    return this.minuty;
}
 
//Deklarace metody navracející počet sekund uložených v datové složce
public int VratSekundy()
{
    return this.sekundy;
}

Kód 8: Deklarace metod VratMinuty a VratSekundy - navrácení hodnoty datové složky

Kódy nezbytné k deklaraci struktury, inicializaci datových složek pomocí konstruktoru a vrácení hodnot v datových složkách už máme vytvořeny. Nyní ale potřebujeme vytvořit proměnnou, která bude datového typu struktura. Tato deklarace v metodě Main je v podstatě stejná jako u předchozích datových typů, ale až na jeden rozdíl. Využívá zavolání konstruktoru pomocí klíčového slova new. Tento konstruktor ze zadané hodnoty (v našem případě 37000 sekund) inicializuje všechny datové složky struktury – viz Kód 9.

//Deklarace proměnné typu struktura, včetně zavolání vlastního konstruktoru
Cas casovyOkamzik = new Cas(37000);

Kód 9: Příklad deklarace proměnné casovyOkamzik datového typu struktura včetně zavolání vlastního konstruktoru

Je asi jasné, že po obětování takového úsilí, bychom chtěli vidět výsledky naší práce. Využijeme proto příkaz výpisu do konzole a necháme vypsat vypočtené datové složky. K tomu využijeme volně přístupné metody (funkce), které nám navrací jednotlivé hodnoty datových složek, tj. VratHodiny, VratMinuty, VratSekundy - viz Kód 10.

//Výpis jednotlivých hodnot v datových složkách
Console.WriteLine("Je právě: {0} hodin, {1} minut, {2} sekund", casovyOkamzik.VratHodiny(), casovyOkamzik.VratMinuty(), casovyOkamzik.VratSekundy());

Kód 10: Výpis hodnot jednotlivých datových složek proměnné

Ve výpisu vidíme přepočtené sekundy na hodiny, minuty a sekundy – viz Výstup programu - StrukturaCas_Main.

Je právě: 10 hodin, 16 minut, 40 sekund

Výstup programu - StrukturaCas_Main: Ukázka výpisu obsahu datových složek uložených v proměnné datového typu struktura

U deklarace proměnné typu struktura lze také použít tzv. modifikátor ?, který uvedeme za typ struktury a přiřadíme prázdnou hodnotu - viz Kód 11.

//Deklarace proměnné typu struktura s inicializací prázdné hodnoty
Cas? casovyOkamzik = null;

Kód 11: Příklad deklarace proměnné datového typu struktura s prázdnou hodnotou (null)