Kurz C++ (16.)

Vφtejte u dalÜφho z kurz∙ v∞novan²ch jazyku C++. V dneÜnφm dφlu dokonΦφme polymorfizmus ve vztahu k p°et∞₧ovßnφ operßtor∙. DalÜφ odstavec bude v∞novßn Üablonßm. P°eji p∞knΘ Φtenφ.

16.1. Polymorfizmus a operßtory

    Op∞t si zopakujeme, ₧e se ned∞dφ p°i°azovacφ operßtor operator=(). Operßtory new a delete definovanΘ jako metody se d∞dφ jako statickΘ metody. Ostatnφ operßtory se d∞dφ a mohou b²t, podobn∞ jako metody, virtußlnφ. Zavolßnφm virtußlnφho operßtoru dojde, op∞t stejn∞ jako u metod, k zavolßnφ operßtoru p°φsluÜnΘmu k levΘmu operandu operßtorovΘ funkce. Uka₧me si p°φklad z naÜφ zoologickΘ zahrady a p°et∞₧me si operßtor << tak, aby slou₧il pro naklßdßnφ zvφ°at na nßkla∩ßk. Je jasnΘ, ₧e r∙znß zvφ°ata se musφ naklßdat rozdφln²m zp∙sobem a proto budeme muset v t°φd∞ CNakladak p°etφ₧it operßtory pro vÜechny druhy zvφ°at, kterß budeme chtφt p°evß₧et. Budeme p°edpoklßdat, ₧e na nßkladnφ v∙z se vejde vÜe, co na n∞j budeme chtφt ulo₧it:

Nakladak.h:

#ifndef _NAKLADAK_H_
  #define _NAKLADAK_H_

  class CNakladak {
  private:
      int m_iBenzin;
  public:
      CNakladak& operator<<(CZirafa _zir);
      CNakladak& operator<<(CLev _lev);
  };

#endif

Nakladak.cpp:

#include <iostream.h>

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

#include "Nakladak.h"

CNakladak& CNakladak::operator<<(CZirafa _zir)
{
   
// ulozeni zirafy na nakladak
    cout << "Ukladam zirafu na nakladak" << endl;

    return *this;
// vratime tento objekt abychom mohli retezit jako pri cout
}

CNakladak& CNakladak::operator<<(CLev _lev)
{
   
// ulozeni lva na nakladak
    cout << "Ukladam lva na nakladak" << endl;

    return *this;
// vratime tento objekt abychom mohli retezit jako pri cout
}

    VÜimn∞te si, ₧e operßtory vracφ proud, abychom je mohli °et∞zit, jak vidφme ve funkci main():

int main(int argc, char* argv[])
{
    CNakladak nakladak;
    CLev lev;
    CZirafa zirafa;

    nakladak << lev << zirafa;
// ulozime je na nakladak

    char c;
    cin >> c;

    return 0;
}

    Pokud bychom nynφ p°idali dalÜφ zvφ°e, museli bychom najφt t°φdu CNakladak a do nφ pak p°idat k≤d pro ulo₧enφ tohoto zvφ°ete. Podφvejme se na jin² zp∙sob °eÜenφ tohoto problΘmu, kde vyu₧ijeme virtußlnφ funkce:

Nakladak.h:

#ifndef _NAKLADAK_H_
  #define _NAKLADAK_H_

  class CZivocich;   
// Predebezna deklarace

  class CNakladak {
  private:
      int m_iBenzin;   
// Napriklad
  public:
      CNakladak& operator<<(CZivocich& _ziv);
  };

#endif

Nakladak.cpp:

#include "Zivocich.h"
#include "Nakladak.h"

CNakladak& CNakladak::operator<<(CZivocich& _ziv)
{
    // ulozeni zivocicha na nakladak
    return _ziv.Naloz(*this);
}

Zivocich.h:

#ifndef _ZIVOCICH_H_
  #define _ZIVOCICH_H_

  class CNakladak;   
// Predebezna deklarace

  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() { ; }
      virtual CNakladak& Naloz(CNakladak& _nakl) = 0;   
// Ciste virtualni
  };

#endif

Lev.h:

#ifndef _LEV_H_
  #define _LEV_H_

  class CLev : public CZivocich {
  protected:

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

      virtual HledejPotravu();
      virtual void Zij();
      virtual CNakladak& Naloz(CNakladak& _nakl);
  };

#endif

Lev.cpp:

#include <iostream.h>

#include "Nakladak.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;
        }
    }
}

CNakladak& CLev::Naloz(CNakladak& _nakl)
{
    cout << "Nakladam lva na nakladak" << endl;
    return _nakl;
}

    Pro objekt CZirafa je to analogickΘ. Funkce main() z∙stßvß stejnß jako v minulΘm p°φpadu.

    V druhΘm p°φkladu mßme operßtor << t°φdy CNakladak p°etφ₧en jen jednou. Tento operßtor zavolß dφky virtußlnφm funkcφm sprßvnou metodu podle pravΘho operandu tohoto operßtoru. Kdy₧ nynφ budeme chtφt p°idat dalÜφ zvφ°e, staΦφ pouze p°epsat metodu Naloz() a nemusφme se ji₧ vracet k hotovΘmu objektu CNakladak. To n∞kdy toti₧ ani nemusφ b²t mo₧nΘ - nap°. n∞jakß knihovna.

16.2. èablony

16.2.1. P°φklady

    Nejprve si ukß₧eme jednoduchou Üablonovou funkci a jako dalÜφ p°φklad si ukß₧eme p°φklad s t°φdami. NejΦast∞ji uvßd∞n²m p°φkladem je funkce pro urΦenφ minima ze dvou objekt∙. NapiÜme nßsledujφcφ funkci:

int min(int a, int b)
{
    if(a < b) { return a; }

    return b;
}

    Jen poznamenejme, ₧e tuto funkci lze zapsat pohodln∞ji pomocφ ternßrnφho operßtoru:

int min(int a, int b)
{
    return a < b ? a : b;
}

    NaÜφm dalÜφm po₧adavkem bude, aby tato funkce fungovala i pro jinΘ typy (nap°. reßlnß Φφsla (double), znaky). Jednφm °eÜenφm je p°et∞₧ovßnφ funkcφ, kdy bychom definovali funkce se stejn²m jmΘnem a r∙zn²mi parametry. Mnohem elegantn∞jÜφ je vÜak vyu₧itφ Üablon:

template <class T> T min(T a, T b)
{
    return a < b ? a : b;
}

    NovΘ klφΦovΘ slovo template znamenß, ₧e se jednß o Üablonovou funkci. V zßvorkßch je pak uveden typov² parametr T. KlφΦovΘ slovo class znamenß, ₧e skuteΦn²m parametrem ÜablonovΘ funkce m∙₧e b²t libovoln² datov² typ (objektov² i neobjektov²). Ve funkci main() pak m∙₧eme Üablonovou funkci min() pou₧φt:

int main(int argc, char* argv[])
{
    int a = -5, b = -8;
    double f = 5.31f, g = 5.32f;

    cout << min(a, b) << endl;
    cout << min(f, g) << endl;

    char
d;
    cin >>
d;

    return 0;
}

    Jak tohle "kouzlo" funguje? JednoduÜe, p°ekladaΦ nejprve zjistφ, ₧e takovΘto funkce neznß, ale vidφ, ₧e znß Üablonu pro funkci min(). Dosadφ tedy do ÜablonovΘ funkce za typov² parametr T v prvnφm p°φpad∞ int a ve druhΘm pak double. Vzniknou tak instance funkcφ int min(int, int) a double min(double, double), kterΘ pak p°ekladaΦ p°elo₧φ. Nynφ m∙₧eme pou₧φt libovoln² typ a dosadit ho jako parametr funkce min(). Libovoln²m typem m∙₧e b²t, krom∞ vestav∞n²ch datov²ch typ∙, i instance n∞jakΘho nßmi definovanΘho objektovΘho typu, kter² mß p°etφ₧en p°φsluÜn² operßtor (v naÜem p°φpad∞ operßtorovou funkci operator<()).

    Musφme si ale dßt pozor na nßsledujφcφ, nefungujφcφ k≤d:

int a = -5;
char c = 'a';

cout << min(a, c) << endl;

    P°ekladaΦ se toti₧ nem∙₧e rozhodnout, kter²m typem by m∞l nahradit typov² parametr T. M∙₧eme mu samoz°ejm∞ pomoci p°etypovßnφm jednoho z parametr∙ funkce na typ, pomocφ kterΘho chceme provΘst porovnßnφ, tedy nap°.:

cout << min(a, (int)c) << endl;

    Napov∞d∞t m∙₧eme i p°ipojenφm typu v zßvorkßch, jak je vid∞t v nßsledujφcφ ukßzce:

cout << min<int>(a, c) << endl;

    Pokud si vzpomeneme na makra, kde by se minimum dalo implementovat takto:

#define MIN(a, b) (((a) < (b)) ? (a) : (b))

    Pak m∙₧eme na Üablony pohlφ₧et jako na lepÜφ makra. èablony jsou zpracovßvßny p°ekladaΦem, kde₧to makra preprocesorem.

    Nynφ si ukß₧eme t°φdu, kterß implementuje tzv. lineßrnφ spojov² seznam, co₧ je velmi Φasto pou₧φvanß datovß struktura. Uzlem spojovΘho seznamu nazveme strukturu obsahujφcφ n∞jakΘ datovΘ prvky (v naÜem p°φpad∞ jednoduchou prom∞nnou typu int) a dßle ukazatel na dalÜφ uzel spojovΘho seznamu. Lineßrnφ spojov² seznam je pak °et∞zem takov²chto prvk∙. Zde se budeme zab²vat jen jednoduchou implementacφ spojovΘho seznamu, ale proto₧e ka₧d² program pracuje s daty, vyjde v blφzkΘ budoucnosti Φlßnek v∞novan² prßv∞ datov²m strukturßm. V n∞m se budeme zab²vat nap°φklad zßsobnφkem, frontami a takΘ stromy. Ale te∩ u₧ zp∞t k implementaci, kterß bude sestßvat ze dvou t°φd. Prvnφ t°φdou bude CUzel, co₧ bude uzel spojovΘho seznamu. Druhou t°φdou bude t°φda CSeznam, kterß bude zajiÜ¥ovat operace jako t°eba p°idßnφ prvku, vypuÜt∞nφ prvku a vyhledßnφ prvku. Nejprve si ukß₧eme k≤d pro t°φdu CUzel a °ekneme si n∞co o jejφm pou₧itφ:

Uzel.h:

// Uzel spojoveho seznamu

template<class T> class CUzel {
private:
    T m_Data;

    CUzel* m_lpDalsi;
public:
    CUzel(T _data) : m_Data(_data), m_lpDalsi(NULL) { ; }

    void NastavData(T _data) { m_Data = _data; }
// ulozi data
    void NastavNasl(CUzel* _dalsi) { m_lpDalsi = _dalsi; }
// ulozi ukazatel na dalsi prvek   
   
    T Data() { return m_Data; }       
// vrati data
    CUzel* Nasl() { return m_lpDalsi; }       
// vrati nasledovnika

    void Vypis() { cout << "Vypis :" << m_Data << endl; }
};

    Vytvo°ili jsme Üablonovou t°φdu, kterß je vzorem pro vytvß°enφ instancφ. Tyto instance vzniknou, podobn∞ jako u funkcφ, dosazenφm n∞jakΘho skuteΦnΘho parametru za typov² parametr T. Nßsleduje soubor main.cpp ukazujφcφ, jak s Üablonou pracovat:

#include <stdio.h>
#include <iostream.h>

#include "Uzel.h"

int main(int argc, char* argv[])
{
    CUzel<int> m_intUzel(4);
    CUzel<double> m_dblUzel(15.32f);
    CUzel m_obecnyUzel();
// Nekde se prelozi, ale potom prvni pristup znamena chybu

    cout << m_intUzel.Data() << " " << m_dblUzel.Data() << endl;

    char c;
    cin >> c;

    return 0;
}

    V programu nesmφ nikde b²t vytvo°ena prom∞nnß typu CUzel, v₧dy to musφ b²t CUzel a v lomen²ch zßvorkßch pak musφ nßsledovat skuteΦn² parametr. N∞kterΘ p°ekladaΦe se sice zßpisu CUzel m_obecnyUzel() nebrßnφ, tak₧e se program v po°ßdku p°elo₧φ. V tom p°φpad∞ vygeneruje p°ekladaΦ hned p°i prvnφm pou₧itφ tΘto prom∞nnΘ chybu.

    Vra¥me se ale k naÜφ t°φd∞ pro prßci s lineßrnφm spojov²m seznamem. Tato t°φda bude obsahovat jeden ukazatel na t°φdu CUzel. Tento prvek se v∞tÜinou naz²vß hlavou seznamu, v naÜem p°φpad∞ nebude hlava obsahovat data (v jejφm datovΘm prvku bychom mohli uchovßvat nap°φklad poΦet prvk∙ v seznamu) a jejφ ukazatel na dalÜφ prvek bude ukazovat na prvnφ datov² prvek obsahujφcφ data. Pro tento Φlßnek si implementujeme pouze vytvo°enφ seznamu, vlo₧enφ prvku na konec seznamu a samoz°ejm∞ uvoln∞nφ seznamu:

Seznam.h:

// Linearni spojovy seznam

template<class T> class CSeznam {
private:
  CUzel<T> *m_lpHlava;

public:
  CSeznam() : m_lpHlava(NULL)
  {
      m_lpHlava = new CUzel<T>(0);
// Vytvorime prvni prvek
  }

  ~CSeznam()
  {
      Uvolni();
// Jen uvolnime vsechny prvky
  }

  bool JePrazdny() { return (m_lpHlava == NULL); }

  void VlozKonec(T _data)
  {
     
// Overime platnost seznamu
      if(m_lpHlava)
      {
          // Vytvorime vkladany uzel
          CUzel<T> *lpVkladany = new CUzel<T>(_data);

          CUzel<T> *lpKonec;
// Najdeme si konec zretezeneho seznamu
          lpKonec = m_lpHlava;

          // Dokud je dalsi prvek platnym prvkem
          while(lpKonec->Nasl() != NULL)
          {
              lpKonec = lpKonec->Nasl();
// Posun na dalsi prvek
          }
          cout << "Vkladam za : " << lpKonec->Data() << "(" << _data << ")" << endl;

          lpKonec->NastavNasl(lpVkladany);
      }
  }

  void Uvolni()
  {
      CUzel<T> *lpMazany = m_lpHlava;

      while(m_lpHlava)
      {
          m_lpHlava = m_lpHlava->Nasl();
          cout << "Mazu : " << lpMazany->Data() << endl;
          delete lpMazany;
          lpMazany = m_lpHlava;
      }
  }

  void Vypis()
  {
     
// Nechceme vypisovat data v hlave ...
      CUzel<T> *lpVypisovany = m_lpHlava->Nasl();

      while(lpVypisovany)
      {
          lpVypisovany->Vypis();
          lpVypisovany = lpVypisovany->Nasl();
      }
  }
};

    Nßsleduje hlavnφ program tΘto Üablony:

#include <stdio.h>
#include <iostream.h>

#include "Uzel.h"
#include "Seznam.h"

int main(int argc, char* argv[])
{
    CSeznam<int> m_intSeznam;

    m_intSeznam.VlozKonec(5);
    m_intSeznam.VlozKonec(8);
    m_intSeznam.VlozKonec(-5);

    m_intSeznam.Vypis();

    char c;
    cin >> c;
    return 0;
}

    Program vytvo°φ lineßrnφ spojov² seznam, kde datov²mi prvky jednotliv²ch uzl∙ jsou prom∞nnΘ typu int. Potom vlo₧φme do seznamu pßr Φφsel a vypφÜeme je. Samoz°ejm∞ bychom mohli do seznamu uklßdat instance t°φdy CZivocich, kterou jsme vytvo°ili v minulΘm dφlu.

    V tomto p°φpad∞ jsme cel² seznam implementovali v hlaviΦkovΘm souboru. A tady je t°eba dßt pozor, Üablony toti₧ vy₧adujφ specißlnφ postup p°i p°ekladu, proto₧e p°ekladaΦ pot°ebuje znßt krom∞ deklarace Üablony i t∞la vÜech metod. Pokud bychom cht∞li odd∞lit v hlaviΦkovΘm souboru deklaraci Üablony a t∞la metod, museli bychom pro ka₧dou funkci vytvo°it Üablonu. P°edvedeme si to na metod∞ CSeznam::Uvolni(). U ostatnφch metod je to analogickΘ a zdrojov² k≤d naleznete v sekci Downloads (projekt Seznam1):

Seznam.h:

// Linearni spojovy seznam

template<class T> class CSeznam {
private:
    CUzel<T> *m_lpHlava;

public:
    CSeznam();
    ~CSeznam();

    bool JePrazdny();
    void VlozKonec(T _data);
    void Uvolni();

    void Vypis();
};

template<class T> void CSeznam<T>::Uvolni()
{
    CUzel<T> *lpMazany = m_lpHlava;

    while(m_lpHlava)
    {
        m_lpHlava = m_lpHlava->Nasl();
        cout << "Mazu : " << lpMazany->Data() << endl;
        delete lpMazany;
        lpMazany = m_lpHlava;
    }
};    // Tady je opravdu strednik

    Pokud bychom cht∞li klasickΘ rozd∞lenφ na hlaviΦkov² a zdrojov² soubor, nabφzφ n∞kterΘ p°ekladaΦe klφΦovΘ slovo export, kterΘ se ve zdrojovΘm souboru vklßdß p°ed ka₧dou Üablonu metody. N∞kterΘ p°ekladaΦe vÜak toto slovo nemajφ, n∞kterΘ ho neumo₧≥ujφ spojit s klφΦov²m slovem template. V∞tÜinou se Üablona prost∞ celß vlo₧φ do hlaviΦkovΘho souboru a ten se pak direktivou #include vklßdß do ostatnφch soubor∙.

16.2.2. Parametry deklarace Üablony

    Vid∞li jsme dva p°φklady na Üablony. Nynφ si up°esnφme, co jeÜt∞ m∙₧eme v deklaraci Üablony p°idat za parametry. Deklaraci Üablony provßdφme nßsledujφcφm zßpisem:

template<parametry> deklarace;

    Parametry mohou b²t

1)    hodnotovΘ - jako u deklarace funkce, mohou mφt implicitnφ hodnotu
2)    typovΘ - pokud nedeklarujeme Üablonovou funkci, pak m∙₧e mφt implicitnφ parametr

    Jako t°etφ druh parametru jsou uvßd∞ny ÜablonovΘ parametry, se kter²mi se setkßme jen v nejnov∞jÜφch p°ekladaΦφch.

    V naÜich p°φkladech jsme se setkali jen s typov²mi parametry. Podφvejme se tedy nejprve na n∞. Specifikujeme je pomocφ klφΦov²ch slov class nebo typename, p°iΦem₧ typename znajφ jen nov∞jÜφ p°ekladaΦe. Za klφΦov²m slovem pak nßsleduje identifikßtor, kter² pak pou₧ijeme vÜude tam, kde chceme v Üablon∞ oznaΦit typ. Zßpis implicitnφho parametru se provede nßsledovn∞:

template<class T = int> class CSeznam {
   
// Stejne jako predtim
};

    V programu pak pro vyu₧itφ implicitnφho parametru musφme pro vytvo°enφ prom∞nnΘ napsat:

CSeznam<> m_intSeznam;

    HodnotovΘ parametry deklarujeme jako parametry funkcφ. Nßsleduje ukßzka deklarace:

template <class T, unsigned int hodnotovy_parametr /*= impl_hodnota*/> class Sablona { telo_sablony; };

    Pomocφ hodnotov²ch parametr∙ lze v²hodn∞ implementovat nap°. vektory, kde prvkem bude ukazatel, kter² bude ukazovat na dynamicky alokovanΘ pole prvk∙ typu T. Jen matematickß poznßmka pro ty, kte°φ jeÜt∞ nem∞li tu Φest setkat se s vektory, tak vektor je n-tice Φφsel. M∙₧e urΦovat nap°. sou°adnice v 2D nebo 3D prostoru. JeÜt∞ zd∙raz≥uji, ₧e hodnotovΘ parametry mohou mφt implicitnφ hodnotu.

16.3. Co bude p°φÜt∞?

    P°φÜt∞ doplnφme jeÜt∞ pßr v∞cφ k Üablonßm. Jako dalÜφ tΘma probereme v²jimky a v n∞kterΘm z dalÜφch dφl∙ se seznßmφme se standardnφ Üablonovou knihovnou, kterß je souΦßstφ C++. NaÜφ pozornosti ale neuniknou ani objektovΘ vstupn∞-v²stupnφ proudy.

PřφÜtě nashledanou.

Ond°ej BuriÜin