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φ.
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;
}
Zdrojov² k≤d naleznete v sekci Downloads (projekt PrevozZOO1).
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.
Zdrojov² k≤d naleznete v sekci Downloads (projekt PrevozZOO2).
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.
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;
}
Zdrojov² k≤d naleznete v sekci Downloads (projekt Seznam).
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∙.
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.
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.