Vφtejte u dalÜφ čßsti kurzu o objektovΘm programovßnφ. Dnes se budeme věnovat kopφrovacφmu konstruktoru, statick²m slo₧kßm třφdy. SlibovanΘ "pravΘ" objektovΘ programovßnφ si tedy nechßme pravděpodobně a₧ na přespřφÜtφ dφl. K tΘto čßsti je takΘ přilo₧en soubor, kter² obsahuje vÜechny ukßzky k≤du probφranΘ v tΘto lekci. Soubory jsou organizovßny jako projekty pro Microsoft Visual C++.
Nejprve se podφvejme, co se stane, jestli₧e okopφrujeme objekt pomocφ operßtoru "=". Mějme tedy nßsledujφcφ k≤d :
class CTrida {
private :
int m_Cislo;
char m_Znak;
public :
CTrida(int _Cislo = 1, char _Znak = 'a') : m_Cislo(_Cislo), m_Znak(_Znak) { ; }
void Vypis() {
cout << "Tento objekt obsahuje : " << m_Cislo << " a " << m_Znak << endl; }
};
void main(void)
{
CTrida prvni(5,'c');
prvni.Vypis();
CTrida druha = prvni;
prvni.Vypis();
druha.Vypis();
char c; // Cekame na stisk klavesy
cin >> c;
}
Přφkazem CTrida druha=prvni se provede něco podobnΘho jako kdybychom přiřazovali jednoduchΘ proměnnΘ, tedy na mφsto v paměti pro druha se přenesou data z paměti pro prvni. V²sledkem tedy budou dvě stejnΘ instance objektu. Z k≤du je vidět, ₧e za tφmto ·čelem jsme nenapsali ani řßdku navφc. Jak se tedy toto "kouzlo" provedlo? JednoduÜe, překladač vytvořil tzv. implicitnφ kopφrovacφ konstruktor, jeho₧ jedin²m ·čelem je přenΘst slo₧ku po slo₧ce z instance prvni do instance druha. Nynφ si ukß₧eme mal² problΘm: Co se stane, jestli₧e třφda obsahuje nějakou dynamicky alokovanou paměť? V implicitnφm konstruktoru se nic nezměnφ, tedy přenesou se hodnoty vÜech slo₧ek z jednΘ instance do druhΘ. To ovÜem nemusφ b²t přesně to, co chceme udělat, proto₧e pak mßme dvě instance, kterΘ sdφlejφ tuto dynamicky alokovanou paměť. Jestli₧e tedy pomocφ instance druha změnφte data v tΘto paměti, změna se promφtne i do objektu prvni. NejhorÜφ přφpad by mohl nastat, kdybychom jednu z instancφ ·plně zruÜili a pou₧ili bychom druhou instanci k přφstupu k tΘto dynamickΘ paměti. Takovßto akce by vedla k chybnΘmu přφstupu do paměti a Windows by naÜi aplikaci okam₧itě ukončily. Prßvě proto mßme k dispozici specißlnφ metodu - kopφrovacφ konstruktor, jeho₧ pomocφ mů₧eme ovlivnit proces kopφrovßnφ jednotliv²ch slo₧ek Mějme tedy třφdu, kterß ve svΘm těle obsahuje dynamicky alokovanou paměť, řekněme třeba pole a uka₧me si, co je potřeba udělat, aby kopφrovßnφ fungovalo sprßvně. Tedy aby v²sledkem byly dvě na sobě nezßvislΘ instance se stejn²mi daty. Vyu₧ijeme přitom třφdu Buffer z minulΘho dφlu, kterou trochu upravφme, abychom si mohli zkontrolovat v²sledek naÜeho sna₧enφ:
class Buffer {
private :
char *m_Buffer;
int m_Velikost;
public :
Buffer(int _velikost)
{
m_Buffer = NULL;
m_Velikost = 0;
m_Buffer = new char[_velikost];
if(m_Buffer) { m_Velikost = _velikost; } // V pripade uspechu nastavime velikost
}
~Buffer() { if(m_Buffer) { delete[] m_Buffer; m_Buffer = NULL; } }
void NastavNa(char hodnota)
{
if(m_Buffer) {
for(int i = 0; i < m_Velikost; i++)
{ m_Buffer[i] = hodnota; }
}
}
void ZapisNa(int _pozice, char _hodnota)
{
if(m_Buffer && (_pozice < m_Velikost)) // Jestlize jsme v mezich
{ m_Buffer[_pozice] = _hodnota; }
}
void Vypis()
{
if(m_Buffer)
{
for(int i = 0; i < m_Velikost; i++)
{ cout << m_Buffer[i] << "|"; }
cout << endl; // Radek mezi dvema vypisy
}
}
};
Jak vidφte, je to jen rozÜφřenß třφda Buffer z minulΘho dφlu o pßr metod. Metoda NastavNa() nastavφ celΘ pole na hodnotu předanou jako parametr. Za povÜimnutφ stojφ snad jen ověřovßnφ přφstupu do dynamickΘho pole, aby omylem nedoÜlo k zßpisu do paměti, kterß nßm nepatřφ. Předvedeme si nynφ problΘmov² k≤d:
void main(void)
{
Buffer prvni(5);
prvni.NastavNa('a');
prvni.Vypis();
Buffer druhy = prvni;
prvni.Vypis();
druhy.Vypis();
druhy.ZapisNa(4, 'k');
prvni.Vypis();
druhy.Vypis();
char c;
cin >> c;
}
Po spuÜtěnφ tohoto k≤du dojde k naplněnφ objektu prvni hodnotou 'a', potom si udělßme kontrolnφ Vypis(). Nynφ přichßzφ na řadu přiřazenφ, jeho₧ nßsledkem se objekt překopφruje slo₧ku po slo₧ce. Po v²pisu je zřejmΘ, ₧e pole majφ stejn² obsah. Přφkaz druhy.ZapisNa(4, 'k') nßm demonstruje zßvislost obsahu obou instancφ, neboť po v²pisu vidφme, ₧e obě pole jsou opět stejnß. Pokud si program spustφte, budete svědky dalÜφho problΘmu, a to při ukončenφ. Vysvětlenφ je jednoduchΘ: ProměnnΘ jsme alokovali staticky, tak₧e budou zruÜeny při v²stupu z funkce main(). Po zruÜenφ prvnφho objektu (teď prvnφ berme jako ten, kter² se opravdu bude ruÜit prvnφ) bude blok paměti, na kter² ukazuje členskß proměnnß m_Buffer, neplatn² a při druhΘm uvolněnφ dojde k chybě, kterß mß za nßsledek ukončenφ naÜφ aplikace systΘmem Windows. Je pravda, ₧e v tomto přφpadě to nenφ tak důle₧itΘ (kromě toho, ₧e takhle se programovat nemß), ale kdybychom měli tyto instance alokovanΘ dynamicky a jednu jsme zruÜili napřφklad v průběhu nějakΘho v²počtu, pak to je větÜφ problΘm (tuto situaci najdete v projektu kopkonstr2 v přilo₧enΘm souboru). Nynφ se tedy pokusφme vytvořit nßÜ vlastnφ kopφrovacφ konstruktor. Hlavička kopφrovacφho konstruktoru vypadß nßsledovně: Jmeno_Tridy(Jmeno_Tridy& promenna). KonkrΘtně pro třφdu Buffer to tedy bude: Buffer(Buffer& p). Typ Buffer& se naz²vß referencφ na třφdu Buffer. Tady je prvnφ pokus, ukß₧eme si jen tuto metodu, proto₧e zbytek třφdy se nezměnφ:
Buffer(Buffer& puvodni)
{
cout << "KopKonst called" << endl; // Abychom videli, ze byl volan
m_Buffer = new char[puvodni.m_Velikost];
// Pokud se buffer vytvori, pak nastavime velikost pole
if(m_Buffer) { m_Velikost = puvodni.m_Velikost; }
// Prekopirujeme vsechny prvky
for(int i = 0; i < puvodni.m_Velikost; i++)
{
m_Buffer[i] = puvodni.m_Buffer[i];
}
}
Do funkce jsme si přidali pomocn² ladicφ text, abychom poznali, kdy je tento konstruktor volßn. Jako parametr pak při pou₧itφ dostaneme instanci, kterß stojφ na pravΘ straně rovnφtka. JeÜtě si shrneme vÜechny mo₧nosti, jak zavolat kopφrovacφ konstruktor. Nejprve dvě ekvivalentnφ mo₧nosti pro staticky vytvořenou třφdu:
A nynφ opět mo₧nost pro dynamicky vytvořenou třφdu :
Pokud jste zklamanφ, ₧e nelze vytvořit dvě instance a pak jen pou₧φt rovnφtko (např. treti = prvni), pak nebuďte, proto₧e zanedlouho se dostaneme k přetě₧ovßnφ metod a poslΘze operßtorů, kde se tφm určitě budeme zab²vat.
Třφda mů₧e, kromě klasick²ch (nestatick²ch) proměnn²ch, obsahovat takΘ zvlßÜtnφ proměnnΘ, takzvanΘ statickΘ. Tyto proměnnΘ nejsou zßvislΘ na existenci instance danΘ třφdy a jsou sdφleny mezi vÜemi instancemi. TakovΘ proměnnΘ mů₧eme napřφklad pou₧φt pro počφtßnφ prßvě existujφcφch instancφ v paměti. V konstruktoru v₧dy hodnotu o jedna zv²Üφme a v destruktoru opět snφ₧φme. V přφpadě třφdy Buffer bychom napřφklad mohli udr₧ovat celkov² počet bytů, kterΘ mßme pomocφ instancφ těchto třφd naalokovßny. StatickΘ proměnnΘ se deklarujφ stejně jako klasickΘ, ale uvedeme před nimi klφčovΘ slovo static. Jako přφklad si ukß₧eme prßvě počφtßnφ obsazenΘ paměti instancemi třφdy Buffer.
class Buffer {
private :
char *m_Buffer;
int m_Velikost;
static int s_PocetByte;
public :
Buffer(int _velikost)
{
m_Buffer = NULL;
m_Velikost = 0;
m_Buffer = new char[_velikost];
if(m_Buffer) { // V pripade uspechu nastavime velikost a pricteme k nasi staticke promenne
m_Velikost = _velikost;
s_PocetByte += m_Velikost; }
}
Buffer(Buffer& puvodni)
{
m_Buffer = new char[puvodni.m_Velikost];
// Pokud se buffer vytvori, pak nastavime velikost pole
if(m_Buffer) {
m_Velikost = puvodni.m_Velikost;
s_PocetByte += m_Velikost;
}
// Prekopirujeme vsechny prvky
for(int i = 0; i < puvodni.m_Velikost; i++)
{
m_Buffer[i] = puvodni.m_Buffer[i];
}
}
~Buffer() {
if(m_Buffer) {
delete[] m_Buffer; m_Buffer = NULL;
s_PocetByte -= m_Velikost;
}
};
void NastavNa(char hodnota)
{
if(m_Buffer) {
for(int i = 0; i < m_Velikost; i++)
{ m_Buffer[i] = hodnota; }
}
}
void ZapisNa(int _pozice, char _hodnota)
{
if(m_Buffer && (_pozice < m_Velikost)) // Jestlize jsme v mezich
{ m_Buffer[_pozice] = _hodnota; }
}
void Vypis()
{
if(m_Buffer)
{
for(int i = 0; i < m_Velikost; i++)
{ cout << m_Buffer[i] << "|"; }
cout << endl; // Radek mezi dvema vypisy
}
}
static int Stav() { return s_PocetByte; }
};
int Buffer::s_PocetByte = 0; // Pocatecni inicializace
Pokud je tedy ·spěÜně vytvořena instance třφdy Buffer, přičte se počet bytů, kterΘ tento nov² objekt spravuje, k celkovΘmu počtu bytů. Na počßtku je hodnota nastavena na 0, jak vidφme z poslednφho řßdku. PovÜimněte si, ₧e i kdy₧ je tato proměnnß private, lze ji tφmto způsobem inicializovat. Zjistit hodnotu tΘto statickΘ proměnnΘ mů₧eme buď přφm²m přφstupem k tΘto proměnnΘ, pokud bychom ji ovÜem dali do sekce public tΘto třφdy. Pro přφstup lze pak pou₧φt nßsledujφcφ mo₧nosti:
Proto₧e se při objektovΘm programovßnφ sna₧φme o zapouzdřenost dat, pou₧ijeme pro přφstup k tΘto statickΘ proměnnΘ členskou metodu. Tato metoda je klasickou vrať metodou, tedy v těle mß jen přφkaz return s_PocetByte. Před touto metodou by mělo b²t uvedeno klφčovΘ slovo static. Hlavička bude tedy mφt tvar static int Stav(). Tφm překladači řekneme, ₧e v těle tΘto procedury přistupujeme pouze ke statick²m datům, kterß existujφ i v přφpadě, ₧e neexistujφ ₧ßdnΘ instance tΘto třφdy. Jestli₧e nebude klφčovΘ slovo static uvedeno, nebude mo₧nΘ zavolat tuto metodu pomocφ zßpisu Buffer::Stav(), proto₧e nestatickΘ metody nejsou pomocφ tohoto volßnφ dostupnΘ. Je to logickΘ, proto₧e kdybychom v těle tΘto metody přistupovali k nějakΘ nestatickΘ proměnnΘ, překladač by nemohl určit, kam ji přiřadit, proto₧e nemusφ existovat ani jedna instance nebo jich naopak mohou existovat tisφce. Pro zavolßnφ funkce Stav() mů₧eme pou₧φt stejnΘ přφkazy jako v přφpadě proměnnΘ.
V dneÜnφm dφle se nßm třφdy poněkud vφce rozrostly, tak₧e přφÜtě bude slibovanΘ dělenφ třφd do souborů, kterΘ v dalÜφch dφlech věnovan²ch dědičnosti velmi ocenφme. Probrat bychom takΘ měli přetě₧ovßnφ funkcφ a operßtorů. Nashledanou u přφÜtφho dφlu.