9. díl - Výčtové typy

C++ Pokročilé konstrukce v C++ Výčtové typy

V minulém článku jsme si řekli o binárních operacích. Zmínil jsem tzv. bitové flagy (nebo také příznaky), na které aplikujeme binární operace. Bylo by nepraktické pamatovat si jednotlivé čísla (ještě k tomu v binární podobě). Jednodušší by bylo si jednotlivé hodnoty pojmenovat a pracovat pouze se symbolickými názvy. To je úkol výčtových typů.

Výčtový typ

Jako ukázku výčtového typu mohou být například měsíce nebo dny v týdnu. Jedno z nejzákladnějších řešení by bylo nadefinovat si celé číslo a říct si, že hodnota 0 bude reprezentovat leden, hodnota 1 únor atd. Toto řešení bude funkční a lze jej v programu použít, ale musíme si (my líní programátoři) pamatovat, který měsíc má jaké číslo. Navíc tento přístup brání IDE k inteligentnímu napovídání a kontrole, zda jsme skutečně napsali to, co jsme napsat chtěli.

Z toho vznikl požadavek pamatovat si namísto hodnot názvy. Určitě vás hned napadne několik způsobů, například nadefinovat si několik konstant. Stejně dobře by mohla posloužit globální struktura s předdefinovanými hodnotami. C++ tento požadavek řeší ještě mnohem lépe - výčtovými typy. Ty jsou stále interně reprezentovány jako celé číslo, ale namísto hodnoty používáme v programu jméno. Výčtový typ se deklaruje pomocí klíčového slova enum.

enum Mesice
{
    MESIC_LEDEN,
    MESIC_UNOR,
    MESIC_BREZEN,
    MESIC_DUBEN
    // ....
};

V programu poté můžeme pracovat s názvy namísto s hodnotami:

void zobraz_mesic(Mesice mesic)
{
    switch(mesic)
    {
        case MESIC_LEDEN:
            cout << "Dnes je leden" << endl;
            break;
        case MESIC_UNOR:
            cout << "Dnes je unor" << endl;
            break;
        case MESIC_BREZEN:
            cout << "Dnes je brezen" << endl;
            break;
        case MESIC_DUBEN:
            cout << "Dnes je duben" << endl;
            break;
    }
}

int main()
{
    Mesice mesic = MESIC_UNOR;
    zobraz_mesic(mesic);
    cin.get();
    return 0;
}
Využití výčtových typů v C++

Výčtové typy jsou pořád interně reprezentovány jako celé číslo (unsigned int). Lze je tedy na celé číslo přetypovat. Standardně má první člen hodnotu 0 a každý následující člen má hodnotu o jednu větší. To ukazuje následující program:

int main()
{
        cout << "Hodnota ledna: " << (int)MESIC_LEDEN << endl;
        cout << "Hodnota unoru: " << (int)MESIC_UNOR << endl;
        cout << "Hodnota breznu: " << (int)MESIC_BREZEN << endl;
        cout << "Hodnota dubna: " << (int)MESIC_DUBEN << endl;
        cin.get();
        return 0;
}

Výstup programu:

Hodnota výčtového typu

Hodnoty pro jednotlivé názvy můžeme samozřejmě upravit. Pokud hodnotu názvu nepřiřadíme, má standardně hodnotu o jednu vyšší, než má předchozí název. Za zmínku také stojí, že dva názvy mohou mít stejnou hodnotu. Předchozí program přepíšeme:

enum Mesice
{
    MESIC_LEDEN = 2,
    MESIC_UNOR,
    MESIC_BREZEN = 5,
    MESIC_DUBEN = 6
    // ....
};

int main()
{
        cout << "Hodnota ledna: " << (int)MESIC_LEDEN << endl;
        cout << "Hodnota unoru: " << (int)MESIC_UNOR << endl;
        cout << "Hodnota breznu: " << (int)MESIC_BREZEN << endl;
        cout << "Hodnota dubna: " << (int)MESIC_DUBEN << endl;
        cin.get();
        return 0;
}

Výsledek:

Upravení hodnot výčtových typů

Bitové příznaky

Někdy nám stačí informace pouze ANO/NE. Protože má C++ nejmenší datový typ velikost 1 bajt - je to zbytečné plýtvání místem. Pro ANO/NE nám stačí pouze 1 bit! Zároveň máme zpravidla nějakou sadu informací, které spolu souvisí a které potřebujeme uchovat. V takových situacích je praktické použít bitové příznaky (nebo také flagy). Princip je takový, že pro celé číslo navážeme každému jednotlivému bitu nějakou informaci. Pomocí binárních operací následně můžeme jednotlivé informace extrahovat. Nejlépe opět poslouží příklad. Nejdříve si vytvoříme bitové příznaky dnů. Následně se ve funkci podíváme na každý den a zhodnotíme, jestli se ten den pracuje.

enum Dny
{
        DEN_PONDELI = 0x1,
        DEN_UTERY = 0x2,
        DEN_STREDA = 0x4,
        DEN_CTVRTEK = 0x8,
        DEN_PATEK = 0x10,
        DEN_SOBOTA = 0x20,
        DEN_NEDELE = 0x40
};

void pracovni_dny( int dny )
{
        if( dny & DEN_PONDELI )
                cout << "Pracuje se v pondeli" << endl;
        if( dny & DEN_UTERY )
                cout << "Pracuje se v utery" << endl;
        if( dny & DEN_STREDA )
                cout << "Pracuje se ve stredu" << endl;
        if( dny & DEN_CTVRTEK )
                cout << "Pracuje se ve ctvrtek" << endl;
        if( dny & DEN_PATEK )
                cout << "Pracuje se v patek" << endl;
        if( dny & DEN_SOBOTA )
                cout << "Pracuje se v sobotu" << endl;
        if( dny & DEN_NEDELE )
                cout << "Pracuje se v nedeli" << endl;
}

int main()
{
        pracovni_dny( DEN_PONDELI | DEN_UTERY | DEN_STREDA | DEN_CTVRTEK | DEN_PATEK );
        cin.get();
        return 0;
}

Je praktické uvádět výčtové typy v šestnáctkové soustavě, která má ke dvojkové blíže než desítková. Za povšimnutí stojí parametr funkce, který je typu int. Díky tomu, že jsou výčtové typy interně reprezentovány jako celočíselné hodnoty, není problém je na typ int přetypovat. Na závěr ještě přidávám výstup programu:

Bitové příznaky v C++

Union

Uniony se deklarují velmi podobně jako struktury, ale mají úplně jinou funkci. Řekněme, že chceme do programu opakovaně přenést nějakou informaci, ale tato informace může být vždy jiného typu. Můžeme to demonstrovat na příklad událostí: událost o pohybu myši ponese informaci, kam se myš posunula, zatímco při události stisku klávesy nás zajímá, která klávesa byla stisknuta. Tyto dvě struktury musíme nějakým způsobem předat programu. Udržovat obě struktury v paměti je náročné (představme si, že by událostí byly stovky). Proto C++ definuje datový typ union, který se může skládat z několika "elementů". Na rozdíl od klasických struktur, uvnitř unionu může být vždy pouze jeden z jeho elementů a celková velikost unionu se rovná velikosti největší elementu, kterou uchovává. Pro představu přidávám ukázku:

struct Struktura
{
        int prvni_cislo;
        int druhe_cislo;
};

union VlastniTyp
{
        char znak;
        int cislo;
        Struktura struktura;
};

int main()
{
        cout << "Velikost typu: " << sizeof( VlastniTyp ) << endl;

        VlastniTyp promenna;
        promenna.cislo = 14;
        cout << "Ulozeno: " << promenna.cislo << endl;

        promenna.struktura.prvni_cislo = 56;
        promenna.struktura.druhe_cislo = 4;
        promenna.znak = 'a'; //nyni jiz struktura neni validni
        cout << "Ulozene cislo nyni (nedefinovane chovani): " << promenna.cislo << endl;

        cin.get();
        return 0;
}

Výsledek:

Uniony v C++

Velikost unionu je 8 bajtů, protože jeho největší složkou je struktura, která má 8 bajtů (dvě 4 bajtová čísla). K jednotlivým prvkům přistupujeme stejně jako by se jednalo o strukturu. Je ale důležité si uvědomit, že přepsání jedné hodnoty zruší původně zapsanou hodnotu (jak to ukazuje poslední výpis). Prakticky tedy potřebujeme uchovávat vedle samotných dat i informaci o tom, jakou informaci union aktuálně obsahuje. Proto se stalo zvykem využívat union uvnitř struktury, které nese informaci o typu (hlavně kvůli kompatibilitě s C). Náš příklad s událostmi by vypadal nějak takhle:

enum TypUdalosti
{
        MYS,
        KLAVESNICE
};

struct UdalostMysi
{
        int posun_x;
        int posun_y;
        int pozice_x;
        int pozice_y;
};

struct UdalostKlavesnice
{
        char napsany_znak;
};

union UnionUdalosti
{
        UdalostMysi mys;
        UdalostKlavesnice klavesnice;
};

struct Udalost
{
        TypUdalosti typ;
        UnionUdalosti udalost;
};

void zpracovani_udalosti( Udalost udalost )
{
        if( udalost.typ == MYS )
        {
                //prekresli ukazatel na pozici
                //udalost.udalost.mys.pozice_x / udalost.udalost.mys.pozice_y
        }
        else if( udalost.typ == KLAVESNICE )
        {
                //napis znak udalost.udalost.klavesnice.napsany_znak
        }
}

Tento způsob se používá například v knihovně SDL. Pro dnešní díl je to vše a příště se podíváme na na operace s jednotlivými typy proměnných.


 

Stáhnout

Staženo 1x (17.73 kB)
Aplikace je včetně zdrojových kódů v jazyce C++

 

  Aktivity (1)

Článek pro vás napsal patrik.valkovic
Avatar
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu.

Jak se ti líbí článek?
Ještě nikdo nehodnotil, buď první!


 


Miniatura
Předchozí článek
Binární operace
Miniatura
Všechny články v sekci
Pokročilé konstrukce v C++
Miniatura
Následující článek
Přetypování a operátory

 

 

Komentáře

Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zatím nikdo nevložil komentář - buď první!