V dalÜφm pokraΦovßnφ kurzu o C++ dokonΦφme povφdßnφ o p°et∞₧ovßnφ operßtor∙.
Minule jsme probrali unßrnφ operßtory a dnes se budeme zab²vat operßtory binßrnφmi. Nejprve si p°etφ₧φme voln∞ p°et∞₧ovateln² operßtor, kter²m je nap°φklad operace sΦφtßnφ a odΦφtßnφ. V minulΘm dφle se vyskytla malß chybiΦka, za kterou se omlouvßm. V poslednφ ukßzce k≤du musφte vÜechny identifikßtory cplx nahradit identifikßtorem complex, jinak dostanete varovßnφ o nedefinovanΘm typu cplx.
Tyto operßtory lze p°et∞₧ovat jako obyΦejnou funkci se dv∞ma parametry, alespo≥ jeden z nich musφ b²t objektovΘho nebo v²ΦtovΘho typu, nebo jako metodu s jednφm parametrem. Pro ukßzku pou₧ijeme op∞t t°φdu pro prßci s komplexnφmi Φφsly z minulΘho dφlu:
class complex {
private: double re, im;
public:
complex(double _re = 0, double _im = 0):re(_re), im(_im) { }
double Re() { return re; }
double Im() { return im; }
complex operator-(complex b) { return complex(re-b.re, im-b.im); }
// Odecte dve komplexni cisla
};
Te∩ si m∙₧eme ukßzat pou₧itφ operßtoru:
void main(void)
{
complex c1, c2, c;
c = c1 - c2;
c = c2 - 5;
c = 5 - c1;
};
Pokud p°elo₧φte takov²to k≤d, dostanete varovßnφ, ₧e neexistuje globßlnφ operßtor, kter² p°ijφmß typ complex za sv∙j parametr nebo neexistuje p°φsluÜnß konverze. Je to zp∙sobeno poslednφm °ßdkem, proto₧e Φφslo 5 nenφ objektov² typ a nelze pro n∞j tedy zavolat verzi operßtoru p°etφ₧enΘho jako metodu. Pro sprßvnou funkci je tedy t°eba vytvo°it globßlnφ operßtor, kter² bude normßlnφ funkcφ se dv∞ma parametry typu complex:
complex operator-(complex a, complex b) { return complex(a.Re()-b.Re(), a.Im()-b.Im()); }
Po p°idßnφ tΘto verze operßtoru vÜak dostaneme op∞t chybu, p°ekladaΦ se nem∙₧e rozhodnout, kterou verzi pou₧φt. Je tedy nutnΘ pou₧φvat v₧dy jen jednu z verzφ. Zavolßnφ operßtorovΘ funkce je mo₧nΘ provΘst i alternativnφm zßpisem:
c = c1.operator-(c2);
// Pro pretizeni jako metoda
c = operator-(5, c1);
// Pro obecnou funkci
Zdrojov² k≤d naleznete v sekci Downloads (projekt PretezMinus).
Tyto operßtory lze p°et∞₧ovat pouze jako nestatickΘ metody objektov²ch typ∙. Ukß₧eme si, jak p°etφ₧it operßtory p°i°azenφ =, indexovßnφ [] a operßtor p°etypovßnφ (typ). Operßtor volßnφ funkce () se p°etypovßvß jako metoda s hlaviΦkou: operator()(parametry).
O tomto operßtoru jsem se ji₧ n∞kolikrßt zmi≥oval, p°edevÜφm ve spojenφ s kopφrovacφm konstruktorem a problΘmem s m∞lkou kopiφ. P°ipomeneme si, ₧e pokud t°φda obsahuje dynamickou pam∞¥ a po₧adujeme, aby ka₧dß instance m∞la svoje vlastnφ, nesdφlenß data v tΘto pam∞ti, pak je nutnΘ vytvo°it kopφrovacφ konstruktor. Nynφ se podφvßme, co se stane pokud v programu pou₧ijeme operßtor p°i°azenφ nßsledujφcφm zp∙sobem:
void main(void)
{
Buffer prvni(5);
prvni.NastavNa('a');
Buffer druhy(10);
druhy.NastavNa('b');
druhy = prvni;
}
Buffer je nßm ji₧ znßmß t°φda z lekce Φφslo 11. K≤d vytvo°φ dv∞ instance t°φdy Buffer, kterΘ obsahujφ dynamicky alokovanΘ pole znak∙. Po provedenφ p°i°azenφ vÜak nastane problΘm, proto₧e pokud nemß t°φda explicitn∞ definovan² operßtor p°i°azenφ, dojde k vytvo°enφ implicitnφho. Implicitnφ p°i°azovacφ operßtor se chovß podobn∞ jako se implicitnφ kopφrovacφ konstruktor, tedy p°enese obsah Φlensk²ch prom∞nn²ch z instance prvni do instance druhy. To mß ale v tomto p°φpad∞ horÜφ nßsledky, ne₧li pou₧itφ implicitnφho kopφrovacφho konstruktoru. Instance druhy mß ji₧ alokovanou pam∞¥, na kterou ukazuje Φlenskß prom∞nnß t°φdy Buffer m_Buffer. P°i°azenφm pak tuto prom∞nnou p°epφÜeme, Φφm₧ ztratφme ukazatel na tuto pam∞¥, a dojde ke ztrßt∞ pam∞ti. V nßsledujφcφ ukßzce je tedy p°etφ₧en² operßtor p°i°azenφ:
Buffer& Buffer::operator=(Buffer& Buf)
{
if(m_Buffer)
{
delete(m_Buffer);
s_PocetByte -= m_Velikost;
m_Velikost = 0;
}
m_Buffer = new char[Buf.m_Velikost];
if(m_Buffer)
{
m_Velikost = Buf.m_Velikost;
s_PocetByte += m_Velikost;
for(int i = 0; i < m_Velikost; i++)
{
m_Buffer[i] = Buf.m_Buffer[i];
}
}
return *this;
}
Nejprve uvolnφme dynamickou pam∞¥ v objektu na levΘ stran∞ p°i°azenφ, potom vytvo°φme pole o velikosti pole objektu, kter² stojφ na pravΘ stran∞ operßtoru p°i°azenφ. Ov∞°φme jestli alokace pam∞ti prob∞hla sprßvn∞, pokud ano, pak p°ekopφrujeme obsah pole. Jazyk C++ nep°edepisuje, co mß operßtorovß funkce p°i°azenφ vracet, tato implementace vracφ ukazatel na objekt na levΘ stran∞ p°i°azovacφho operßtoru, tedy jako standardn∞ definovan² operßtor =. O ukazateli this se jeÜt∞ zmφnφm.
Pro optimalizaci by jeÜt∞ bylo lepÜφ, pokud bychom nejprve zkontrolovali, jestli nßhodou nemajφ oba objekty stejnou velikost dynamicky alokovanΘ pam∞ti. Potom by odpadla nutnost uvol≥ovat a znovu alokovat pam∞¥, kterß ve srovnßnφ s jednφm porovnßnφm zabere mnohem vφce Φasu:
Buffer& Buffer::operator=(Buffer& Buf)
{
if(m_Velikost == Buf.m_Velikost)
{
cout << "- just copying\n";
s_PocetByte -= m_Velikost;
}
else
{
cout << "- deleting memory\n";
if(m_Buffer)
{
delete(m_Buffer);
s_PocetByte
-= m_Velikost;
m_Velikost =
0;
}
m_Buffer = new char[Buf.m_Velikost];
}
if(m_Buffer)
{
s_PocetByte += m_Velikost;
m_Velikost = Buf.m_Velikost;
for(int i = 0; i < m_Velikost; i++)
{
m_Buffer[i] =
Buf.m_Buffer[i];
}
}
return *this;
}
Zdrojov² k≤d naleznete v sekci Downloads (projekt PretezPrir).
Tento operßtor je nßm dob°e znßm ve spojenφ s poli. V p°φkladu tento operßtor p°etφ₧φme tak, aby pracoval stejn²m zp∙sobem, bude tedy mo₧nΘ p°istupovat k prvk∙m v dynamicky alokovanΘm poli prßv∞ p°es indexy. Operßtor p°et∞₧ujeme ve tvaru: operator[](parametr). Nßsleduje p°etφ₧enß verze:
char& Buffer::operator[](int
_indx)
{
if(m_Buffer)
{
if(_indx < m_Velikost) // Osetrime preteceni mezi
{
return m_Buffer[_indx];
}
else
{
cout << "m_Velikost je mensi nez index : " << m_Velikost << endl;
return m_Buffer[m_Velikost - 1];
}
}
else
{
// jinak vytvorime pole, kam se index vejde
cout << "vytvarim nove pole" << endl;
m_Buffer = new char[_indx+1]; // Pole se indexuje od 0
if(m_Buffer)
{
m_Velikost = _indx + 1;
s_PocetByte += m_Velikost;
return m_Buffer[_indx];
}
else
{
cout << "vracim zacatek pole, ale dojde k chybe pristupu do pameti" << endl;
return *m_Buffer;
}
}
}
Jak vidφte, tak je mo₧nΘ pomocφ p°etφ₧enφ provßd∞t kontrolu mezφ, ale jeÜt∞ by bylo pot°eba oÜet°it poslednφ p°φpad, kdy stejn∞ musφme vrßtit pam∞¥, kterß nenφ alokovanß. Bylo by mo₧nΘ pou₧φt nap°φklad v²jimek, ale v ka₧dΘm p°φpad∞ je lepÜφ, kdy₧ dojde k chybnΘmu p°φstupu do pam∞ti poka₧dΘ, ne₧ aby aplikace n∞kdy pracovala sprßvn∞ a n∞kdy by p°epsala ₧ivotn∞ d∙le₧itß data jinΘ aplikace nebo dokonce operaΦnφho systΘmu. JeÜt∞ je vhodnΘ zmφnit, ₧e parametr operßtoru m∙₧e b²t jakΘhokoliv typu, tak₧e nenφ problΘm indexovat pomocφ znak∙ nebo dokonce °et∞zc∙.
Zdrojov² k≤d p°φkladu je v sekci Downloads (projekt PretezIndx).
Pomocφ tohoto operßtoru m∙₧eme definovat funkce, kterΘ p°evedou dan² objektov² typ na jin² objektov² nebo i neobjektov² typ. P°ekladaΦ potom m∙₧e tento operßtor vyu₧φvat i k implicitnφm konverzφm. Pro p°φklad nßm op∞t poslou₧φ t°φda Buffer, pro kterou si p°etφ₧φme operßtor p°etypovßnφ na (char *):
Buffer::operator char*()
{
if(m_Buffer) { return m_Buffer; }
return NULL;
}
Nynφ m∙₧eme pou₧φvat funkce jako je nap°. memcpy() pro kopφrovßnφ blok∙ pam∞ti, p°iΦem₧ jako parametr pou₧ijeme jmΘno instance. P°ekladaΦ toti₧ pou₧ije pro implicitnφ konverzi nßmi definovan² operßtor.
Zdrojov² k≤d p°φkladu je v sekci Downloads (projekt PretezPret).
V t∞le ka₧dΘ nestatickΘ metody je k dispozici ukazatel se jmΘnem this, kter² ukazuje v₧dy na instanci, pro kterou byla ta danß metoda volßna. Tento ukazatel se p°edßvß jako skryt² parametr ka₧dΘ metod∞. Tuto hodnotu nesmφme v t∞le funkce m∞nit a p°ekladaΦ nßm to ani neumo₧nφ, proto₧e tento ukazatel nenφ l-hodnotou - nem∙₧e stßt na levΘ stran∞ p°i°azovacφho p°φkazu. V t∞le metody complex::Re() mß im stejn² v²znam jako this->im.
Toto klφΦovΘ slovo umo₧≥uje poruÜit p°φstupovß prßva k prvk∙m t°φdy, ve kterΘ je uvedeno. V odstavci pro p°etφ₧enφ binßrnφho operßtoru jsme vytvo°ili globßlnφ funkci operator-(), v jejφm₧ t∞le nelze p°istupovat k soukrom²m (private) nebo chrßn∞n²m (protected) prvk∙m t°φdy complex. Pomocφ friend:
class complex {
friend complex operator-(complex a, complex b);
private: double re, im;
public:
complex(double _re = 0, double _im = 0):re(_re), im(_im) { }
double Re() { return re; }
double Im() { return im; }
};
complex operator-(complex a, complex b) { return complex(a.re + b.re, a.im + b.im); }
Zdrojov² k≤d p°φkladu je v sekci Downloads (projekt Friends1).
Takto lze zp°φstupnit prvky i
jinΘ t°φd∞: friend class JinaTrida;
nebo jenom urΦitΘ metod∞ jinΘ t°φdy:
friend double JinaTrida::Fce1(complex&);.
V obou p°φpadech m∙₧e Φlenskß funkce t°φdy JinaTrida::Fce1(complex&) p°istupovat k prvk∙m re a im t°φdy complex.
Zdrojov² k≤d p°φkladu je v sekci Downloads (projekt Friends2).
V jazyce C se parametry p°enßÜφ hodnotou, kdy p°ekladaΦ vytvo°φ lokßlnφ kopii objektu, se kterou se pak v t∞le funkce pracuje. Dßle nabφzφ p°enos parametr∙ pomocφ ukazatele, kdy se lokßlnφ prom∞nnß nevytvß°φ. Druh² zp∙sob se pou₧φvß pro rozm∞rnß pole a struktury. Vzhledem k tomu, ₧e v C++ m∙₧e b²t objektov² typ velmi rozsßhl² (co se t²Φe dat) a vytvo°enφ kopie tak m∙₧e zabrat velmi mnoho Φasu a pam∞ti, nenφ vhodnΘ p°edßvat parametry hodnotou. Lze pou₧φt op∞t bu∩ ukazatele na typ p°φsluÜnΘ t°φdy (a k prvk∙m objektu p°istupovat pomocφ operßtoru ->) nebo jazyk C++ zavßdφ mo₧nost pou₧φt p°enos parametr∙ odkazem. Pro p°enos odkazem staΦφ parametr definovat jako referenci na typ p°φsluÜnΘ t°φdy (nap°. Buf& ref_buf) a v t∞le funkce nebo metody pak p°istupovat k objektu pomocφ standardnφ teΦkovΘ notace (nap°. ref_buf.NastavNa('a')). Tento zp∙sob takΘ umo₧≥uje m∞nit data vn∞ funkce, podobn∞ jako kdybychom p°edßvali parametr pomocφ ukazatele.
Jak jsme si uvedli tak t°φda v objektov∞ orientovan²ch programovacφch jazycφch slou₧φ p°edevÜφm ke spojenφ dat a operacφ nad t∞mito atributy. Jednou z vlastnostφ konstrukce t°φdy je, ₧e k ji₧ stßvajφcφm prvk∙m m∙₧eme jednoduÜe p°idat dalÜφ metodu nebo datov² prvek. D∞diΦnost nßm prßv∞ nabφzφ tyto mo₧nosti s n∞kter²mi v²hodn²mi vlastnostmi. T°φdu, od kterΘ d∞dφme prvky (metody i data) naz²vßme t°φdou zßkladnφ, bßzovou nebo tΘ₧ rodiΦovskou, t°φda kterß vznikne se pak naz²vß t°φdou odvozenou, podt°φdou nebo potomkem. D∞diΦnost se vyu₧φvß zpravidla tak, ₧e od obecn∞jÜφ t°φdy odvodφme t°φdu specializovan∞jÜφ. Jin²m mo₧n²m zp∙sobem vytvß°enφ slo₧it∞jÜφch objekt∙ z jednoduch²ch je metoda kompozice, kdy jeden objekt obsahuje dalÜφ objektovΘ typy.
Rozhodovßnφ o tom, jestli novou specializovan∞jÜφ t°φdu vytvo°it metodou d∞d∞nφ nebo kompozice nßm m∙₧e usnadnit takzvan² "je test". Vezm∞me si jako p°φklad dvojici CMotor a CMotoroveVozidlo. M∙₧eme si polo₧it otßzku: "Je motorove vozidlo motor?". Odpov∞dφ na tuto otßzku samoz°ejm∞ je, ₧e nenφ, motorovΘ vozidlo ale urΦit∞ obsahuje motor. NaÜe rozhodnutφ tedy bude pro metodu kompozice, kdy ve t°φd∞ CMotoroveVozidlo vytvo°φme Φlenskou prom∞nnou m_motor typu CMotor. Na kompozici nßs takΘ m∙₧e p°ivΘst "mß test", kdy se zeptßme: "Mß motorovΘ vozidlo motor?".
Pro dvojici CZivocich a CPes budeme postupovat ·pln∞ stejn∞, polo₧φme si otßzku: "Je pes zivocichem?". Pokud se nepletu, tak odpov∞∩ na tuto otßzku bude ano a tedy pou₧ijeme mo₧nosti vytvo°it specializovan∞jÜφ t°φdu CPes od t°φdy CZivocich zd∞d∞nφm. T°φd∞ CPes pak p°idßme n∞kterΘ specißlnφ metody a data specifickß pro psy, mohla by to b²t t°eba prom∞nnß m_hlasitoststekotu a z metod nap°φklad Stekej().
D∞diΦnost je tedy vlastn∞ o hledßnφ spoleΦn²ch vlastnostφ skupiny souvisejφcφch objekt∙. Pokud bychom programovali nap°φklad aplikaci pro sprßvu zoologickΘ zahrady mohli bychom jeÜt∞ nalΘzt spoleΦnΘ vlastnosti pro savce, ptßky apod. Od t∞chto specializovan∞jÜφch t°φd bychom pak odvozovali jednotlivß zvφ°ata. Pokud bychom hledali peΦliv∞, a s nejv∞tÜφ pravd∞podobnostφ s pomocφ zkuÜenΘho zoologa, dopracovali bychom se k rozΦlen∞nφ ₧ivoΦich∙ do systΘmu ₧ivoΦiÜnΘ °φÜe, se kterou se ka₧d² urΦit∞ ve Ükole setkal.
P°φÜt∞ si jeÜt∞ povφme n∞co o p°et∞₧ovßnφ a p°edefinovßnφ standardnφch operßtor∙ new a delete a dßle se budeme v∞novat d∞diΦnosti. TakΘ si ukß₧eme n∞jakΘ diagramy hierarchie t°φd. V nßsledujφcφch dφlech se pak budeme v∞novat obsluze v²jimek.
PřφÜtě nashledanou.