7. Zßklady OOP VII
  1. V tΘto kapitole budeme vyvφjet slo╛it∞j╣φ konzolovou aplikaci. Jednß se o jednoduchou databßzi, ve kterΘ si ukß╛eme komplexn∞j╣φ pou╛itφ virtußlnφch funkcφ. Nejprve vytvo°φme deklaraci a implementaci t°φdy osoba. Tato t°φda bude obsahovat dv∞ chrßn∞nΘ datovΘ slo╛ky a jednu virtußlnφ metodu.

  2. class osoba {
    protected:
      char jmeno[25];
      int plat;
    public:
      virtual void zobraz(void);
    };
    void osoba::zobraz(void){
      cout << "osoba::zobraz - chyb∞jφcφ metoda v odvozenΘ t°φd∞\n";
    }
    Implementace tΘto t°φdy je snadnß, bude obsahovat pouze implementaci virtußlnφ metody zobraz. Tato t°φda bude zßkladem pro odvozovßnφ dal╣φch specializovan²ch t°φd, ale nikdy ji nebudeme pou╛φvat (budeme ji pou╛φvat pouze jako t°φdu p°edka). Z tohoto d∙vodu by metoda zobraz nem∞la b²t nikdy volßna a do jejφ implementace vlo╛φme pouze v²pis signalizace chyby.
    Dßle vytvo°φme t°i odvozenΘ t°φdy. Jsou to t°φdy vedouci, programator a sekretarka. V╣echny obsahujφ metodu zobraz, kterß je stejnß jako tato metoda v t°φd∞ p°edka. Jsou to tedy virtußlnφ metody.
    class vedouci : public osoba {
      char titul[25];
    public:
      void inicializace(char jm[], int pl, char ti[]);
      void zobraz(void);
    };
    class programator : public osoba {
      char titul[25];
      char jazyk[25];
    public:
      void inicializace(char jm[], int pl, char ti[], char ja[]);
      void zobraz(void);
    };
    class sekretarka : public osoba {
      char tesnopis;
      int rychlost_psani;
    public:
      void inicializace(char jm[], int pl, char te, int ry);
      void zobraz(void);
    };
    void vedouci::inicializace(char jm[], int pl, char ti[]){
      strcpy(jmeno, jm);
      plat = pl;
      strcpy(titul, ti);
    }
    void vedouci::zobraz(void){
      cout << "Vedoucφ     --> " << jmeno << " mß plat " << plat <<
              " a je " << titul << ".\n\n";
    }
    void programator::inicializace(char jm[], int pl, char ti[], char ja[]){
      strcpy(jmeno, jm);
      plat = pl;
      strcpy(titul, ti);
      strcpy(jazyk, ja);
    }
    void programator::zobraz(void){
      cout << "Programßtor --> " << jmeno << " mß plat " << plat <<
              " a je " << titul << ".\n\n";
      cout << "                " << jmeno<<" programuje v "<<jazyk<<" .\n\n";
    }
    void sekretarka::inicializace(char jm[], int pl, char te, int ry){
      strcpy(jmeno, jm);
      plat = pl;
      tesnopis = te;
      rychlost_psani = ry;
    }
    void sekretarka::zobraz(void){
      cout << "Sekretß°ka  --> " << jmeno << " mß plat " << plat << ".\n\n";
      cout << "                " << jmeno << " pφ╣e rychlostφ " <<
      rychlost_psani << " znak∙ za minutu a ";
      if (!tesnopis) cout << "ne";
      cout << "ovlßdß t∞snopis.\n\n";
    }
    V ka╛dΘ z t∞chto t°φd nalezneme metodu inicializace, kterß provßdφ inicializaci datov²ch slo╛ek t°φdy a virtußlnφ metodu zobraz vypisujφcφ data t°φdy a to pro ka╛dou t°φdu jin²m zp∙sobem.
    Vytvo°enΘ t°φdy nynφ pou╛ijeme v jednoduchΘm programu. Na zaΦßtku programu je deklarace pole ukazatel∙ o 10 prvcφch, typu ukazatel na osoba. Do tohoto pole budeme uklßdat ukazatele na odvozenΘ t°φdy. V programu alokujeme n∞kolik objekt∙ odvozen²ch t°φd, inicializujeme je a ukazatele na n∞ vlo╛φme do na╣eho pole. Na zßv∞r programu informace ulo╛enΘ v objektech vypφ╣eme. Program si prostudujte a vyzkou╣ejte.
    osoba *stav[10];
    int main(int argc, char **argv)
    {
      vedouci *ved;
      programator *prog;
      sekretarka *sekr;
      ved = new vedouci;
      ved->inicializace("Karel Velk²", 27000, "prezident");
      stav[0] = ved;
      prog = new programator;
      prog->inicializace("Jan Novßk", 21000, "analytik", "Pascal");
      stav[1] = prog;
      prog = new programator;
      prog->inicializace("Ji°φ Novotn²", 17000, "programaror", "C++");
      stav[2] = prog;
      sekr = new sekretarka;
      sekr->inicializace("Marie Hezkß", 10370, 1, 350);
      stav[3] = sekr;
      ved = new vedouci;
      ved->inicializace("Jarmila Starß", 17000, "vedoucφ ·Φetnφ");
      stav[4] = ved;
      prog = new programator;
      prog->inicializace("Jan Kluzk²",11000,"pomocn² programator", "Pascal");
      stav[5] = prog;
      for (int index = 0; index < 6; index++)
        stav[index]->zobraz();
      return 0;
    }
  3. Metodu zobraz ve t°φd∞ osoba m∙╛eme deklarovat takΘ jako Φirou virtußlnφ metodu. ╚irß virtußlnφ metoda nemß implementaci a nezam²╣lφme ji volat. Deklarujeme ji konstrukcφ prototyp_metody = 0; v na╣em p°φpad∞ jde o deklaraci

  4. virtual void zobraz(void) = 0;
    T°φda, kterß obsahuje alespo≥ jednu Φirou metodu se v terminologii C++ oznaΦuje jako abstraktnφ. P°ekladaΦ nedovoluje definovat instance abstraktnφch t°φd. ╚irΘ metody nemajφ definiΦnφ deklaraci. Pokus o volßnφ ΦirΘ metody skonΦφ chybou. Zm∞≥te t°φdu osoba na abstraktnφ a vyzkou╣ejte.
  5. V tomto zadßnφ budeme pokraΦovat ve v²voji databßze zam∞stnanc∙. Nßsleduje deklarace a implementace dal╣φch dvou t°φd, kterΘ vyu╛ijeme k vytvo°enφ spojovΘho seznamu zam∞stnanc∙. Pov╣imn∞te si, ╛e prvky spojovΘho seznamu neobsahujφ data, ale ukazatele na t°φdu osoba, kterou jsme vytvo°ili v p°edchozφm programu, a m∙╛eme tedy vytvo°it spojov² seznam prvk∙ t°φd osoba a to bez nutnosti modifikace tΘto t°φdy. V tomto souboru si pov╣imn∞te pou╛itφ dop°ednΘ deklarace t°φdy seznam_zamest (musφme ji pou╛φt, nebo╗ ob∞ t°φdy se odkazujφ na sebe navzßjem). Dßle si pov╣imn∞te poslednφho °ßdku v deklaraci t°φdy prvek_seznamu (friend class seznam_zamest;), kter² dßvß t°φd∞ seznam_zamest voln² p°φstup k polo╛kßm t°φdy prvek_seznamu. Je to nutnΘ, proto╛e metoda pridej_osobu musφ p°istupovat k polo╛ce dalsi.

  6. class seznam_zamest;                         // Dop°ednß deklarace
    class prvek_seznamu {                         // Jeden prvek z°et∞zenΘho seznamu
      osoba *data;
      prvek_seznamu *dalsi;
    public:
      prvek_seznamu(osoba *novy_zamest){
        dalsi = NULL;
        data = novy_zamest;
      };
      friend class seznam_zamest;
    };
    class seznam_zamest{                          // Z°et∞zen² seznam
      prvek_seznamu *zacatek;
      prvek_seznamu *konec;
    public:
      seznam_zamest() {zacatek = NULL;}
      void pridej_osobu(osoba *novy_zamest);
      void zobraz_seznam(void);
    };
    void seznam_zamest::pridej_osobu(osoba *novy_zamest){
      prvek_seznamu *pom;
      pom = new prvek_seznamu(novy_zamest);
      if (zacatek == NULL)
        zacatek = konec = pom;
      else {
        konec->dalsi = pom;
        konec = pom;
      }
    }
    void seznam_zamest::zobraz_seznam(void){
      prvek_seznamu *pom;
      pom = zacatek;
      do {
        pom->data->zobraz();
        pom = pom->dalsi;
      } while (pom != NULL);
    }
    Databßze zam∞stnanc∙ je realizovßna pomocφ spojovΘho seznamu, kter² vytvß°φme prost°ednictvφm na╣ich dvou nov²ch t°φd. Program je jednoduch² a nepot°ebuje ╛ßdnΘ vysv∞tlenφ. Prostudujte si jej a vyzkou╣ejte jej.
    seznam_zamest seznam;
    int main(int argc, char **argv)
    {
      vedouci *ved;
      programator *prog;
      sekretarka *sekr;
      ved = new vedouci;
      ved->inicializace("Karel Velk²", 27000, "prezident");
      seznam.pridej_osobu(ved);
      prog = new programator;
      prog->inicializace("Jan Novßk", 21000, "analytik", "Pascal");
      seznam.pridej_osobu(prog);
      prog = new programator;
      prog->inicializace("Ji°φ Novotn²", 17000, "programaror", "C++");
      seznam.pridej_osobu(prog);
      sekr = new sekretarka;
      sekr->inicializace("Marie Hezkß", 10370, 1, 350);
      seznam.pridej_osobu(sekr);
      ved = new vedouci;
      ved->inicializace("Jarmila Starß", 17000, "vedoucφ ·Φetnφ");
      seznam.pridej_osobu(ved);
      prog = new programator;
      prog->inicializace("Jan Kluzk²",11000,"pomocn² programator", "Pascal");
      seznam.pridej_osobu(prog);
      seznam.zobraz_seznam();
      return 0;
    }
  7. Do na╣eho programu p°idejte deklaraci a implementaci novΘ t°φdy nazvanΘ poradce (p°idanΘ slo╛ky si zvolte) a p°idejte p°φkazy na vyzkou╣enφ pou╛itφ novΘ t°φdy.
  8. V p°edchozφ kapitole jsme se seznßmili s vφcenßsobnou d∞diΦnostφ. P°i vφcenßsobnΘ d∞diΦnosti mohou ale vznikat problΘmy. Nap°. pro vstupy a v²stupy v C++ pou╛φvßme datovΘ proudy. Zßkladem datov²ch proud∙ je t°φda ios. Tato t°φda definuje vlastnosti spoleΦnΘ v╣em datov²m proud∙m (stavovΘ p°φznaky, formßtovacφ p°φznaky apod.). Od t°φdy ios je odvozena °ada specializovan∞j╣φch t°φd. Jednß se takΘ o t°φdy istream a ostream, kterΘ definujφ vstupnφ a v²stupnφ datovΘ proudy. SpoleΦn²m potomkem t∞chto obou t°φd je t°φda iostream pro proudy, kterΘ umo╛≥ujφ zßrove≥ vstup i v²stup dat. iostream tedy d∞dφ v╣e od istream a ostream a nic dal╣φho nep°idßvß. Z pravidel vφcenßsobnΘho d∞d∞nφ vypl²vß, ╛e iostream bude obsahovat dva zd∞d∞nΘ podobjekty t°φdy ios a tedy i dvakrßt formßtovacφ a stavovΘ p°φznaky, co╛ nenφ v∙bec vhodnΘ. Jazyk C++ nabφzφ °e╣enφ v podob∞ tzv. virtußlnφho d∞d∞nφ. To zajistφ, ╛e vφcekrßt zd∞d∞nΘ prvky se slouΦφ. Deklarujeme-li t°φdu ios jako virtußlnφho p°edka t°φd istream a ostream, bude jejich spoleΦn² potomek iostream obsahovat pouze jeden podobjekt typu ios.

  9. Virtußlnφho p°edka vytvo°φme tak, ╛e p°i specifikaci p°edka pou╛ijeme klφΦovΘ slovo virtual. Nßsleduje p°φklad pou╛itφ:
    class a {
      double x;
    public:
      a(){};
    };
    class aa : public virtual a {
      double a;
    public:
      aa() {};
    };
    class ab : public virtual a {
      double b;
    public:
      ab() {};
    };
    class X : public aa, public ab {
      double xx;
    };
    Instance t°φdy X bude nynφ obsahovat pouze jeden podobjekt t°φdy a. Vyzkou╣ejte.
  10. Jednou z v²hod, kterΘ nßm C++ nabφzφ, je mo╛nost p°et∞╛ovat nejen funkce, ale i p°evß╛nou v∞t╣inu operßtor∙. Nelze p°et∞╛ovat operßtory: . (teΦka), .* (teΦka_hv∞zdiΦka), :: (dv∞ dvojteΦky), ? : (podmφn∞n² v²raz), sizeof, typeid, dynamic_cast, static_cast, reinterpret_cast a const_cast. Z hlediska p°et∞╛ovßnφ lze rozd∞lit operßtory na t°i skupiny. Prvnφ skupinu tvo°φ operßtory, kterΘ m∙╛eme p°et∞╛ovat pouze jako nestatickΘ metody objektov²ch typ∙. Pat°φ sem operßtory () (volßnφ funkce), [] (indexovßnφ), -> (nep°φmΘho p°φstupu), = (prostΘ p°i°azenφ) a operßtor p°etypovßnφ (typ). Druhou (nejrozsßhlej╣φ) skupinu tvo°φ operßtory, kterΘ lze p°et∞╛ovat jako nestatickΘ metody objektov²ch typ∙ nebo jako °adovΘ funkce. Jsou to v╣echny operßtory, kterΘ nejsou v ostatnφch skupinßch a nejsou operßtorem, kter² nelze p°et∞╛ovat. Tyto operßtory musφ mφt alespo≥ jeden operand objektovΘho nebo v²ΦtovΘho typu. Poslednφ skupinu tvo°φ operßtory pro sprßvu pam∞ti new a delete. Platφ pro n∞ zvlß╣tnφ pravidla. Nelze zm∞nit chovßnφ operßtor∙ pro argumenty standardnφch datov²ch typ∙. Pro p°et∞╛ovßnφ operßtor∙ pou╛φvßme takΘ termφn homonyma operßtor∙. Nejprve se seznßmφme s obecn²mi pravidly p°et∞╛ovßnφ:
  11. Mimo t∞chto zßsad, kterΘ jsou dßny definicφ jazyka, je vhodnΘ p°i definovßnφ homonym operßtor∙ dodr╛ovat nßsledujφcφ omezenφ: Nejprve se budeme zab²vat p°i°azovacφmi operßtory. Operßtor prostΘho p°i°azenφ (=) definuje p°ekladaΦ implicitn∞, kdykoli je t°eba. Prost² p°i°azovacφ operßtor m∙╛eme p°et∞╛ovat pouze jako nestatickou metodu objektovΘho typu, zatφmco slo╛enΘ operßtory (nap°. += apod.) m∙╛eme p°et∞╛ovat i jako funkce. P°i°azovacφ operßtor ve t°φd∞ X je metoda s prototypem
    X& X::operator=(X&)     p°φpadn∞        X& X::operator=(const X&)
    Je-li a instance t°φdy X, znamenß zßpis    a = b;  totΘ╛ jako    a.operator=(b);
    Implicitnφ verze p°i°azovacφho operßtoru °e╣φ p°i°azovßnφ hodnot objekt∙ prost²m okopφrovßnφm jednoho objektu do druhΘho. To nßm p°estane vyhovovat nap°. ve chvφli, kdy na╣e objekty budou rozd∞leny na n∞kolik Φßstφ svßzan²ch navzßjem ukazateli. V nßsledujφcφm p°φkladu je ukßzka t°φdy umo╛≥ujφcφ pracovat s °et∞zci.
    class retezec {
      int delka;
      char *text;
      void Platny(const char* =(const char*)2, int = 0) const;
      retezec& Prirad(const char*, int);
      retezec& Pridej(const char*, int);
      friend ostream& operator<< (ostream&, const retezec&);
    public:
      retezec() {text = NULL;};                         //NULL indikuje je╣t∞ nep°i°azenou hodnotu °et∞zci
      retezec(const char *);
      retezec(const retezec&);
      retezec(int, const char * s) {
        delka = strlen(s);
        text = (char*)s;
      };
      ~retezec(){if (text) delete text;};
      retezec& operator= (const char* s) {return Prirad(s, strlen(s));};
      retezec& operator= (const retezec& S) {return Prirad(S.text, S.delka);};
      retezec& operator+= (const char* s) {return Pridej(s, strlen(s));};
      retezec& operator+= (const retezec& S) {return Pridej(S.text, S.delka);};
    };
    retezec::retezec(const char *s) {
      Platny(s, 1);
      delka = strlen(s);
      text = new char[delka+1];
      strcpy(text, s);
    }
    retezec::retezec(const retezec& S) {
      S.Platny();
      delka = S.delka;
      text = new char[delka+1];
      strcpy(text, S.text);
    }
    void retezec::Platny(const char* s, int i) const {    //pomocnß metoda kontrolujφcφ korektnost operacφ
      if (s && (i || text)) return;
      cerr << "\n\nPou╛itφ °et∞zce bez hodnoty\n\n";
      abort();
    }
    retezec& retezec::Prirad(const char* s, int i) {
      Platny(s, 1);
      if (text) delete text;
      delka = i;
      text = new char[delka+1];
      strcpy(text, s);
      return *this;
    }
    retezec& retezec::Pridej(const char* s, int i) {
      Platny(s);
      if (*s){
        char* T = new char[delka+ i+1];
        strcpy(T, text);
        strcpy(T+delka, s);
        delka += i;
        delete text;
        text = T;
      }
      return *this;
    }
    inline ostream& operator<< (ostream& o, const retezec& s) {
      o << (void*)s.text << ": " << s.delka << ">>" << s.text << "<<\n";
      return o;
    }
    int main(int argc, char **argv)
    {
      retezec a = "Karel";
      retezec b("Milada");
      retezec bb(b);
      retezec c;
      const retezec d(1, "David");
      cout << "a="<<a<<"b="<<b<<"c="<<c<<"d="<<d;
      c = a;
      cout << "\nc2="<<c;
      cout << "bb2="<< (bb = "Bohuslav ");
      bb += d;
      cout << "bb3=" << bb;
      cout << "bb4=" << (bb += " Bo╛ena");
      a = bb = NULL;                                      // nedovolenß operace, bude vypsßna signalizace chyby
      return 0;
    }
    Ukazatelem s hodnotou NULL v tΘto ukßzce oznaΦujeme neinicializovanou instanci. Je v²hodnΘ, pokud m∙╛eme definovat neinicializujφcφ konstruktor tak, aby operßtory, kterΘ cht∞jφ danou prom∞nnou pou╛φt, um∞ly poznat, ╛e dotyΦnß prom∞nnß nemß p°i°azenou hodnotu, a na tuto skuteΦnost nßs n∞jak²m zp∙sobem upozornili. Dßle si pov╣imn∞te dvouparametrickΘho konstruktoru, u jeho╛ prvnφho parametru nenφ uveden ╛ßdn² identifikßtor. Tento parametr nßm slou╛φ pouze k tomu, abychom odli╣ili dan² konstruktor od druhΘho konstruktoru, jeho╛ parametrem je takΘ textov² °et∞zec. Tφm, ╛e jsme neuvedli u parametru jmΘno, jsme p°ekladaΦi naznaΦili, ╛e dotyΦn² parametr nehodlßme pou╛φvat a ╛e nßs na jeho nepou╛itφ p°ekladaΦ nemß upozor≥ovat. U tohoto konstruktoru nealokujeme pro inicializaΦnφ text mφsto v hromad∞, ale nasm∞rujeme ukazatel p°φmo na inicializaΦnφ text (v tomto p°φpad∞ text nesmφme zm∞nit).
    V ukßzce jsou vytvo°eny i operßtory p°i°azenφ. Jsou ve dvou verzφch a to jak pro klasickΘ textovΘ °et∞zce, tak i pro °et∞zce prßv∞ deklarovanΘho typu. T∞la funkcφ u t∞chto dvou verzφ si jsou velmi podobnß, a proto jsme vytvo°ili pomocnΘ funkce realizujφcφ spoleΦnou Φßst algoritmu. Prostudujte si implementaci uvedenΘ t°φdy a vyzkou╣ejte.
  12. Podobn∞ jako u operßtoru p°i°azenφ m∙╛eme p°et∞╛ovat i zßkladnφ binßrnφ operßtory (+ = * / % > < >= <= == != && || | & ^ << >>). Identifikßtor operßtoru je zde tvo°en klφΦov²m slovem operßtor, za nφm╛ nßsleduje symbol danΘho operßtoru, kter² m∙╛e b²t od slova operator odd∞len libovoln²m poΦtem mezer. Pokud binßrnφ operßtor definujeme jako normßlnφ funkci musφ mφt dva parametry a alespo≥ jeden z nich musφ b²t objektovΘho nebo v²ΦtovΘho typu. U operßtoru definovanΘho jako metoda je jeho lev²m argumentem instance, jejφ╛ metodou operßtor je, tak╛e v definici ji╛ deklarujeme pouze jeden parametr (prav² operand). Je vhodnΘ, kdy╛ binßrnφ operßtory nem∞nφ hodnoty sv²ch operand∙ a v²sledek je p°edßvßn hodnotou (pou╛itφ odkazu z mnoha d∙vod∙ nenφ vhodnΘ). Pou╛itφ binßrnφho operßtoru + si ukß╛eme na roz╣φ°enφ p°edchozφ t°φdy (retezec). Do t°φdy p°idßme:

  13. friend retezec operator+ (const retezec&, const retezec&);
    a tuto sp°ßtelenou funkci implementujeme takto:
    inline retezec operator+ (const retezec& a, const retezec& b){
      retezec pom = a;
      return pom += b;
    }
    Do hlavnφho programu vlo╛φme na vyzkou╣enφ nap°. tyto p°φkazy:
    retezec vyrok;
    vyrok = a + " a " + b;
    cout << ("H°φ╣nφci " + vyrok + "!!!!");
    Mohli bychom deklarovat i operßtory se smφ╣en²mi parametry:
    friend retezec operator+ (const char *, const retezec&);
    friend retezec operator+ (const retezec&, const char *);
    ale nenφ to nutnΘ. Ve t°φd∞ jsme definovali konverznφ konstruktor s parametrem typu char *. P°ekladaΦ dφky tomu umφ zkonstruovat pomocn² objekt, kterΘmu p°i°adφ hodnotu p°edßvanΘho °et∞zce a kter² pak p°edß operßtoru jako skuteΦn² parametr. Dodefinovßnφm t∞chto dvou homonym operßtoru sΦφtßnφ nep°idßme programu ╛ßdnΘ novΘ, d°φve neexistujφcφ funkce. Vyzkou╣ejte si pou╛itφ v²╣e uvedenΘho operßtoru sΦφtßnφ.
7. Zßklady OOP VII