22. Seznámení s OOP II
  1. Následují program zavádí použití konstruktorů a destruktorů.

  2. class obdelnik {                  // jednoduchá třída
      int vyska;
      int sirka;
    public:
      obdelnik(void);                  // s konstruktorem,
      int plocha(void);                // s dvěmi metodami
      void inicializace(int, int);
      ~obdelnik(void);                // a s destruktorem
    };
    obdelnik::obdelnik(void){     // konstruktor
      vyska = 6;
      sirka = 6;
    }
    int obdelnik::plocha(void){   // plocha obdélníku
      return vyska * sirka;
    }
    void obdelnik::inicializace(int nova_vyska, int nova_sirka){
      vyska = nova_vyska;
      sirka = nova_sirka;
    }
    obdelnik::~obdelnik(void){ // destruktor
      vyska = 0;
      sirka = 0;
    }
    int main(int argc, char **argv)
    {
      obdelnik okno, ctverec;
      cout << "Plocha okna je " << okno.plocha() << endl;
      cout << "Plocha ctverce je " << ctverec.plocha() << endl;
      okno.inicializace(12,10);
      ctverec.inicializace(8,8);
      cout << "Plocha okna je " << okno.plocha() << endl;
      cout << "Plocha ctverce je " << ctverec.plocha() << endl;
      return 0;
    }
    Tento program je identický s programem uvedeným na konci předchozí kapitoly (do třídy je přidán konstruktor a destruktor a je vynechána struktura). Konstruktor je v C++ volán automaticky při deklaraci objektu a je tedy velkou pomocí k zabránění použití neinicializovaných proměnných. Když deklarujeme objekt okno, konstruktor je volán automaticky systémem a nastaví hodnoty jeho položek (v našem případě na hodnoty 6). Obdobně to platí i pro objekt ctverec. Konstruktor má stejné jméno jako třída (oboje se v našem případě jmenuje obdelnik). U konstruktoru nedefinujeme návratový typ. I když položky objektu jsou již inicializovány konstruktorem, můžeme jim přiřadit jiné hodnoty. Destruktor je velmi podobný konstruktoru, s tím rozdílem, že je volán automaticky, když se objekt dostane mimo rozsah. Destruktor má stejné jméno jako třída, předchází mu ale znak ~. Destruktor nemá žádný návratový typ. V našem případě destruktor provádí pouze vynulování obou položek. Destruktor je zde pouze použit pro ilustraci jeho použití. Jestliže objekt alokoval nějakou paměť, pak destruktor by ji měl uvolnit.
  3. Do konstruktoru a destruktoru přidejte příkaz vypisující informaci o volání, aby jste se přesvědčili, že jsou opravdu volány systémem automaticky.
  4. Podívejte se na následující program. Tento program se velmi podobá předchozímu. Třída se nyní jmenuje okno.  Program by vám měl být srozumitelný až na jednu věc. Metoda označená červeně obsahuje implementaci metody jako část deklarace. Je to obdoba vnořené funkce. Program vyzkoušejte.

  5. class okno {                                                          // jednoduchá třída
      int vyska;
      int sirka;
    public:
      okno(void);                                                          // s konstruktorem,
      int ziskej_plochu(void){ return vyska * sirka; }; // s dvěmi metodami
      void nastav(int, int);
      ~okno(void);                                                        // a s destruktorem
    };
    okno::okno(void){                                                  // konstruktor
      vyska = 6;
      sirka = 6;
    }
    void okno::nastav(int nova_vyska, int nova_sirka){
      vyska = nova_vyska;
      sirka = nova_sirka;
    }
    okno::~okno(void){                                                 // destruktor
      vyska = 0;
      sirka = 0;
    }
    int main(int argc, char **argv)
    {
      okno male, stredni, velke;
      male.nastav(5, 7);                                     // stredni okno použije hodnoty nastavené konstruktorem
      velke.nastav(15, 20);
      cout << "Plocha malého okna je " << male.ziskej_plochu() << endl;
      cout << "Plocha středního okna je " << stredni.ziskej_plochu() << endl;
      cout << "Plocha velkého okna je " << velke.ziskej_plochu() << endl;
      return 0;
    }
    Namísto termínu ?volání funkce používáme ?zasílání zpráv. V těchto dvou operacích je rozdíl. Jelikož data objektu jsou pevně v objektu obsažena, neexistuje mimo metod způsob jak tato data získat. Zašleme tedy objektu zprávu, aby provedl nějakou operaci na svých vnitřních datech. Při volání funkcí používáme pro přístup k datůe? používáme
  6. Následující program ukazuje použití pole objektů.

  7. class okno {
      int delka;
      int sirka;
    public:
      okno(void);                                                               // konstruktor
      void nastav(int nova_delka, int nova_sirka);
      int ziskej_plochu(void) {return(delka * sirka);}
    };
    okno::okno(void){                                                        // konstruktor
      delka = 8;
      sirka = 8;
    }
    void okno::nastav(int nova_delka, int nova_sirka){
      delka = nova_delka;
      sirka = nova_sirka;
    }
    int main(int argc, char **argv)
    {
      okno male, stredni, velke, skupina[4];                      // sedm oken
      male.nastav(5, 7);
      for (int index = 1; index < 4; index++)                      // skupina[0] je implicitní
        skupina[index].nastav(index + 10, 10);
      cout << "Plocha malého okna je " << male.ziskej_plochu() << endl;
      cout << "Plocha středního okna je " << stredni.ziskej_plochu() << endl;
      for (int index = 0; index < 4; index++)
        cout << "Plocha pole okna je " <<
          skupina[index].ziskej_plochu() << endl;
      return 0;
    }
    V tomto programu je přidána deklarace pole 4 objektů třídy okno. Každý z těchto objektů je inicializován na hodnoty uvedené v konstruktoru, neboť konstruktor je proveden pro všechny deklarované objekty. Pole objektů je v programu dále používáno. Povšimněte si jak se zasílají zprávy jednomu objektu v poli objektů. Pokuste se pochopit tento program a vyzkoušejte jej.
  8. Další program používá jako složku třídy řetězec znaků. Ve skutečnosti v objektu není vložený řetězec znaků, ale je zde ukazatel na řetězec znaků.

  9. class okno {
      int delka;
      int sirka;
      char *radek_textu;
    public:
      okno(char *radek);
      void nastav(int nova_delka, int nova_sirka);
      int ziskej_plochu(void);
    };
    okno::okno(char *radek){
      delka = 8;
      sirka = 8;
      radek_textu = radek;
    }
    void okno::nastav(int nova_delka, int nova_sirka){
      delka = nova_delka;
      sirka = nova_sirka;
    }
    okno::ziskej_plochu(void){
      cout << radek_textu << " = ";
      return (delka * sirka);
    }
    int main(int argc, char **argv)
    {
      okno male("malé okno "), stredni("střední okno "), velke("velké okno ");
      male.nastav(5, 7);                 // stredni okno použije hodnoty nastavené konstruktorem
      velke.nastav(15, 20);
      cout << "Plocha ";
      cout << male.ziskej_plochu() << endl;
      cout << "Plocha ";
      cout << stredni.ziskej_plochu() << endl;
      cout << "Plocha ";
      cout << velke.ziskej_plochu() << endl;
      return 0;
    }
    V deklaraci třídy je nyní použit ukazatel na char pojmenovaný radek_textu. Konstruktor obsahuje vstupní parametr, kterým je ukazatel na řetězec a tento ukazatel bude v konstruktoru přiřazen složce radek_textu. Mohli bychom definovat proměnnou radek_textu jako pole znaků ve třídě a potom použít funkci strcpy k překopírování řetězce do objektu. Tím se budeme zabývat v následujícím zadání.
    Náš konstruktor má nyní jeden parametr, který slouží k předání informací do objektu. Mohli bychom jich použít i více. Když nyní deklarujeme objekty, předáváme každé instanci jako aktuální parametr řetězcovou konstantu, která je potom použita konstruktorem k přiřazení ukazatele na ní, složce radek_textu. V metodě ziskej_plochu tento text vypisujeme. Výpis textu sice s výpočtem plochy nesouvisí, ale zde je to použito pro ilustraci toho, že objekt obsahuje skutečně ukazatel na text použitý při deklaraci. Vhodnější by bylo vytvořit další metodu pro zjištění tohoto řetězce (a případně další pro jeho nastavení).
  10. Změňte předchozí program tak, aby třída používala pole znaků namísto ukazatele na znak a do pole překopírujte řetězec znaků parametru konstruktoru. Přidejte další metodu umožňující změnit uloženou hodnotu řetězce zasláním zprávy z hlavního programu a použijte novou metodu ke změně hodnot uložených ve všech třech objektech. Výpisem se přesvědčte o provedené změně.
  11. Ve třídě může být také obsažen ukazatel na nějaká dynamicky alokovaná data. To ukazuje následující program.

  12. class okno {
      int delka;
      int sirka;
      int *ukazatel;
    public:
      okno(void);
      void nastav(int nova_delka, int nova_sirka, int ulozena_hodnota);
      int ziskej_plochu(void) {return(delka * sirka);}
      int ziskej_hodnotu(void) {return *ukazatel;}
      ~okno(void);
    };
    okno::okno(void){
      delka = 8;
      sirka = 8;
      ukazatel = new int;
      *ukazatel = 122;
    }
    void okno::nastav(int nova_delka, int nova_sirka, int ulozena_hodnota){
      delka = nova_delka;
      sirka = nova_sirka;
      *ukazatel = ulozena_hodnota;
    }
    okno::~okno(void){
      delka = 0;
      sirka = 0;
      delete ukazatel;
    }
    int main(int argc, char **argv)
    {
      okno male, stredni, velke;
      male.nastav(5, 7, 177);
      velke.nastav(15, 20, 999);
      cout << "Plocha malého okna je " << male.ziskej_plochu() << endl;
      cout << "Plocha středního okna je " << stredni.ziskej_plochu() << endl;
      cout << "Plocha velkého okna je " << velke.ziskej_plochu() << endl;
      cout << "Uložená hodnota malého okna je "<<male.ziskej_hodnotu()<<endl;
      cout << "Uložená hodnota středního okna je "<<stredni.ziskej_hodnotu()<<endl;
      cout << "Uložená hodnota velkého okna je "<<velke.ziskej_hodnotu()<<endl;
      return 0;
    }
    V našem případě jsme do třídy přidali ukazatel na int (je to pouze ukazatel a alokaci místa v hromadě musíme provést v konstruktoru). V našem programu jsou deklarovány tři objekty a každý z nich obsahuje ukazatel, který ukazuje do hromady na tři různá místa. Každý objekt má svou vlastní dynamicky alokovanou datovou složku pro své vlastní soukromé použití. V tomto malém programu se nedostaneme do situace, kdy bude nedostatek paměti v hromadě. Ve skutečných programech, je ale vhodné testovat hodnotu vráceného ukazatele na NULL a zjistit tak, zda proměnná byla skutečně alokována.
    Metoda nastav má nyní tři parametry a poslední z nich se používá k nastavení hodnoty nové dynamicky alokované proměnné. V programu dále zasíláme dvě zprávy, jednu malému oknu a druhou velkému oknu k nastavení jejich položek. Střední okno má stále implicitní hodnoty. Alokovanou dynamickou paměť musíme v destruktoru třídy opět uvolnit. K zjištění hodnoty dynamické proměnné je použita metoda ziskej_hodnotu. Uložené hodnoty v dynamických proměnných jsou vypsány na konci programu. Prostudujte si tento program a vyzkoušejte jak pracuje.
  13. V následujícím programu je použit dynamicky alokovaný objekt. Vidíme, že používání dynamických objektů se neliší od jiných dynamických proměnných. Program si prostudujte.

  14. class okno {
      int delka;
      int sirka;
    public:
      okno(void);
      void nastav(int nova_delka, int nova_sirka);
      int ziskej_plochu(void);
    };
    okno::okno(void){
      delka = 8;
      sirka = 8;
    }
    void okno::nastav(int nova_delka, int nova_sirka){
      delka = nova_delka;
      sirka = nova_sirka;
    }
    okno::ziskej_plochu(void){
      return (delka * sirka);
    }
    int main(int argc, char **argv)
    {
      okno male, stredni, velke;
      okno *ukazatel;
      male.nastav(5, 7);
      velke.nastav(15, 20);
      ukazatel = new okno;                                            // použití imlicitních hodnot
      cout << "Plocha malého okna je " << male.ziskej_plochu() << endl;
      cout << "Plocha středního okna je " << stredni.ziskej_plochu() << endl;
      cout << "Plocha velkého okna je " << velke.ziskej_plochu() << endl;
      cout << "Plocha nového okna je " << ukazatel->ziskej_plochu() << endl;
      ukazatel->nastav(12,12);
      cout << "Plocha nového okna je " << ukazatel->ziskej_plochu() << endl;
      delete ukazatel;
      return 0;
    }
    Na konci programu je použit příkaz ukazatel->nastav(12,12). Zdůvodněte, zda tento příkaz by bylo možno nahradit příkazem (*ukazatel).nastav(12, 12). Posledním příkazem programu je dynamicky alokovaný objekt zrušen. Změňte tento program tak, aby také objekty male a stredni byly alokovány dynamicky.
  15. Následující program používá objekt s vnitřním odkazem na jiný objekt své vlastní třídy. Je to standardní struktura používaná pro jednosměrně zřetězený seznam. V našem programu je použita velmi jednoduchým způsobem.

  16. class okno {
      int delka;
      int sirka;
      okno *dalsi_okno;
    public:
      okno(void);
      void nastav(int nova_delka, int nova_sirka);
      int ziskej_plochu(void);
      void nastav_ukazatel(okno *ktere_okno);
      okno *ziskej_dalsi(void);
    };
    okno::okno(void){
      delka = 8;
      sirka = 8;
      dalsi_okno = NULL;
    }
    void okno::nastav(int nova_delka, int nova_sirka){
      delka = nova_delka;
      sirka = nova_sirka;
    }
    int okno::ziskej_plochu(void){
      return (delka * sirka);
    }
    void okno::nastav_ukazatel(okno *ktere_okno){
      dalsi_okno = ktere_okno;
    }
    okno *okno::ziskej_dalsi(void){
      return dalsi_okno;
    }
    int main(int argc, char **argv)
    {
      okno male, stredni, velke;
      okno *ukazatel;
      male.nastav(5, 7);
      velke.nastav(15, 20);
      cout << "Plocha malého okna je " << male.ziskej_plochu() << endl;
      cout << "Plocha středního okna je " << stredni.ziskej_plochu() << endl;
      cout << "Plocha velkého okna je " << velke.ziskej_plochu() << endl;
      male.nastav_ukazatel(&stredni);
      stredni.nastav_ukazatel(&velke);
      ukazatel = &male;
      ukazatel = ukazatel->ziskej_dalsi();
      cout << "Plocha okna je " << ukazatel->ziskej_plochu() << endl;
      return 0;
    }
    V konstruktoru je inicializován ukazatel na NULL. Takto je vhodné inicializovat všechny ukazatele, které zatím na nic neukazují. Přiřazení ukazatele v konstruktoru zajistí, že všechny objekty této třídy budou mít svůj ukazatel inicializovaný. Do třídy byly přidány další dvě metody. Pokuste se zjistit, co dělají a co dělá celý program.
  17. V C++ je další klíčové slovo a to this. Toto this je definováno v každém objektu a označuje ukazatel na objekt, ve kterém je obsaženo. Je implicitně deklarováno jako

  18. jmeno_třidy *this;
    a je inicializováno k ukazování na objekt jehož metoda je volána. Ukazatel this nesmíme nikdy modifikovat. Tento ukazatel je velmi užitečný pro práci s ukazateli a obzvláště se zřetězenými seznamy, které potřebují ukazatel na objekt při vkládání do seznamu. Ukázka použití bude uvedena později.
  19. Ukazatel this se také často používá v aplikacích GUI. Když v aplikaci GUI vkládáme komponenty na formulář, pak je většina práce udělána za nás. Komponentu lze ale také vytvořit za běhu aplikace. Např. při stisku tlačítka můžeme chtít vytvořit na formuláři jiné tlačítko. Obsluha stisku tlačítka v tomto případě bude:

  20. void __fastcall TForm1::Button1Click(TObject *Sender)
    {
      TButton *tlacitko = new TButton(this);
      tlacitko->Parent = this;
      tlacitko->Caption = "Nové tlačítko";
      tlacitko->Left = 100;
      tlacitko->Top = 50;
      tlacitko->Show();
      // další kód
    }
    V tomto kódu vidíme použití ukazatele this (parametr konstruktoru tlačítka a vlastnost Parent vytvořeného tlačítka). Vyzkoušejte.

Nové pojmy:

22. Seznámení s OOP II