na obsah klávesové zkratky na hlavní stránku na menu

Struktury a výčty

Pro uchování množiny různorodých dat slouží struktury. Pro zacházení s množinou identifikátorů slouží enumerace. Nízkoúrovňový kód zase využije unie. V tomto článku se zmíníme o všech těchto konstrukcích.

Výčtový typ enum

V programování se často stává, že máme proměnnou, která může nabývat jen přesně daného množství hodnot. Příkladem může být ovladač myši, kde proměnná tlacitko bude nabývat hodnot leve, prave, nebo prostredni. Jednou z možností je používat číslo typu int a v dokumentaci zmínit pouze povolené hodnoty. To ale není příliš praktické, protože když pak uvidíme kód tlacitko = 2, nemáme ponětí, které tlačítko odpovídá kódu dva, dokud nekonzultujeme dokumentaci. Další možností je si vytvořit sadu celočíselných konstant:

const int leve = 0;
const int prave = 1;
const int prostredni = 2;

const int shift = 3;
const int control = 4;
const int alt = 5;

// ...
tlacitko = leve;
modifikator = alt;

Tento přístup je už srozumitelnější, nicméně stále nám nezabraňuje napsat:

tlacitko = alt;

Proto existuje výčtový typ enum.

Deklarace enum

Deklarace má následující syntaxi:

enum <[jméno typu]> {<výčet hodnot>}<[deklarace]>;

Začínáme klíčovým slovem enum, za kterým následuje nepovinné jméno nového výčtového typu. Ve složených závorkách je výčet možných hodnot oddělených čárkami. Jako poslední položka před středníkem je nepovinný seznam deklarací oddělený čárkami. V našem ovladači bychom tedy měli následující dva výčtové typy:

enum tlacitka
{
  leve,
  prave,
  prostredni
} tlacitko;

enum modifikatory
{
  shift = 3,
  control,
  alt
} modifikator;

Vytvořili jsme dvě proměnné tlacitko a modifikator typu enum tlacitka a enum modifikatory. Ve druhé deklaraci je zajímavé použití přiřazení hodnoty tři. Jelikož jsou výčty číselné množiny, každý člen je interně reprezentován číslem z nejmenšího rozsahu, který pokryje všechny prvky výčtu. Pokud žádné číslo nezadáme, jako je tomu v případě výčtu tlacitka, začne překladač od nuly a každá další položka dostane o jedničku vyšší hodnotu. V případě výčtu modifikatory se začne od tří a bude se pokračovat čtyřmi a pěti (překladač inkrementuje poslední zadanou hodnotu). Číselné hodnoty se ve výčtu mohou opakovat.

Pokud budeme chtít deklarovat proměnnou výčtového typu později, provedeme to následovně:

enum tlacitka alternativni_tlacitko;

Musíme použít celé označení typu včetně slova enum. V jazyce C++ už toto klíčové slovo není nutné psát a stačí pouze jmenovka typu. Pokud se chceme opisování enum vyhnout, můžeme využít konstrukce s typedef.

Deklarace s použitím typedef

typedef <starý typ> <nový typ>;

Klíčové slovo typedef umožňuje pojmenovat nový typ, který se skládá z již existujících typů. Například je možné vytvořit typ ukazatelnaint, který bude ve skutečnosti ukazatelem na hodnotu typu int.

typedef int* ukazatelnaint;

//...

int a = 3;
ukazatelnaint ua = &a;

Pojmenovaný výčet můžeme vytvořit tímto zápisem:

typedef enum
{
  leve,
  prave,
  prostredni
} tlacitka;

tlacitka tlacitko;

Přetypování na int

Jelikož jsou prvky výčtu ve skutečnosti čísla, můžeme je implicitně přetypovat na číselnou hodnotu. V jazyce C je možné i opačné přetypování a proměnné tak lze přiřadit i hodnotu mimo rozsah výčtu. Jazyk C++ je už mnohem náročnější a takové přetypování nepovolí.

int a = leve; // v a bude 0
tlacitko = 4; // ok v C, chybně v C++

tlacitko = (enum tlacitka)2; // explicitní přetypování nutné v C++

Porovnávání výčtových proměnných

Výčtové proměnné se při porovnávání chovají jako číselné hodnoty. V jazyce C jde bez problému porovnávat hodnoty z jiných výčtů. Překladač jazyka C++ nás na tuto chybu upozorní.

Struktura

Struktury jsou kontejnery pro heterogenní data. Tak jako pole shromažďuje hodnoty stejného typu, struktury shromažďují data různých typů. Typickým příkladem použití struktury je uchování dat zaměstnance, jako je jméno, věk a plat.

Deklarace struktury

Deklarace struktury má následující syntaxi:

struct <[jméno typu]> {<výčet členů>}<[deklarace]>;

Od deklarace výčtu se liší pouze použitým klíčovým slovem a také výčtem členů. Výše uvedený záznam zaměstnance by se uchoval ve struktuře:

struct zamestnanec
{
  char jmeno[30];
  int vek;
  float plat;
} Pepa;

Pro deklaraci s klíčovým slovem typedef a bez něj platí stejná pravidla jako u výčtového typu. Uvnitř struktury může být jakýkoliv typ včetně ukazatelů na strukturu samotnou nebo jiné vnořené struktury.

Inicializace struktury

Struktura se inicializuje podobně jako pole, tedy výčtem hodnot oddělených čárkami ve složených závorkách. V případě vnořených struktur inicializujeme vnitřní strukturu dalším vnořeným párem složených závorek. Pokud vynecháme některé hodnoty, překladač zbytek doplní nulami stejně jako u polí.

struct zamestnanec Franta = {"Franta Novak", 28, 21000.0};
// chudák Tonda má plat 0
struct zamestnanec Tonda = {"Tonda Voprsalek", 30};
// bezejmenný, bezletý a neohodnocený zaměstnanec
struct zamestnanec Nikdo = {};

Přístup ke členům struktury

Pro přístup ke členům struktury slouží operátor tečka. Máme-li našeho zaměstnance Pepu, změníme mu věk následovně:

Pepa.vek = 31;

Pokud máme pouze ukazatel na strukturu, máme dvě možnosti, jak se dostat ke členským proměnným. Buď ukazatel nejprve dereferencujeme a poté použijeme operátor tečka, nebo použijeme přímo operátor šipka, který provede prvně zmíněné kroky interně.

struct zamestnanec *uk = &Pepa;
(*uk).vek = 31; // použití dereference
uk->vek = 31; // pohodlnější, ekvivalentní dereferenci a tečkování

Kopírování a porovnávání

Proměnné struktury stejného typu lze jednoduše kopírovat (na rozdíl od polí). Stačí pouze přiřadit jednu proměnnou do druhé a automaticky dojde ke zkopírování všech členů struktury.

// Franta a Pepa se stanou jednou osobou
Pepa = Franta;

Problém nastává, když máme ve struktuře dynamicky alokovaná data. Při kopírování se zkopírují pouze ukazatele, ale data zůstanou sdílená (jde o tzv. mělkou kopii). V takovém případě se o zkopírování dat musíme postarat sami.

Pokud chceme proměnné struktur porovnávat, musíme tak učinit člen po členu, nelze jednoduše napsat:

if (Pepa == Franta) //...

Skládané struktury neboli unie

Unie se používají ve velmi specifických případech v nízkoúrovňovém kódu a běžně se s nimi nesetkáme. Syntaxí se velmi podobají strukturám, jen místo klíčového slova struct se použije union. jediným rozdílem od struktur je to, že v daný okamžik existuje v paměti pouze jeden člen unie. Unie má tak velikost odpovídající prostorově největšímu typu v unii, mění se pouze interpretace dat.

union ZnakCislo
{
  int cislo;
  char znak;
} unie;

// unie bude mít velikost rovnou sizeof(int)

// ...

// nyní bude v unii číslo
unie.cislo = 65;
/*
Dotazujeme se na data v unii jako na znak.
Hodnota ve znaku závisí na rozložení dat
v paměti (little nebo big endian). Dostaneme
buď nejvýznamnější, nebo nejméně významný bajt
čísla. Vyhodnocení podmínky tak závisí na architektuře
stroje.
*/
if (unie.znak == 'A') //...