Kurz C++ (15.)

Jak jsem slφbil minule, budeme se v dalÜφm pokraΦovßnφ v∞novat konstruktor∙m a destruktor∙m odvozen²ch t°φd. Probereme vφcenßsobnou a opakovanou d∞diΦnost a  pak se seznßmφme s tzv. virtußlnφmi metodami a abstraktnφmi t°φdami.

15.1. Konstruktory a destruktory odvozen²ch t°φd

    V minul²ch dφlech jsme se dozv∞d∞li, ₧e odvozenß t°φda obsahuje vÜechny prvky, a¥ ji₧ datovΘ nebo metody, zßkladnφ t°φdy. Toto je pravda, a₧ na pßr specißlnφch prvk∙, kter²mi jsou prßv∞ konstruktor, destruktor a jeÜt∞ operßtor p°i°azenφ, operator=().

    Je z°ejmΘ, ₧e zd∞d∞nφ konstruktoru a destruktoru zßkladnφ t°φdy by nem∞lo velk² smysl, nebo¥ nov∞ odvozenß t°φda mß jist∞ n∞jakΘ novΘ prvky, o kter²ch zßkladnφ t°φda nem∙₧e mφt tuÜenφ. Aby novß t°φda mohla zinicializovat svΘ prom∞nnΘ, obsahuje op∞t sv∙j vlastnφ konstruktor a jeho jmΘno je op∞t stejnΘ jako jmΘno t°φdy. Proto₧e vÜak novß t°φda obsahuje i prvky t°φdy zßkladnφ, je nutnΘ je zinicializovat. Uka₧me si krßtk² p°φklad a pak si °ekneme, co se p°i konstrukci vlastn∞ d∞je:

class A {
private:
    int a;
public:
    A() { a = 0; }
};

class B : public A {
private:
    int b;
public:
    B() { b = 0; }
};

    Po vytvo°enφ prom∞nnΘ typu B dojde k zavolßnφ konstruktoru tΘto t°φdy. Nejprve je vÜak zavolßn konstruktor t°φdy A, kter² se volß p°ed inicializacφ datov²ch slo₧ek odvozenΘ t°φdy. N∞kterΘ inicializace prvk∙ t°φdy B by toti₧ mohly b²t zßvislΘ na prvcφch z A. Vidφme, ₧e v tomto p°φpad∞ doÜlo k zavolßnφ jedinΘho definovanΘho konstruktoru. Pokud t°φda mß definovßn explicitnφ konstruktor a implicitnφ definovßn nenφ, musφme za°φdit jeho zavolßnφ v inicializaΦnφ Φßsti konstruktoru t°φdy B. P°φklad s explicitnφm volßnφm konstruktoru:

class A {
private:
    int a;
public:
    A(int _a) : a(_a) { ; }
};

class B : public A {
private:
    int b;
public:
    B(int _b, int _a) : b(_b), A(_a) { ; }
};

    Pokud tomu tak nebude, p°ekladaΦ ohlßsφ chybu p°i p°ekladu. V tomto p°φkladu si takΘ m∙₧ete vÜimnout, ₧e konstruktor odvozenΘ t°φdy v∞tÜinou obsahuje i parametry, kterΘ se pak p°edajφ konstruktoru zßkladnφ t°φdy.

    Stejnß pravidla platφ i pro destruktor, ale s tφm rozdφlem, ₧e nynφ se volßnφ destruktoru zßkladnφ t°φdy provede a₧ jako poslednφ p°φkaz destruktoru t°φdy odvozenΘ. Pro toto volßnφ nemusφme nic napsat, proto₧e destruktor mß ka₧dß t°φda pouze jeden. K volßnφ konstruktor∙ se vrßtφme jeÜt∞ jednou, a₧ se dovφme co je to vφcenßsobnß d∞diΦnost.

15.2. Vφcenßsobnß d∞diΦnost

    Jazyk C++ nßm umo₧≥uje uvΘst v seznamu rodiΦ∙ vφce t°φd. Pro tyto t°φdy, tedy i v p°φpad∞ jednoduchΘ d∞diΦnosti, platφ, ₧e musφ b²t pln∞ deklarovßny. Tφm se zabrßnφ nap°. specifikovßnφ jmΘna prßv∞ deklarovanΘ t°φdy jako p°edka. Ukß₧eme si p°φklad na vφcenßsobnou d∞diΦnost:

class A {
protected:
    int a;
public:
    A() { a = 0; }
};

class B {
protected:
    int b;
public:
    B() { b = 0; }
};

class C : public A, private B {
private:
    int c;
public:
    C() { c = 0; }
};

    Vidφme, ₧e pro r∙znΘ t°φdy m∙₧eme pou₧φt r∙znΘ specifikßtory p°φstupu. V tomto p°φpad∞ budou ve t°φd∞ C dostupnΘ nßsledujφcφ prom∞nnΘ: c s p°φstupov²m prßvem private, b takΘ s p°φstupov²m prßvem private a koneΦn∞ a s p°φstupov²m prßvem protected. VÜe vychßzφ z tabulky uvedenΘ v minulΘm pokraΦovßnφ kurzu. P°edstavme si ale situaci, kdy se v rodiΦovsk²ch t°φdßch budou vyskytovat prvky se shodn²m jmΘnem:

class A {
protected:
    int a;
public:
    A() { a = 0; }
};

class B {
protected:
    int a;
public:
    B() { a = 0; }
};

class C : public A, public B {
private:
    int c;
public:
    C() { c = 0; }
};

    Nynφ jsme odvozovali pomocφ public v obou p°φpadech, co₧ znamenß, ₧e p°φstupovß prßva v odvozenΘm objektu se nem∞nφ. T°φda C nynφ obsahuje prom∞nnΘ: c (private), a z B (protected) a koneΦn∞ a z A (takΘ protected). P°idßme do t°φdy C metodu Vypis(), kterß bude vypisovat prvek a:

Vypis() { cout << a << endl; }

    Po p°ekladu ale dostaneme varovßnφ, proto₧e p°ekladaΦ nevφ, kterΘ a vlastn∞ chceme. Tento problΘm se naz²vß konfliktem jmen. ╪eÜenφ je nßsledujφcφ: pokud budeme chtφt pou₧φt prom∞nnou a pochßzejφcφ z t°φdy A, musφme pou₧φt Φty°teΦku (::) a p°ed nφ uvΘst identifikßtor t°φdy z kterΘ prvek pochßzφ (nap°. A::a, B::a). Stejnß pravidla platφ i pro metody. Pro metody je vÜak t°eba jeÜt∞ zmφnit nßsledujφcφ p°φklad:

class A {
protected:
    int a;
public:
    A() { a = 0; }

    void fce() { ; }
};

class B : public A {
protected:
    int b;
public:
    B() { b = 0; }

    int fce(int _b) { return b; }
};

void main(void)
{
    B b;
    b.fce();
}

    V tomto p°φpad∞ p°ekladaΦ op∞t ohlßsφ chybu, proto₧e nevidφ bezparametrickou metodu A::fce(). Pro zavolßnφ sprßvnΘ metody musφme op∞t uvΘst b.A::fce().

    JeÜt∞ si povφme, v jakΘm po°adφ se zavolajφ konstruktory rodiΦovsk²ch t°φd. Platφ nßsledujφcφ jednoduchΘ pravidlo: konstruktory se zavolajφ v po°adφ, ve kterΘm jsou uvedeny v seznamu p°edk∙ v deklaraci odvozenΘ t°φdy. Je vhodnΘ zmφnit, ₧e p°edkem (platφ samoz°ejm∞ i pro jednoduchou d∞diΦnost) m∙₧e b²t op∞t odvozenß t°φda, potom dojde vlastn∞ k rekurzivnφmu volßnφ konstruktor∙.

15.2. Opakovanß d∞diΦnost a virtußlnφ d∞d∞nφ

    Nejprve si uka₧me p°φklad:

class A {
protected:
    int a;
public:
    A() { a = 0; }
};

class B : public A {
protected:
    int b;
public:
    B() { b = 0; }
};

class C : public A {
private:
    int c;
public:
    C() { c = 0; }
};

class D : public B, public C {
private:
    int d;
public:
    D() { d = 0; }
};

    T°φdy B a C jsou potomky t°φdy A. T°φda D pak d∞dφ vlastnosti t°φd B a C a tφm tedy i vÜechny jejich prvky. Znamenß to, ₧e t°φda D obsahuje jak t°φdu A zd∞d∞nou po B , tak t°φdu A zd∞d∞nou po C. AΦkoliv tedy nem∙₧eme v seznamu p°edk∙ uvΘst stejnou t°φdu dvakrßt, tak v tomto p°φpad∞ je p°esto A dvakrßt zd∞d∞na. To, ₧e t°φda C obsahuje dv∞ prom∞nnΘ a si m∙₧eme vyzkouÜet p°idßnφm nßsledujφcφ metody, kterou p°idßme do t°φdy D:

class D : public B, public C {
private:
    int d;
public:
    D() { d = 0; }

    void Test()
    {
        // a = 0;    // Nelze
        B::a = 1;
        C::a = 5;

        cout << "B::a = " << B::a << endl;
        cout << "C::a = " << C::a << endl;
    }
};

    Zdrojov² k≤d naleznete v sekci Downloads (projekt Dedic1).

    To se n∞kdy sice m∙₧e hodit, ale ve v∞tÜin∞ p°φpad∙ je to na obtφ₧. Kdybychom si °ekli, ₧e budeme vÜude pou₧φvat jen jednu z prom∞nn²ch, nap°. tu z B zßpisem B::b, a druhou nechßme b²t, pak bychom jen zbyteΦn∞ pl²tvali pam∞tφ. JeÜt∞ si ukß₧eme nßsledujφcφ t°φdu:

class E : public A, public B, public C {
private:
    int e;
public:
    E() { e = 0; }
};

    Pokud budeme mφt takto deklarovanou t°φdu, p°ekladaΦ nßm ohlßsφ nßsledujφcφ chybu: t°φda A nenφ p°φstupnß, proto₧e je u₧ zßkladnφ t°φdou B a C. D∙vodem je, ₧e k prom∞nnΘ a bychom mohli p°istupovat pouze zßpisem A::a, ale to by m∙₧e znamenat i prom∞nnou a v B nebo C (A je rodiΦem B i C ). Abychom vφcenßsobnΘmu zd∞d∞nφ spoleΦnΘho p°edka p°edeÜli, existuje klφΦovΘ slovo virtual, kterΘ lze uvΘst mezi specifikßtory p°φstupu, p°iΦem₧ nezßle₧φ jestli uvedeme nap°φklad public virtual nebo virtual public. Pokusφme se tedy p°idat do seznamu prvk∙ t°φdy E klφΦovΘ slovo virtual p°ed t°φdu A. P°i p°ekladu dostaneme ale stßle stejnou chybu, je toti₧ nutnΘ uvΘst virtual jeÜt∞ do seznamu p°edk∙ t°φd B a C. Pro ·plnost jeÜt∞ upravenß verze:

class A {
protected:
    int a;
public:
    A() { a = 0; }
};

class B : public virtual A {
protected:
    int b;
public:
    B() { b = 0; }
};

class C : public virtual A {
private:
    int c;
public:
    C() { c = 0; }
};

class D : public B, public C {
private:
    int d;
public:
    D() { d = 0; }
};

class E : public virtual A, public B, public C {
private:
    int e;
public:
    E() { e = 0; }

   
void Test()
    {
        B::a = 1;
        C::a = 5;
        a = 0;        // Pokud dedime virtualne, pak je to povoleno

        cout << "a    = " << a << endl;
        cout << "B::a = " << B::a << endl;
        cout << "C::a = " << C::a << endl;
    }

};

    Zdrojov² k≤d naleznete v sekci Downloads (projekt Dedic2).

    Na v²stupu programu vidφme, ₧e hodnoty vÜech t°φ prom∞nn²ch jsou stejnΘ a rovnΘ hodnot∞ poslednφho p°i°azenφ. Je to tedy  jedna "pravß" prom∞nnß a ostatnφ zßpisy jsou pak referencemi na tuto prom∞nnou, Φφm₧ se uÜet°φ mφsto v pam∞ti.

    M∙₧e takΘ nastat situace, kdy je t°φda d∞d∞na n∞kolikrßt virtußln∞ a n∞kolikrßt nevirtußln∞. V tom p°φpad∞ bude v odvozenΘ t°φd∞ jednou za vÜechna virtußlnφ d∞d∞nφ a jednou za ka₧dΘ d∞d∞nφ  nevirtußlnφ.

    Konstruktory se volajφ nejprve pro t°φdy uvedenΘ s klφΦov²m slovem virtual a to op∞t v po°adφ v jakΘm jsou uvedeny v seznamu p°edk∙, po nich teprve p°ijdou na °adu konstruktory nevirtußln∞ d∞d∞n²ch t°φd.

15.3. Polymorfizmus, virtußlnφ metody a abstraktnφ t°φdy

   Polymorfizmus je vlastnost, kterß Φinφ objektov∞ orientovanΘ programovßnφ pou₧iteln²m. Polymorfizmus znamenß, ₧e potomek n∞jakΘ t°φdy m∙₧e kdekoliv zastoupit tuto t°φdu. Pokud se vrßtφme k naÜemu p°φkladu se zoologickou zahradou, tak nßm prßv∞ polymorfizmus zajistφ, ₧e m∙₧eme mφsto instance t°φdy CZivocich napsat nap°φklad instanci t°φdy CZirafa nebo CLev. Mo₧nß se ptßte na co to m∙₧e b²t dobrΘ. Nßsledujφcφ ukßzka je sestavena z vφce soubor∙ a vysv∞tlφ, k Φemu m∙₧e b²t polymorfizmus dobr²:

CZivocich.h:

#ifndef _ZIVOCICH_H_
  #define _ZIVOCICH_H_

  class CZivocich {
  protected:
      int m_dwMaxVek;
      int m_dwVek;
  public:
      CZivocich(int _dwMaxVek, int _dwVek) : m_dwMaxVek(_dwMaxVek), m_dwVek(_dwVek) { ; }

      virtual HledejPotravu() { ; }
      virtual void Zij() { ; }
  };

#endif

CLev.h:

#ifndef _LEV_H_
  #define _LEV_H_

  class CLev : public CZivocich {
  protected:

  public:
      CLev() : CZivocich(15, 0) { ; }

      virtual HledejPotravu();
      virtual void Zij();
  };

#endif

CLev.cpp:

#include <iostream.h>

#include "Zivocich.h"
#include "Lev.h"

CLev::HledejPotravu()
{
    cout << "Lev : hledam nejake maso" << endl;
}

void CLev::Zij()
{
    if(-1 == m_dwVek)
    {
        cout << "Lev : jsem uz po smrti" << endl;
    }
    else
    {
        if(m_dwVek < m_dwMaxVek)
        {
            HledejPotravu(); // Najime se
            m_dwVek++; // Posuneme o den
            cout << "Lev : mam prave narozeniny " << m_dwVek << endl;
        }
        else
        {
            cout << "Lev : umiram ve veku (" << m_dwVek << ")" << endl;
            m_dwVek = -1;
        }
    }
}

CZirafa.h:

#ifndef _ZIRAFA_H_
  #define _ZIRAFA_H_

  class CZirafa : public CZivocich {
  protected:

  public:
      CZirafa() : CZivocich(20, 0) { ; }

      virtual HledejPotravu();
      virtual void Zij();
  };

#endif

CZirafa.cpp:

#include <iostream.h>

#include "Zivocich.h"
#include "Zirafa.h"

CZirafa::HledejPotravu()
{
    cout << "Zirafa : hledam nejakou peknou zelen" << endl;
}

void CZirafa::Zij()
{
    if(-1 == m_dwVek)
    {
        cout << "Zirafa : jsem uz po smrti" << endl;
    }
    else
    {
        if(m_dwVek < m_dwMaxVek)
        {
            HledejPotravu(); // Najime se
            m_dwVek++; // Posuneme o den
            cout << "Zirafa : mam prave narozeniny " << m_dwVek << endl;
        }
        else
        {
            cout << "Zirafa : umiram ve veku (" << m_dwVek << ")" << endl;
            m_dwVek = -1;
        }
    }
}

Virt1.cpp (hlavnφ soubor):

#include <iostream.h>
#include <string.h>

#include "Zivocich.h"
#include "Lev.h"
#include "Zirafa.h"

int main(int argc, char* argv[])
{
    CZivocich *zvirata[2];

    zvirata[0] = new CZirafa();
    zvirata[1] = new CLev();

    // Zij pet dni
    for(int i = 0; i < 5; i++)
    {
        zvirata[0]->Zij();
        zvirata[1]->Zij();
    }

    char c;
    cin >> c;

    return 0;
}

    Zdrojov² k≤d naleznete v sekci Downloads (projekt Virt1).

    Zam∞°φme se zatφm jen na hlavnφ program: nejprve vytvo°φme pole slo₧enΘ z ukazatel∙ na rodiΦovskou t°φdu (CZivocich). Pak pomocφ dynamickΘ alokace vytvo°φme dv∞ zvφ°ata, reprezentovanß t°φdami CLev a CZirafa. Konstruktory jednotliv²ch t°φd pak nastavφ maximßlnφ v∞k zvφ°ete. Pak u₧ jen ve smyΦce zavolßme pro jednotlivΘ prvky pole metodu Zij(), kterß ve svΘm t∞le ov∞°uje v∞k a pokud zvφ°e stßle ₧ije, zavolß metodu HledejPotravu(). VÜimn∞te si, ₧e aΦkoliv volßme metodu Zij() pro instanci typu CZivocich, kterß je ale prßzdnß, tak p°ekladaΦ zajistφ zavolßnφ metody Zij() pro sprßvnou t°φdu (tedy CLev nebo CZirafa). Tento k≤d by Üel takΘ p°epsat s vyu₧itφm jen jednΘ virtußlnφ funkce, proto₧e v tomto p°φpad∞ se ob∞ funkce shodujφ, tedy krom∞ vypisovanΘho jmΘna zvφ°ete. Ale proto₧e se ₧ivot r∙zn²ch zvφ°at m∙₧e liÜit, rozhodl jsem se pro °eÜenφ v podob∞ dvou virtußlnφch funkcφ. K≤d naleznete v sekci Downloads (projekt Virt2).

    Nynφ je Φas pov∞d∞t si, jak tohle p°ekladaΦ zajistφ. Nevirtußlnφ metody, kter²mi jsme se zab²vali doposud, vyu₧φvaly tzv. ΦasnΘ vazby (angl. early binding). P°i ΦasnΘ vazb∞ p°ekladaΦ urΦφ typ instance ji₧ v okam₧iku p°ekladu. Zavolßnφ metody je pak jen otßzkou adresace. Naopak t°φdy obsahujφcφ alespo≥ jednu virtußlnφ metodu (vysv∞tlφme za chvφli) vyu₧φvajφ tzv. pozdnφ vazby (angl. late binding). Pozdnφ vazba se pou₧ije p°i volßnφ metod pomocφ ukazatel∙, referencφ nebo p°i volßnφ metody z t∞la jinΘ metody (to je vid∞t v projektu Virt2). SpoΦφvß v tom, ₧e p°ekladaΦ musφ pro ka₧dΘ zavolßnφ virtußlnφ metody p°idat ·sek k≤du, kter² zajistφ za b∞hu programu v²poΦet adresy, kde se nachßzφ sprßvnß metoda. ZajiÜt∞no je to tφm, ₧e p°ekladaΦ p°ipojφ k datov²m prvk∙m t°φdy tabulku adres virtußlnφch metod, kterß je pro programßtora skrytß. Z tΘto tabulky se pak za b∞hu programu urΦuje, kterß metoda se mß opravdu zavolat. Znamenß to tedy jistΘ operace navφc, kterΘ samoz°ejm∞ ovlivnφ v²kon aplikace.

    Virtußlnφ metodou se stane libovolnß metoda (krom∞ konstruktoru), p°ed kterou p°idßme klφΦovΘ slovo virtual. Specifikaci virtual neopakujeme u implementace metody. Virtußlnφ metody se stejn∞ jako normßlnφ metody d∞dφ, tuto zd∞d∞nou metodu m∙₧eme ve t°φd∞ zm∞nit nebo ponechat stejnou jako v rodiΦovskΘ t°φd∞. P°i zm∞n∞ lze pak p∙vodnφ verzi vyvolat op∞t pomocφ Φty°teΦky (::). Pokud je v zßkladnφ t°φd∞ metoda oznaΦena jako virtual, pak je virtußlnφ i ve vÜech odvozen²ch t°φdßch.V novΘ t°φd∞ nenφ ji₧ nutnΘ u tΘto metody klφΦovΘ slovo znovu opakovat (jß ho ale rad∞ji uvßdφm). ╚ist∞ virtußlnφ metoda je metoda s nßsledujφcφ deklaracφ:

    virtual jmeno(parametry) = 0;

    T°φdu, kterß obsahuje alespo≥ jednu Φist∞ virtußlnφ metodu, naz²vßme abstraktnφ t°φdou. Ve v²Üe uvedenΘm p°φkladu bychom klidn∞ mohli z virtußlnφch metod HledejPotravu() a Zij() ud∞lat p°idßnφm = 0 Φist∞ virtußlnφ metody. Tyto metody mohou sice mφt t∞lo, ale obvykle se to nepou₧φvß. Abstraktnφ t°φdy obsahujφ metody, kterΘ jsou spoleΦnΘ vÜem dalÜφm odvozen²m t°φdßm, ale mohou obsahovat i datovΘ prvky. Platφ pro n∞ navφc pravidlo, ₧e je lze pou₧φt jen jako p°edky jin²ch t°φd. Znamenß to, ₧e nem∙₧eme vytvo°it instanci abstraktnφ t°φdy, pou₧φt ji jako parametr p°edßvan² hodnotou nebo v²sledek funkce vracen² hodnotou. To, ₧e nelze vytvo°it instanci abstraktnφ t°φdy neznamenß, ₧e nem∙₧e mφt konstruktor. Konstruktor je vyvolßn v rßmci vytvß°enφ odvozenΘho objektu a vyu₧φvß se jako obvykle k inicializaci p°φpadn²ch datov²ch slo₧ek tΘto t°φdy.

    Konstruktor nem∙₧e b²t virtußlnφ, nebo¥ je p°i vytvo°enφ objektu v₧dy znßm typ vytvß°enΘ instance. Naopak destruktor ve v∞tÜin∞ p°φpad∙ virtußlnφ b²t musφ. Uka₧me si nßsledujφcφ p°φklad:

#include <iostream.h>

class A {
protected:
    int *lp_dwa;
public:
    A()
    {
        cout << "Alokuji lp_dwa" << endl;
        lp_dwa = new int[10];
    }

    ~A()
    {
        cout << "Mazu pole lp_dwa" << endl;
        if(lp_dwa) delete lp_dwa;
    }
};

class B : public A {
protected:
    int *lp_dwb;
public:
    B()
    {
        cout << "Alokuji lp_dwb" << endl;
        lp_dwb = new int[20];
    }

    ~B()
    {
        cout << "Mazu pole lp_dwb" << endl;
        if(lp_dwb) delete lp_dwb;
    }
};

int main(int argc, char* argv[])
{
    A *a;
    a = new B;
   
    delete a;

    char c;
    cin >> c;

    return 0;
}

    Zdrojov² k≤d naleznete v sekci Downloads (projekt Virt3).

    Po spuÜt∞nφ tΘto verze vidφme, ₧e prob∞hly sice dv∞ alokace, ale pam∞¥ byla uvoln∞na jen pro objekt A. Jak jsme uvedli, tak p°i konstrukci je znßm typ ukazatele. P°i destrukci objektu tomu tak nenφ a proto se volß destruktor t°φdy na kter² je typovßna prom∞nnß (tedy A). To mß za nßsledek neuvoln∞nφ dynamicky alokovanΘ pam∞ti pro t°φdu B. ProblΘm vy°eÜφ nßsledujφcφ ·prava:

class A {
protected:
    int *lp_dwa;
public:
    A()
    {
        cout << "Alokuji lp_dwa" << endl;
        lp_dwa = new int[10];
    }

    virtual ~A()
    {
        cout << "Mazu pole lp_dwa" << endl;
        if(lp_dwa) delete lp_dwa;
    }
};

    Nynφ vidφme, ₧e byla uvoln∞na pam∞¥ pro ob∞ dv∞ pole. TakΘ je vid∞t v jakΘm po°adφ jsou volßny konstruktory a destruktory rodiΦovskΘ a odvozenΘ t°φdy. Pokud bychom znali typ instance, co₧ v∞tÜinou nem∙₧eme urΦit, pak bychom mohli samoz°ejm∞ ve volßnφ delete p°etypovat A na B:

int main(int argc, char* argv[])
{
    A *a;
    a = new B;
   
    delete (B*)a;

    char c;
    cin >> c;

    return 0;
}

15.4. Co bude p°φÜt∞?

    Pro dneÜek je to tedy vÜe a p°φÜt∞ se jeÜt∞ jednou a takΘ naposledy vrßtφme k p°et∞₧ovßnφ operßtor∙, tentokrßt v souvislosti s polymorfizmem. Podφvßme se na Üablony a pravd∞podobn∞ i v²jimky.

PřφÜtě nashledanou.

Ond°ej BuriÜin