15. Struktury I
  1. Struktura je typ, který reprezentuje uživatelsky definovanou množinu pojmenovaných složek (prvků). Tyto složky mohou být libovolného typu a mohou mít libovolné pořadí. Struktury se deklarují pomocí klíčového slova struct, např.

  2. struct TStruktura {                                // TStruktura je označení struktury
        deklarace složek struktury
    };
    ...
    struct TStruktura s, *ps, poleS[10]     // s je struktura TStruktura, ps je ukazatel na strukturu TStruktura
                                                                  // poleS je pole prvků typu TStruktura
    Pokud vynecháme jméno (označení) struktury, dostaneme tzv. nepojmenovanou strukturu. Nepojmenované struktury se mohou používat při deklaraci proměnných daného strukturového typu, ale nikde jinde již další objekty tohoto typu definovat nemůžeme:
    struct { ... } s, *ps, poleS[10];
    Při deklarování struktur je možné pomocí typedef (stejně jako pro jiné typy - poslední identifikátor na řádku je nový název pro typ popsaný mezi typedef a tímto identifikátorem) vytvořit nový specifikátor datového typu:
    typedef struct TStruktura { ... } STRUKT;
    STRUKT s, *ps, poleS[10];                  // totéž jako původní deklarace
    Většinou není zapotřebí pojmenování struktury a typedef současně. Můžeme je ale použít. Je také možné vytvořit specifikátor nepojmenované struktury.
    Seznam deklarací složek uzavřený ve složených závorkách deklaruje typy a jména složek struktury.
    Funkce může vracet strukturu nebo ukazatel na strukturu. Struktura může být předána jako parametr funkce přímo, jako ukazatel nebo odkaz na strukturu.
    Přístup ke složkám struktur se provádí prostřednictvím operátorů .a ->. Operátor .se nazývá přímý selektor složky, operátor -> se nazývá nepřímý selektor složky (výraz ukazs->m je synonymum pro (*ukazs).m - používá se, když máme ukazatel na strukturu).
    struct TStructura {
        int i;
        char str[2];
        double d;
    } s, *ukazs = &s;
    ...
    s.i = 3;                     // přiřazení složce i struktury s
    ukazs->d = 1.23;    // přiřazení složce d struktury s (struktura je identifikovaná ukazatelem ukazs)
    Pokud jednou ze složek struktury B je struktura A, pak složky struktury A mohou být zpřístupněny dvojí aplikací selekčních operátorů. Struktury je možno přiřazovat jen v tom případě, že jsou stejného typu.
    Ukazatel na strukturu typu A se může objevit v deklaraci jiné struktury B ještě předtím, než je A deklarováno. Např.
    struct A;                                    // předběžná deklarace
    struct B { struct A *pa; };
    struct A { struct B *pb; };
    První deklarace A se nazývá předběžná, protože A prozatím není celá definována. Takováto předběžná deklarace stačí k tomu, aby jsme mohli použít v definici B ukazatel na A, neboť B nepotřebuje znát velikost A.
  3. Předpokládejme, že si chceme udržovat adresář svých známých. Vytvoříme tedy strukturu se složkami, které se obvykle používají v adresáři. Např.

  4. struct adresar {
      char jmeno[20];
      char prijmeni[20];
      char ulice[50];
      char mesto[20];
      int psc;
      bool pritel;
    };
    Struktura má 4 znakové, jednu celočíselnou a jednu logickou složku. Po deklaraci struktury ji můžeme použít. Nejprve je nutno vytvořit instanci struktury (proměnnou pro uložení struktury). To provedeme takto:
    adresar zaznam;
    Tento příkaz alokuje paměť pro strukturu a přiřadí alokovanou paměť proměnné zaznam. Nyní, když máme instanci struktury, můžeme přiřadit hodnoty jejím složkám:
    strcpy(zaznam.jmeno, "Karel");
    strcpy(zaznam.prijmeni, "Novak");
    strcpy(zaznam.ulice, "Palackeho 876");
    strcpy(zaznam.mesto, "Pardubice"
    zaznam.psc = 53002;
    zaznam.pritel = false;
    Toto lze provést také přímo při deklaraci struktury:
    adresar zaznam = { "Karel", "Novak", "Palackeho 876",
                       "Pardubice", 53002, false};
    Stejně jako můžeme mít pole celých čísel nebo pole znaků, můžeme mít i pole struktur. Např.
    adresar seznam[5];
    strcpy(seznam[0].jmeno, "Karel");
    seznam[4].pritel = false;
    // atd.
    Zde používáme operátor indexace a operátor selektoru složky.
  5. Zdrojový soubor je textový soubor obsahující zdrojový kód programu. Překladač přebírá soubory zdrojového kódu, zpracovává je a vytváří strojový kód, který může být spuštěn na počítači.

  6. Při našem seznamování s programováním, řešíme samé jednoduché úlohy. Při vytváření reálných aplikací se bude jednat o podstatně složitější úlohy. Normální program se obvykle skládá z několika zdrojových souborů. Programový kód je rozdělen do několika různých zdrojových souborů z několika důvodů. Základním důvodem je udržování kódu týkajícího se jednoho problému v jednom souboru, což umožňuje v případě potřeby snadné nalezení jisté části kódu.
    Při překladu, je nejprve přeložen každý zdrojový soubor (CPP) do objektového souboru (OBJ) a potom každý přeložený modul je sestaven sestavovacím programem do jednoho spustitelného souboru (EXE). Sestavovací program také připojí další potřebné soubory, jako jsou soubory zdrojů (RES) a knihovní soubory (LIB).
    Deklarace pro třídy a struktury jsou drženy v oddělených souborech nazvaných hlavičkové soubory. Tyto soubory mají příponu H nebo HPP. Hlavičkové soubory obsahují pouze deklarace tříd, struktur a funkcí. Není vhodné vkládat do nich kód příkazů. Z tohoto pravidla je jedna výjimka. Do hlavičkových souborů je možno vkládat vložené (inline) funkce. S tím se ale seznámíme později.
    Jestliže potřebujeme vytvořit hlavičkový soubor, pak zvolíme File | New a na stránce New dvojitě klikneme na ikonu Text. C++ Builder vytvoří nový textový soubor a zobrazí jej v Editoru kódu. Zadáme kód našeho hlavičkového souboru a uložíme vytvořený soubor s příponou H.
    Po vytvoření hlavičkového souboru pro třídu nebo strukturu, můžeme direktivou include vložit tento hlavičkový soubor do libovolného zdrojového souboru, ve kterém deklaraci třídy nebo struktury potřebujeme.
    Hlavičkové soubory obvykle vytváříme tak, aby bylo zajištěno, že do programu budou vloženy pouze jednou. Používáme k tomu direktivy podmíněného překladu (viz zadání 1 v kapitole 10).
    Hlavičkové soubory mohou obsahovat více než jednu deklaraci třídy nebo struktury. Použití samostatného hlavičkového souboru pro každou třídu nebo strukturu ale pomáhá udržovat organizaci projemtu a usnadňuje opětovné použití tříd a struktur v dalších programech. Někdy je možno vložit skupinu svázaných tříd do jednoho hlavičkového souboru.

  7. Nyní se pokusíme vytvořit konzolovou aplikaci, ve které uživatel zadá tři jména a adresy a uloží tyto záznamy do pole struktur. Po zadání těchto informací, jsou informace zobrazeny na obrazovce. Dále je možno zadat číslo záznamu, který chceme opětovně zobrazit. V této aplikaci definici struktury uvedeme v samostatném hlavičkovém souboru. Nejprve následuje výpis vlastního programu.

  8. #include <iostream.h>
    #include <conio.h>
    #include <stdlib.h>
    #pragma hdrstop
    #include "structur.h"
    void zobrazZaznam(int, adresar adrZaz);
    int main(int argc, char **argv)
    {
      adresar seznam[3];
      cout << endl;
      int index = 0;
      do {
        cout << "Jméno: ";
        cin.getline(seznam[index].jmeno, sizeof(seznam[index].jmeno)-1);
        cout << "Příjmení: ";
        cin.getline(seznam[index].prijmeni,
                    sizeof(seznam[index].prijmeni)-1);
        cout << "Ulice: ";
        cin.getline(seznam[index].ulice, sizeof(seznam[index].ulice)-1);
        cout << "Město: ";
        cin.getline(seznam[index].mesto, sizeof(seznam[index].mesto)-1);
        cout << "Psč: ";
        char buff[10];
        cin.getline(buff, sizeof(buff)-1);
        seznam[index].psc = atoi(buff);
        index++;
        cout << endl;
      } while (index < 3);
      clrscr();
      for(int i = 0; i < 3; i++) {
        zobrazZaznam(i, seznam[i]);
      }
      cout << "Zadej číslo záznamu: ";
      int zaz;
      do {
        zaz = getch();
        zaz -= 49;
      } while (zaz < 0 || zaz > 2);
      adresar pom = seznam[zaz];
      clrscr();
      zobrazZaznam(zaz, pom);
      getch();
      return 0;
    }
    void zobrazZaznam(int cis, adresar adrZaz)
    {
      cout << "Záznam " << (cis + 1) << ":" << endl;
      cout << "Jméno: " << adrZaz.jmeno << " " << adrZaz.prijmeni << endl;
      cout << "Adresa: " << adrZaz.ulice << endl;
      cout << "        " << adrZaz.mesto << endl;
      cout << "        " << adrZaz.psc << endl << endl;
    }
    a hlavičkový soubor STRUCTUR.H je tento (uložíme jej do stejného adresáře jako zdrojový soubor):
    #ifndef _STRUCTUR_H
    #define _STRUCTUR_H
    struct adresar {
      char jmeno[20];
      char prijmeni[20];
      char ulice[50];
      char mesto[20];
      int psc;
    };
    #endif
    V programu vidíme několik nám zatím neznámých věcí. Program používá funkci getline třídy cin k získání vstupu od uživatele. Je to z důvodu nepříjemného chování operátoru >> při čtení textů, které mohou obsahovat mezery. Druhý parametr getline je použit k omezení délky vstupujícího řetězce (zajistíme tím nepřepsání informací za koncem složky). Operátor sizeof je použit k určení velikosti složky a tedy k určení, kolik znaků je do ní možno bezpečně uložit.
    Funkce atoi je také nová (je použita při čtení poštovního směrovacího čísla). Tato funkce přebírá znakový řetězec a převádí jej na celočíselnou hodnotu. Zadaný text pro složku psc je nutno převést na celé číslo.
    Funkce zobrazZaznam má dva parametry. První parametr udává číslo zobrazovaného záznamu. Záznamy jsou číslovány od nuly a jelikož ve výpisu dáváme přednost jejich číslování od 1, přičítáme k tomuto číslu před výpisem jedničku. Druhý parametr funkce zobrazZáznam je instance struktury adresar. Je použita lokální instance této struktury a uvnitř funkce je její obsah zobrazen.
    V tomto případě předáváme strukturu hodnotou. Tzn. během volání funkce je vytvořena kopie struktury. Toto není moc efektivní, protože vytvoření kopie nějakou dobu trvá a spotřebovává místo v paměti. Je výhodnější předávat strukturu odkazem, ale to zatím neumíme.
    Funkci zobrazZaznam voláme na dvou místech v našem programu. Na těchto místech by mohl být uveden obsah těla funkce a program by pracoval stejně. Vložením tohoto kódu do funkce se snažíme zabránit opakovanému výskytu stejného kódu. Pokud se v našem programu opakovaně vyskytuje stejný kód, je vhodné z něj udělat funkci, kterou pak voláme, když tento kód je zapotřebí provést.
    Podívejme se ještě na následující kód:
    do {
      zaz = getch();
      zaz -= 49;
    } while (zaz < 0 || zaz > 2);
    Tento kód čte znak z klávesnice pomocí funkce getch (zatím jsme ji používali k čekání na konci programu). Tato funkce vrací kód stisknuté klávesy. Protože ASCII hodnota klávesy 1 je 49, klávesy 2 je 50, atd., získáme odečtením 49 od kódu přečteného znaku číselnou hodnotu klávesy zmenšenou o 1 (např. při stisknuté klávese 1 dostaneme hodnotu 0). Tento cyklus tedy probíhá tak dlouho, dokud nestiskneme některou z kláves 1, 2 nebo 3.
    Podívejme se ještě na řádek
    adresar pom = seznam[zaz];
    Tento kód vytváří instanci struktury adresar a přiřadí ji obsah jedné struktury z pole struktur. Je zde možno použít operátor přiřazení, protože překladač ví, jak kopírovat jednu strukturu do jiné. Můžeme tedy snadno vytvořit kopii celé struktury.
  9. Pokuste se upravit předchozí program tak, aby bylo možno zadávat více záznamů (až 9).
  10. V předchozí konzolové aplikaci jsme přečetli údaje o několika osobách. Tyto údaje je možno vložit do binárního souboru. Musíme deklarovat:

  11. FILE *soubor;
    a před ukončení programu vložíme příkazy (předpokládáme, že aktuální počet záznamů je uložen v proměnné n):
    if ((soubor = fopen("KART.DTA", "wb")) == NULL) {
      cout << "Nelze vytvořit soubor" << endl;
      return 1;
    }
    for (i = 0; i < n; i++)
      fwrite(&seznam[i], sizeof(adresar), 1, soubor);
    fclose(soubor);
    Proveďte tyto změny a program vyzkoušejte. Funkce fwrite zapisuje počet vět určený třetím parametrem do souboru určeného čtvrtým parametrem. Délku věty určuje druhý parametr a počátek první věty určuje první parametr.
  12. Soubor vytvořený v předchozím zadání je možno opět načíst a používat v jiném programu. Následující program ukazuje, jak informace ze souboru uložit do pole v paměti a vypsat je na obrazovce.

  13. #include "struktur.h"
    FILE *soubor;
    int n, i;
    adresar veta[10];
    if ((soubor = fopen("KART.DTA", "rb")) == NULL) {
      cout << "Nelze otevřít soubor" << endl;
      return 1;
    }
    i = 0;
    while (fread(&veta[i], sizeof(adresar), 1, soubor) > 0)
      i++;
    n = i;
    fclose(soubor);
    for (i = 0; i < n; i++)
      zobrazZaznam(i, veta[i]);
    Vyzkoušejte. Vložením hlavičkového souboru STRUCTUR.H zajistíme, že program bude pracovat se stejnou strukturou záznamu.
  14. Přejdeme opět k aplikacím GUI. Nejprve se začneme zabývat formulářem. Mimo vlastností, se kterými jsme se již seznámili, má formulář další důležité vlastnosti. Vlastnost BorderStyle určuje styl okraje formuláře (např. hodnota bsNone určuje formulář bez okrajů, tzn. nelze měnit jeho velikost). Měňte tuto vlastnost a vyzkoušejte, jak se změna projeví na vzhledu a chování formuláře za běhu aplikace.
  15. Formulář má dále vlastnost BorderIcons (ikony rámu formuláře, tj. minimalizační a maximalizační tlačítka apod.). Před jménem této vlastnosti je v Inspektoru objektů uveden znak + (indikace, že vlastnost se skládá z podvlastností). Klikneme-li dvojitě na takto označeném jméně vlastnosti, jsou na následujících řádcích zobrazeny podvlastnosti a znak + se změní na - (opětovným dvojitým kliknutím se vrátíme k původnímu zobrazení). Při tomto ?rozbalení? vlastnosti, můžeme jednotlivé podvlastnosti nastavovat samostatně. Pokuste se z formuláře odstranit maximalizační tlačítko (BorderStyle musí být nastaveno na bsSizeable; změna se projeví až při spuštění aplikace).
  16. Vlastnosti Position a WindowState určují způsob zobrazení formuláře na obrazovce. Position určuje, zda formulář získá pozici a velikost danou při návrhu, nebo zda se použije velikost a pozice navržená Windows za běhu aplikace. WindowState určuje počáteční stav formuláře (minimalizovaný, maximalizovaný nebo normální). Pokuste se změnit některou z těchto vlastností a vyzkoušejte jak se změna projeví.
  17. Vlastnost ShowHint komponenty určuje, zda komponenta zobrazí nápovědu, když se na ní na okamžik zastavíme kurzorem myši. Zobrazený text je určen vlastností Hint. Umístěte na formulář nějakou komponentu a zajistěte pro ní zobrazování nápovědy.
  18. Komponentu Panel lze používat pro ukládání dalších komponent. Můžeme z ní vytvořit např. stavový řádek nebo na ní vložit komponenty, které tvoří nějaký logický celek. Vlastnost Align určuje umístění komponenty. Možné hodnoty této vlastnosti mají následující význam: alTop (panel je umístěn na horní okraj formuláře, zabírá celou šířku formuláře a sleduje i změnu šířky formuláře; obdobně platí i pro další hodnoty této vlastnosti), alRight (panel je umístěn na pravý okraj formuláře), alBottom (spodní okraj), alLeft (levý okraj), alClient (celá plocha formuláře) a alNone (nemění se; zůstává tak jak nastavil uživatel). Umístěte na formulář komponentu panelu a vyzkoušejte vliv hodnot vlastnosti Align na umístění komponenty.
  19. U komponenty Panel lze pomocí vlastností BevelInner, BevelOuter, BevelWidth, BorderStyle a BorderWidth měnit způsob orámování panelu. Nastavte hodnotu vlastnosti BevelWidth na 10 (pro lepší viditelnost) a vyzkoušejte vliv změn ostatních těchto vlastností na zobrazení okraje.
15. Struktury I