5. Základy OOP V
  1. Pokud jsme doposud v našich programech pracovali se soubory, pak to bylo způsobem, který se používal v jazyku C. Je nutno se také seznámit se čtením a zápisem souborů pomocí datových proudů. Základní souborové vstupně/výstupní operace jsou v C++ prováděny pomocí těchto tříd:
  2. Následující jednoduchá konzolová aplikace čte svůj vlastní zdrojový soubor a zobrazuje jej na obrazovce (program uložíme do CTENISOUBORU.CPP). Pokud program uložíme pod jiným jménem, pak nebude správně pracovat.
    #include <condefs.h>
    #include <fstream.h>
    #include <conio.h>
    #pragma hdrstop
    #pragma argsused
    int main(int argc, char **argv)
    {
      char buff[80];
      ifstream vstsoubor;
      vstsoubor.open("ctenisouboru.cpp");
      if (!vstsoubor) return 0;
      while (!vstsoubor.eof()){
        vstsoubor.getline(buff, sizeof(buff));
        cout << buff << endl;
      }
      vstsoubor.close();
      getch();
      return 0;
    }
    Na začátku programu vytváříme instanci třídy ifstream nazvanou vstsoubor. Na dalším řádku otvíráme náš soubor pro vstup. Následuje test úspěšnosti otevření souboru (v případě neúspěchu je program ukončen). Podmínkou ukončení cyklu je volání metody eof třídy ifstream. Tato metoda vrací true při nalezení konce souboru. Jednotlivé řádky souboru jsou čteny metodou getline. Text řádku je umístěn do znakového pole buff a potom je vypsán na obrazovku. Po dočtení souboru je soubor ještě uzavřen (metoda close). Pro používání třídy ifstream je nutno vložit hlavičkový soubor FSTREAM.H.
    Jeden z konstruktorů ifstream přebírá jako parametr ukazazel na char a umožňuje tak zadat jméno souboru při vytváření instance třídy. Pomocí tohoto konstruktoru lze nahradit červené řádky z předchozího programu následujícím řádkem:
    ifstream vstsoubor("ctenisouboru.cpp");
    Když vytvoříme objekt tímto způsobem, pak použití metody open již není nutné, neboť soubor je automaticky otevřen konstruktorem. Nezapoměňte při zadávání specifikace souboru i s adresářevou cestou nahrazovat znaky \ pomocí \\.
    Uzavření souboru není nutné. Destruktor ifstream testuje, zda soubor zůstal otevřen a pokud ano, pak jej před zrušením instance třídy uzavře. Používání close tedy není vyžadováno. Pokud jej použijeme, pak tím naznačujeme, že soubor již dále není zapotřebí.
  3. V našem programu je malá chyba. Program vypíše před ukončením cyklu jeden prázdný řádek navíc. Abychom tuto chybu odstranili, musíme náš cyklus zapsat takto:

  4. while (!vstsoubor.getline(buff, sizeof(buff)).eof()){
      cout << buff << endl;
    }
    Zřetězení metod je dovoleno, i když není moc používáno. Jeho použitím získáme méně srozumitelný kód. Umožňuje ale snadno odstranit některé chyby.
  5. Protože třídy zpracovávající soubory jsou odvozeny od iostream, můžeme také používat operátory vložení a výběru do/z datového proudu a to stejně jako u datových proudů cin a cout. V našem programu jsme použili getline, protože >> ukončí čtení po nalezení první mezery (nebo odřádkování).

  6. Operátor >> je výhodný při čtení jednotlivých hodnot. Např. následující část programu čte celá čísla ze souboru a zobrazuje je na obrazovce.
    ifstream vstsoubor("nejakysoubor.dat");
    while (!vstsoubor.eof()){
      int x;
      vstsoubor >> x;
      cout << x << endl;
    }
    Operátor výběru z proudu čte data z textového souboru ale ne z binárního souboru. Tento operátor čte text ze souboru a převádí jej na číslo.
  7. Vytváření souborů je snadnější než jejich čtení. Namísto vytvoření instance třídy ifstream, vytvoříme instanci třídy ofstream a pro zápis do souboru používáme operátor vložení do proudu. Např.

  8. ofstream vystsoubor("Test.dat");
    if (!vystsoubor) return 0;
    for (int i = 0; i < 10; i++) {
      vystsoubor << "Toto je řádek " << (i + 1) << endl;
    }
    vystsoubor.close();
  9. Vytvořte konzolovou aplikaci, která bude přebírat vstup od uživatele a zapíše jej do textového souboru.

  10. Modifikujte předchozí zadání tak, že na závěr vytvořený soubor opět přečtete a vypíšete na obrazovku. V tomto případě musíme, před otevřením souboru pro čtení, zapsaný soubor uzavřít.
  11. V C++ je možno dědit při vytváření nové třídy metody a datové složky od dvou nebo více tříd předků. Toto se označuje jako vícenásobná dědičnost. Vícenásobná dědičnost je ukázána v následujícím programu.

  12. class dodavka {
      protected:
      float naklad;
      float vaha_objektu;
      float spotreba;
    public:
      void inicializace(float na, float va, float sp){
        naklad = na;
        vaha_objektu = va;
        spotreba = sp;
      }
      float ucinnost(void){
        return (naklad / (naklad + vaha_objektu));
      }
      float naklady_na_tunu(float cena_paliva){
        return (cena_paliva / (naklad / 2000.0));
      }
    };
    class ridic {
    protected:
      float hod_mzda;
    public:
      void inicializace(float mzda){hod_mzda = mzda; }
      float naklady_na_km(void) {return (hod_mzda / 90.0); }
    };
    class rizene_auto : public dodavka, public ridic {
    public:
      void celk_inicializace(float na, float va, float sp, float mzda){
        naklad = na;
        vaha_objektu = va;
        spotreba = sp;
        hod_mzda = mzda;
      }
      float naklady_na_cely_den(float naklady_na_palivo){
        return (8.0 * hod_mzda +
                8.0 * naklady_na_palivo * 90.0 / spotreba);
      }
    };
    int main(int argc, char **argv)
    {
      rizene_auto novak_ford;
      novak_ford.celk_inicializace(5000.0, 3000.0, 8.3, 76.5);
      cout << "Účinnost Fordu je " << novak_ford.ucinnost() << endl;
      cout << "Náklady na km pro řidiče Nováka jsou " <<
               novak_ford.naklady_na_km() << endl;
      cout << "Náklady na řízení Fordu řidičem Novákem jsou na den " <<
               novak_ford.naklady_na_cely_den(20.0) << endl;
      return 0;
    }
    Pro zjednodušení hledání kódu v tomto programu jsou všechny metody implementovány jako vnořené. Všechny složky tříd jsou deklarovány jako chráněné a jsou tedy přístupné v odvozených třídách. Kód pro všechny třídy je velmi jednoduchý, neboť se zaměřujeme hlavně na studium dědičnosti. Na červeném řádku deklarujeme třídu rizene_auto, která dědí všechna data a metody od obou dříve definovaných tříd. Při vícenásobné dědičnosti zapisujeme za klíčové slovo public (případně jiné přístupové specifikátory) jména všech tříd předků. V tomto případě nedefinujeme v odvozené třídě žádné nové datové složky, ale přidáváme do ní dvě nové metody.
    V hlavním programu deklarujeme objekt novak_ford, který popisuje nějakého Nováka řídícího dodávku Ford. Tento objekt se skládá ze čtyř datových složek z nichž tři pocházejí od třídy dodavka a jedna od třídy ridic. Se všemi těmito čtyřmi složkami můžeme pracovat ve všech metodách třídy rizene_auto a to stejně jako při jednoduché dědičnosti. Všechna pravidla platící pro jednoduchou dědičnost platí i pro vícenásobnou dědičnost. V hlavním programu jsme nedeklarovali žádný objekt tříd předků. Použily bychom je normálním způsobem. V našem programu je v obou třídách předků deklarována metoda inicializace. Zjistěte, která z nich se použije, zašleme-li objektu novak_ford zprávu inicializace.
  13. V následující verzi programu jsou ve všech třídách metody naklady_na_cely_den. Tento program ukazuje jak používat tyto stejnojmenné metody a jak určit, kterou z nich chceme použít. Program si prostudujte.

  14. class dodavka {
    protected:
      float naklad;
      float vaha_objektu;
      float spotreba;
    public:
      void inicializace(float na, float va, float sp){
        naklad = na;
        vaha_objektu = va;
        spotreba = sp;
      }
      float ucinnost(void){
        return (naklad / (naklad + vaha_objektu));
      }
      float naklady_na_tunu(float cena_paliva){
        return (cena_paliva / (naklad / 2000.0));
      }
      float naklady_na_cely_den(float naklady_na_palivo){
        return (8.0 * naklady_na_palivo * 90.0 / spotreba);
      }
    };
    class ridic {
    protected:
      float hod_mzda;
    public:
      void inicializace(float mzda){hod_mzda = mzda; }
      float naklady_na_km(void) {return (hod_mzda / 90.0); }
      float naklady_na_cely_den(float prescas_mzda){
        return (8.0 * hod_mzda);
      }
    };
    class rizene_auto : public dodavka, public ridic {
    public:
      void celk_inicializace(float na, float va, float sp, float mzda){
        naklad = na;
        vaha_objektu = va;
        spotreba = sp;
        hod_mzda = mzda;
      }
      float naklady_na_cely_den(float naklady_na_palivo){
        return (8.0 * hod_mzda +
          8.0 * naklady_na_palivo * 90.0 / spotreba);
      }
    };
    int main(int argc, char **argv)
    {
      rizene_auto novak_ford;
      novak_ford.celk_inicializace(5000.0, 3000.0, 8.3, 76.5);
      cout << "Účinnost Fordu je " << novak_ford.ucinnost() << endl;
      cout << "Náklady na km pro řidiče Nováka jsou " <<
        novak_ford.naklady_na_km() << endl;
      cout << "Náklady na Ford jsou na den " <<
       novak_ford.dodavka::naklady_na_cely_den(20.0) << endl;
      cout << "Náklady na řidiče Nováka jsou na den " <<
       novak_ford.ridic::naklady_na_cely_den(95.0) << endl;
      cout << "Náklady na řízení Fordu řidičem Novákem jsou na den " <<
       novak_ford.naklady_na_cely_den(20.0) << endl;
      return 0;
    }
  15. V další ukázce se používá u obou tříd předků datová složka se jménem vaha_objektu. Objekt třídy rizene_auto tedy dědí dvě složky stejného jména. Prostudujte si v tomto programu, jak se takovéto složky používají.

  16. class dodavka {
    protected:
      float naklad;
      float vaha_objektu;
      float spotreba;
    public:
      void inicializace(float na, float va, float sp){
        naklad = na;
        vaha_objektu = va;
        spotreba = sp;
      }
      float ucinnost(void){
        return (naklad / (naklad + vaha_objektu));
      }
      float naklady_na_tunu(float cena_paliva){
        return (cena_paliva / (naklad / 2000.0));
      }
    };
    class ridic {
    protected:
      float hod_mzda;
      float vaha_objektu;
    public:
      void inicializace(float mzda, float vaha){
        hod_mzda = mzda;
        vaha_objektu = vaha;
      }
      float naklady_na_km(void) {return (hod_mzda / 90.0); }
      float vaha_ridice(void) {return (vaha_objektu);}
    };
    class rizene_auto : public dodavka, public ridic {
    public:
      void celk_inicializace(float na, float va, float sp, float mzda){
        naklad = na;
        dodavka::vaha_objektu = va;
        spotreba = sp;
        hod_mzda = mzda;
      }
      float naklady_na_cely_den(float naklady_na_palivo){
        return (8.0 * hod_mzda +
                8.0 * naklady_na_palivo * 90.0 / spotreba);
        }
      float celkova_vaha(void){
        return (dodavka::vaha_objektu + ridic::vaha_objektu);
      }
    };
    int main(int argc, char **argv)
    {
      rizene_auto novak_ford;
      novak_ford.celk_inicializace(5000.0, 3000.0, 8.3, 76.5);
      novak_ford.ridic::inicializace(80.0, 85.0);
      cout << "Učinnost Fordu je " << novak_ford.ucinnost() << endl;
      cout << "Náklady na km pro řidiče Nováka jsou " <<
               novak_ford.naklady_na_km() << endl;
      cout << "Náklady na řízení Fordu řidičem Novákem jsou na den " <<
               novak_ford.naklady_na_cely_den(20.0) << endl;
      cout << "Celková váha je " << novak_ford.celkova_vaha() << endl;
      return 0;
    }
    Protože Object Pascal nezná vícenásobnou dědičnost, nelze používat vícenásobnou dědičnost ve třídách odvozených od tříd VCL (tříd knihovny vizuálních komponent; knihovna je naprogramovaná v Object Pascalu). Vícenásobnou dědičnost lze tedy používat pouze mimo rámec VCL.
5. Základy OOP V