6. Zßklady OOP VI
  1. Nßsledujφcφ program je prvnφ z °ady program∙, na kter²ch se budeme seznamovat s virtußlnφmi funkcemi a polymorfnφmi objekty. Objekty jsou polymorfnφ, jestli╛e si jsou podobnΘ, ale p°esto se li╣φ. Nß╣ program zatφm neobsahuje virtußlnφ funkce (pou╛ijeme je a╛ v dal╣φch verzφch). Jednß se o zjednodu╣enou verzi jednoho z p°edchozφch program∙. Do t°φdy p°edka byla p°idßna metoda nazvanß zprava. Nynφ se budeme zab²vat studiem operacφ s metodami zprava a to ve t°φdßch p°edk∙ a t°φdßch potomk∙. P°idßme tedy tuto metodu i do t°φdy automobil a do nov∞ p°idanΘ t°φdy lod. Do t°φdy nakladni jsme metodu zprava nep°idali, ale je zde zd∞d∞na od t°φdy vozidlo.

  2. V programu je deklarovßno n∞kolik objekt∙ a je jim v╣em zaslßna zprava. Prostudujte si tento program a zd∙vodn∞te, proΦ je kdy kterß metoda zprava volßna.
    class vozidlo {
      int kola;
      float vaha;
    public:
      void zprava(void) { cout << "Vozidlo\n";}
    };
    class automobil : public vozidlo {
      int osob;
    public:
      void zprava(void) { cout << "Automobil\n";}
    };
    class nakladni : public vozidlo {
      int osob;
      float naklad;
    public:
      int pasazeru(void){return osob;}
    };
    class lod : public vozidlo {
      int osob;
    public:
      int pasazeru(void){return osob;}
      void zprava(void) { cout << "Lo∩\n";}
    };
    int main(int argc, char **argv)
    {
      vozidlo unicykl;
      automobil sedan;
      nakladni tatra;
      lod plachetnice;
      unicykl.zprava();
      sedan.zprava();
      tatra.zprava();
      plachetnice.zprava();
      unicykl = sedan;
      return 0;
    }
  3. P°edchozφ verzi programu nynφ nepatrn∞ zm∞nφme. Na zaΦßtek ΦervenΘho °ßdku, p°ed deklaraci metody zprava ve t°φd∞ p°edka p°idßme klφΦovΘ slovo virtual. Po spu╣t∞nφ programu zjistφme, ╛e funkce programu se nezm∞nila. Je to z toho d∙vodu, ╛e objekty pou╛φvßme p°φmo a virtußlnφ metody nemajφ nic co d∞lat s objekty, ale pouze s ukazateli na objekty, jak bude ukßzßno dßle. Modr² p°φkaz v programu ukazuje, ╛e p°esto╛e v╣echny Φty°i objekty v programu jsou r∙zn²ch t°φd, je mo╛no p°i°adit potomka p°edku.
  4. Vrßtφme se op∞t k p∙vodnφ verzi na╣eho programu (odstranφme tedy p°idanΘ klφΦovΘ slovo virtual) a program zm∞nφm tak, aby pou╛φval ukazatele na objekty. Nßsleduje pouze v²pis t∞la funkce main (jinak se program nezm∞nil; vynechali jsme takΘ poslednφ p°φkaz).

  5. vozidlo *unicykl;
    unicykl = new vozidlo;
    unicykl->zprava();
    automobil *sedan;
    sedan = new automobil;
    sedan->zprava();
    nakladni *tatra;
    tatra = new nakladni;
    tatra->zprava();
    lod *plachetnice;
    plachetnice = new lod;
    plachetnice->zprava();
    Po spu╣t∞nφ tohoto programu zjistφte, ╛e pracuje op∞t stejn∞ jako p∙vodnφ verze. V programu jsme neprovedli dealokaci objekt∙ p°ed ukonΦenφm programu. Dopl≥te pot°ebnΘ p°φkazy sami.
  6. Do na╣eho upravenΘho programu op∞t vra╗te klφΦovΘ slovo virtual a program vyzkou╣ejte. Zjistφte, ╛e program pracuje op∞t stejn∞. Je to z d∙vodu, ╛e p°i pou╛itφ ukazatel∙ na ka╛d² objekt je ukazatel stejnΘho typu jako objekt na kter² ukazuje.
  7. P°edchozφ verze na╣eho programu ukazujφ jak virtußlnφ funkce nepracujφ, nynφ si ji╛ ukß╛eme jak virtußlnφ funkce pracujφ. Program zm∞nφme tak, ╛e ve funkci main ji╛ nebudeme pou╛φvat Φty°i r∙znΘ ukazatele na r∙znΘ objekty, ale pouze jeden a to na t°φdu p°edka. Hlavnφ program bude tedy vypadat takto:

  8. vozidlo *unicykl;
    unicykl = new vozidlo;
    unicykl->zprava();
    delete unicykl;
    unicykl = new automobil;
    unicykl->zprava();
    delete unicykl;
    unicykl = new nakladni;
    unicykl->zprava();
    delete unicykl;
    unicykl = new lod;
    unicykl->zprava();
    delete unicykl;
    Nejprve vyzkou╣φme verzi s vynechan²m klφΦov²m slovem virtual, tedy p°φpad, kdy se nejednß o virtußlnφ funkce. Jeliko╛ pou╛φvßme stßle typ ukazatele na t°φdu vozidlo, bude v╛dy p°i zaslßnφ zprßvy zprava kterΘmukoli na╣emu objektu vyvolßna metoda zprava t°φdy vozidlo.
    V na╣em programu vidφme, ╛e m∙╛eme pou╛φt ukazatel na jednu t°φdu k odkazovßnφ se na jinou t°φdu. Jestli╛e se odkazujeme na vozidlo (v reßlnΘm sv∞t∞ a ne jen v na╣em programu), m∙╛eme se odkazovat na automobil, nßkladnφ auto, motocykl nebo jin² typ dopravnφho prost°edku, nebo╗ je to obecn∞j╣φ forma objektu. NicmΘn∞, pokud se odkazujeme na automobil nelze ji╛ pou╛φt nßkladnφ auto, motocykly a jinΘ typy dopravnφch prost°edk∙, proto╛e automobil je ji╛ specializovanou t°φdou. Obecn∞ji termφnem vozidlo se m∙╛eme odkazovat na mnoho typ∙ vozidel, ale specifiΦt∞j╣φm termφnem automobil pouze na jeden typ vozidel nazvan² automobil. Ukazatel na t°φdu p°edka m∙╛e b²t pou╛it k ukazovßnφ na objekt odvozenΘ t°φdy od tΘto t°φdy p°edka, ale ukazatel na odvozenou t°φdu nelze pou╛φt k ukazovßnφ na t°φdu p°edka nebo jinou odvozenou t°φdu od t°φdy p°edka.
    Do programu nynφ op∞t p°idßme klφΦovΘ slovo virtual. Tφm zm∞nφme metodu na virtußlnφ a zajistφme dynamickΘ sestavovßnφ neboli polymorfismus. Nynφ se urΦuje volanß metoda zprava (je to virtußlnφ metoda) na zßklad∞ skuteΦnΘho objektu na kter² ukazatel ukazuje a to a╛ p°i provßd∞nφ v²poΦtu (nenφ-li pou╛ito virtual je volanß metoda urΦena ji╛ p°i p°ekladu programu). Toto dynamickΘ sestavovßnφ je v n∞kter²ch situacφch velmi u╛iteΦnΘ. Virtußlnφ funkce musφ b²t implementovßna v rodiΦovskΘ t°φd∞ a v odvozen²ch t°φdßch musφ b²t stejnΘho typu, mφt stejn² poΦet a typy parametr∙. Ve v╣ech odvozen²ch t°φdßch musφ b²t jejφ hlaviΦka identickß. KlφΦovΘ slovo virtual nemusφ b²t ji╛ u virtußlnφch funkcφ v odvozen²ch t°φdßch pou╛ito.
    V  na╣em programu p°idejte i do t°φdy nakladni metodu zprava a ov∞°te, ╛e tato metoda bude pou╛φvßna v rßmci tΘto t°φdy namφsto metody zd∞d∞nΘ od t°φdy p°edka.
  9. Vrßtφme se op∞t k souborov²m datov²m proud∙m. Soubory mohou b²t otevφrßny v r∙zn²ch re╛imech. Nap°. n∞kdy m∙╛eme chtφt p°idat data na konec existujφcφho proudu namφsto vytvß°enφ novΘho souboru. V tomto p°φpad∞ soubor otevφrßme v re╛imu app. To zadßvßme v konstruktoru ofstream:

  10. ofstream vystsoubor("Test.dat", ios::app);
    Tento soubor byl otev°en tak, ╛e v╣echna data budou zapisovßna na konec souboru. Pov╣imn∞te si, ╛e p°φznak app pou╛φvß operßtor rozsahu pro t°φdu ios. To proto, ╛e tyto p°φznaky jsou definovßny ve t°φd∞ ios (prap°edek v╣ech t°φd datov²ch proud∙). Specifikßtory re╛im∙ souborov²ch datov²ch proud∙ jsou uvedeny v nßsledujφcφ tabulce:
     
    Specifikßtor Popis
    app Otevφrß soubor a v╣echna novß data umis╗uje na jeho konec.
    ate Ukazatel souboru je p°i otev°enφ umφst∞n na konec souboru.
    in Otevφrß soubor pro vstup (Φtenφ). Je to implicitnφ p°φznak pro t°φdu ifstream.
    out Otevφrß soubor pro v²stup (zßpis). Je to implicitnφ p°φznak pro t°φdu ofstream.
    binary Otevφrß soubor v binßrnφm re╛imu (implicitn∞ je soubor otevφrßn v textovΘm re╛imu).
    trunc Otevφrß soubor a zru╣φ jeho obsah (pokud nenφ specifikovßno app nebo ate, pak trunc je implicitnφ).
    Je mo╛no pou╛φt i vφce specifikßtor∙ najednou. Nap°. kdy╛ otvφrßme soubor v binßrnφm re╛imu pro p°idßvßnφ nov²ch dat na konec souboru, pak pou╛ijeme:
    ofstream vystsoubor("test.dat", ios::app | ios::binary);
  11. S binßrnφmi daty se pracuje jin²m zp∙sobem ne╛ s textov²mi daty. Data musφ b²t zapsßna v n∞jakΘm logickΘm uspo°ßdßnφ a Φtena stejn²m zp∙sobem. To nßm usnad≥ujφ struktury. Nap°. struktura:

  12. struct osoba {
      char Jmeno[20];
      char Telefon[20];
      int Vek;
      int IdentCis;
    };
    poskytuje logickΘ uspo°ßdßnφ dat. Pro zßpis obsahu tΘto struktury do souboru pou╛ijeme t°φdu ofstream takto:
    osoba MojeData = {"Karel Novßk", "nenφ", 41, 1};
    ofstream vystsoubor("jmena.dat", ios::binary);
    vystsoubor.write((char *)&MojeData, sizeof(osoba));
    Metoda write t°φdy ofstream akceptuje jako prvnφ parametr char * a musφme tedy adresu struktury p°etypovat. PoΦet zapsan²ch slabik je urΦen druh²m parametrem. ╚tenφ binßrnφch dat je takΘ snadnΘ:
    ifstream vstsoubor("jmena.dat", ios::binary);
    if (!vstsoubor) return 0;
    osoba MojeData;
    vstsoubor.read((char *)&MojeData, sizeof(osoba));
  13. Jednou z d∙le╛it²ch v∞cφ p°i pou╛φvßnφ t°φd souborov²ch proud∙ je pozice souboru. Pozice souboru je Φφselnß hodnota, kterß urΦuje nßsledujφcφ Φtenou slabiku (v p°φpad∞ ifstream) nebo zapisovanou slabiku (v p°φpad∞ ofstream). Po otev°enφ souboru je pozice souboru nastavena na 0. Pokud p°eΦteme 10 slabik dat, pak se pozice souboru zm∞nφ na 10. Po p°eΦtenφ dal╣φch 20 slabik dat mß pozice souboru hodnotu 30. Pozice souboru je automaticky aktualizovßna p°i Φtenφ a zßpisu dat.

  14. I kdy╛ pozice souboru je automaticky aktualizovßna, jsou metody, kterΘ m∙╛eme pou╛φt k zji╣t∞nφ nebo nastavenφ pozice souboru. Pro t°φdu ifstream metoda seekg nastavuje pozici souboru na specifikovanou hodnotu a metoda tellg vracφ souΦasnou pozici souboru. Pomocφ t∞chto dvou metod m∙╛eme urΦovat, kterß data budou Φtena ze souboru. Pro t°φdu ofstream tyto ·lohy provßd∞jφ metody seekp a tellp.
    JednotlivΘ slabiky z binßrnφho souboru lze Φφst metodou get a zapisovat je metodou put. Nap°. nßsledujφcφ ·sek programu vytvß°φ kopii souboru:
    ifstream vstsoubor("jmena.dat", ios::binary);
    ofstream vystsoubor("docasny.soub", ios::binary);
    vstsoubor.seekg(0, ifstream::end);
    int pocetSlabik = vstsoubor.tellg();
    vstsoubor.seekg(0);
    for (int i = 0; i < pocetSlabik; i++) {
      char c;
      vstsoubor.get(c);
      vystsoubor.put(c);
    }
    Pov╣imn∞te si pou╛itφ metod seekg a tellg v p°edchozφm k≤du. Prvnφ metoda seekg p°esouvß indikßtor pozice souboru na konec souboru (0 slabik od ifstream::end - urΦuje konec souboru). Metoda tellg zjistφ pozici souboru, tj. velikost souboru ve slabikßch. Druhß metoda seekg p°esune indikßtor pozice na zaΦßtek souboru a v cyklu jsou postupn∞ zpracovßvßny jednotlivΘ slabiky souboru.
  15. P°edpoklßdejme, ╛e mßme soubor, ve kterΘm je ulo╛eno 1000 zßznam∙ a pot°ebujeme p°eΦφst zßznam Φφsla 999. M∙╛eme to vy°e╣it tak, ╛e v cyklu budeme Φφst jednotlivΘ zßznamy a╛ do po╛adovanΘho zßznamu nebo nastavit pozici souboru p°φmo na zßznam 999 a p°eΦφst prßv∞ jen po╛adovan² zßznam. Nßhodn² p°φstup k zßznam∙m souboru je efektivnφ v p°φpad∞, kdy soubor je tvo°en zßznamy znßmΘ dΘlky nebo jestli╛e znßme p°esn∞ rozmφt∞nφ zßznam∙ v souboru. Pokud znßme velikost zßznamu, pak pozici zφskßme jednoduch²m v²poΦtem. Nap°. v p°φpad∞ struktury osoba to bude:

  16. int pos = 998 * sizeof(osoba);
    Prvnφ zßznam je na pozici 0 a tedy 999 zßznam je na pozici 998 * velikost struktury. M∙╛eme tedy otev°φt soubor, p°ejφt na pozici zßznamu 999 a zßznam p°eΦφst:
    ifstream vstsoubor("jmena.dat", ios::binary);
    vstsoubor.seekg(pos);
    osoba MojeData;
    vstsoubor.read((char *)&MojeData, sizeof(osoba));
    Podobn∞ m∙╛eme nahradit nebo aktualizovat zßznam v souboru:
    ofstream vystsoubor("jmena.dat", ios::binary | ios::ate);
    int pos = 998 * sizeof(osoba);
    vystsoubor.seekp(pos);
    vystsoubor.write((char *)&MojeData, sizeof(osoba));
    Zde musφme pou╛φt p°φznak ate (stejn² v²sledek dostaneme i p°i pou╛itφ app), abychom zabrßnili p°epsßnφ souboru p°i jeho otev°enφ.
  17. Pokud pot°ebujeme otev°φt soubor jak pro Φtenφ, tak i pro zßpis, pak musφme pou╛φt t°φdu fstream. Tato t°φda je potomkem t°φd ifstream a ofstream a tedy t°φda fstream d∞dφ v╣e od obou t°φd p°edk∙. Nßsledujφcφ ·sek k≤du ukazuje, jak pou╛φt fstream k p°ehozenφ prvnφho a desßtΘho zßznamu v souboru:

  18. osoba zaznam1;
    osoba zaznam2;
    fstream soubor("jmena.dat", ios::binary | ios::in | ios:: out);
    soubor.seekg(9 * sizeof(osoba));
    soubor.read((char *)&zaznam1, sizeof(osoba));
    soubor.seekg(0);
    soubor.read((char *)&zaznam2, sizeof(osoba));
    soubor.seekg(0);
    soubor.write((char *)&zaznam1, sizeof(osoba));
    soubor.seekg(9 * sizeof(osoba));
    soubor.write((char *)&zaznam2, sizeof(osoba));
    soubor.close();
    Abychom soubor mohli Φφst i zapisovat do n∞j, je nutno v konstruktoru fstream pou╛φt p°φznaky in i out.

NovΘ pojmy:


 
6. Zßklady OOP VI